【译者:这个系列教程是以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,那么计算结果将为当前标量类型所表示数值范围的最大值。
l VTK_INVERT: 图像取反。根据DivideByZeroToC的值来决定当除数为0时的计算结果。
如果IPn != 0,OPn = 1.0/ IPn
如果IPn == 0并且DivideByZeroToC为on,OPn = C
如果IPn == 0并且DivideByZeroToC为off,OPn = 数据类型范围的最大值
l VTK_SIN:对输入图像做正弦计算。
OPn = sin(IPn)
l VTK_COS: 对输入图像做余弦计算。
OPn = cos(IPn)
l VTK_EXP: 对输入图像做指数运算。底数为e,约等于2.71828。
OPn = exp(IPn)
l VTK_LOG: 计算图像的自然对数。
OPn = log(IPn)
l VTK_ABS: 对输入图像取绝对值。
OPn = fabs(IPn)
l VTK_SQR: 对输入图像计算平方。
OPn = IPn* IPn
l VTK_SQRT: 计算输入图像的平方根。
OPn = sqrt(IPn)
l 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章 翻译完成】
第06章-图像处理及可视化(2)