vtk用户指南 第六章 图像处理与科学可视化

        由vtkImageData类表示的图像数据集在拓扑和几何上是规则的,如图6-1所示。此数据类型是结构化的,这意味着数据点的位置仅使用几个参数(origin、spacing和dimensions)隐式定义。医学和科学扫描设备,如CT、MRI、超声扫描仪和共聚焦显微镜经常产生这种类型的数据。从概念上讲,vtkImageData数据集由体素(vtkVoxel)或像素(vtkPixel)单元组成。然而,这个数据集的结构化特性允许我们将数据值存储在一个简单的数组中,而不是显式地创建vtkVoxel或vtkPixel单元格。

        在VTK中,图像数据是一种特殊的数据类型,可以用几种方式处理和渲染。虽然不是一个详尽的分类,但在VTK中对图像数据执行的大多数操作都属于三类之一-图像处理,几何提取或直接渲染。有几十种图像处理过滤器可以对图像数据集进行操作。这些过滤器将vtkImageData作为输入并产生vtkImageData作为输出。存在将vtkImageData转换为vtkPolyData的几何提取过滤器。例如,vtkContourFilter可以提取等值轮廓

图像数据集中的三角形补丁。最后,有各种映射器和专门的演员来渲染vtkImageData,包括从简单的2D图像显示到体渲染的技术。
在本章中,我们将研究一些重要的图像处理技术。我们将讨论基本的图像显示、图像处理和高程图的几何提取。其他几何形状提取技术,如等高线将在第5章中介绍。vtkImageData和vtkUnstructuredGrid的体渲染将在第7章中介绍。

6.1手动创建vtkImageData

        创建图像数据很简单:您只需要定义数据集的尺寸、原点和间距。原点是数据集左下角的世界坐标位置。维度是沿着三个主要轴的体素或像素的数量。间距是体素的高度、长度和宽度,或者相邻像素之间的距离,这取决于您是将数据视为同质框还是连续函数中的样本点。

        在第一个例子中,我们将假设我们有一个由变量data指向的无符号字符值数组,并且包含大小为[0]的大小为[1]的大小为[2]的样本。我们在VTK之外生成了这些数据,现在我们想要将这些数据放入vtkImageData中,以便我们可以使用VTK过滤和渲染操作。我们将给VTK一个指向内存的指针,但是我们将自己管理内存的删除。

        我们需要做的第一件事是创建一个无符号字符数组来存储数据。我们使用SetArray()方法来指定指向数据的指针及其大小,最后一个参数表明VTK不应该释放该内存

vtkUnsignedCharArray *array = vtkUnsignedCharArray::New();
array->SetArray( data, size[0]*size[1]*size[2], 1);

第二步是创建图像数据。我们必须注意所有值的匹配——图像数据的标量类型必须是unsigned char,并且图像数据的维度必须与数据的大小匹配。

imageData = vtkImageData::New();
imageData->GetPointData()->SetScalars(array);
imageData->SetDimensions(size);
imageData->SetScalarType(VTK_UNSIGNED_CHAR);
imageData->SetSpacing(1.0, 1.0, 1.0 );
imageData->SetOrigin(0.0, 0.0, 0.0 );

        关于图像数据集的重要之处在于,由于几何和拓扑是由维度、原点和间距隐式定义的,因此表示数据集结构所需的存储空间很小。由于结构排列整齐,计算速度快。需要存储的是与数据集一起的属性数据。

        在下一个示例中,我们将使用c++创建图像数据。我们将使用vtkImageData对象为我们创建标量数据,而不是手动创建数据数组并将其与图像数据相关联。这消除了标量的大小与图像数据的尺寸不匹配的可能性。

// Create the image data
 vtkImageData *id = vtkImageData::New();
 id->SetDimensions(10,25,100);
 id->SetScalarTypeToUnsignedShort();
 id->SetNumberOfScalarComponents(1);
 id->AllocateScalars();
 // Fill in scalar values
 unsigned short *ptr = (unsigned short *) id->GetScalarPointer();
 for (int i=0; i<10*25*100; i++) {
  *ptr++ = i;
 }

        在本例中,使用了方便的方法AllocateScalars()为图像数据分配存储空间。注意,这个调用是在设置了标量类型和标量组件数量之后进行的(最多可以设置四个标量组件)。然后调用返回void*的方法GetScalarPointer(),并将结果强制转换为unsigned short。我们可以这样做,因为我们知道类型是unsigned short,因为我们在前面指定了它。成像滤波器在VTK工作对任何标量类型的图像。它们的RequestData()方法查询标量类型,然后在实现中将该类型转换为模板化函数。VTK通过设计选择避免在其公共接口中将标量类型暴露为模板参数。这使得为包装语言(如Tcl、Java和Python)提供接口变得容易,这些语言缺乏模板。

