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

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

【译者:这个系列教程是以Kitware公司出版的《VTK User’s Guide -11th edition》一书作的中文翻译(出版时间2010年,ISBN: 978-1-930934-23-8),由于时间关系,我们不能保证每周都能更新本书内容,但尽量做到一周更新一篇到两篇内容。敬请期待^_^。欢迎转载,另请转载时注明本文出处,谢谢合作!同时,由于译者水平有限,出错之处在所难免,欢迎指出订正!】


【本节对应原书中的第125页至第138页】


6.5图像源

VTK中有些图像处理对象本身并不接收任何的数据对象,但是会有结果产生。他们被称为图像源,这里会介绍其中的一部分。参考444页“源对象”或者查阅Doxygen文档或者更完整的可用图像源列表。

ImageCanvasSource2D

vtkImageCanvasSource2D类根据指定的大小和数量类型生成一个二维的空白图像,并提供了函数完成在空白图像中绘制不同的几何图形。这些几何图形包括方形,线段和圆;同样还提供了一个填充操作函数。下面例子演示了利用该图像源来生成一个512*512像素的图像,并在图像中绘制了几个图形。结果如图6-4所示。

 

图6-4 用类vtkImageCanvasSource2D

#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 SetDraw 170

imCan FillTriangle 100 100 300 150 150 300

 

#show the resulting image

vtkImageViewer viewer

viewer SetInputConnection [ imCan GetOutputoirt ]

viewer SetColorWindow 256

viewer SetColorLevel 127.5

ImageEllipsoidSource

如果你希望用一个模板函数来写图像,那么vtkImageEllipsoidSource会是一个好的起点。这个对象根据指定的中心点,每个轴的半径,椭圆内部和外部值来生成一个包含椭圆的二值图像。输出的图像数据类型也可以指定,这也是为什么采用模板函数的原因。一些图像filter内部会使用这个图像源,如vtkImageDilateErode3D。

如果你想创建一个vtkImageBoxSource对象,来生成一个二值盒状图。你可以先拷贝一份vtkImageEllipsoidSource源文件和头文件,并在里面搜索和替换。你很可能会改变变量radius为length,因为对于盒状图像源这个命名更有意义。最后,你需要替换掉模板函数vtkImageBoxSourceExecute来创建盒状图像而不是椭圆图像。(参考401页“多线程图像filter”)。

ImageGaussianSource

vtkGaussianSource对象根据指定的中心位置,最大值和标准差来生成一个像素值服从高斯分布的图像。其输出图像的像素类型为浮点型(如double)。

如果你想写一个仅仅生成一种类型图像的图像源,如float,那么这个类就是一个很好的例子。比较vtkImageGaussianSource和vtkImageEllipsoidSource,你会发现vtkImageGaussianSource实现代码是在RequestData()中,而vtkImageEllipsoidSource的RequestData()函数是通过调用了一个模板函数在完成功能。

ImageGridSource

如果你想采用一个二维网格标注图像,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


图6-5 用类vtkImageGridSource生成的网格覆盖在CT图像上

ImageNoiseSource

vtkImageNoiseSource用来生成一个由随机数填充的图像,随机数大小介于用户指定的最大和最小数之间。输出图像的数据类型是浮点型。

需要注意的是vtkImageNoiseSource每次都生成一个不同的图像。通常对于一个噪声源这是正常的。但是对于流水线中两次的Request会造成重叠区域数据发生变化。例如有这样一个流水线,vtkImageNoiseSource连接到了一个ImageMedianFilter,ImageMedianFilter再连接到一个vtkImageDataStreamer。如果你在streamer中指定一个内存限制使图像分为两半进行计算。第一个Request请求操作的是半幅图像。中值filter需要的图像要比半幅图像略大(由于核函数的范围)。当中值filter第二次执行时,两次需要的图像的重叠区域的值会发生变化,从而两次计算结果中重叠区域会变得不一致。

ImageSinusoidSource

ImageSinusoidSource对象可以用来生成图像,图像的大小由用户指定,而像素值则由sinusoid函数根据指定的方向、周期、相位和幅度来生成。

