05-VTK在图像处理中的应用(1)

5、VTK在图像处理中的应用

图像是VTK中一个非常重要的数据。数字图像广泛应用于工业生产、生物医学、媒体娱乐、地质、气象等重要领域,数字图像处理具有重要的应用价值。我们在掌握了VTK的基本知识后,这一章着重讲解数字图像处理相关技术,学完本章你会觉得原来图像处理是如此简单!

5.1 VTK图像数据结构

数字图像文件内容由两个部分组成:图像头信息和数据。图像头信息定义了图像的基本信息,主要包括起点位置(Origin),像素间隔(space)和维数(dimension)。通过这三个参数即可以决定图像空间位置和规模。图像可以看做是空间中的一个规则网格,网格中的每个最小单元称之为一个像素(二维)或者体素(三维),这样网格在每个方向上的像素或者体素个数即为图像在该方向的维数。像素索引表示每个像素在图像网格中的位置,是图像内部的网格坐标。而在医学图像中,每个图像除了内部坐标外,还存在一个世界坐标。这个世界坐标依赖于成像设备。在医学图像中起点位置(Origin),像素间隔(space)和图像维数决定了世界坐标系。这样通过起点位置,像素间隔和像素索引即可计算每个像素的世界坐标位置。

 

图5.1图像结构和空间表示

 

如图5.1所示,该图表示一个4x2x3的图像,即图像的维数,每一个小球表示一个像素;而图像的原点为(5.1,10.0,6.5),两两像素之间的间隔表示像素间隔,每个方向的像素间隔为1.5,1.5,和1.8。

图像数据即为图像像素的像素值,一般采用一维数组来表示和存储。已知像素索引和图像维数下,即可计算每个像素对应的像素值。通常图像的像素值为一个标量,例如一般灰度图像;图像像素值也可以是一个向量,例如彩色图像;另外图像像素值还可以是张量,如梯度场图像。医学图像处理中大部分的图像都是灰度图像。

这里需要注意灰度图像的灰度值的数据类型,在一般的灰度图像处理中不需要考虑,因为其范围默认为0-255,可以采用一个unsigned char类型类表示。但是在医学图像处理中,256灰度级远远不能满足要求,因此灰度范围往往大于256级。常见的医学图像的像素数据类型为unsigned short,灰度范围为0-65536。另外,有时为了精度的考虑,也会使用int、float甚至double类型,因此需要格外注意。

通过前面的章节我们已经知道,在VTK中图像数据结构由vtkImageData类表示。利用vtkImageData,我们可以方便的创建、读写、和访问图像数据。下面我们以VTK图像创建为起点,一步步走进VTK图像处理的世界中来。

5.2 VTK图像创建

5.2.1图像源(Source)

VTK中内置了多个创建图像的Source,利用这些Source可以快速的创建图像,其中以vtkImageCanvasSource2D为代表。该Source功能是创建一个画布(空白图像),并提供了多种几何图形(点、线段、圆、矩形以及图像等)的绘制填充功能。下列代码显示了该source的使用方法。

  1: vtkSmartPointer<vtkImageCanvasSource2D> canvas =

  2:     vtkSmartPointer<vtkImageCanvasSource2D>::New();

  3:  canvas->SetScalarTypeToUnsignedChar();

  4: canvas->SetNumberOfScalarComponents(1);

  5:  canvas->SetExtent(0, 100,0, 100, 0, 0);

  6:  

  7:  canvas->SetDrawColor(0, 0,0, 0);

  8: canvas->FillBox(0,100,0,100);

  9:  

 10:  canvas->SetDrawColor(255,0, 0, 0);

 11: canvas->FillBox(20,40,20,40);

 12:  canvas->Update();

 

在上面代码片段中,首先定义了一个vtkImageCanvasSource2D的指针,然后设置画布的像素数据类型,像素成分数目和画布的大小。然后,在该画布中,利用FillBox绘制两个填充矩形,并通过SetDrawColor()来设置两个矩形的颜色。

 

图5.2 vtkImageCanvasSource2D绘图

 