6.2图像数据的子采样

        正如我们在第103页的“提取单元的子集”中看到的,提取数据集的部分通常是可取的。过滤器vtkExtractVOI提取输入图像数据集的片段。过滤器也可以对数据进行子采样,尽管vtkImageReslice(稍后会介绍)在重新采样数据方面提供了更大的灵活性。过滤器的输出也是vtkImageData类型。

        实际上有两个类似的过滤器在VTK中执行此剪辑功能:vtkExtractVOI和vtkImageClip。两个独立版本的原因是历史-成像管道过去是与图形管道分开的,vtkImageClip只在成像管道中的vtkImageData上工作,vtkExtractVOI只在图形管道中的vtkStructuredPoints上工作。这些区别现在已经消失了,但这些过滤器之间仍然存在一些差异。vtkExtractVOI将提取卷的一个子区域,并产生包含此信息的vtkImageData。此外,vtkExtractVOI可用于重新采样VOI内的卷。另一方面,vtkImageClip默认情况下会将输入数据传递到输出,除了区段信息。可以在此过滤器上设置一个标志,以强制它仅产生精确的数据量,在这种情况下,该区域将被复制到输出vtkImageData中。vtkImageClip过滤器不能重新采样卷。

        下面的Tcl示例(取自VTK/Examples/ImageProcessing/Tcl/ Contours2D.tcl)演示了如何使用vtkExtractVOI。它提取输入体积的一部分,然后对其进行子采样。输出被传递到vtkContourFilter。(您可能想尝试删除vtkExtractVOI并比较结果。)

# Quadric definition
vtkQuadric quadric
    quadric SetCoefficients .5 1 .2 0 .1 0 0 .2 0 0
vtkSampleFunction sample
    sample SetSampleDimensions 30 30 30
    sample SetImplicitFunction quadric
    sample ComputeNormalsOff
vtkExtractVOI extract
    extract SetInputConnection [sample GetOutputPort]
    extract SetVOI 0 29 0 29 15 15
    extract SetSampleRate 1 2 3
vtkContourFilter contours
    contours SetInputConnection [extract GetOutputPort]
    contours GenerateValues 13 0.0 1.2
vtkPolyDataMapper contMapper
    contMapper SetInputConnection [contours GetOutputPort]
    contMapper SetScalarRange 0.0 1.2
vtkActor contActor
    contActor SetMapper contMapper

注意,该脚本通过指定感兴趣的体积(VOI)为(0,29,0,29,15,15)(imin,imax, jmin,jmax, kmin,kmax)从原始数据中提取平面,并且沿每个i-j-k拓扑轴设置采样率不同。您还可以通过修改VOI规范提取子卷,甚至提取线或点。(使用0偏移值指定感兴趣的体积。)

6.3基于标量值的Warp

        图像数据的一个常见用途是将高程值存储为图像。这些图像通常被称为距离图或高程图。图像中每个像素的标量值表示一个高程或范围值。可视化中的一个常见任务是获取这样的图像并将其扭曲以产生精确的高程或距离数据的3D表示。考虑图6-2,它显示了一个基于标量值被扭曲的图像。左图显示的是原始图像,右图显示的是经过翘曲生成3D表面后的图像。图6-2图像被标量值扭曲。

        执行这种可视化的管道相当简单,但是有一个重要的概念需要理解。原始数据是具有隐式几何和拓扑的图像。扭曲图像将导致三维表面,其中几何形状不再隐式。为了支持这一点,我们首先使用vtkImageDataGeometryFilter将图像转换为vtkPolyData表示。然后我们执行warp并连接到映射器。在下面的脚本中,您将注意到我们还使用了vtkWindowLevelLookupTable来提供一个灰度查找表,而不是默认的红到蓝查找表。

vtkImageReader reader
    reader SetDataByteOrderToLittleEndian
    reader SetDataExtent 0 63 0 63 40 40
    reader SetFilePrefix "$VTK_DATA_ROOT/Data/headsq/quarter"
    reader SetDataMask 0x7fff
vtkImageDataGeometryFilter geometry
    geometry SetInputConnection [reader GetOutputPort]
vtkWarpScalar warp
    warp SetInputConnection [geometry GetOutputPort]
    warp SetScaleFactor 0.005
vtkWindowLevelLookupTable wl
vtkPolyDataMapper mapper
    mapper SetInputConnection [warp GetOutputPort]
    mapper SetScalarRange 0 2000
    mapper ImmediateModeRenderingOff
    mapper SetLookupTable wl
vtkActor actor
    actor SetMapper mapper

这个例子经常与其他技术结合使用。如果你想用标量值扭曲图像,然后用不同的标量字段给它着色,你可以使用vtkMergeFilter。另一种常见的操作是减少弯曲表面中的多边形数量。因为这些表面是由图像生成的,它们往往有大量的多边形。您可以使用vtkDecimatePro来减少这个数字。您还应该考虑使用vtkTriangleFilter,然后使用vtkStripper将多边形(正方形)转换为三角形条,这往往渲染更快,消耗更少的内存。