图6-6中为sinusoid图像源生成的图像转换为unsigned char类型后体绘制的效果图。该结果传递到一个轮廓filter中后来创建了一个图像包围盒。

vtkImageSinusoidSource ss

ss SetWholeExtent 0 99 0 99 0 99

ss SetAmplitude 63

ss SetDirection 1 0 0

ss SetPeriod 25


图6-6 由vtkImageSinusoidSource生成的数据转成unsigned char型并体绘制的效果

6.6图像处理

下面我们来看几个图像处理的例子。这里通过介绍部分图像处理Filter来演示怎样使用VTK的图像处理Filter。如果想了解更多相关信息,请参考Doxygen文档内容。另外,450页“图像Filters”章节中会有更完整的介绍。

标量类型转换

图像标量数据类型的转换是一种很常用的操作。例如,一些fitlers只能处理特点数据类型的图像,如float浮点型或者int整型。另外,你可能需要直接利用图像彩色值,而不是通过lookup颜色查找表映射。而这个操作要求图像数据类型必须是unsigned char。

VTK中有两种filter实现图像标量数据类型转换。第一个是vtkImageCast。该Filter允许用户指定输出标量类型。例如当已知一个图像中的像素灰度范围为0-255,而灰度数据的存储类型为无符号整型,此时可以采用vtkImageCast将其转换为unsigned char类型。需要注意该filter的ClampOverflow变量,如果该变量设置为on,那么超出输出数据类型范围的数据就会被截断。如图像中存在一个像素值为257,当ClampOverflow为on事,该像素值在输出图像中为255。如果ClampOverflow为off,那么输出值就为1。

当需要将一个数据范围为[-1,1]范围的浮点型图像转换为unsigned char类型时,vtkImageCast将不能满足要求,此时需要vtkImageShiftScale进行图像转换。该filter可以指定偏移和比例参数来对输入图像数据进行操作。在上例中,需要设置shift值为+1,比例系数设置为127.5。那么输入数据-1则映射为(-1+1)*127.5=0,+1则会映射为(+1+1)*127.5=255。

修改图像间距、原点或范围

VTK中修改图像的间距,原点或者范围常常令用户困惑。一个常采用的方法是获取filter的输出结果,然后将其调整到需要的大小。但是当管线update更新后这些改变又会恢复到原来的值。因此管线中需要一个filter来执行这些修改操作。这就是vtkImageChangeInformation。利用该filter,origin和图像间隔spacing、以及范围的起始点可以显示的指定。由于图像维数没有发生改变,因此范围的起点即决定了图像的范围。另外,vtkImageChangeInformation还定义了一些方法来方便的实现图像居中,平移图像范围,平移原点和缩放图像间隔操作。

下面例子中定义了vtkImageReader读取医学图像数据,将数据传递至一个三维vtkImageGradient中,然后将计算结果以彩色图像显示。

 

vtkImageReader reader

  readerSetDataByteOrderToLittleEndian

  reader SetDataExtent0 63 0 63 1 93

  readerSetFilePrefix “$VTK_DATA_ROOT/Data/headsq/quarter”

  readerSetDataMask 0x7fff

 

vtkImageGradient gradient gradient

  gradientSetInputConnection [reader GetOutputPort]

  gradientSetDimensionality 3

 

vtkImageViewer viewer

  viewerSetInputConnection [gradient GetOutputPort]

  viewerSetZSlice 22

  viewerSetColorWindow 400

  viewerSetColorLevel 0

图像合并

VTK中支持合并图像空间和合并图像成分。利用vtkImageAppend在空间上合并图像可以生成一个更大的图像,而vtkImageAppendComponents则可以将单独的单成分图像合并为一个RGB彩色图像。


图6-7 沿着X方向将三个2D图像合并在一起

注意图像可以是一维、二维或者三维。当在空间上进行图像合并时,输出图像的维数可能会增加。例如,可以将一组一维图像组成一个二维图像,也可以将一组二维图像合并为一个三维图像。vtkImageAppend合并图像时,有两种策略。如果PreserveExtents变量关闭,那么就沿着AppendAxis变量指定的轴进行合并;所以图像必须要有相同的维数,相同的数据类型以及标量成分数目。输出图像的原点和间隔与第一幅图像相同。图6-7说明了将3个二维图像沿着X轴进行合并的过程。而图6-8中演示了将一组二维图像沿着Z轴合并成一个三维图像的过程。