除了vtkImageCanvasSource2D外,VTK中还提供了其他类似的source类来快速生成特定的图像,例如vtkImageEllipsoidSource,该类根据指定的中心,各个轴的半径来生成一个前景为椭圆(球)的二值图像;vtkImageGaussianSource类生成一副像素值服从高斯分布的图像;vtkImageGridSource用于生成网格线图像;vtkImageNoiseSource生成一个像素值为随机数的噪声图像;vtkImageSinusoidSource生成的图像像素值由正弦函数决定。

5.2.2直接创建图像

利用上述图像Source可以快速的生成特定的图像,不过相对来说,在实际应用中较少用到他们。VTK中可以手动生成图像,然后根据需要进行像素赋值。下面代码演示了怎样手动创建图像。

 

1:  vtkSmartPointer<vtkImageData> img= vtkSmartPointer<vtkImageData>::New();

2:  img->SetDimensions(10,10,10);

3:  img->SetScalarTypeToUnsignedChar();

4:  img->SetNumberOfScalarComponents(1);

5:  img->AllocateScalars();

6:  

7:  unsigned char *ptr = (unsignedchar*)img->GetScalarPointer();

8:  for(int i=0; i<10*10*10; i++)

9:  {

10:     *ptr ++ =i%256;

11: }

 

首先定义vtkImageData指针,然后指定图像的维数,而图像的原点和像素间隔则都是采用默认值,因此不需要设置。SetScalarTypeToUnsignedChar指定图像的每个像素值的数据类型为unsigned char,SetNumberOfScalarComponents则指定了每个像素值的数据成分为1,每个像素值为1个标量值,参数设置完毕后,调用AllocateScalars()分配内存,生成图像数据。图像生成后,默认所有像素值为0。可以通过访问图像数据数组来设置每个像素值,GetScalarPointer()即返回图像的数据数组(图像数据数组都采用一维数组),然后根据图像的大小,访问每个像素并为其赋值。生成的图像结果如图5.3所示。

 

图5.3 用VTK的类直接创建图像数据

5.3 图像显示

5.3.1vtkImageViewer2

在VTK早期版本中,提供了vtkImageViewer类来显示图像。随着版本的发展,目前使用vtkImageViewer2来代替vtkImageViewer实现图像的显示。vtkImageViewer2中封装了VTK图像显示的管线,包括vtkActor,vtkRender,vtkRenderWindow,vtkInteractorStypeImage等对象,可以方便的完成图像显示和交互。该类提供的主要交互操作有:图像放缩,窗宽窗位调节,并提供切片选择,切片方向设置接口,尤其适合三维图像的显示。下面代码说明了怎样使用vtkImageViewer2显示图像。

 

1:   vtkSmartPointer<vtkMetaImageReader>reader =

  2:    vtkSmartPointer<vtkMetaImageReader>::New();

  3:   reader->SetFileName ("…\\\\brain.mhd" );

  4:   reader->Update();

  5:  

  6:  vtkSmartPointer<vtkImageViewer2> imageViewer =

  7:    vtkSmartPointer<vtkImageViewer2>::New();

  8:  imageViewer->SetInputConnection(reader->GetOutputPort());

  9:   vtkSmartPointer<vtkRenderWindowInteractor>renderWindowInteractor =

 10:    vtkSmartPointer<vtkRenderWindowInteractor>::New();

 11:  imageViewer->SetupInteractor(renderWindowInteractor);

 12:  

 13:  imageViewer->SetColorLevel(500);

 14:   imageViewer->SetColorWindow(2000);

 15:  imageViewer->SetSlice(40);

 16:  imageViewer->SetSliceOrientationToXY();

 17:   imageViewer->Render();

 18:  

 19:  renderWindowInteractor->Start();

 

这里为了更好的说明vtkImageViewer2功能,使用一副三维医学图像为例进行说明。首先使用vtkMetaImageReader读入一个mhd图像,然后定义一个vtkImageViewer2对象显示图像。第9和10行定义了一个vtkRenderWindowInteractor对象,并传递给vtkImageViewer2对象,用于完成鼠标、键盘等消息响应,便于进行图像的交互操作。接下来分别设置了四个参数,窗位(ColorLevel)、窗宽(ColorWindow)、切片(Slice)和切片方向(Orientation)。设置完毕后,运行程序,图像显示如下。另外,按下鼠标左键拖动鼠标,可以调节图像的窗宽窗位,从而显示不同灰度范围内容;按下鼠标右键拖动鼠标可以放缩图像。当然这些交互操作可以由用户根据需要自己定义vtkInteractorStyle子类,并响应相应的操作。