6.4图像显示

有几种方法可以直接显示图像数据。本节将介绍两种通常适用于显示2D图像的方法。体绘制是直接显示三维图像(体)的方法,在第7章中有详细的描述。

图像查看器

        vtkImageViewer2是一个方便的类显示图像。它取代了类vtkImageViewer的早期版本。vtkImageViewer2内部封装了几个对象- vtkRenderWindow, vtkRenderer, vtkImageActor和vtkImageMapToWindowLevelColors,提供了一个易于使用的类可以放入您的应用程序。这个类还创建了一个为图像定制的交互器样式(vtkInteractorStyleImage),它允许缩放和平移图像,并支持图像上的交互式窗口/级别操作。(参见43页的“交互器样式”和283页的“vtkRenderWindow交互样式”了解更多关于交互器样式的信息。)vtkImageViewer2(不像vtkImageViewer)使用3D渲染和纹理映射引擎在平面上绘制图像。这允许快速渲染、缩放和平移。图像被放置在基于特定图像切片的深度坐标的3D场景中。每次调用SetSlice()都会改变显示的图像数据(切片),并改变3D场景中显示的切片的深度。这可以通过InteractorStyle上的AutoAdjustCameraClippingRange选项来控制。您还可以设置方向以显示XY、YZ或XZ切片。

        使用图像查看器浏览卷中的切片的示例可以在Widgets/Testing/Cxx/TestImageActorContourWidget.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 );

        这可以用来用“边缘”的PolyData来注释图像,或者高亮图像的部分,或者用体的切片来显示3D等值面,等等。在显示切片前面的几何体的任何部分将是可见的;在显示切片后面的任何几何部分都将被遮蔽。

        窗口级传递函数的定义如图6-3所示。级别是窗口中心的数据值。宽度(即窗口)定义映射到显示的数据值的范围。所得到的传递函数的斜率决定了最终图像的对比度。窗口外的所有数据值都被固定在窗口边界的数据值上。

Image Actor

        使用vtkImageViewer是方便的,当你只是想在一个窗口中显示图像本身或伴随着一些简单的2D注释。vtkImageActor actor类很有用图6-3窗口级传递函数当你想要在3D渲染窗口中显示你的图像时,使用level window。通过创建一个表示图像边界的多边形,并使用硬件纹理映射将图像粘贴到该多边形上,可以显示图像。在大多数平台上,这使您能够通过双线性插值实时旋转,平移和缩放图像。通过将交互器更改为vtkInteractorStyleImage,您可以限制旋转,以便3D渲染窗口作为2D图像查看器运行。使用3D渲染窗口显示图像的优点是,您可以轻松地将多个图像和复杂的3D注释嵌入到一个窗口中。

        vtkImageActor对象是一个复合类,它将actor和映射器封装到一个类中。如本例所示,它使用起来很简单。

vtkBMPReader bmpReader
bmpReader SetFileName "$VTK_DATA_ROOT/Data/masonry.bmp"
vtkImageActor imageActor
imageActor SetInput [bmpReader GetOutput]

然后可以使用AddProp()方法将这个图像actor添加到渲染器中。vtkImageActor类期望它的输入沿三个维度之一的长度为1,图像沿另外两个维度扩展。这允许vtkImageActor通过使用剪辑过滤器连接到一个卷,而不需要重新组织数据,如果剪辑沿着X或Y轴执行。(注意:输入到vtkImageActor的图像必须是unsigned char类型。如果您的图像类型不同,您可以使用vtkImageCast或vtkImageShiftScale转换为unsigned char。)

vtkImagePlaneWidget

        小部件将在第255页的“交互、小部件和选择”中介绍。只要提到这个小部件定义了一个平面,可以交互式地放置在图像卷中,平面通过该卷显示重切片数据就足够了。插值选项重新切片的数据包括最近邻,线性和立方。平面的位置和方向可以交互操作。我们还可以在重切片平面上交互式地显示窗口级别,并可选地显示窗口级别和位置注释。

vtkImagePlaneWidget* planeWidgetX = vtkImagePlaneWidget::New();
planeWidgetX->SetInteractor( iren);
planeWidgetX->RestrictPlaneToVolumeOn();
planeWidgetX->SetResliceInterpolateToNearestNeighbour();
planeWidgetX->SetInput(v16->GetOutput());
planeWidgetX->SetPlaneOrientationToXAxes();
planeWidgetX->SetSliceIndex(32);
planeWidgetX->DisplayTextOn();
planeWidgetX->On();

6.5图像来源

有一些图像处理对象产生输出,但不接受任何数据对象作为输入。这些被称为图像源,这里描述了一些VTK图像源。
请参阅第444页的“源对象”或氧文档,以获得可用图像源的更完整列表。

ImageCanvasSource2D

        vtkImageCanvasSource2D类创建一个指定大小和类型的空白二维图像,并提供在此空白图像中绘制各种原语的方法。原语包括框、线和圆;还提供了洪水填充操作。下面的示例通过创建一个512x512像素的图像并在其中绘制几个原语来说明该源的使用。如图6-4所示。

#set up the size and type of the image canvas
vtkImageCanvasSource2D imCan
imCan SetScalarTypeToUnsignedChar
imCan SetExtent 0 511 0 511 0 0
# Draw various primitives
imCan SetDrawColor 86
imCan FillBox 0 511 0 511
imCan SetDrawColor 0
imCan FillTube 500 20 30 400 5
imCan SetDrawColor 255
imCan DrawSegment 10 20 500 510
imCan SetDrawColor 0
imCan DrawCircle 400 350 80.0
imCan SetDrawColor 255
imCan FillPixel 450 350
imCan SetDrawColor 170
imCan FillTriangle 100 100 300 150 150 300
#Show the resulting image
vtkImageViewer viewer
viewer SetInputConnection [imCan GetOutputPort]
viewer SetColorWindow 256
viewer SetColorLevel 127.5

ImageEllipsoidSource

        如果您想使用模板化的执行函数编写自己的图像源,vtkImageEllipsoidSource是一个很好的起点。该对象根据中心位置、沿每个轴的半径以及内部和外部值生成椭球的二值图像作为输出。还可以指定输出标量类型,这就是将execute函数模板化的原因。这个源是内部使用的一些成像过滤器,如vtkImageDilateErode3D。

        如果你想创建一个vtkImageBoxSource,例如,生成一个盒子的二进制图像,你可以从复制vtkImageEllipsoidSource源和头文件开始,并做一个全局搜索和替换。您可能会将实例变量Radius更改为Length,因为这对于框源来说是更合适的描述。最后,您将替换模板函数vtkImageBoxSourceExecute中的代码,以创建框图像而不是椭球图像。(有关创建图像处理过滤器的更多信息,请参阅第401页的“线程成像过滤器”。)

ImageGaussianSource

        vtkImageGaussianSource对象生成一个图像,其像素值根据高斯分布确定,使用中心位置、最大值和标准偏差。该图像源输出的数据类型始终是浮点数(即双精度)。

        如果您想编写自己的源代码,只生成一种类型的输出图像,例如float,那么这个类可能是一个很好的起点。比较vtkImageGaussianSource的源代码与vtkImageEllipsoidSource的源代码,您会注意到过滤器的实现是在vtkImageGaussianSource的RequestData()方法中,而在vtkImageEllipsoidSource中,RequestData()方法调用包含实现的模板化函数

ImageGridSource

        如果你想用2D网格来注释你的图像,vtkImageGridSource可以用来创建一个网格模式的图像(图6-5)。下面的示例通过将网格模式与CT数据集的切片混合来说明这种用法。阅读器是一个vtkImageReader,它产生64 × 64的图像。

vtkImageGridSource imageGrid
imageGrid SetGridSpacing 16 16 0
imageGrid SetGridOrigin 0 0 0
imageGrid SetDataExtent 0 63 0 63 0 0
imageGrid SetLineValue 4095
imageGrid SetFillValue 0
imageGrid SetDataScalarTypeToShort
vtkImageBlend blend
blend SetOpacity 0 0.5
blend SetOpacity 1 0.5
blend AddInputConnection [reader GetOutputPort]
blend AddInputConnection [imageGrid GetOutputPort]
vtkImageViewer viewer
viewer SetInputConnection [blend GetOutputPort]
viewer SetColorWindow 1000
viewer SetColorLevel 500
viewer Render

ImageNoiseSource

        vtkImageNoiseSource图像源可用于生成在一些指定的最小值和最大值之间填充随机数的图像。输出图像的类型为浮点型。

        关于vtkImageNoiseSource需要注意的一点是,它每次执行都会生成不同的图像。通常,这是噪声源的期望行为,但这在具有重叠的流管道中具有负面影响,因为重叠区域在两个请求之间不会具有相同的值。例如,假设你用vtkImageNoiseSource设置了一个管道图6-5由vtkImageNoiseSource创建的网格模式叠加在CT数据集的切片上。连接到ImageMedianFilter,它又连接到vtkImageDataStreamer。如果您在streamer中指定内存限制,以便将图像分为两部分计算,那么streamer发出的第一个请求将是一半图像。中值过滤器将需要略多于一半的输入图像(基于内核的范围)来生成所请求的输出图像。当中值过滤器第二次执行以生成输出图像的后半部分时,它将再次请求重叠区域,但是该区域将包含不同的值,导致使用重叠区域计算的任何值都不一致。

ImageSinusoidSource

        vtkImageSinusoidSource对象可用于创建指定大小的图像,其中像素值由给定方向,周期,相位和幅度值的正弦函数确定。正弦波源的输出是浮点型的。在图6-6所示的图像中,正弦波源的输出已被转换为无符号字符值并渲染体积。这个相同的输出通过一个轮廓过滤器来创建图像中看到的边界框。

vtkImageSinusoidSource ss
ss SetWholeExtent 0 99 0 99 0 99
ss SetAmplitude 63
ss SetDirection 1 0 0
ss SetPeriod 25

6.6图像处理

        现在我们将考虑几个处理图像数据的示例。这不是所有过滤器的详尽描述,但它会让你开始使用VTK的图像处理过滤器。您可能希望参考oxygen文档以获取更多信息。此外,在450页的“成像滤光片”中可以找到更完整的描述。

转换标量类型

        有时需要将一种标量类型的输入图像转换为另一种标量类型的输出图像。例如,某些过滤器仅对特定标量类型(如float或integer)的输入进行操作。或者,您可能希望直接使用图像作为颜色值,而不使用查找表将标量映射到颜色。要做到这一点,图像标量类型必须是unsigned char。

        VTK中有两个类可用于转换图像的标量类型。vtkImageCast过滤器允许您指定输出标量类型。例如,当您知道图像只包含0到255之间的值,但当前存储为无符号整数时,此过滤器可以很好地工作。然后可以使用vtkImageCast将图像转换为无符号字符。如果将ClampOverflow实例变量设置为on,则输出标量类型范围之外的值如图6-6所示,左边的正弦波源的输出已被转换为unsigned char和volume rendering。6.6图像处理129将在分配前进行箝位。

        例如,如果你的输入图像包含一个257,它将被存储在输出图像作为255,如果ClampOverflow是在;如果ClampOverflow没有开启,它将被存储为1。如果您需要将包含[-1,1]范围内强度的浮点图像转换为无符号char图像,vtkImageCast将不起作用。在这种情况下,vtkImageShiftScale将是执行转换的正确过滤器。该过滤器允许您指定在输入图像像素值存储到输出图像之前对其执行移位和缩放操作。要执行此转换,移位将被设置为+1,比例将被设置为127.5。这会将值-1映射为(-1+1)*127.5 = 0,并将值+1映射为(+1+1)*127.5 = 255。

更改间距、原点或范围

        在VTK中,当用户需要更改数据的起源、间距或范围时,经常会出现混淆。获取某些过滤器的输出,并将这些参数调整为所需的值是很诱人的。然而,正如用户很快注意到的那样,这只是一个临时解决方案——下一次管道更新时,这些更改将丢失,数据将恢复到以前的形状和位置。要更改这些参数,有必要在管道中引入一个过滤器来进行更改。该vtkImageChangeInformation过滤器可以用来调整原点,间距,和范围的vtkImageData。可以显式设置原点和间距值,也可以设置输出整个范围的开始位置。由于数据的维数不变,因此整个范围的开始完全定义了输出的整个范围。vtkImageChangeInformation过滤器还包含几个方便的方法来居中图像,翻译范围,或翻译和缩放原点和间距值。

        在下面的示例中,我们使用vtkImageReader读取CT扫描的原始医疗数据。然后,我们通过3D vtkImageGradient传递这些数据,并将结果显示为彩色图像。

vtkImageReader reader
    reader SetDataByteOrderToLittleEndian
    reader SetDataExtent 0 63 0 63 1 93
    reader SetFilePrefix "$VTK_DATA_ROOT/Data/headsq/quarter"
    reader SetDataMask 0x7fff
vtkImageGradient gradient
    gradient SetInputConnection [reader GetOutputPort]
    gradient SetDimensionality 3
vtkImageViewer viewer
    viewer SetInputConnection [gradient GetOutputPort]
    viewer SetZSlice 22
    viewer SetColorWindow 400
    viewer SetColorLevel 0

附加的图片

        在VTK中有两个不同的附加图像类,允许图像在空间上或通过连接组件进行组合。使用vtkImageAppend可以将图像在空间上组合成一个更大的图像,而vtkImageAppendComponents可以使用,例如,将独立的红、绿、蓝图像组合成一个RGB图像。

        回想一下,图像可以是一维、二维或三维的。当在空间上组合时,输出图像可以增加维数。例如,您可以组合多个独立的一维行来形成二维图像,或者您可以组合一堆二维图像来形成一个卷。vtkImageAppend过滤器使用两种方法之一在空间上组合一组图像。如果关闭了PreserveExtents实例变量,那么图像将沿着AppendAxis实例变量定义的轴追加。除了沿着AppendAxis之外,输入图像必须具有相同的尺寸,并且它们必须具有相同的标量类型和标量分量的数量。输出图像的原点和间距将与第一个输入图像的原点和间距相同。如图6-7所示,将三个2D图像沿着AppendAxis 0 (X轴)组合成一个更宽的2D图像。在图6-8中,我们看到一个例子,其中一组2D XY图像沿着附件轴2 (Z轴)组合形成一个体积。

        如果PreserveExtents实例变量打开,vtkImageAppend过滤器将创建一个输出,其中包含基于其整个区段的并集的输入图像集。从第一个输入图像复制原点和间距,并将输出图像初始化为0。然后将每个输入图像复制到输出图像中。当两个输入图像都在输出图像中定义相同的像素时,不执行混合。相反,输入图像的顺序决定了输出图像中的值,将编号最高的(最后添加的)输入图像值存储在输出像素中。图6-9显示了一个将带有PreserveExtents的图像与三个共面非重叠的2D输入图像附加在一起的示例。

        注意,vtkImageAppend考虑数据的像素或体素范围,而不是世界坐标。该过滤器的作用就好像所有输入图像都具有相同的原点和间距,因此每个图像相对于其他输入图像的位置仅由该图像的范围定义。

        vtkImageAppendComponents过滤器可用于组合具有相同标量类型和维度的多个输入的组件。输出图像的原点和间距将从第一输入图像获得。输出图像的分量数量等于所有输入图像的分量数量之和。这个滤镜经常被用来将独立的红、绿、蓝图像组合成一个单一的彩色图像。这方面的一个例子见于
图6 - 10。

将图像映射为彩色

        vtkImageMapToColors用于将灰度图像转换为彩色图像。(参见图6 - 11。)输入的标量值可以是任何数据类型。输入标量值的用户选择组件(使用vtkImageMapToColors的SetActiveComponent()方法选择)通过vtkScalarsToColors的实例进行映射,并且查找表中的颜色值存储在输出图像中。vtkImageMapToWindowLevelColors, vtkImageMapToColors的子类,在将颜色值存储在输出图像之前,使用窗口级函数(见图6-3)额外调节颜色值。两个过滤器的输出图像的标量类型都是无符号字符。

图像亮度

vtkImageLuminance过滤器基本上与vtkImageMapToColors相反。(参见图6 -
12)。它使用以下公式将RGB图像(红色、绿色和蓝色组件)转换为单组件灰度图像。

luminance = 0.3*R + 0.59*G + 0.11*B

式中,R为输入图像的第一分量(红色),G为第二分量(绿色),B为第三分量(蓝色)。此计算计算使用RGB组件指定的给定颜色的亮度。

直方图

        vtkImageAccumulate是一个图像过滤器,可产生多达四维的广义直方图。这是通过将组件空间划分为离散的bin,然后计算每个bin对应的像素数来完成的。输入图像可以是任何标量类型,但输出图像将始终是整数类型。如果输入图像只有一个标量分量,则输出图像为一维,如图6-13所示。(这个例子取自VTK/
例子/ ImageProcessing / Tcl / Histogram.tcl)。

图像逻辑

        vtkImageLogic是一个图像处理过滤器,它接受一个或两个输入,并对它们执行布尔逻辑操作(图6-14)。支持大多数标准操作,包括AND, OR, XOR,
非,非,非。这个过滤器有两个输入,但是对于像NOT这样的一元操作,只需要第一个输入。在下面的示例中,您将注意到我们使用了vtkImageEllipsoidSource来生成两个输入图像。

vtkImageEllipsoidSource sphere1
    sphere1 SetCenter 95 100 0
    sphere1 SetRadius 70 70 70
vtkImageEllipsoidSource sphere2
    sphere2 SetCenter 161 100 0
    sphere2 SetRadius 70 70 70
vtkImageLogic xor
    xor SetInputConnection 0 \
        [sphere1 GetOutputPort]
    xor SetInputConnection 1 [sphere2 \
        GetOutputPort]
    xor SetOutputTrueValue 150
    xor SetOperationToXor
vtkImageViewer viewer
    viewer SetInput [xor GetOutput]
    viewer SetColorWindow 255
    viewer SetColorLevel 127.5

渐变

        vtkImageGradient是一个过滤器,计算图像或体积的梯度。您可以使用SetDimensionality()方法控制它是计算二维梯度还是三维梯度。根据您指定的维度,它将生成每个像素具有两个或三个标量分量的输出。标量分量对应于梯度向量的x、y和可选的z分量。如果你只想要渐变幅度,你可以使用vtkImageGradientMagnitude过滤器或vtkImageGradient跟着vtkimagemaggrate。

        vtkImageGradient通过使用中心差来计算梯度。这意味着要计算一个像素的梯度,我们必须查看它的左右邻居。这对图像外部边缘的像素产生了一个问题,因为它们将丢失两个相邻像素中的一个。这个问题有两个解决方案,它们由HandleBoundaries实例变量控制。如果HandleBoundaries是on的,那么vtkImageGradient将对所有的边缘像素使用一个修改过的梯度计算。如果HandleBoundaries是off的,vtkImageGradient将忽略那些边缘像素,并产生一个比原始输入图像小的结果图像。

高斯平滑

        用高斯核平滑图像类似于上面的梯度计算。它有一个维数控制高斯核卷积的维数。类vtkgaussianssmooth也有SetStandardDeviations()和SetRadiusFactors()方法,它们控制高斯核的形状和何时截断它。下面提供的示例与梯度计算非常相似。我们从连接到vtkimagegaussianssmooth的vtkImageReader开始,最后连接到vtkImageViewer。

vtkImageReader reader
    reader SetDataByteOrderToLittleEndian
    reader SetDataExtent 0 63 0 63 1 93
    reader SetFilePrefix "$VTK_DATA_ROOT/Data/headsq/quarter"
    reader SetDataMask 0x7fff
vtkImageGaussianSmooth smooth
    smooth SetInputConnection [reader GetOutputPort]
    smooth SetDimensionality 2
    smooth SetStandardDeviations 2 10
vtkImageViewer2 viewer
    viewer SetInputConnection [smooth GetOutputPort]
    viewer SetSlice 22
    viewer SetColorWindow 2000
    viewer SetColorLevel 1000

图像翻转

        vtkImageFlip过滤器可用于沿FilteredAxis实例变量指定的轴反映输入图像数据。默认情况下,FlipAboutOrigin实例变量被设置为0,图像将沿着FilteredAxis实例变量指定的轴(默认为0—X轴)围绕其中心翻转,并且输出的原点、间距和范围将与输入相同。但是,如果您有一个与图像相关联的坐标系,并且您希望使用翻转将沿一个轴的正坐标值转换为负坐标值(反之亦然),那么实际上您希望围绕坐标(0,0,0)而不是围绕图像的中心翻转图像。如果将FlipAboutOrigin实例变量设置为1,则将调整输出的原点,使翻转发生在(0,0,0)附近,而不是图像的中心。在图6-15中,我们在左侧看到一个输入图像;中间的图像显示了沿Y轴翻转该图像并关闭FlipAboutOrigin的结果;在右边的图像中,FlipAboutOrigin是打开的,所有其他变量与用于生成中心图像的变量相比都没有变化。

图像交换

        vtkImagePermute允许您重新排序输入图像或体积的轴。(参见图6 -
16)。FilteredAxes实例变量指示了哪个指示了轴应该如何重新排序—哪个输入轴将在输出中标记为X,哪个Y和哪个Z。

数学形象思维

        vtkImageMathematics过滤器提供基本的一元和二进制数学运算。根据操作集的不同,该过滤器需要一个或两个输入图像。当需要两个输入图像时,它们必须具有相同的标量类型和相同数量的组件,但不需要具有相同的尺寸。输出图像将具有一个区段,该区段是输入图像的区段的并集。输出图像的原点和间距将与第一个输入图像的原点和间距匹配。

        一元操作如下所述。注意,IPn是组件n的输入像素值,OPn是组件n的输出像素值,C和K是常量值,可以指定为实例变量。DivideByZeroToC是一个实例变量,它指定遇到除零时发生的情况。当DivideByZeroToC为on时,则C常数值是除零的结果;否则,当出现除零时,将使用输出标量类型范围内的最大值。

VTK_INVERT:输入反相。当遇到除零时,使用C或最大输出标量值,具体取决于DivideByZeroToC实例变量的值。

        if IPn != 0; OPn = 1.0 / IPn

        if IPn == 0 and DivideByZeroToC; then OPn = C

        if IPn == 0 and !DivideByZeroToC; then OPn = maximum scalar value

VTK_SIN:取输入图像的正弦值。

        OPn = sin(IPn)

VTK_COS:计算输入图像的余弦值。

        OPn = cos(IPn)

VTK_EXP:计算输入图像的指数。这是e的输入图像的幂,其中e是自然对数的底数,大约是2.71828。

        OPn = exp(IPn)

VTK_LOG:计算输入图像的自然对数(以e为底的对数)。

        OPn = log (IPn)

VTK_ABS:计算输入图像的绝对值。

        OPn = fabs(IPn)

VTK_SQR:输入图像值的平方。

        OPn = IPc * IPn

VTK_SQRT:取输入图像值的平方根。

        OPn = sqrt(IPn)

VTK_ATAN:计算输入图像值的arctan。

        OPn = atan(IPn)

VTK_MULTIPLYBYK:将每个输入的图像值乘以常数K。

        OPn = IPn * K

VTK_ADDC:将每个输入的图像值加上常数C。

