作为一个成熟的图形可视化函数库,VTK可以在处理三维重建问题时有方便的处理流程。以下是为了快速使用VTK而做的一些总结。
一、两个基本的对象模型
1、绘图模型
绘图模型包括以下核心对象:
●vtkActor/vtkActor2D、vtkVolume——vtkProp/vtkProp3D 的子类;
●vtkLight
●vtkCamera
●vtkProperty/vtkProperty2D
●vtkMapper/vtkMapper2D——vtkAbstractMapper 的子类
●vtkTransform
●vtkLookupTable、vtkColorTransferFunction——vtkScalarsToColors 的子类
●vtkRenderer
●vtkRenderWindow
●vtkRenderWindowInteractor
将以上几个对象结合起来我们就创建了一个场景,Props 代表我们在这个场景里面所看到的东西,如果 Props 按照 3D 处理,那么他是 Prop3D 类型,2D 的话就是 Actor2D 类型,Props 并不直接代表几何学的东西,而是涉及绘图,它也涉及一个 Property 对象,这个对象是用来控制 Props 的外观的(如颜色等),Actors 和 Volumes 有一个内部的变换对象,vtkTransform,这个对象压缩 4*4 的变换矩阵来控制 Props 的位置、方向。
vtkLight 用来代表和处理一个场景的光效。(仅限于 3D 情形)
vtkCamera 控制 3D 图形如何投影到 2D 图像,它有多种方法定位、指向、定向 3D 图形,另外它还可以控制透视投影和立体视角。(仅用于 3D)
vtkMapper 与 vtkLookupTable 关联一起用于变换和绘制几何图形,Mapper 提供可视化管道和绘图模型之间的接口,vtkRenderer 和 vtkRenderWindow 一起用于管理绘图引擎和电脑视窗之间的接口,绘图窗口是 Renderer 向电脑绘图的窗口,可以将多个 Renderer 绘制在一个绘图窗口,因而可以创建一个多视图的绘图窗口。
一旦网绘图窗口绘制对象就意味着需要与数据进行交互,vtkRenderWindowActor 是常用的一种方法,它可以操纵 Camera、选择对象、进入/推出立体视角、改变对象的一些属性。
2、可视化模型
绘图管道的作用是将绘图数据转化为图像,那么可视化管道的作用是将信息转化为绘图数据,因此它可以看作是为绘图模块提供原始数据接口。
下面我们关注一下两个基本的类:
●vtkDataObject
●vtkProcessObject
DataObject 代表不同类型的数据,他可以看成是数据“滴”,具有正规结构的数据通常指 Dataset (vtkDataSet) , DataObject 由绘图结构和拓扑结构组成,也可与属性数据(如 Points、Cells)发生联系, Cells 是点的拓扑结构组织,它形成 Dataset 的原结构,通常用来插入 Points之间。
ProcessObject 指滤波器,用来操作数据对象产生新的数据对象,它代表系统的运算法则。
这两个类结合起来形成了 VTK 的可视化管道。
有几个重要的论点解释可视化管道:
第一,管道拓扑可以用多种方法来解释。
aFilter->SetInput(anotherFilter->GetOutput());
第二,必须有一个机制来控制管道的执行。
第三,管道的配置要求只有那些相互兼容的对象才能结合用于 SetInout()、GetOutput()方法。
最后,我们必须决定当管道执行完毕时是否隐藏、保留那些数据对象。
(1)管道的执行
前面已经提到,可视化管道只有在数据需要计算时才开始执行(懒惰赋值),例如:
vtkPoly3DReader reader
reader SetXYZFilename ”Filename”
[reader GetOutput] GetNumberOfPoints
这时的返回值为 0(当前点数),因为数据并不需要计算。
如果加上:
reader-〉Update;
[reader GetOutput] GetNumberOfPoints;
这时才会返回真是值。
Pipeline Execution
<--------------------------------Update()方法的方向--------------------------------------------------
Source--->Filter---->Mapper------>Actor<-------------Render()
-------------------------------数据生成方向------------------------------------------->
通常不需手动激活Update()方法:滤波器是嵌入在可视化管道内,其中的Render方法经常用于初始化数据请求。
(2)图像处理
一个图像数据集是一个规范的、按轴排列的数据矩阵,像素映射、位图是 2D 图像的例子,体积(2D 图像的堆栈)是 3D 图像的例子。
二、使用基础
1、简单模块
VTK 的使用不外乎以下几个步骤:读取或产生数据、筛选或过滤数据、呈现数据、与
之交互。
首先是读取或产生数据:
数据的获取有两种基本的方法:一是从文件读取,二是用程序产生数据。产生数据的对象叫做 Procedural Source Object,读取数据的对象叫做 Reader Source Object。
(1)Procedural Source Object
下面举个例子说明如何应用 Procedural Source Object,以下代码十分典型,需要好好揣摩:
vtkCylinderSource *cylinder =vtkCylinderSource::New();
cylinder->SetResolution(100);
vtkCoordinate *pCoord = vtkCoordinate::New ();
pCoord->SetCoordinateSystemToNormalizedViewport ();
vtkPolyData *pPData = vtkPolyData::New();
pPData=cylinder->GetOutput();
vtkPolyDataMapper *pPDMapper = vtkPolyDataMapper::New ();
pPDMapper->SetInput (pPData);
pCoord->Delete();
vtkActor *pActorLine = vtkActor::New ();
pActorLine->SetMapper ( pPDMapper );
pActorLine->RotateX(30.0);
pActorLine->RotateY(-45.0);
pPDMapper->Delete ();
vtkProperty *pProperty = pActorLine->GetProperty ();
pProperty->SetColor ( 0, 0, 1 );
vtkRenderer *pRen = vtkRenderer::New ();
pRen->AddActor ( pActorLine );
vtkRenderWindow *pRWindow = vtkRenderWindow::New ();
pRWindow->AddRenderer ( pRen );
pRWindow->SetSize ( 512, 512 );
vtkRenderWindowInteractor *pRWInteractor = vtkRenderWindowInteractor::New
();
pRWInteractor->SetRenderWindow ( pRWindow );
pRWInteractor->Initialize ();
pRWInteractor->Start ();
其结果如下图所示:
(2)Reader Source Object
下面举个例子说明如何应用 Reader Source Object,以下代码十分典型,需要好好揣摩:
vtkDICOMImageReader * dicom =vtkDICOMImageReader::New();
dicom->SetDataByteOrderToLittleEndian();
dicom->SetFileName("E://FDS//project//my design//test//CT_008.dcm");
dicom->SetDataOrigin(0.0,0.0,0.0);
vtkCoordinate *pCoord = vtkCoordinate::New ();
pCoord->SetCoordinateSystemToNormalizedViewport ();
vtkContourFilter *skinext= vtkContourFilter::New();
skinext->SetInputConnection(dicom->GetOutputPort());
skinext->GenerateValues( 6, -600, 1150);
skinext->Update();
vtkPolyDataMapper *pPDMapper = vtkPolyDataMapper::New ();
pPDMapper->SetInputConnection (skinext->GetOutputPort());
pPDMapper->ScalarVisibilityOn();
pPDMapper->SetScalarRange(skinext->GetOutput()->GetScalarRange());
vtkActor *pActor = vtkActor::New ();
pActor->SetMapper ( pPDMapper );
pPDMapper->Delete ();
vtkProperty *pProperty = pActor->GetProperty ();
pProperty->SetColor ( 0, 0, 0 );
vtkRenderer *pRen = vtkRenderer::New ();
pRen->AddActor ( pActor);
pRen->SetBackground( 0, 0, 0);
vtkRenderWindow *pRWindow = vtkRenderWindow::New ();
pRWindow->AddRenderer ( pRen );
pRWindow->SetSize ( 512, 512 );
vtkRenderWindowInteractor *pRWInteractor= vtkRenderWindowInteractor::New ();
pRWInteractor->SetRenderWindow ( pRWindow );
pRWInteractor->Initialize ();
pR WInteractor->Start ();
其结果如下图所示:
2、交互器
一旦我们将数据可视化,我们就需要与之交互。
一种方法是用 vtkRenderWindowInteractor,另一种方法就是自己创建一个交互类。我们还希望知道如何从屏幕上“选择”数据。
(1)vtkRenderWindowInteractor
按 t 可以锁定对象,使得按左键和右键不再发生变化;
按 a 可以锁定对象,使得在对象之外的窗口按左键或右键不再发生变化;
按左键可以旋转对象;
按右键和滑轮可以缩放对象;
Shift+左键使对象向鼠标位置移动;
按 e/q 退出程序;
按 f 可以使对象滑至鼠标位置(鼠标必须在对象区域之内);
按 p 当鼠标在对象区域时可以选定对象;
按 r 在当前视角重置对象;
按 u 调用自定义的方法进行交互;
当然还有其它很多种交互方法和模式。
(2)Interactors Styles
有两种完全不同的方法可以控制交互方式,一是使用子类 vtkInteractorStyle,二是管理
event loop。
使用子类 vtkInteractorStyle,只需调用 SetInteractorStyle()即可。
管理 event loop 的方法,就是自己捆绑相应的效果到相应的操作上。
3、数据过滤
滤波器是通过使用 SetInput()与 GetOutput()联系起来的,需要注意的是,GetOutput()的结果必须与 SetInput()的参数相匹配。
4、控制 Camera
在 3D 绘图中,视角和光线是 render 对象所必须的。默认情况下, render 会自动创建 camera和 lights 对象。
(1)camera 用法示例
vtkCamera * cam1 = vtkCamera::New();
cam1->SetClippingRange(0.0475572,2.37786);
cam1->SetFocalPoint(0.052665,-0.129454,-0.0573973);
cam1->SetPosition(0.327637,-0.116299,-0.256418);
caml->ComputViewPlaneNormal;
caml->SetViewUp(-0.0225386,0.999137,0.034901);
vtkRender * ren1 = vtkRender::New();
ren1->SetActiveCamera (cam1);
//如果想从一个已经存在的 render 对象中提取 camera,可以这么搞:
cam1 = ren1->GetActiveCamera();
cam1->Zoom(1.4);
下面对以上调用的一些函数做一个简单的说明: SetClippingRange()设置距离视平面最近和最远的夹平面, FocalPoint 和 Position 则是设置 camera 的方向和位置, ComputeViewPlane()则是基于当前的位置和焦点重置视平面的法线,SetViewUp()控制向上的方向,Zoom()通过改变视角放大对象,还可以使用 Dolly()使 camera 沿着视平面法线上下移动,也可以进行可视对象的缩放。 (2)简单的操作方法
上面讲的方法不是总是很方便的,如果想看某些我们想看的点,我们可以使用 Azimuth()和 Elavation()基于焦点移动 camera,例如:
caml->Azimuth(150);
caml->Elavation(60);
以上方法是在以焦点为中心的球坐标系下通过在经纬线方向移动给定角度的方式来移动 camera 的。
(3)控制视角
Camera 的一个常用功能就是在一个特定的方向产生一个视图,我们可以通过调用SetFocalPoint(),SetPosition(),
ComuptViewPlaneNormal(),紧跟 ResetCamera(),为了避免 view-up矢量和视平面法线平行可以调用OrthogonalizeViewUp(),例如:
cam1->SetFocalPoint(0,0,0);
cam1->SetPosition(1,1,1);
cam1->ComputeViewPlaneNormal();
cam1->SetViewUp(1,0,0);
cam1->OrthogonalizeViewUp();
ren1->SetActiveCamera(cam1);
ren1->ResetCamera();
(4)透视与正交视角
在前面的例子中我们假设照相机是可以透视的,即有一个视角控制视平面上对象的发射,这一点在为了产生更自然的图像而引入扭曲时显得不可取,那么正交发射是一个可取的办法,此时视线是平行的不受距离影响的。
通过调用 vtkCamera::ParallelProjectionOn()可以开启正交模式,此时通过此时通过观察角度来实现缩放已经无效,取而代之的是调用 vtkCamera::SetParallelScale()来达到缩放的功能。
(5)、存储 Camera 状态
为了存储一个 Camera 状态必须存储剪辑范围、焦点及其位置、View-Up 矢量、计算视平面法线,如果要恢复 Camera 状态,直接调用一个已经存储的 Camera 状态即可,使用SetActiveCamera()方法即可。
有时还需要存储更多的信息,比如 View angle 或者 Parallel Scale,如果使用了立体视图,还必须存储 EyeAngle 和 Stereo flag。
5、控制灯光Light
最常用的几个方法是 SetPosition(),SetFocalPoint(),SetColor()。
Light 是通过调用 SwitchOn()和 SwitchOff()来开关的,亮度可由 SetIntensity()来控制,Lights 是这样和 Renderer 发生联系的:
vtkLight * light = vtkLight::New();
light->SetColor(1,0,0);
light->SetFocalPoint(cam1->GetFocalPoint());
light->SetPosition(cam1->GetPosition());
ren1->AddLight(light);
这里我们创建了一个红色的前灯,它随着 Camera 一起移动。例如:
上图中亮红色区域是正对灯光的,较暗的区域则是背对灯光的,而且灯光随着相机一起移动。
PositionalOn()与 SetConeAngle()一起使用可以控制斑点灯光的范围,将锥形角设置成为180 度就退化为普通的灯光效果。例如 10 度的情形:
6、控制 3D Props
在一个窗口中显示出来的对象我们通常称之为 Prop ,有不同类型的 Props 包括vtkProp3D 和 vtkActor, vtkProp3D 是一个概要的描述存在于 3D 空间中的对象集合的一个超类,vtkActor 是他的一个子类,用来定义一些简单的解析图形,如多边形和线。
(1)指定一个 Prop3D 类型对象的位置
前面我们已经学会了如何使用 vtkCamera 围绕着一个对象转动,下面我们将学习如何在Camera 固定的前提下确定 Prop3D 的位置。
SetPosition(x,y,z)——在 world 坐标系下确定对象的位置;
AddPosition(δx,δy,δz)——按照给定的增量改变对象当前的位置;
Rotate(α), Rotate(β), Rotate(γ)——按照给定的角度围绕着 x,y,z 轴旋转;
SetOrientation(x,y,z)——通过一次绕着 z,x,y 轴进行旋转来确定对象的指向;
AddOrientation(δx,δy,δz)——增加到当前的指向;
RotateWXYZ(δ,x,y,z)——绕着矢量(x,y,z)旋转δ角度;
Scale(sx,sy,sz)——在 x,y,z 方向确定对象的范围;
SetOrigin(x,y,z)——该点是指旋转、确定范围等操作的原始起点。
这些方法的一个特定组合就对应着一个特定的变换矩阵,VTK 中应用这些方法的顺序是这样的:
其中较复杂的是旋转变换,旋转轴的顺序对结果的影响很大。
下面详细介绍各种不同类型的 Props 3D 类型对象。
(2)Actors
Actor 是最常见的类型,vtkProp3D 的其它一些子类、vtkActor 等描述了诸如表面属性、表示法、纹理图、几何学定义。
Actor 的几何学由 SetMapper()方法指定,用法如下:
vtkPolyDataMapper * mapper = vtkPolyDataMapper::New();
mapper->SetInput( aFilter->GetOutput() );
vtkActor * anActor = vtkActor::New();
anActor->SetMapper(mapper);
vtkPolyDataMapper 类型使用解析元素来描述 actor 的几何学,mapper 终止了可视化管道,提供了可视化子系统和绘图子系统的桥梁。Actor 涉及到 vtkProperty 的一个实例,这个类是用来描述外观的诸如表示法、颜色、阴影方案、不透明性、散播、镜子颜色、相关系数。例如:
vtkActor * anActor = vtkActor::New();
anActor->SetMapper(mapper);
anActor->GetProperty()->SetOpacity(0.25);
anActor->GetProperty()->SetAmbient(0.5);
anActor->GetProperty()->SetDiffuse(0.6);
anActor->GetProperty()->SetSpecular(1.0);
anActor->GetProperty()->SetSpecularPower(10.0);
或者使用一下方式:
vtkProperty * prop = vtkProperty::New();
prop ->SetOpacity(0.25);
prop ->SetAmbient(0.5);
prop ->SetDiffuse(0.6);
prop ->SetSpecular(1.0);
prop ->SetSpecularPower(10.0);
vtkActor * anActor = vtkActor::New();
anActor->SetMapper(mapper);
anActor->SetProperty(prop);
使用后面的方式的好处是我们可以为多个 actor 重复调用一个相同的属性。颜色是 actor 最重要的属性之一了,最简单的方法调用 SetColor()方法,还可以单独的设置周围环境、弥漫、镜子的颜色。
anActor->SetColor(0.1,0.2,0.4);
anActor->SetMapper(mapper);
anActor->GetProperty->SetAmbientColor(0.1,0.1,0.1);
anActor->GetProperty->SetDiffuseColor(0.1,0.2,0.4);
anActor->GetProperty->SetSpecularColor(1,1,1);