窗宽是CT图像上显示的CT值范围。一般显示器的灰度范围为256级,而X光图像的灰度范围则远远大于该范围,因此通过显示器显示时不能显示所有灰度级。因此需要窗宽来定义需要显示的灰度范围。当灰度值高于该范围的最大值时,均以白影显示;而低于该范围时均显示为黑色。增大窗宽,显示具有不同灰度值的组织结构增多,但是会降低组织之间的对比度。减小窗宽,则可视的不同灰度组织结构会减少,同时增大组织结构的对比度。

窗位是窗的中心位置。窗宽只是确定了CT图像灰度范围上的可视部分范围,还需要窗位来确定可视灰度范围的具体位置。同样的窗宽,会根据窗位的位置变化来显示不同的组织结构。比如,窗宽为200时,当窗位为100时,可视灰度范围为0至200;当窗位为500时,则可视灰度范围为400至600。当窗宽窗位确定以后,显示时底层会将可视灰度范围转换到256灰度级进行显示(参考图5.4)。

 

图5.4医学图像窗宽窗位示意图

 

而显示三维图像时,需要确定当前显示切片和方向。vtkImageViewer2提供了SetSlice()函数设置切片号,SetSliceOrientationToXY()则将切片的方向设置为垂直XY平面方向。此外还可以设置为垂直YZ或者XZ平面方向,其对应函数分别为SetSliceOrientationToYZ()和SetSliceOrientationToXZ()。默认情况下切片方向为垂直于XY平面即沿着Z轴方向,根据设置的切片号获取Z轴方向的具体切片进行显示。

切片(slice)与切面是三维图像比较常用的概念。尤其是在医学图像中,不同方向的切面都有特定的名字。矢状面(sagital)是沿着身体前后径所做的与地面垂直的切面;冠状面(coronal)是沿着身体左右径所做的与地面垂直的切面;而横断面(transverse/axial)是指横断身体与地面平行的切面。(图5.5所示)

 

图5.5 矢状面、冠状面和横断面示意图

 

设置切片的方向即是通过不同的方向来观察人体内部组织结构。因此,常见的医学图像可视化软件常常提供四个视图用来显示图像:横断面视图,矢状面视图,冠状面视图和三维视图。

5.3.2vtkImageActor

vtkImageActor是一个三维图像渲染Actor,通过纹理映射将图像映射到一个多边形上进行显示。使用vtkImageActor较vtkImageViewer2要复杂一些,需要建立完整的渲染管线:包括vtkImageActor,vtkRender,vtkRenderWindow,vtkRenderWindowInteractor管线。另外,作为图像二维浏览器,不需要在三维空间中进行旋转操作,因此还需要为vtkRenderWindow定义一个vtkInteractorStyleImage对象。下面给出vtkImageActor显示图像的示例(结果如图5.6所示)。

 

  1:      vtkSmartPointer<vtkBMPReader> reader=

  2:         vtkSmartPointer<vtkBMPReader>::New();

  3:      reader->SetFileName ("…\\\\lena.bmp" );

  4:      reader->Update();

  5:  

  6:      vtkSmartPointer<vtkImageActor>imgActor =

  7:         vtkSmartPointer<vtkImageActor>::New();

  8:     imgActor->SetInput(reader->GetOutput());

  9:  

 10:     vtkSmartPointer<vtkRenderer> renderer =

 11:         vtkSmartPointer<vtkRenderer>::New();

 12:      renderer->AddActor(imgActor);

 13:     renderer->SetBackground(.4, .5, .6);

 14:  

 15:     vtkSmartPointer<vtkRenderWindow> renderWindow =

 16:         vtkSmartPointer<vtkRenderWindow>::New();

 17:     renderWindow->SetSize(500, 500);

 18:     renderWindow->AddRenderer(renderer);

 19:  

 20:     vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor=

 21:         vtkSmartPointer<vtkRenderWindowInteractor>::New();

 22:     vtkSmartPointer<vtkInteractorStyleImage> style =

 23:         vtkSmartPointer<vtkInteractorStyleImage>::New();

 24:  

 25:     renderWindowInteractor->SetInteractorStyle(style);

 26:     renderWindowInteractor->SetRenderWindow(renderWindow);

 27:     renderWindowInteractor->Initialize();

 28:  

 29:     renderWindowInteractor->Start();

 

