04-VTK可视化管线(1)
4、VTK可视化管线
通过第3章的学习,我们已经了解了VTK的一些基础概念。在这一章里,我们将更深入地学习VTK,其中包括VTK的系统框架结构、引用计数、智能指针、Observer/Command设计机制以及本章的重点内容——VTK可视化管线结构。通过本章的学习,可能你对VTK的设计框架将会有更深一层的理解。
所谓追根溯源,首先我们先了解一下VTK里绝大多数类的共同的父类vtkObjectBase和vtkObject。
4.1 vtkObjectBase和vtkObject
4.1.1 引用计数
如果很多对象有相同的值,将这个值存储多次是很无聊的。更好的办法是让所有的对象共享这个值的实现。这么做不但节省内存,而且可以使得程序运行更快,因为不需要构造和析构这个值的拷贝。引用计数就是这样一个技巧,它允许多个有相同值的对象共享这个值的实现。引用计数是个简单的垃圾回收体系,只要其它对象引用某对象(记为对象O),对象O就会存在一个引用计数,当最后引用对象O的对象移除,O对象就会自动析构。VTK里使用引用计数的好处是,可以实现数据之间的共享而不用拷贝,从而达到节省内存的目的。
我们可以看一个简单的例子(4.1.1_ReferenceCounting):
1: #include"vtkSmartPointer.h"
5: int main(int argc, char*argv[])
7: vtkSmartPointer<vtkBMPReader>reader = vtkSmartPointer<vtkBMPReader>::New();
8: reader->SetFileName("../test.bmp");
11: std::cout<<"Reference Count of reader->GetOutput (BeforeAssignment) = "
12: <<reader->GetOutput()->GetReferenceCount()<<std::endl;
14: vtkSmartPointer<vtkImageData> image1 = reader->GetOutput();
15: std::cout<<"Reference Count of reader->GetOutput (Assignto image1) = "
16: <<reader->GetOutput()->GetReferenceCount()<<std::endl;
17: std::cout<<"Reference Count of image1 = "
18: <<image1->GetReferenceCount()<<std::endl;
20: vtkSmartPointer<vtkImageData> image2 = reader->GetOutput();
21: std::cout<<"Reference Count of reader->GetOutput (Assignto image2) = "
22: <<reader->GetOutput()->GetReferenceCount()<<std::endl;
23: std::cout<<"Reference Count of image2 = "
24: <<image2->GetReferenceCount()<<std::endl;
图4.2image1,image2,reader->GetOutput()及引用计数之间的结构关系
4.1.2 智能指针
智能指针会自动管理引用计数的增加与减少,如果检测到某对象的引用计数值减少为0,则会自动地释放该对象的资源,从而达到自动管理内存的目的。
在前面的内容我们已经介绍过,VTK里,要创建一个对象可以用两种方法,一种是使用vtkObjectBase里的静态成员变量New(),用Delete()方法析构;另一种就是我们示例里使用多次的使用智能指针vtkSmartPointer<T>。
对于第一种方法,用New()创建的对象,程序最后必须要调用Delete()方法释放对应的内存,而且由于vtkObjectBase及其子类的构造函数都是声明为受保护的,这意味着它们不能在栈区(栈区上的内存是由编译器自动分配与释放的,堆区上的内存则是由程序员分配和手动释放的。)上分配内存。比如:
vtkBMPReader*reader = vtkBMPReader::New(); //创建vtkBMPReader对象
……
reader->Delete();//程序最后要调用Delete(),这里并没有直接析构对象,而是使引用计数值减1。
用New()创建的对象,如果没有用Delete()方法删除的话,程序有可能会出现内存泄漏,即用户负责对象内存的管理。
如果使用智能指针创建的对象,则无需手动调用Delete()方法让引用计数减少,因为引用计数的增加与减少都是由智能指针自动完成的。使用智能指针时,首先是要包含智能指针的头文件:#include "vtkSmartPointer.h"。vtkSmartPointer是一个模板类,所需的模板参数就是待创建的对象的类名,如:
vtkSmartPointer<vtkImageData>image = vtkSmartPointer< vtkImageData >::New();
注意上面一行代码等号右边写法,不能写为:
vtkSmartPointer<vtkImageData > image = vtkImageData::New();
也就是不能把对象的原始指针赋给智能指针,上行代码编译的时候可以通过,但程序退出时会有内存泄漏,就是因为智能指针无法自动释放该对象的内存,如图4.3所示。
图4.3 将对象的原始指针赋予智能指针会引起内存泄漏
如果没有给对象分配内存,仍然可以使用智能指针,比如:
vtkSmartPointer<vtkBMPReader>reader =vtkSmartPointer<vtkBMPReader>::New();
vtkImageData* imageData=reader->GetOutput();
vtkSmartPointer< vtkImageData> imageData = reader->GetOutput();
第一种情况,当reader超出其作用域时,数据即会被删除;第二种情况,使用了智能指针,所以数据的引用计数会自动加1,除非reader和imageData都超出它们的作用域,数据才会被删除。
vtkSmartPointer<vtkImageData>MyFunction()
vtkSmartPointer<vtkImageData> myObject= vtkSmartPointer<vtkImageData>::New();
vtkSmartPointer<vtkImageData>MyImageData = MyFunction();
函数MyFunction()的返回值是通过拷贝的方式,将数据赋予调用的变量,因此该数据的引用计数保持不变,而且函数MyFunction里的myObject对象也不会删除。
vtkSmartPointer< vtkImageData >MyObject = vtkSmartPointer< vtkImageData >::New();
vtkImageData* MyImageData = MyFunction();
智能指针类型也可以作为类的成员变量,而且会使得类在析构时更加容易,不用人为去做任何释放内存的事情,把这些工作都交给智能指针来完成,例如:
vtkSmartPointer<vtkFloatArray>Distances;
Distances = vtkSmartPointer<vtkFloatArray>::New();
智能指针有一个让人困惑的地方:当你创建一个智能指针类型的对象,然后改变它的指向,这时引用计数就会出错。例如:
vtkSmartPointer<vtkImageData>imageData = vtkSmartPointer<vtkImageData>::New();
imageData= Reader->GetOutput();
上面两行代码里,我们首先创建一个imageData,并给他分配好了内存,接着我们又把imageData指向Reader的输出,而不是一直指向我们创建的那块内存。对于这种情况,我们只要简单地调用:
vtkImageData*imageData = Reader->GetOutput();
这里没有必要使用智能指针,因为我们没有实际创建任何新的对象。
综上所述,可以看出引用计数和智能指针是息息相关的,它们主要都是用于内存管理。使用智能指针可以免去很多手动删除变量的烦恼,所以在本教程里,我们从一开始都使用智能指针来创建VTK对象。如果你想了解更多关于引用计数和智能指针的内容,可以参考C++的经典著作《More Effective C++》这本书。
4.1.3 运行时类型识别 (Run-Time Type Information,RTTI)
在C++里,对象类型是通过typeid (需要包含头文件#include<type_info>)获取的;VTK里在vtkObjectBase定义了获取对象类型的方法:GetClassName()和IsA()。GetClassName()返回的是该对象类名的字符串(VTK用类名来识别各个对象),如:
vtkSmartPointer<vtkBMPReader>Reader = vtkSmartPointer<vtkBMPReader>::New();
constchar* type = Reader->GetClassName(); //返回“vtkBMPReader”字符串
IsA()方法用于测试某个对象是否为指定字符串的类型或其子类,比如:
if(Reader->IsA(“vtkImageReader”) ) {……}; // 这里IsA()会返回真。
类比C++里的操作RTTI操作符,除了typeid之外,还有dynamic_cast,主要用于基类向子类的类型转换,称为向下转型。VTK里同样提供了类似的方法,也就是vtkObject里定义的SafeDownCast(),它是vtkObject里的静态成员函数,意味着它是属于类的,而不是属于对象的,即可以用vtkObject::SafeDownCast()直接调用,比如:
vtkSmartPointer<vtkImageReader>ReaderBase = vtkSmartPointer<vtkImageReader>::New();
vtkBMPReader*bmpReader = vtkBMPReader::SafeDownCast(ReaderBase);
与dynamic_cast类似,SafeDownCast也是运行时才转换的,这种转换只有当bmpReader的类型确实是ReaderBase的派生类时才有效,否则返回空指针。
除了运行时类型识别,vtkObjectBase还提供了用于调试的状态输出接口Print()。虽然vtkObjectBase里除了Print()还提供PrintSelf()、PrintHeader()、PrintTrailer()等公共接口,但在调试VTK程序时,如果需要输出某个对象的状态信息时,一般都是调用Print()函数,如:
bmpReader->Print(std::cout);
4.1.4 关于vtkObject的两三事
第一件事是,vtkObject里定义了与程序调试相关的一些公共接口,包括:
GetDebug() / SetDebug(unsignedchar)
SetGlobalWarningDisplay(int) / GetGlobalWarningDisplay()
GlobalWarningDisplayOn() / GlobalWarningDisplayOff()
其中后四个是静态成员函数。我们可以通过示例(4.1.4_vtkObjectDemo)来看看vtkObject做的第一件事到底是什么。
1: #include"vtkSmartPointer.h"
3: #include "vtkImageViewer2.h"
4: #include"vtkRenderWindowInteractor.h"
6: int main(int argc, char*argv[])
8: vtkSmartPointer<vtkBMPReader> reader =vtkSmartPointer<vtkBMPReader>::New();
9: reader->SetFileName("../monochrome.bmp");
13: //reader->GlobalWarningDisplayOff();
16: vtkSmartPointer<vtkImageViewer2> viewer =vtkSmartPointer<vtkImageViewer2>::New();
17: viewer->SetInput(reader->GetOutput());
19: vtkSmartPointer<vtkRenderWindowInteractor> interactor =
20: vtkSmartPointer<vtkRenderWindowInteractor>::New();
21: viewer->SetupInteractor(interactor);
因为VTK不支持1位BMP图像的读取,所以调用reader->GetOutput()时不能得到正确的输出,最终会导致程序在关闭时,出现内存溢出的错误,如图4.5所示。
图4.5 示例4.1.4_vtkObjectDemo程序退出时内存溢出错误
注:示例4.1.4_vtkObjectDemo也可以作一下更改,以便让它支持命令行参数,作为BMP图像的浏览器,修改后的工程为BMPImageViewer。比如在CMD窗口里输入命令:
如果你感觉程序运行时后面老是跟着控制台窗口,让你觉得不爽的话,你可以在main()函数之前加入下面的语句隐藏控制台窗口,一般建议在发布程序之前不要隐藏,方便调试程序时随时输出调试信息。
#pragmacomment(linker,"/subsystem:\"windows\"/entry:\"mainCRTStartup\"")
vtkObject的第二件事是,实现观察者/命令(Observer/Command)设计模式。本教程不是专门介绍设计模式的,如果你想更深入地了解Observer/Command设计模式,可以翻翻相关的书籍,下面只是就这两种设计模式蜻蜓点水般的做一概述。
如果你还是觉得抽象的话,我们就看看下面的示例(ObserverCommandDemo.cpp):
1: #include"vtkSmartPointer.h"
3: #include"vtkImageViewer2.h"
4: #include"vtkRenderWindowInteractor.h"
5: #include"vtkCallbackCommand.h"
11: void MyCallbackFunc(vtkObject*, unsigned long eid, void* clientdata,void *calldata)
13: std::cout<<"You have clicked:"<<++pressCounts<<" times."<<std::endl;
16: int main(int argc, char*argv[])
18: vtkSmartPointer<vtkBMPReader> reader =
19: vtkSmartPointer<vtkBMPReader>::New();
20: reader->SetFileName("../test.bmp");
23: reader->GlobalWarningDisplayOff();
26: vtkSmartPointer<vtkImageViewer2> viewer =
27: vtkSmartPointer<vtkImageViewer2>::New();
28: viewer->SetInput(reader->GetOutput());
30: vtkSmartPointer<vtkRenderWindowInteractor> interactor =
31: vtkSmartPointer<vtkRenderWindowInteractor>::New();
32: viewer->SetupInteractor(interactor);
36: vtkSmartPointer<vtkCallbackCommand> mouseCallback =
37: vtkSmartPointer<vtkCallbackCommand>::New();
38: mouseCallback->SetCallback ( MyCallbackFunc );
40: //第三步,将vtkCallbackCommand对象添加到观察者列表。
41: interactor->SetRenderWindow(viewer->GetRenderWindow());
42: interactor->AddObserver(vtkCommand::LeftButtonPressEvent,mouseCallback);
示例ObserverCommandDemo运行结果如图4.6所示。
图4.6 示例ObserverCommandDemo运行结果
从示例ObserverCommandDemo可以看到,VTK里使用事件回调函数时,需要分三步走。首先,定义回调函数。回调函数的签名只能是以下形式:
void MyCallbackFunc(vtkObject*obj,unsigned long eid, void* clientdata, void *calldata)
其次是创建一个vtkCallbackCommand对象,并调用vtkCallbackCommand::SetCallback()设置第一步定义的回调函数。
unsigned longAddObserver (unsigned long event, vtkCommand *, float priority=0.0f)
第一个参数是要监听的事件,这些事件定义在vtkCommand类里,如:
示例中我们监听的是鼠标左键的单击事件,即LeftButtonPressEvent。
AddObserver的第二个参数是vtkCommand类型的指针,即我们创建的已经绑定回调函数的vtkCallbackCommand对象。第三个参数是设置命令响应的优先权。
AddObserver函数的返回值是unsigned long型的,可以用于把观察者从观察者列表中删除RemoveObserver(eventID)。
示例ObserverCommandDemo监听交互过程中的鼠标左键单击事件,如果监听到该事件,就在控制台中输出目前为止鼠标的单击次数,该示例仅仅是为了演示观察者/命令模式的工作方式,除此之外没有任何实用价值。除了用以上介绍的回调函数形式来完成事件/回调的工作,同样也可以用类(派生自vtkCommand)的形式来完成。示例ObserverCommandDemo2里,我们会看到稍微复杂一点的应用。
1: #include"vtkSmartPointer.h"
3: #include"vtkPolyDataMapper.h"
4: #include"vtkRenderWindow.h"
5: #include"vtkRenderWindowInteractor.h"
12: #include"vtkInteractorStyleTrackballCamera.h"
15: class vtkMyCallback : publicvtkCommand
18: static vtkMyCallback *New()
19: { return newvtkMyCallback; }
21: virtual voidExecute(vtkObject *caller, unsigned long eventId, void* callData)
23: vtkTransform *t =vtkTransform::New();
24: vtkBoxWidget *widget =reinterpret_cast<vtkBoxWidget*>(caller);
26: widget->GetProp3D()->SetUserTransform(t);
33: vtkSmartPointer<vtkConeSource> cone =vtkSmartPointer<vtkConeSource>::New();
38: vtkSmartPointer<vtkPolyDataMapper> coneMapper =
39: vtkSmartPointer<vtkPolyDataMapper>::New();
40: coneMapper->SetInputConnection( cone->GetOutputPort() );
42: vtkSmartPointer<vtkActor> coneActor = vtkSmartPointer<vtkActor>::New();
43: coneActor->SetMapper(coneMapper );
45: vtkSmartPointer<vtkRenderer> ren1=vtkSmartPointer<vtkRenderer>::New();
46: ren1->AddActor(coneActor );
47: ren1->SetBackground(0.1, 0.2, 0.4 );
49: vtkSmartPointer<vtkRenderWindow> renWin =
50: vtkSmartPointer<vtkRenderWindow>::New();
51: renWin->AddRenderer(ren1 );
52: renWin->SetSize( 300,300 );
54: vtkSmartPointer<vtkRenderWindowInteractor> iren =
55: vtkSmartPointer<vtkRenderWindowInteractor>::New();
56: iren->SetRenderWindow(renWin);
58: vtkSmartPointer<vtkInteractorStyleTrackballCamera> style =
59: vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
60: iren->SetInteractorStyle(style);
62: //通过vtkBoxWidget可以控制coneActor的变换矩阵,从而实现coneActor的形变
63: vtkSmartPointer<vtkBoxWidget> boxWidget =vtkSmartPointer<vtkBoxWidget>::New();
64: boxWidget->SetInteractor(iren);
65: boxWidget->SetPlaceFactor(1.25);
66: boxWidget->SetProp3D(coneActor);
70: vtkSmartPointer<vtkMyCallback> callback =vtkSmartPointer<vtkMyCallback>::New();
73: boxWidget->AddObserver(vtkCommand::InteractionEvent,callback);
75: //激活Widget。按“i”键可以关闭或激活Widget。
virtual voidExecute(vtkObject *caller, unsigned long eventId,void *callData) = 0;
void MyCallbackFunc(vtkObject*obj,unsigned long eid, void* clientdata, void *calldata)
第二步(69-70行),实例化一个vtkMyCallback对象。
示例ObserverCommandDemo2运行结果如图4.7。
图4.7ObserverCommandDemo2运行结果(按“i”键可以关闭或激活Widget)
Observer/Command模式在VTK里的应用是非常广泛也是非常重要的,可以用回调函数或者类的形式来实现,以上的内容已经给出比较详细的介绍,使用VTK的话,这两种方式是没有理由不掌握的。
==========欢迎转载,转载时请保留该声明信息==========
版权归@东灵工作室所有,更多信息请访问东灵工作室
教程系列导航:http://blog.csdn.net/www_doling_net/article/details/8763686
04-VTK可视化管线(1)相关推荐
- Cesium开发基础篇 | 04空间数据可视化之Entity
前面介绍了Cesium如何加载影像数据.地形数据.以及矢量数据,但是作为一个完整的三维系统,仅仅包括这些数据还是远远不够的.当然,还需要一些其他数据,比如空间可视化数据.三维数据数据等,今天我们先从空 ...
- 阿里云服务器ubuntu18.04安装可视化界面
这里写目录标题 一.远程连接ubuntu 二.安装可视化界面 三.安装过程中的问题 一.远程连接ubuntu 输入root密码即可(这个为实例密码) 二.安装可视化界面 依次输入以下shell命令 a ...
- Ubuntu16.04 tensorflow可视化工具---tensorboard
在有events.out的文件夹下打开终端, 在终端输入 tensorboard --logdir=/home/username/Mask_RCNN/logs/balloon20181015T1514 ...
- VTK修炼之道60:体绘制_体绘制管线图形渲染管线
1.几何渲染与体绘制 1.1 几何渲染 前面练习的渲染技术都是几何渲染技术.所谓的几何渲染技术,就是通过绘制几何图元(顶点.线段.面片等)来渲染数据,例如:绘制图像需要在空间中建立一个四边形图元,然后 ...
- 基于VTK的MFC应用程序开发(3)
基于VTK的MFC应用程序开发(3) 分类: VTK应用示例 2013-05-17 13:37 3307人阅读 评论(23) 收藏 举报 目录(?)[+] 之前介绍了基于VTK的单文档应用程序开发,并 ...
- 6、VTK基本数据结构
我们已经学习了VTK的一个重要概念--可视化管线,了解了VTK数据的流动过程.好比我们做一道菜,在做每一道菜之前,首先要掌握这道菜的做法,什么时候放盐什么时候放酱油等调料,除了需要弄清楚做每一道菜的流 ...
- 一个稍微复杂的VTK程序
3.VTK基础概念 在第2章里,我们已经接触了一个简单的VTK工程,也掌握了怎么使用CMake来构建VTK工程的步骤,本书后续章节的所有例子都是采用第2章介绍的步骤来构建VTK的工程. 本章我们先在第 ...
- VTK修炼之道13:数据读写_图像数据的读写
1.前言 VTK应用程序所需的数据可以通过两种途径获取: 第一种是生成模型 ;第二种是从外部存储介质里导入相关的数据文件,(如vtkBMPReader读取 BMP图像) .VTK 也可以将程序中处理完 ...
- VTK修炼之道11:基本数据结构_数据对象数据集
1.前言 前面学习了VTK的一个重要概念--可视化管线,了解了VTK数据的流动过程.好比我们做一道菜,在做每一道菜之前,首先要掌握这道菜的做法,什么时候放盐什么时候放酱油等调料,除了需要弄清楚做每一道 ...
最新文章
- 用VirtualBox在XP环境下虚拟Ubuntu的过程
- 018_rate评分
- oracle中order by 2,关于oracle中ROWNUM和ORDER BY的问题(2)
- 莱洛三角形和定宽曲线
- android:layout_marginbottom=,Android: Retrieve layout_marginBottom programmatically?
- 信号的概念以及网络布线
- 解析极限编程--Kent Beck, Cynthia Andres读后感
- charset参数 sqluldr2_SQL*Loader 的使用sqlldr和sqluldr2方法详解
- 测试显卡矿卡用什么软件,3分钟看懂:AMD二手矿卡简明鉴别、检测教程,从此脱坑不求人...
- SpringBoot项目配置明文密码泄露问题处理
- 利用matlab符号变量进行矩阵乘法公式推导
- 判断一个数是否为素数
- python神奇的小海龟_Python绘图——认识turtle小海龟
- 纪念愚人节微博禁止评论
- 数字图像处理Python语言实现-图像增强-导向滤波(Guided Filter)
- 一个可以截取其他App素材的办法Visual Studio Code
- AsyncTask用法
- 阿里云ECS部署Docker
- 云计算机有辐射吗,电脑休眠还有辐射吗
- CF4A Watermelon(洛谷水题记)
热门文章
- mysql alter engine_MySQL_mysql下修改engine引擎的方法,修改my.ini,在[mysqld]下加上 - phpStudy...
- python cs开发框架_我的第一个python web开发框架(24)——系统重构与ORM
- Django1.11 扩展User属性增加头像上传功能
- 一机玩转docker之十:创建及使用ssh镜像
- 【转】iOS开发-Protocol协议及委托代理(Delegate)传值
- Django admin coercing to Unicode: need string or buffer, tuple found
- nginx的小总结(二)
- 使用Cacti监测系统与网络性能(3)
- 卷积神经网络原理_怎样设计最优的卷积神经网络架构?| NAS原理剖析
- awstats linux日志分析,(总结)Linux下使用awstats分析Nginx的日志详情