VTK 动画:用 Qt 控制时间
VTK 动画:利用 Qt 的时间控制
- VTK 动画:利用 Qt 的时间控制
- VS2022 + QT6.2.3 + VTK9.3.0 环境配置
- Qt 定时器类 QTimer
- 实现
- 运行结果
- 参考
VTK 动画:利用 Qt 的时间控制
第一篇文章 《VTK 动画:框架、流程与实现》 讲到了 VTK 的动画框架、动画流程,并给出了一个简单的 VTK 动画程序。
第二篇文章 《VTK 动画:面向对象的设计》 介绍了面向对象的 VTK 动画框架设计,达到了动画场景和动画内容的隔离的效果。
视频参考:https://www.bilibili.com/video/BV1Ya4y1V7ed
VS2022 + QT6.2.3 + VTK9.3.0 环境配置
VTK9.3.0 的编译:使用 Cmake 对 VTK-9.3.0 进行编译
这里重点就是把QT的内容全设成YES,VTK_QT_VERSION 一定要设置为自己的版本,我的是 6。
VS2022 的插件 Qt Visual Studio Tools 的安装:Visual Studio 2022 中 Qt 开发环境的搭建
都配好后,新建 VS Qt Widgets 项目,再把 VTK 的路径配置一下就行了,这里不赘述。
还有就是,用户变量 Path 要配置 Qt mvsc bin 的路径,比如我的就是C:\Qt\Qt6.2.3\6.2.3\msvc2019_64\bin,如果你有多个 Qt 版本,要把当时使用的路径放到 Path 的靠前位置,不如有可能出错。
Qt 定时器类 QTimer
Qt定时器类QTimer是一个用于重复执行或延迟执行函数的类,它直接从QObject类继承而来,不是界面组件类。
它可以在一定时间间隔内发送一个信号,也可以在指定的时间后发送一个信号。QTimer是一个基于事件的定时器,即它使用Qt的事件循环来触发定时器事件。
要使用它,只需创建一个QTimer类对象,然后调用其 start() 函数开启定时器,此后QTimer对象就会周期性的发出 timeout() 信号。
槽函数:
// 构造函数
// 如果指定了父对象, 创建的堆内存可以自动析构
QTimer::QTimer(QObject *parent = nullptr);
// 设置定时器时间间隔为 msec 毫秒
// 默认值是0,一旦窗口系统事件队列中的所有事件都已经被处理完,一个时间间隔为0的QTimer就会触发
void QTimer::setInterval(int msec);
// 获取定时器的时间间隔, 返回值单位: 毫秒
int QTimer::interval() const;
// 根据指定的时间间隔启动或者重启定时器, 需要调用 setInterval() 设置时间间隔
[slot] void QTimer::start();
// 启动或重新启动定时器,超时间隔为msec毫秒。
[slot] void QTimer::start(int msec);
// 停止定时器。
[slot] void QTimer::stop();
// 设置定时器精度
/*
参数:
- Qt::PreciseTimer -> 精确的精度, 毫秒级
- Qt::CoarseTimer -> 粗糙的精度, 和1毫秒的误差在5%的范围内, 默认精度
- Qt::VeryCoarseTimer -> 非常粗糙的精度, 精度在1秒左右
*/
void QTimer::setTimerType(Qt::TimerType atype);
Qt::TimerType QTimer::timerType() const; // 获取当前定时器的精度
// 如果定时器正在运行,返回true; 否则返回false。
bool QTimer::isActive() const;
// 判断定时器是否只触发一次
bool QTimer::isSingleShot() const;
// 设置定时器是否只触发一次, 参数为true定时器只触发一次, 为false定时器重复触发, 默认为false
void QTimer::setSingleShot(bool singleShot);
信号:
这个类的信号只有一个,当定时器超时时,该信号就会被发射出来。给这个信号通过 conect() 关联一个槽函数,就可以在槽函数中处理超时事件了。
void QTimer::timeout();
静态函数:
/*
功能: 在msec毫秒后发射一次信号, 并且只发射一次
参数:
- msec: 在msec毫秒后发射信号
- receiver: 接收信号的对象地址
- method: 槽函数地址
*/
[static] void QTimer::singleShot(
int msec, const QObject *receiver,
PointerToMemberFunction method);
事件:
void timerEvent(QTimerEvent *event);
实现
QVTKAnimation.h:
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtVTKAnimation.h"
#include <QTimer>
#include <QVTKOpenGLStereoWidget.h>
#include <vtkCameraOrientationWidget.h>
#include <vtkInterpolateDataSetAttributes.h>
class QtVTKAnimation : public QMainWindow
{
Q_OBJECT
public:
QtVTKAnimation(QWidget* parent = nullptr);
~QtVTKAnimation();
private:
Ui::QtVTKAnimationClass ui;
QVTKOpenGLStereoWidget* _pVTKWidget = NULL;
vtkNew<vtkInterpolateDataSetAttributes> interpolate;
int _nTimeStep = 0;
void timerEvent(QTimerEvent* event);
};
QtVTKAnimation.cpp:
#include "QtVTKAnimation.h"
#include <vtkRenderWindow.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
#include <vtkVectorText.h>
#include <vtkImplicitModeller.h>
#include <vtkContourFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkAnimationScene.h>
#include <vtkAnimationCue.h>
QtVTKAnimation::QtVTKAnimation(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
_pVTKWidget = new QVTKOpenGLStereoWidget();
this->setCentralWidget(_pVTKWidget);
this->showMaximized();
vtkNew<vtkRenderer> renderer;
this->_pVTKWidget->renderWindow()->AddRenderer(renderer);
this->_pVTKWidget->renderWindow()->Render();
vtkNew<vtkVectorText> letterV;
letterV->SetText("V");
vtkNew<vtkVectorText> letterT;
letterT->SetText("T");
vtkNew<vtkVectorText> letterK;
letterK->SetText("K");
vtkNew<vtkImplicitModeller> bloddyV;
bloddyV->SetInputConnection(letterV->GetOutputPort());
bloddyV->SetMaximumDistance(0.2);
bloddyV->SetSampleDimensions(50, 50, 12);
bloddyV->SetModelBounds(-0.5, 1.5, -0.5, 1.5, -0.5, 0.5);
vtkNew<vtkImplicitModeller> bloddyT;
bloddyT->SetInputConnection(letterT->GetOutputPort());
bloddyT->SetMaximumDistance(0.2);
bloddyT->SetSampleDimensions(50, 50, 12);
bloddyT->SetModelBounds(-0.5, 1.5, -0.5, 1.5, -0.5, 0.5);
vtkNew<vtkImplicitModeller> bloddyK;
bloddyK->SetInputConnection(letterK->GetOutputPort());
bloddyK->SetMaximumDistance(0.2);
bloddyK->SetSampleDimensions(50, 50, 12);
bloddyK->SetModelBounds(-0.5, 1.5, -0.5, 1.5, -0.5, 0.5);
// 插值实现动画过渡
interpolate->AddInputConnection(bloddyV->GetOutputPort());
interpolate->AddInputConnection(bloddyT->GetOutputPort());
interpolate->AddInputConnection(bloddyK->GetOutputPort());
interpolate->SetT(0.0);
vtkNew<vtkContourFilter> bloddyIso;
bloddyIso->AddInputConnection(interpolate->GetOutputPort());
bloddyIso->SetValue(0, 0.1);
vtkNew<vtkPolyDataMapper> bloddyMapper;
bloddyMapper->AddInputConnection(bloddyIso->GetOutputPort());
bloddyMapper->ScalarVisibilityOff();
vtkNew<vtkActor> bloddyActor;
bloddyActor->SetMapper(bloddyMapper);
bloddyActor->GetProperty()->EdgeVisibilityOn();
renderer->AddActor(bloddyActor);
startTimer(100);
}
QtVTKAnimation::~QtVTKAnimation()
{}
void QtVTKAnimation::timerEvent(QTimerEvent * event)
{
_nTimeStep++;
int nLen = 100;
if (_nTimeStep <= 100)
{
interpolate->SetT(_nTimeStep * 2.0 / nLen);
interpolate->Modified(); // 更新
this->_pVTKWidget->renderWindow()->Render(); // 渲染
}
}
运行结果
创建了3个矢量对象:V、T、K,并网格化。
利用定时器类实现了它们转换的动画,利用 vtkInterpolateDataSetAttributes 实现了插值过渡。
参考
- https://www.bilibili.com/video/BV1Ya4y1V7ed
- https://blog.****.net/weixin_43780415/article/details/131389737
- https://blog.****.net/qq_45445740/article/details/127826774
推荐阅读
-
VTK 动画:用 Qt 控制时间
-
简单易懂版 - 什么是粒子群算法(PSO)?" - PSO 是这样工作的: 想象一群小鸟寻找食物,它们会互相学习、竞争并跟随最优秀的伙伴。这就是模仿群体智慧(Swarm Intelligence,SI)的粒子群优化算法,由 Eberhart 博士和 Kennedy 博士创造,属于多智能体优化系统(MAOS)的一员。 - 数学背后的逻辑: - 每只“鸟”(粒子)依据邻居过去的发现来飞得更好: 1. 受到激励的好位置(Pbest) 2. 与附近伙伴的成绩对比 3. 阿婆姨领先者的模仿 - 模型简化来说,每个粒子像 D 维空间的理想点,按特定速度飞行,速度随自身经验和同伴表现实时调整。我们用 Xi 表示 D 个粒子的集合,其中 Pi 存储过最佳位置,Pg 是群体中最优的位置,Vi 是粒子的速度。 - 更新规则: - **速度更新**:有点像梯度下降法中的导数概念,但因鸟群数量大,能有效跳出局部最优区域,引导群体朝全局最优方向前进。 - **位置更新**:在固定的时间内,新移动的距离就是 Vi(即速度向量在单位时间内的累积效果)。 - 参数简述:粒子群算法涉及多个参数,如粒子数量、学习因子(影响对过去经验的重视程度)、加速常数(控制探索与利用之间的平衡),这些参数的选择会影响算法的实际性能和收敛速度。
-
[Halcon&拟合] 拟合直线边缘并计算距离-图像预处理: 一般是去噪或抠图(blob分析抠图或手绘ROI区域抠图)两方面 轮廓提取: 1)boundary:区域轮廓提取 2)edges_sub_pix:图像轮廓提取 3)threshold_sub_pix:图像轮廓提取 使用算子edges_sub_pix进行亚像素的边缘提取最为普遍。其用到的滤波器有Deriche, Lanser, Shen, or Canny filters。 关于这几个滤波器的对比,帮助文档有如下介绍: Deriche, Lanser, Shen为递归滤波器,Canny 为掩膜滤波器; 递归滤波器的执行时间不依赖滤波器的大小,Canny的执行时间与滤波器大小成正相关。 参数alpha数值越大,Deriche, Lanser, Shen滤波器宽度越小,平滑越差,细节越突出,而Canny效果相反。 分割、联合(根据情况而定) 分割算子: segment_contours_xld:可分割’lines’,‘lines_circles’,‘lines_ellipses’,原理是多边形逼近,逼近程度通过算子中后两个阀值参数控制。 联合算子: 临近:union_adjacent_contours_xld (Operator) 共线:union_collinear_contours_xld (Operator) 共圆:union_cocircular_contours_xld (Operator) 拟合 fit_line_contour_xld:拟合直线 fit_line_contour_xld:拟合圆 fit_ellipse_contour_xld:拟合椭圆 fit_rectangle2_contour_xld:拟合矩形 注:有时候在拟合轮廓之前需要判断一下轮廓属性,以确定应拟合成直线还是还是圆,可通过算子:get_contour_global_attrib_xld (SingleSegment,‘cont_approx’,)名字:获取轮廓属性描述:用于确定应拟合成直线还是还是圆参数:SingleSegment:输入轮廓(input_object)cont_approx:属性名称,即采用什么方式去计算 ,一般用这个参数就可以了(input_control)Attrib:属性值: Attrib>0:拟合圆,否则拟合直线(output_control) ) 求距离 二、示例: