首页 > 代码库 > 第06章-图像处理及可视化(1)

第06章-图像处理及可视化(1)

【译者:这个系列教程是以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

手动创建图像数据非常简单,只需要定义图像的维数,原点和间距。原点是图像左下角点的世界坐标系位置。维数是沿着三个主轴方向上体素或者像素的个数。间距则是体素的长、宽和高,或者是每个方向上相邻像素相邻像素的距离,这将区别与你将图像像素看做是同类的盒子或者是连续函数的采样点。

在第一个例子中我们假设一个包含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();