图6-8 沿着Z轴方向将多个2D图像合并成3D图像

如同PreserveExtents变量设置为on,那么vtkImageAppend则会生成一个包含所有输入图像的图像,该图像的范围为所有图像范围的并集。输出图像的原点和间隔与输入的第一个图像相同,并且像素值初始化为0。然后每一个图像都拷贝到输出图像中。当两个输入图像存在覆盖的像素时,不会执行混合操作,而是根据图像的输入顺序决定该像素的数值大小,即顺序靠后面图像对应的像素大小。图6-9说明了PreserveExtents状态为on时图像合并的过程。


图6-9 PreserveExtents变量的状态为On时的2D图像合并效果

注意vtkImageAppend采用的是像素坐标而非世界坐标,所有输入图像如同具有相同的原点和间隔,因此输入图像之间的相对位置仅仅由图像的范围extent定义。

vtkImageAppendComponents可以将多个具有相同数据类型和维数的图像合并到一个多成分图像。输出图像的原点和间隔与输入的第一个图像一致,其成分数据与输入图像的个数相等。该fliter常用来将单成分的红、绿、蓝色图像合并为i一个RGB彩色图像。如图6-10。


图6-10 用类vtkImageAppendComponets将单通道的图像组合成一个彩色图像

图像彩色映射

vtkImageMapToColors将灰度图像转换为彩色图像。如图6-11。该filter接收任意数据类型的图像作为输入,将用户选定的像素分量(通过vtkImageMapToColors的函数SetActiveCompnent())通过vtkScalarsToColors类实例进行映射彩色值,并存储至输出图像中。vtkImageMapToColors的子类vtkImageMapToWindowLevelColors在存储映射后的彩色值前,对彩色值通过一个窗宽-窗位函数规整。两个filter的输出图像的类型均为unsigned char。


图6-11 左图是vtkImageMapToColors类的输入,右图是其输出,右图下方是颜色映射的标量条

图像灰度映射

vtkImageLuminance执行与vtkImageMapToColors相反的操作,将一个RGB彩色图像转换为一个单成分的灰度图像。映射公式如下:

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

该公式中,R为输入图像的第一分量(红色),G为第二分量(绿色),B为第三分量(蓝色)。这个计算结果计算一个RGB颜色的亮度。


图6-12 左图是vtkImageLuminance的输入,右图是其输出。要注意区别类vtkImageMapToColors的输入与类vtkImageLumiance的输出的相似性

直方图

vtkImageAccumulate计算一个图像的直方图,其维数最高可至四维。其计算原理是将图像的每个颜色分量空间划分为离散的区间,然后统计落入每个区域的像素个数。输入图像可以是任意的像素类型,但是输出结果通常为整型。如果一个图像只有一个颜色分量,那么其直方图就是一维的。如图6-13所示。(示例取自VTK/Examples/ImageProcessing/Tcl/Histogram.tcl)


图6-13 vtkImageAccumulate类主要应用是从单个通道的输入图像生成一维的直方图

图像逻辑运算

vtkImageLogic接收一个或者两个图像进行布尔逻辑运算。如图6-14。该Filter支持大部分的逻辑操作,包括AND,OR,XOR,NAND,NOR和NOT。它有两个输入,尽管一元操作只需要第一个输入。下面例子采用vtkImageEllipsoidSource来产生两个输入图像。

 

vtkImageEllipsoidSource sphere1

 

  sphere1SetCenter 95 100 0

  shpere1 SetRadius70 70 70

 

vtkImageEllipsoidSource sphere2

  sphere2SetCenter 161 100 0

  sphere2SetRadius 70 70 70

 

vtkImageLogic xor

  xorSetInputConnection 0 \

[sphere1 GetOutputPort]

  xorSetInputConnection 1 [sphere2 \

    GetOutputPort]

  xorSetOutputTrueValue 150

  xorSetOperationToXor

 