VTK_REPLACECBYK:

        if IPn == C; OPn = K

        if IPn != C; OPn = IPn

VTK_CONJUGATE:要使用此操作,输入图像必须具有双分量标量。将双分量标量转换成复共轭对。

        Op0 = ip0 op1 = -ip1

接下来是二进制运算。所使用的符号与一元运算类似,除了
IP1n为要素n的第一输入像素值,IP2n为要素n的第二输入像素值。

VTK_ADD:将第二个输入图像添加到第一个输入图像。
        OPn = IP1n + IP2n
VTK_SUBTRACT:从第一个输入图像的值中减去第二个输入图像的值。
        OPn = IP1n - IP2n
VTK_MULTIPLY:将第一个输入图像的值乘以第二个输入图像的值。
        OPn = IP1n * IP2n
VTK_DIVIDE:将第一个输入图像的值除以第二个输入图像的值。当遇到除零时,使用C或最大输出标量值,具体取决于DivideByZeroToC实例变量的值。

        if IP2n != 0; OPn = IP1n / IP2n

        if IP2n == 0 and DivideByZeroToC; then OPn = C

        if IP2n == 0 and !DivideByZeroToC; then OPn = 最大标量值

VTK_COMPLEX_MULTIPLY:此操作要求两个输入图像都具有双分量标量。第一个分量是实值,第二个分量是虚数。使用复乘法将第一个输入图像的值与第二个输入图像的值相乘。
        Op0 = ip10 * ip20 - ip11 * ip21
        Op1 = ip11 * ip20 + ip10 * ip21
VTK_MIN:比较两张图片对应的值,返回较小的值。

        if IP1n < IP2n; OPn = IP1n

        if IP2n < IP1n; OPn = IP2n

VTK_MAX:比较两张图片对应的值,并返回较大的值

        if IP1n > IP2n; OPn = IP1n

        if IP2n > IP1n; OPn = IP2n

VTK_ATAN2:对于来自两个输入的每一对值,将第一个值除以第二个值,并计算结果的arctan。如果第二个输入的值为零,或者两个输入的值都为零,则输出值被设置为0。

        if IP2n = 0; OPn = 0

        if IP1n = 0 and IP2n = 0; OPn = 0

        IP2n != 0; OPn = atan( IP1n / IP2n )

图像Reslice

        vtkImageReslice是一个已贡献的类,它提供沿任意面向的体积(或图像)进行高性能图像重采样。输出数据的范围、原点和采样密度也可以设置。这个类提供了其他几个成像过滤器:它可以排列、翻转、旋转、缩放、重新采样和垫入任何组合的图像数据。它还可以从图像体积中提取斜切片,这是其他VTK成像滤波器无法做到的。下面的脚本演示如何使用vtkImage-Reslice。

vtkBMPReader reader
 reader SetFileName "$VTK_DATA_ROOT/
Data/masonry.bmp"
 reader SetDataExtent 0 255 0 255 0 0
 reader SetDataSpacing 1 1 1
 reader SetDataOrigin 0 0 0
 reader UpdateWholeExtent
vtkTransform transform
 transform RotateZ 45
 transform Scale 1.414 1.414 1.414
vtkImageReslice reslice
 reslice SetInputConnection [reader GetOutputPort]
 reslice SetResliceTransform transform
 reslice SetInterpolationModeToCubic
 reslice WrapOn
 reslice AutoCropOutputOn
vtkImageViewer2 viewer
 viewer SetInputConnection [reslice \
GetOutputPort]
 viewer SetSlice 0
 viewer SetColorWindow 256.0
 viewer SetColorLevel 127.5
 viewer Render

        在图6-17中,以大小为642的卷为例
读取93。使用变换来定位要重新采样(或重新切片)数据的体积,并使用体素之间的三次插值。打开包装垫特性,并且(通过设置变量AutoCropOutput)输出范围将被调整到足够大的大小,从而不会裁剪任何切片数据。默认情况下,输出音量的间距设置为1.0,并调整输出原点和范围以包围输入音量。查看器用于显示生成的体积的一个z切片。

迭代图像

VTK还提供了类似STL的迭代器,以便于迭代和检索/设置图像中的像素值。类vtkImageIterator可用于完成此操作。它是在图像的数据类型上模板化的。它的构造函数将迭代所遍历的子区域作为参数。

int subRegion[6] = { 10, 20, 10, 20, 10, 20 };
 vtkImageIterator< unsigned char > it( image, subRegion );
 while( !it.IsAtEnd() )
 {
   unsigned char *inSI = it.BeginSpan();
   unsigned char *inSIEnd = it.EndSpan();
 while (inSI != inSIEnd)
 {
   *inSI = (255 - *inSI);
   ++inSI;
 }
   it.NextSpan();
 }
 }

本书为英文翻译而来,供学习vtk.js的人参考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值