【译者:这个系列教程是以Kitware公司出版的《VTK User’s Guide -11th edition》一书作的中文翻译(出版时间2010年,ISBN: 978-1-930934-23-8),因为时间关系,我们不能保证每周都能更新本书内容。但尽量做到一周更新一篇到两篇内容。敬请期待^_^。
欢迎转载。另请转载时注明本文出处。谢谢合作!同一时候,因为译者水平有限,出错之处在所难免,欢迎指出订正!】
【本节相应原书中的第119页至第125页】
第6章图像处理及可视化
图像数据,如图6-1所看到的,在拓扑上和几何上都是规则的,VTK中用vtkImageData表示。规则数据意味着能够用较少的參数如原点,间距和维数就能够隐式定义每一个数据点的空间位置。
医学及科学扫描设备如CT、MRI、超声仪和共聚焦显微镜等都是产生这样的类型的数据。概念上讲,vtkImageData是由体素(vtkVoxel)或者像素(vtkPixel)单元组成。然而,这样的数据的规则特性使得能够通过一个简单的数组来存储数据,而不是显示的定义vtkVoxel或者vtkPixel单元。
图6-1 vtkImageData结构依据图像的维数、像素间距和原点来定义。维数是每一个轴上体素或者像素的个数。
原点是第一页图像左下角点的世界坐标。像素间隔则是每一个轴上相邻像素的距离。
在VTK中图像数据是一类比較特殊的数据类型,它能够由多种方式进行处理和渲染。
尽管没有严格的定义。VTK中处理图像数据的大部分算子能够分为三类:图像处理,几何提取或者是渲染。
图像处理Filter数据比較多,他们接收vtkImageData输入,输出的结果也是vtkImageData类型。几何提取Filter是将vtkImageData转换为vtkPolyData类型。比如,vtkContourFilter从图像数据中提取由三角面片组成的等值面。最后还有很多不同的Mapper和专门的Actor来渲染vtkImageData,从简单的二维图像渲染到体绘制。
本章中我们主要学习一些重要的图像处理技术。
我们将讨论主要的图像显示、处理和高程图几何提取技术。其它的几何提取技术如等值面已在第5章中介绍。vtkImageData和vtkUnstructuredGrid数据的体绘制技术将在第7章中解说。
6.1 手动创建vtkImageData
手动创建图像数据很easy,仅仅须要定义图像的维数,原点和间距。
原点是图像左下角点的世界坐标系位置。维数是沿着三个主轴方向上体素或者像素的个数。间距则是体素的长、宽和高,或者是每一个方向上相邻像素相邻像素的距离,这将差别与你将图像像素看做是同类的盒子或者是连续函数的採样点。
在第一个样例中我们如果一个包括size[0]*size[1]*size[2]个元素的数组“data”。该数据在VTK外部生成,如今我们须要将其读入到vtkImageData数据中以便用VTK Filter进行处理或者渲染操作。因为我们仅仅是将数据内存指针传到VTK中,因此须要我们自己控制数据内存的释放。
vtkUnsignedCharArray*array = vtkUnsignedCharArray::New();
array->SetArray(data,size[0]*size[1]*size[2], 1);
接下来创建图像。我们必须保证各个数值的匹配-数据类型必须是标量类型,并且数据长度必须与图像的维数一致。
imageData= vtkImageData::New();
imageData->GetPointData()->SetScalars(array);
imageData->SetDimensions(size);
imageData->SetScalarType(VTK_UNSIGNED_CHAR);
imageData->SetSpacing(1.0,1.0, 1.0);
imageData->Set Origin(0.0, 0.0, 0.0);
因为图像维数、原点和间距隐式的定义图像数据的几何和拓扑结构。因此表示结构的数据存储需求就很小。另外。因为数据的规则分布也使得该结构上的计算比較高速。真正须要内存的则是数据集上的属性数据。
以下样例中,我们将用C++语言来创建图像。这次不是直接创建数据数组并将其指定到一个图像中,而是用vtkImageData对象自己主动创建标量数据。这也将排除掉数据数组的大小与图像维数不一致的问题。
//Createthe image data
vtkImageData*id = vtkImageData::New();
id->SetDimensions(10,25, 100);
id->SetScalarTypeToUnsignedShort();
id->SetNumberOfScalarComponents(1);
id->AllocateScalars();
//Fillin scalar values
Unsignedshort *ptr = (unsigned shor*)id->GetScalarPointer();
for(int i=0; i<10*25*100; i++)
{
*ptr ++= i;
}
在这个样例中AllocateScalars()方法用来为图像分配内存。须要注意的是,这种方法调用之前,必须先设置标量类型(scalar type)和标量成分的个数(最多4个标量成分)。然后GetScalarPointer()方法。并将其返回的void*结果强制转换成unsigned short类型。
我们仅仅能在已知数据类型为unsigned short时才干这样做。
RequestData()函数查询标量数据类型,然后然后依据类型选择模板函数来实现。VTK在设计时就尽量避免在公有接口中暴露标量类型为模板參数。这样就为那些缺少模板的语言如Tcl,Java和Python等提供了一个简单的接口函数。
6.2图像降採样
如103页“提取单元子集”。提取部分数据是常见的需求。vtkExtractVOI能够提取输入图像的部分数据。
这个Filter也能够对图像降採样。尽管vtkImageReslice(后面会介绍)能够更方便的对数据重採样。其输出类型是vtkImageData。
VTK中有两个类似的Filter来运行裁剪功能:vtkExtractVOI和vtkImageClip。
之所以分为两个Filter,是因为历史原因——图像管线与图形管线分开,在图像管线中vtkImageClip仅仅处理vtkImageData数据。而在图形管线中vtkExtractVOI仅仅处理vtkStructuredPoints数据。如今这些差别已经没有了,可是这两个Filter之间另一些不同。
vtkExtractVOI提取一个体数据的子区域。将其输出为一个vtkImageData。另外,vtkExtractVOI也能够在感兴趣区域VOI中进行重採样。
另一方面,vtkImageClip默认情况下会保持输出图像数据和输入一致,除了图像范围信息。能够通过设置该filter的一个标志来强制产生精确地数据量,这样的情况下相应区域也会直接复制到输出vktImageData中。vtkImageClip不能重採样图像。
以下Tcl实例(取自VTK/Examples/ImageProcessing/Tcl/Contours2D.tcl)说明了怎么样使用vtkExtractVOI。它提取输入图像的局部区域数据,然后对其重採样。输出数据再传递给vtkContourFilter。(你或许会像去掉vtkExtractVOI再比較结果。)
vtkQuadric quadric
quadricSetCoefficients .5 1 .2 0 .1 0 0 .2 0 0
vtkSampleFunction sample
sampleSetSampleDimensions 30 30 30
sampleSetImplicitFunction quadric
sampleComputeNormalsOff
vtkExtractVOI extract
extractSetInputConnection [sample GetOutputPort]
extractSetVOI 0 29 0 29 15 15
extractSetSampleRate 1 2 3
vtkContourFilter contours
contoursSetInputConnection [extract GetOutputPort]
contoursGenerateValues 13 0.0 1.2
vtkPolyDataMapper contMapper
contMapperSetInputConnection [contours GetOutputPort]
contMapperSetScalarRange 0.0 1.2
vtkActor contActor
contActorSetMapper contMapper
注意上面代码中通过指定一个感兴趣区域(0,29,0,29,15,15)来提取原始数据中的一个平面,而沿三个轴方向的採样率也是设置为不同的值。通过指定VOI大小,能够从数据中提取一个区域。甚至一条线或者一个点(VOI设置採用0偏移数值)。
6.3基于标量值的变形
图像数据的一个常见应用是存储高程值。
这样的图像常为称为范围图或者高程图。
图像中每一个像素的标量值表示一个高程值或者范围值。而可视化中一个常见的目的就是将这样的图像显示成为一个精确地三维高程表示。图6-2中显示了一个依据高程值显示的图像。左边图像为原始图像,右边图像则是由原始图像产生的三维曲面。
图6-2 Image warped by scalar values
高程图可视化管线比較简单,可是有一个重要的概念须要理解。原始图像中隐藏了几何和拓扑结构。而图像经Warpping操作后会产生一个三维几何曲面。为了支持该操作。我们首先利用vtkImageDataGeometryFilter将图像转换成vtkPolyData类型,然后运行Warp操作并连接到mapper上。以下脚本中你会注意到我们用到了vtkWindowLevelLookupTable来提供一个灰度颜色查找表,代替默认的红到蓝颜色查找表。
vtkImageReader reader
readerSetDataByteOrderToLittleEndian
readerSetDataExtent 0 63 0 63 40 40
readerSetFilePrefix “$VTK_DATA_ROOT/Data/headsq/quarter”
readerSetDataMask 0x7fff
vtkImageDataGeometryFilter geometry
geometrySetInputConnection [reader GetOutputPort]
vtkWarpScalar warp
warpSetInputConnection [geometry GetOutputPort]
warpSetScalarFactor 0.005
vtkWindowLevelLookupTable wl
vtkPolyDataMapper mapper
mapperSetInputConnection [warp GetOutputPort]
mapperSetScalarRange 0 2000
mapperImmediateModeRenderingOff
mapperSetLookupTable wl
vtkActor actor
actorSetMapper mapper
这个样例经常会结合其它技术使用。
假设你想使用图像的标量值进行Warp,然后使用其它的标量场对其进行着色。还有一个经常使用的操作是对产生的曲面消减多边形个数。由于由图像产生的结果往往含有大量的多边形面片。能够使用vtkDecimatePro来消减面片数量。
另外还能够考虑使用vtkTriangleFilter和vtkStripper来将多边形网格转换为三角形网格。这样能够提高渲染速度,降低内存占用。
6.4图像显示
VTK中显示图像有多种方式,本节中将介绍两种经常使用的方式。
体绘制技术用来直接绘制三维图像。其细节将在第7章中介绍。
图像浏览器
vtkImageViewer2类代替了早期版本号的vtkImageViewer。能够方便的显示图像。vtkImageViewer2类内部包括了vtkRenderWindow,vtkRenderer, vtkImageActor和vtkImageMapToWindowLevelColors对象,能够方便的在用户应用程序中调用。这个类也依据图像来实例化一个交互器类型(vtkInteractorStypeImage),提供了图像放缩、拉伸和交互式的窗宽/窗位调节(參考43页“交互器类型”和283页“vtkRenderWindow交互器类型”)。vtkImageViewer2(与vtkImageViewer不同)採用的是3D渲染和纹理映射技术将图像绘制到平面上,从而方便了高速渲染,放缩和拉伸。依据特定的图像切片所在的深度坐标。图像显示在三维空间中。
每次调用SetSlice()函数都会更改显示的图像切片和相应的三维空间深度。该功能通过InteractorStyle中的AutoAdjustCameraClippingRange选项来控制。你还能够设置方向来显示XY,YZ或者XZ方向切片。
利用图像浏览器来显示三维图像切片的样例能够參考Widgets/Testing/CXX/TestingImageActorContourWidget.cxx。
以下代码演示了该类的一个典型用法。
vtkImageViewer2 *ImageViewer =vtkImageViewer2::New();
ImageViewer->SetInput(shifter->GetOutput());
ImageViewer->SetColorLevel(127);
ImageViewer->SetColorWindow(255);
ImageViewer->SetupInteractor(iren);
ImageViewer->SetSlice(40);
ImageViewer->SetOrientationToXY();
ImageViewer->Render();
该类也支持图像和几何图形的混合显示。比如:
viewer->SetInput( myImage );
viewer->GetRenderer()->AddActor( myActor );
这样能够用一些边来凝视图像或者高亮显示部分图像或者同一时候显示图像的一个切片和等值面等等。
全部在当前显示的图像切片前面的几何图形都是可见的,而后面的部分则被遮挡。
Window-level传输函数定义如图6-3。Level值是位于window中央的值。窗宽(window)则是用来映射显示的数据范围。传输函数的斜率决定了终于图像的对照度。全部位于window以外的数据都会截取到windows边界值。
图6-3 窗宽窗位传输函数
图像Actor
当你想在一个窗体中显示图像以及一些简单的2D凝视时,能够使用vtkImageViewer类。而当你想在3D渲染窗体中显示图像时,须要使用的是vtkImageActor。创建一个多边形来表示图像边界,然后通过纹理映射将图像拷贝到多边形上来显示显示图像。在大部分的平台下,在实时进行旋转、拉伸和放缩图像时须要对图像进行双线性插值。假设将其交互器替换为一个vtkInteractorStyleImage类型时。旋转操作就能够禁用。这样三维渲染窗体操作就如同二维图像浏览器。
vtkImageActor是一个包括Actor和Mapper对象的合成类。它的使用很的简单。例如以下例所看到的。
vtkBMPReader bmpReader
bmpReader SetFileName “$VTK_DATA_ROOT/Data/masonry.bmp”
vtkImageActor imageActor
imageActor SetInput [ bmpReader GetOutput ]
图像Actor能够通过AddProp()函数加入至renderer中。vtkImageActor类期望输入的图像在一个方向上长度为1。而在其它两个方向上扩展。这样假设裁剪操作是沿着X或者Y轴时,就不须要又一次组织数据,从而使vtkImageActor通过一个裁剪Filter与一个Volume连接。
vtkImagePlaneWidget
Widget会在255页“Interaction,widgets and Selections”中讲述。这个widget能够交互地放置到图像中,并通过该平面来显示又一次生成的图像切片。
生成切片数据时採用的插值选项包含近期邻、线性和立方插值。平面的位置和方向能够进行交互控制。
当然也能够交互地控制新切片的窗宽窗位,而且能够选择性的显示窗宽窗位和位置凝视。
vtkImagePlaneWidget* planeWidgetX =vtkImagePlaneWidget::New();
planeWidgetX->SetInteractor(iren);
planeWidgetX->RestrictPlaneToVolumeOn();
planeWidgetX->SetResliceInterpolateToNearestNeighbour();
planeWidgetX->SetInput(v16->GetOutput());
planeWidgetX->SetPlaneOrientationToXAxes();
planeWidgetX->SetSliceIndex(32);
planeWidgetX->DisplayTextOn();
planeWidgetX->On();