vtkImageViewer viewer

  viewerSetInput [xor GetOutput]

  viewerSetColorWindow 255

  viewerSetColorLevel 127.5


图6-14 图像逻辑运算的结果

梯度计算

vtkImageGradient计算图像梯度。通过SetDimensionality()函数设置要计算梯度的维数(二维或者三维)。根据设置的维数,该filter输出图像的每个像素都会对应有两个或者三个标量成分,分别对应于梯度向量的每个成分。如果仅仅计算梯度模值的话,可以使用vtkImageGradientMagnitude。

vtkImageGradient采用中间差分法计算梯度。当计算一个像素梯度时,需要利用该像素的左右相邻像素。由于在边界处像素会缺少其中一个像素,因此需要进行边界处理。改处理通过HandleBoundaries变量来控制。当该变量设置为on时,vtkImageGradient针对边界像素采用一种改进的计算方法。当该变量为off时,vtkImageGradient会忽略掉边界,因而输出图像要小于输入图像。

高斯平滑

基于高斯核的图像平滑类似于梯度计算。它可以控制用于卷积的高斯核的维数。vtkGaussianSmooth通过SetStandardDeviations()和SetRadiusFactors()方法控制高斯核的形状和截断半径大小。下面例子跟梯度计算比较类似。开始vtkImageReader连接到vtkImageGaussianSmooth,接下来再连接到vtkImageViewer。

 

vtkImageReader reader

   readerSetDataByteOrderToLittleEndian

   readerSetDataExtent 0 63 0 63 1 93

   readerSetFilePrefix “$VTK_DATA_ROOT/Data/headsq/quarter”

   readerSetDataMask 0x7fff

 

vtkImageGaussianSmooth smooth

   smoothSetInputConnection [reader GetOutputPort]

   smoothSetDimensionality 2

   smoothSetStandardDeviations 2 10

 

vtkImageViewer2 viewer

   viewerSetInputConnection [smooth GetOutputPort]

   viewerSetSlice 22  

   viewerSetColorWindow 2000

   viewerSetColorLevel 1000

图像翻转

vtkImageFlip实现图像的翻转,翻转轴由FilteredAxis变量决定。默认情况下,FlipAboutOrigin变量设置为0,此时该filter沿着FilteredAxis定义的轴向做关于图像中心的翻转(默认下为0,即X轴),输出图像的原点、间距和范围与输入图像一致。然而如果图像有自己的坐标系统,当需要将图像正的坐标值变换为负坐标值时,应该将图像做关于(0,0,0)的翻转,而不再是图像中心。如果FlipAboutOrigin变量设置为1,那么该filter就以(0,0,0)做翻转。图6-15中左边图像为输入图像,中间图像是FlipAboutOrigin为0时对输入图像沿着Y轴翻转的结果;最右边图像是FlipAboutOrigin为1时的翻转结果。


图6-15 使用类vtkImageFlip翻转图像

图像排列

vtkImagePermute可以实现输入图像的坐标值重排。如图6-16。通过FilteredAxes变量值来决定输入图像的哪个轴分别对应到输出图像的X轴、Y轴和Z轴。


图6-16 使用类vtkImagePermute重新排列体数据集的坐标轴。左图为输入的数据集,大小为(114,100,74)。FilteredAxes变量的值设置为(1,2,0),表示Y轴重置成X轴,Z轴重置成Y,X轴重置成Z轴。输出结果如图所示,其大小了(100,74,114)

图像计算

vtkImageMathematics提供了基本的一元和二元数学操作。根据不同的操作,需要一个或者两个输入图像。当需要两个输入图像时,这两个图像必须有相同的像素数据类型,颜色分量,但是可以不是不同的图像范围。计算输出图像的范围为两个输入图像范围的并集。原点和间隔与第一个输入图像保持一致。

下面介绍下一元操作。注意IPn 为输入像素的第n个颜色分量值,OPn为输出像素第n个颜色分量值,C和K为常量。DivideByZeroToC变量用于处理除数为0的情况。如果该变量为On,那么当除数为0时计算结果为C。如果为off,那么计算结果将为当前标量类型所表示数值范围的最大值。

