1、区域提取
感兴趣区域(Volume of Interest,VOI)是图像内部的一块子区域。在VTK中,vtkExtractVOI类可根据用户指定的区域范围提取子图像。该Filter的输入和输出都是一个vtkImageData,因此其结果可以直接作为图像保存。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);// VTK was built with vtkRenderingOpenGL2
VTK_MODULE_INIT(vtkInteractionStyle);#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkBMPReader.h>
#include <vtkExtractVOI.h>//测试图像:../data/lena.bmp
int main(int argc, char* argv[])
{vtkSmartPointer<vtkBMPReader> reader =vtkSmartPointer<vtkBMPReader>::New();reader->SetFileName ("../data/lena.bmp");reader->Update();int dims[3];reader->GetOutput()->GetDimensions(dims);vtkSmartPointer<vtkExtractVOI> extractVOI =vtkSmartPointer<vtkExtractVOI>::New();extractVOI->SetInputConnection(reader->GetOutputPort());extractVOI->SetVOI(0, 3. * dims[0]/4.,0., 3. * dims[1]/4., 0, 0);extractVOI->Update();vtkSmartPointer<vtkImageActor> originalActor =vtkSmartPointer<vtkImageActor>::New();originalActor->SetInputData(reader->GetOutput());vtkSmartPointer<vtkImageActor> voiActor =vtkSmartPointer<vtkImageActor>::New();voiActor->SetInputData(extractVOI->GetOutput());double originalViewport[4] = {0.0, 0.0, 0.5, 1.0};double voiviewport[4] = {0.5, 0.0, 1.0, 1.0};vtkSmartPointer<vtkRenderer> originalRenderer =vtkSmartPointer<vtkRenderer>::New();originalRenderer->SetViewport(originalViewport);originalRenderer->AddActor(originalActor);originalRenderer->ResetCamera();originalRenderer->SetBackground(1.0, 1.0, 1.0);vtkSmartPointer<vtkRenderer> shiftscaleRenderer =vtkSmartPointer<vtkRenderer>::New();shiftscaleRenderer->SetViewport(voiviewport);shiftscaleRenderer->AddActor(voiActor);shiftscaleRenderer->ResetCamera();shiftscaleRenderer->SetBackground(1.0, 1.0, 1.0);vtkSmartPointer<vtkRenderWindow> renderWindow =vtkSmartPointer<vtkRenderWindow>::New();renderWindow->AddRenderer(originalRenderer);renderWindow->AddRenderer(shiftscaleRenderer);renderWindow->SetSize(900, 300);renderWindow->Render();renderWindow->SetWindowName("ExtractVOIExample");vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =vtkSmartPointer<vtkRenderWindowInteractor>::New();vtkSmartPointer<vtkInteractorStyleImage> style =vtkSmartPointer<vtkInteractorStyleImage>::New();renderWindowInteractor->SetInteractorStyle(style);renderWindowInteractor->SetRenderWindow(renderWindow);renderWindowInteractor->Initialize();renderWindowInteractor->Start();return EXIT_SUCCESS;
}
上述代码用于提取图像的感兴趣区域。先读取一幅 BMP图像,并获取图像的维数;然后定义vtkExtractVOI 对象,该对象接收两个输入:第一个是图像数据,第二个是区域大小。设置区域大小的函数原型的代码如下:
void SetvOI(int _arg1, int _arg2, int _arg3, int _arg4, int _arg5, int _arg6)
void SetVOI(int _arg[])
其参数是所提取区域各个方向的大小,共 6个参数,依次表示X方向坐标最小值、X方向最大值、Y方向最小值、Y方向最大值、Z方向最小值和Z方向最大值。本例中由于读取的是二维图像,因此 Z方向的区域为[0,0],而 X方向的范围[dims[0]/4,3dims[0]/4],Y 方向的范围为[dims[1]/4,3dims[1]/4],即提取图像原图中间 1/4 图像。
2、灰度直方图
直方图统计是图像处理中一种非常重要的操作。VTK 中的 vtkImageAccumulate 类用于实现直方图统计功能。它将每个组分的数值范围划分为离散的间隔,然后统计每个灰度间隔上的像素数目。vtkImageAccumulate 输入和输出都是 vtkImageData 类型,因此直方图也可以看作一幅图像;输入图像的像素数据类型可以是任意的,但是最大支持3个组分像素类型,而输出图像的像素数据类型为 int 型。对于灰度图像使用该Filter 会产生一个一维图像。下述代码演示了计算灰度图像直方图的方法。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);// VTK was built with vtkRenderingOpenGL2
VTK_MODULE_INIT(vtkInteractionStyle);#include <vtkActor.h>
#include <vtkBarChartActor.h>
#include <vtkFieldData.h>
#include <vtkImageAccumulate.h>
#include <vtkImageData.h>
#include <vtkIntArray.h>
#include <vtkJPEGReader.h>
#include <vtkLegendBoxActor.h>
#include <vtkProperty2D.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkSmartPointer.h>
#include <vtkTextProperty.h>//测试图像:../data/lena-gray.jpg
int main(int argc, char* argv[])
{vtkSmartPointer<vtkJPEGReader> reader =vtkSmartPointer<vtkJPEGReader>::New();reader->SetFileName ( "..\\data\\lena-gray.jpg" );reader->Update();int bins = 16;int comps = 1;vtkSmartPointer<vtkImageAccumulate> histogram =vtkSmartPointer<vtkImageAccumulate>::New();histogram->SetInputData(reader->GetOutput());histogram->SetComponentExtent(0, bins-1, 0, 0, 0, 0);histogram->SetComponentOrigin(0, 0, 0);histogram->SetComponentSpacing(256.0/bins, 0, 0);histogram->Update();int* output = static_cast<int*>(histogram->GetOutput()->GetScalarPointer());vtkSmartPointer<vtkIntArray> frequencies = vtkSmartPointer<vtkIntArray>::New();frequencies->SetNumberOfComponents(1);for(int j = 0; j < bins; ++j){for(int i=0; i<comps; i++){frequencies->InsertNextTuple1(*output++);}}vtkSmartPointer<vtkDataObject> dataObject = vtkSmartPointer<vtkDataObject>::New();dataObject->GetFieldData()->AddArray( frequencies );vtkSmartPointer<vtkBarChartActor> barChart = vtkSmartPointer<vtkBarChartActor>::New();barChart->SetInput(dataObject);barChart->SetTitle("Histogram");barChart->GetPositionCoordinate()->SetValue(0.05,0.05,0.0);barChart->GetPosition2Coordinate()->SetValue(0.95,0.95,0.0);barChart->GetProperty()->SetColor(0,0,0);barChart->GetTitleTextProperty()->SetColor(0,0,0);barChart->GetLabelTextProperty()->SetColor(0,0,0);barChart->GetLegendActor()->SetNumberOfEntries(dataObject->GetFieldData()->GetArray(0)->GetNumberOfTuples());barChart->LegendVisibilityOff();barChart->LabelVisibilityOff();double colors[3][3] = {{ 1, 0, 0 },{ 0, 1, 0 },{ 0, 0, 1 } };int count = 0;for( int i = 0; i < bins; ++i ){for( int j = 0; j < comps; ++j ){barChart->SetBarColor( count++, colors[j] );}}vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();renderer->AddActor(barChart);renderer->SetBackground(1.0, 1.0, 1.0);vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();renderWindow->AddRenderer(renderer);renderWindow->SetSize(640, 480);renderWindow->Render();renderWindow->SetWindowName("ImageAccumulateExample");vtkSmartPointer<vtkRenderWindowInteractor> interactor =vtkSmartPointer<vtkRenderWindowInteractor>::New();interactor->SetRenderWindow(renderWindow);interactor->Initialize();interactor->Start();return EXIT_SUCCESS;
}
该示例先读入一幅灰度图像。一般灰度图像的灰度范围为0~255。定义了一个变量bins
-16,表示图像灰度直方图的间隔数目,也可以理解为直方图一维数组的维数。然后定义vtkImageAccumulate 对象,并设置输入数据为读入的图像数据,接着调用了如下三个函数。
- SetComponentExtent(0,bins-1,0,0,0,0)。该函数设置要计算每个组分的直方图的最小值和最大值。vtkImageAccumulate 最大支持像素值为三个组分(如 RGB图像)的直方图,该方法共有六个参数,分别表示每个组分的直方图最小值和最大值。该例中由于计算的是灰度图像直方图,只有一个组分,因此第二个组分和第三个组分都设置为0:而第一组分直方图维数为 bins= 16,那么其灰度范围是[0,bins-1]。
- SetComponentOrigin(0,0,0)。该函数设置的是统计每个组分直方图时的起始灰度值。这里设置为 0,表示灰度从0开始统计直方图。由于vtkImageAccumulate 最大支持像素值为三个组分,因此这里也要设置三个参数。如果图像的灰度范围为[1000,2000],那么计算直方图时,其起始灰度应该设置为1000。
- SetComponentSpacing(16,0,0)。该函数设置直方图每个间隔代表的灰度范围,例如当一个图像灰度范围为[1000, 2000],统计直方图的间隔数 bins为 100时,对应的 space 应该设置为SetComponentSpacing(100,0, 0)。参数设置完毕,执行Update()即可计算直方图。前面已经提到过,vtkImageAccumulate的输出结果也是一个vtkImageData类型数据,这样就可以方便地访问图像的每个数据。需要注意的是,输出直方图图像的数据类型为int。
这里顺便介绍一下直方图的显示。虽然vtkImageAccumulate 的输出类型为vtkImageData,但是并不能直接以图像的方式进行显示。VTK中的vtkBarChartActor 类用来显示条形图,可以利用它来显示直方图。但是该类接收的数据类型为vtkDataObject类型,因此需要先对直方图数据进行类型转换:先将直方图数组存储到 vtkIntArray数组frequencics 中,然后通过vtkDataObjcct 函数 GetFieldData()->AddArray(frequencies)将其添加到 vtkDataObject 对象中。
vtkBarChartActor 对象接收vtkDataObject对象作为输入,另外还需要设置图表的名字、颜色等。这里需要注意两个函数:
barChart->GetPositionCoordinate0->SetValue(0.05,0.05,0.0);
barChart>GetPosition2Coordinate0->SetValue(0.95,0.95,0.0);
这里设置的是窗口中显示图表的所在矩形的左下角点和右上角点坐标。VTK的坐标系原点位于左下角点,设置时需要格外注意。设置完毕,即可定义相应的vtkRenderer、vtkRenderWindow和 vtkRenderWindowInteractor 对象以显示图像直方图。本示例的运行结果如图所示。
3、彩色直方图
彩色图像有三个颜色通道,因此需要提取RGB三个通道数据分别计算直方图。每个通道计算直方图的方法与灰度图像直方图的计算方法一致。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);// VTK was built with vtkRenderingOpenGL2
VTK_MODULE_INIT(vtkInteractionStyle);#include <vtkSmartPointer.h>
#include <vtkActor.h>
#include <vtkImageAccumulate.h>
#include <vtkImageData.h>
#include <vtkImageExtractComponents.h>
#include <vtkBMPReader.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkXYPlotActor.h>
#include <vtkAxisActor2D.h>
#include <vtkProperty2D.h>
#include <vtkTextProperty.h>//测试图像:../data/lena.bmp
int main(int argc, char* argv[])
{vtkSmartPointer<vtkBMPReader> reader =vtkSmartPointer<vtkBMPReader>::New();reader->SetFileName ("../data/lena.bmp");reader->Update();int numComponents = reader->GetOutput()->GetNumberOfScalarComponents();vtkSmartPointer<vtkXYPlotActor> plot = vtkSmartPointer<vtkXYPlotActor>::New();plot->ExchangeAxesOff();plot->SetLabelFormat( "%g" );plot->SetXTitle( "Intensity" );plot->SetYTitle( "Frequency" );plot->SetXValuesToValue();plot->GetProperty()->SetColor(0.0, 0.0, 0.0);plot->GetAxisLabelTextProperty()->SetColor(0.0, 0.0, 0.0);plot->GetAxisTitleTextProperty()->SetColor(0.0, 0.0, 0.0);double colors[3][3] = {{ 1, 0, 0 },{ 0, 1, 0 },{ 0, 0, 1 } };const char* labels[3] = { "Red", "Green", "Blue" };int xmax = 0;int ymax = 0;for( int i = 0; i < numComponents; ++i ){vtkSmartPointer<vtkImageExtractComponents> extract = vtkSmartPointer<vtkImageExtractComponents>::New();extract->SetInputConnection( reader->GetOutputPort() );extract->SetComponents( i );extract->Update();double range[2];extract->GetOutput()->GetScalarRange( range );int extent = static_cast<int>(range[1])-static_cast<int>(range[0])-1;vtkSmartPointer<vtkImageAccumulate> histogram = vtkSmartPointer<vtkImageAccumulate>::New();histogram->SetInputConnection( extract->GetOutputPort() );histogram->SetComponentExtent( 0,extent, 0,0, 0,0);histogram->SetComponentOrigin( range[0],0,0 );histogram->SetComponentSpacing( 1,0,0 );histogram->SetIgnoreZero( 1 );histogram->Update();if( range[1] > xmax ) { xmax = range[1];}if( histogram->GetOutput()->GetScalarRange()[1] > ymax ) {ymax = histogram->GetOutput()->GetScalarRange()[1];}plot->AddDataSetInput( histogram->GetOutput() );plot->SetPlotColor(i,colors[i]);plot->SetPlotLabel(i,labels[i]);plot->LegendOn();}plot->SetXRange( 0, xmax);plot->SetYRange( 0, ymax);vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();renderer->AddActor(plot);renderer->SetBackground(1.0, 1.0, 1.0);vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();renderWindow->AddRenderer( renderer );renderWindow->SetSize(640, 480);renderWindow->Render();renderWindow->SetWindowName("ImageAccumulateExample2");vtkSmartPointer<vtkRenderWindowInteractor> interactor =vtkSmartPointer<vtkRenderWindowInteractor>::New();interactor->SetRenderWindow( renderWindow );interactor->Initialize();interactor->Start(); return EXIT_SUCCESS;
}
该例先通过 vtkImageExtractComponents 来提取每个组分图像,再利用 vtkImageAccumulate统计每个组分的直方图。在本例中计算直方图的间隔取(1,0,0),即每个灰度计算统计一个频率,而且灰度起点为图像的最小灰度值。同时,SetIgnoreZero(1)设置在统计直方图时,像素值为 0的像素不进行统计。
在灰度图像直方图示例中使用vtkBarChartActor柱状图来显示直方图,本例中则使用vtkXYPlotActor 曲线来表示直方图,这里做一简单介绍。vtkXYPlotActor类可以用来显示二维曲线,它可以接收多个输入数据,如本例中输入了三条曲线,分别是图像红色分量的直方图曲线、绿色分量的直方图曲线和蓝色分量的直方图曲线。SetXRange()和 SetYRange()用来设置X轴和Y轴的数据范围,另外还可以设置X轴和Y轴的名字、曲线的标题等属性(详细信息可以查阅 vtkXYPlotActor 类的文档)。
vtkXYPlotActor 类是一个 vtkActor2D 的子类,因此定义相应的 vtkRenderer、vtkRenderWindow 和vtkRenderWindowInteractor 对象建立可视化管道即可显示图像直方图曲线。本示例的运行结果如图5-23 所示,其中,红色曲线代码红色分量的直方图,绿色代表绿色分量的直方图曲线,蓝色代表蓝色分量的直方图曲线。