上面代码中在读入图像后,依次建立vtkImageActor,vtkRender,vtkRenderWindow,vtkRenderWindowInteractor,并组装为管线。为了屏蔽旋转操作,建立vtkInteractorStyleImage对象,并通过renderWindowInteractor->SetInteractorStyle(style)设置交互对象。需要注意的是,vtkImageActor接收的图像vtkImageData数据类型必须为unsigned char类型,因此在显示之前,需要利用vtkImageCast将图像数据类型转换为unsigned char。

 

图5.6 使用vtkImageActor显示图像数据

5.3.3图像融合

上面两种方法都是在一个窗口中显示一个图像。但是在常见的医学处理软件中,经常会遇到在一个窗口中显示多个图像,这就会用到图像融合技术。图像融合是利用图像的alpha通道和不透明度来实现。VTK中vtkImageBlend实现图像的融合。

vtkImageBlend可以接收多个图像输入,输出为融合图像。输出图像的像素间隔、原点、范围(extent)以及像素组分个数与第一个图像一致。该类提供了两种融合模式,第一种是标准模式,也是默认的融合方式。其计算公式如下:

 

1:output<- input[0]

2:foreachinput i {

3:foreachpixel px {

4:  r <- input[i](px)(alpha)* opacity[i]

5:  f <- (255 – r)

6:  output(px) <- output(px) * f + input(px) *r

7:}

8:}

 

第二种是混合模式(Compound)。该模式下输出结果经过alpha/opacity不透明度的和做过归一化。另外还可以设置一个阈值,当alpha*opacity小于等于该阈值时会忽略该像素。其计算公式如下:

 

1:output<- 0

2:foreachpixel px {

3:  sum <- 0

4:  foreach input i {

5:    r <- input[i](px)(alpha)* opacity(i)

6:    sum <- sum + r

7:    if r > threshold {

8:      output(px) <- output(px) + input(px) *r

9:     }

10:}

11:output(px) <- output(px) / sum

12:}

 

下面代码说明了怎么融合图像并显示。代码中读入了一副灰度图像,并生成了一个二值图像;然后定义了vtkImageBlend对象,函数SetInput()设置两个图像作为输入。这里设置输入图像时,由于可以输入多个图像,因此需要给定图像的id号来设置输入。SetOpacity()用于设置对应id号的图像不透明度的大小,当不透明度为1.0时,为完全不透明。程序的执行结果如图5.7所示。

1:      vtkSmartPointer<vtkJPEGReader>reader =

  2:         vtkSmartPointer<vtkJPEGReader>::New();

  3:      reader->SetFileName ("..\\\\lena2.jpg" );

  4:      reader->Update();

  5:  

  6:     vtkSmartPointer<vtkImageCanvasSource2D> imageSource =

  7:         vtkSmartPointer<vtkImageCanvasSource2D>::New();

  8:     imageSource->SetNumberOfScalarComponents(1);

  9:     imageSource->SetScalarTypeToUnsignedChar();

 10:     imageSource->SetExtent(0, 512, 0, 512, 0, 0);

 11:     imageSource->SetDrawColor(0.0);

 12:     imageSource->FillBox(0, 512, 0, 512);

 13:     imageSource->SetDrawColor(255.0);

 14:     imageSource->FillBox(100,400,100,400);

 15:      imageSource->Update();

 16:  

 17:     vtkSmartPointer<vtkImageBlend> imageBlend =

 18:         vtkSmartPointer<vtkImageBlend>::New();

 19:     imageBlend->SetInput(0, reader->GetOutput());

 20:     imageBlend->SetInput(1, imageSource->GetOutput());

 21:     imageBlend->SetOpacity(0, 0.4);

 22:     imageBlend->SetOpacity(1, 0.6);

 23:      imageBlend->Update();

 

图5.7 VTK图像整合效果

==========欢迎转载,转载时请保留该声明信息==========

版权归@东灵工作室所有,更多信息请访问东灵工作室


教程系列导航:http://blog.csdn.net/www_doling_net/article/details/8763686

================================================



 

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片