VTK_INVERT: 图像取反。根据DivideByZeroToC的值来决定当除数为0时的计算结果。

如果IPn != 0,OPn = 1.0/ IPn

如果IPn == 0并且DivideByZeroToC为on,OPn = C

如果IPn == 0并且DivideByZeroToC为off,OPn = 数据类型范围的最大值

VTK_SIN:对输入图像做正弦计算。

OPn = sin(IPn)

VTK_COS: 对输入图像做余弦计算。

OPn = cos(IPn)

VTK_EXP: 对输入图像做指数运算。底数为e,约等于2.71828。

OPn = exp(IPn)

VTK_LOG: 计算图像的自然对数。

OPn = log(IPn)

VTK_ABS: 对输入图像取绝对值。

OPn = fabs(IPn)

VTK_SQR: 对输入图像计算平方。

OPn = IPn* IPn

VTK_SQRT: 计算输入图像的平方根。

OPn = sqrt(IPn)

VTK_ATAN: 对输入图像做反正切运算。

OPn = atan(IPn)

l VTK_MULTIPLYBYK:图像的每个像素值乘以常数K。

OPn = IPn*K

l VTK_ADDC: 图像每个像素值都加上常量C。

OPn = IPn+C

l VTK_REPLACECBYK: 将图像中所有等于C的像素值替换为K。

如果IPn=C,OPn = K

如果IPn!=C,OPn = IPn

l VTK_CONJUGATE: 该操作要求输入图像有两个标量数据。将两个标量值表示为共轭复数。

OP0 = IP0

OP1 = -IP1

图像重切(ImageReslice)

vtkImageReslice能够沿着任意方向对图像冲采样。输出图像的范围,原点和采样密度都可以进行设置。另外它还能实现其他的功能:图像排列,翻转,旋转,放缩,冲采样,图像填补及其组合功能。而图像斜切功能则是其他filters所不能实现的。下面代码演示了怎么使用vtkImageReslice。

vtkBMPReader reader

  reader SetFileName“$VTK_DATA_ROOT/Data/masonry.bmp”

  readerSetDataExtent 0 255 0255 0 0

  readerSetDataSpacing 1 1 1

  readerSetDataOrigin 0 0 0

  readerUpdateWholeExtent

 

vtkTransform transform

  transformRotateZ 45

  transformScale 1.414 1.414 1.414

 

vtkImageReslice reslice

  resliceSetInputConnection [reader GetOutputPort]

  resliceSetResliceTransform transform

  resliceSetInterpolationModeToCubic

  resliceWrapOn

  resliceAutoCropOutputOn

 

vtkImageViewer2 viewer

  viewerSetpInputConnection [reslice \

       GetOutputPort]

  viewerSetSlice 0

 

  viewerSetColorWindow 256.0

  viewerSetColorLevel 127.5

  viewerRender


图6-17 vtkImageReslice的输出

例子中读入一个64*64*93大小的图像。定义一个transform来指定图像进行重切的位置,并且设置插值方式为三次插值。Wrap-pad为打开状态,而设置AutoCropOutput使得输出图像的范围足够大以至于不会被裁剪掉。默认情况下,输出图像的间距为1,原点和范围则会自动调整以包含输入图像。而viewer对象则用来沿着z轴方向显示计算结果。

迭代遍历图像

VTK中提供了迭代器来方便的访问、查找和设置图像像素值。vtkImageIterator实现该功能。该类是一个模板类,模板参数为图像的数据类型,而构造函数的参数为要迭代的图像范围。

 

int subRegion[6] = {10, 20, 10, 20, 10, 20};

vtkImageIterator< unsigned char > it(imagesubRegion);

while( !it.IsAtEnd())

{
  unsigned char *inSI = it.BeginSpan();

  unsignedchar *inSIEnd = it.EndSpan();

  while( inSI!= inSIEnd )

  {

   *inSI = (255-*inSI);

   ++ inSI;

  }

  it.NextSpan();

}

 

【第6章 翻译完成】