这个专栏是用于记录我在学习VTK过程中的一些心得体会。参考的资料主要有以下三个:
1. 张晓东 罗火灵《VTK图形图像开发进阶》
2. https://examples.vtk.org/site/
3. 沈子恒 《VTK 三维数据渲染进阶》
遇到的一个大问题就是由于版本更新,这些资料中很多代码无法正常运行,需要进行一定的修改,所以这个专栏会记录下来我修改后的程序代码,以便于我之后温习。也希望能给和我有同样困扰的小伙伴们一些帮助。
我使用的版本:VTK9 + VS2022
1.VTK中的图像重采样实现
图像重采样是指对采样后形成的由离散数据组成的数字图像按所需的像元位置或像元问距重新采样,以构成几何变换后的新图像。重采样过程本质上是图像恢复过程,它用输入的离散数字图像重建代表原始图像二维连续函数,再按新的像元间距和像元位置进行采样。其数学过程是根据重建的连续函数(曲面),用周围若干像元点的值估计或内插出新采样点的值。图像重采样在图像处理中应用非常广泛,如SIFT特征提取。
图像重采样后图像的维数会发生改变。当重采样图像小于原图像维数时,称为降采样;当重采样图像维数大于原图像时,称为升采样。
在VTK中,vtkImageShrink3D类用于实现图像降采样。降采样需要设置每个方向的采样率,降采样率越大,图像越模糊。升采样的原理与降采样原理一致,只是增加采样点数来增加图像的维数。在VTK中,vtkImageMagnify类用于实现图像的升采样。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkBMPReader.h>
#include <vtkImageData.h>
#include <vtkImageShrink3D.h> //降采样头文件
#include <vtkImageMagnify.h> //升采样头文件
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h> //交互样式
int main()
{
vtkSmartPointer<vtkBMPReader> reader =
vtkSmartPointer<vtkBMPReader>::New();
reader->SetFileName("D:\lena.bmp");
reader->Update();
vtkSmartPointer<vtkImageShrink3D> shrinkFilter =
vtkSmartPointer<vtkImageShrink3D>::New();
shrinkFilter->SetInputConnection(reader->GetOutputPort());//建立管道
shrinkFilter->SetShrinkFactors(16, 16, 1);//设置降采样率
shrinkFilter->Update();
vtkSmartPointer<vtkImageMagnify> magnifyFilter =
vtkSmartPointer<vtkImageMagnify>::New();
magnifyFilter->SetInputConnection(reader->GetOutputPort());
magnifyFilter->SetMagnificationFactors(10, 10, 1);//设置升采样率
magnifyFilter->Update();
int originalDims[3];
reader->GetOutput()->GetDimensions(originalDims);
double originalSpacing[3];
reader->GetOutput()->GetSpacing(originalSpacing);
int shrinkDims[3];
shrinkFilter->GetOutput()->GetDimensions(shrinkDims);
double shrinkSpacing[3];
shrinkFilter->GetOutput()->GetSpacing(shrinkSpacing);
int magnifyDims[3];
magnifyFilter->GetOutput()->GetDimensions(magnifyDims);
double magnifuSpacing[3];
magnifyFilter->GetOutput()->GetSpacing(magnifuSpacing);
std::cout << "原始维度: " << originalDims[0] << "*" << originalDims[1] << "*" << originalDims[2] << std::endl;
std::cout << "原始像素间距: " << originalSpacing[0] << " " << originalSpacing[1] << " " << originalSpacing[2] << std::endl;
std::cout << "降采样维度: " << shrinkDims[0] << "*" << shrinkDims[1] << "*" << shrinkDims[2] << std::endl;
std::cout << "降采样像素间距:" << shrinkSpacing[0] << " " << shrinkSpacing[1] << " " << shrinkSpacing[2] << std::endl;
std::cout << "升采样维度: " << magnifyDims[0] << "*" << magnifyDims[1] << "*" << magnifyDims[2] << std::endl;
std::cout << "升采样像素间距:" << magnifuSpacing[0] << " " << magnifuSpacing[1] << " " << magnifuSpacing[2] << std::endl;
vtkSmartPointer<vtkImageActor> origActor =
vtkSmartPointer<vtkImageActor>::New();
origActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> shrinkActor =
vtkSmartPointer<vtkImageActor>::New();
shrinkActor->SetInputData(shrinkFilter->GetOutput());
vtkSmartPointer<vtkImageActor> magnifyActor =
vtkSmartPointer<vtkImageActor>::New();
magnifyActor->SetInputData(magnifyFilter->GetOutput());
///
double origView[4] = { 0, 0, 0.3, 1.0 };
double shrinkView[4] = { 0.3, 0, 0.6, 1.0 };
double magnifyView[4] = { 0.6, 0, 1, 1 };
vtkSmartPointer<vtkRenderer> origRender =
vtkSmartPointer<vtkRenderer>::New();
origRender->SetViewport(origView);
origRender->AddActor(origActor);
origRender->ResetCamera();
origRender->SetBackground(1, 0, 0);
vtkSmartPointer<vtkRenderer> shrinkRender =
vtkSmartPointer<vtkRenderer>::New();
shrinkRender->SetViewport(shrinkView);
shrinkRender->AddActor(shrinkActor);
shrinkRender->ResetCamera();
shrinkRender->SetBackground(0, 1, 0);
vtkSmartPointer<vtkRenderer> magnifyRender =
vtkSmartPointer<vtkRenderer>::New();
magnifyRender->SetViewport(magnifyView);
magnifyRender->AddActor(magnifyActor);
magnifyRender->ResetCamera();
magnifyRender->SetBackground(0, 0, 1);
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(origRender);
rw->AddRenderer(shrinkRender);
rw->AddRenderer(magnifyRender);
rw->SetSize(720, 240);
rw->SetWindowName("Image-Shrink-Magnify");
rw->Render();
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
2.图像二值化_阈值法
二值图像是图像分割中经常用到的图像。二值图像的每个像素只有两种可能的取值,例如0或者255。通常0代表图像的背景,而255代码图像前景。图像二值化是最简单的图像分割模型。设置一个灰度阈值,将图像中阈值以下的像素值设置为背景,阈值以上的像素值设置为前景,即可得当一副二值图像。
在VTK中可以通过vtkImageThreshold类实现图像二值化。
vtkImageThreshold类中定义了两个阈值,UpperThreshold和LowerThreshold,这两个值将图像的值域划分为三部分:大于UpperThreshold,小于LowerThreshold,以及位于LowerThreshold和UpperThreshold之间的三段。函数ThresholdByUpper()设置为取大于UpperThreshold的灰度范围为有效范围,并通过函数SetInValue()来设置该范围内的输出值,SetOutValue()设置范围外的输出值。
在下面的这个例子中,我们是取大于100所有像素输出值为255,而小于100的像素值为0。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageThreshold.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena.jpg");
reader->Update();
vtkSmartPointer<vtkImageThreshold> threshould =
vtkSmartPointer<vtkImageThreshold>::New();
threshould->SetInputConnection(reader->GetOutputPort());
threshould->ThresholdByUpper(90);
threshould->SetInValue(255);
threshould->SetOutValue(0);
threshould->Update(); //算法执行后必须添加更新消息!!!
vtkSmartPointer<vtkImageActor> origActor =
vtkSmartPointer<vtkImageActor>::New();
origActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> binaryActor =
vtkSmartPointer<vtkImageActor>::New();
binaryActor->SetInputData(threshould->GetOutput());
double origView[4] = { 0, 0, 0.5, 1 };
double binaryView[4] = { 0.5, 0, 1, 1 };
vtkSmartPointer<vtkRenderer> origRender =
vtkSmartPointer<vtkRenderer>::New();
origRender->SetViewport(origView);
origRender->AddActor(origActor);
origRender->ResetCamera();
origRender->SetBackground(1, 0, 0);
vtkSmartPointer<vtkRenderer> binaryRender =
vtkSmartPointer<vtkRenderer>::New();
binaryRender->SetViewport(binaryView);
binaryRender->AddActor(binaryActor);
binaryRender->ResetCamera();
binaryRender->SetBackground(1, 1, 1);
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(origRender);
rw->AddRenderer(binaryRender);
rw->SetSize(640, 240);
rw->SetWindowName("Binary Image");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
这里需要注意的是:输入的原始图像需要是灰度图像,不能是彩色图像,不然会出现如下情况
3.边缘检测
图像中不连续的灰度值会产生边缘,图像的边缘检测是基于边界的图像分割方法,如分水岭算法,通常是分割原图的梯度图像,梯度实际上也是反应的图像边缘信息。图像边缘一般常用图像一阶导数和二阶导数来检测。
梯度算子
梯度算子对应于图像一阶导数。图像一阶导数计算一般是通过差分运算来近似的。
VTK中可以使用vtkImageGradient计算图像梯度。注意图像梯度是一个向量,具有方向和大小。因此vtkImageGradient的计算结果是一个梯度场,也就是每个像素值都是一个梯度向量。显示梯度图像时需要计算每个像素点的梯度大小,即模值。
vtkImageGradient的使用比较简单,只需要设置输入图像即可。计算梯度时,采用的是中间差分法,即像素在每个方向的差分,都是利用的前后两个像素值之差。这样在图像在边界处的差分计算需要特殊处理。其内部定义了HandleBoundaries变量,通过函数SetHandleBoundaries()定赋值。当HandleBoundaries为真时算子会特殊处理计算边界像素的梯度;当为假时不计算边界像素的梯度值,因此输出图像大小要小于输入图像。
函数SetDimensionality()用于设置要计算的图像维数,默认为二维,此时梯度向量也为二维。
下面的代码中定义了vtkImageMagnitude对象来计算梯度向量的2范数,即向量的模。
利用vtkImageShiftScale将图像的数据范围调整到0-255然后显示。
另外还可以通过vtkImageExtractComponents来提取每个方向的梯度分量进行显示。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageGradient.h>
#include <vtkImageMagnitude.h>
#include <vtkImageData.h>
#include <vtkImageShiftScale.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageGradient> imgGradient =
vtkSmartPointer<vtkImageGradient>::New();
imgGradient->SetInputConnection(reader->GetOutputPort());
imgGradient->SetDimensionality(2);
vtkSmartPointer<vtkImageMagnitude> imgMagnitude =
vtkSmartPointer<vtkImageMagnitude>::New();
imgMagnitude->SetInputConnection(imgGradient->GetOutputPort());
imgMagnitude->Update();
double Range[2];
vtkSmartPointer<vtkImageData> getRange =
vtkSmartPointer<vtkImageData>::New();
imgMagnitude->GetOutput()->GetScalarRange(Range);//图像灰度范围最小值、最大值
vtkSmartPointer<vtkImageShiftScale> imgShiftScale =
vtkSmartPointer<vtkImageShiftScale>::New();
imgShiftScale->SetOutputScalarTypeToUnsignedChar(); //强制类型转换 0~255
imgShiftScale->SetScale(255 / Range[1]); //灰度映射间距
imgShiftScale->SetInputConnection(imgMagnitude->GetOutputPort());
imgShiftScale->Update();
vtkSmartPointer<vtkImageActor> origActor =
vtkSmartPointer<vtkImageActor>::New();
origActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> GradientActor =
vtkSmartPointer<vtkImageActor>::New();
GradientActor->SetInputData(imgShiftScale->GetOutput());
double origView[4] = { 0, 0, 0.5, 1 };
double gradientView[4] = { 0.5, 0, 1, 1 };
vtkSmartPointer<vtkRenderer> origRender =
vtkSmartPointer<vtkRenderer>::New();
origRender->SetViewport(origView);
origRender->AddActor(origActor);
origRender->ResetCamera();
origRender->SetBackground(1.0, 0, 0);
vtkSmartPointer<vtkRenderer> gradientRender =
vtkSmartPointer<vtkRenderer>::New();
gradientRender->SetViewport(gradientView);
gradientRender->AddActor(GradientActor);
gradientRender->ResetCamera();
gradientRender->SetBackground(1, 1, 1);
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(origRender);
rw->AddRenderer(gradientRender);
rw->SetSize(640, 320);
rw->SetWindowName("Image Gradient");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetRenderWindow(rw);
rwi->SetInteractorStyle(style);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
注意,彩色图像不能直接用来计算梯度,需要先转换为灰度图像。
Sobel算子
Sobel算子也是一种常用的梯度算子。Sobel算子计算稍微复杂,它采用3x3的模板。计算时模板在图像上移动,并在每个位置上计算对应中心像素的梯度值。
-1 | 0 | 1 |
-2 | 0 | 2 |
-1 | 0 | 1 |
1 | 2 | 1 |
0 | 0 | 0 |
-1 | -2 | -1 |
下面的例子展示了如何利用Sobel算子计算图像的梯度图像,然后提取X方向的梯度分量和Y方向的梯度分量。由于计算Sobel算子的值可能存在负值,因此利用vtkImageMathematics对各个分量图像计算绝对值,再由vtkImageShiftScale将图像的数值范围调节到0-255之间再显示。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageSobel2D.h>
#include <vtkImageExtractComponents.h>
#include <vtkImageMathematics.h>
#include <vtkImageData.h>
#include <vtkImageShiftScale.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageSobel2D> sobelFilter =
vtkSmartPointer<vtkImageSobel2D>::New();
sobelFilter->SetInputConnection(reader->GetOutputPort());//包含横向和竖向边缘
//提取X向边缘成分
vtkSmartPointer<vtkImageExtractComponents> xSobel =
vtkSmartPointer<vtkImageExtractComponents>::New();
xSobel->SetComponents(0);//提取第一成分即X向梯度
xSobel->SetInputConnection(sobelFilter->GetOutputPort());
xSobel->Update();
vtkSmartPointer<vtkImageMathematics> absFilter =
vtkSmartPointer<vtkImageMathematics>::New();
absFilter->SetOperationToAbsoluteValue();//将属性设置为绝对值模式
absFilter->SetInputConnection(xSobel->GetOutputPort());
absFilter->Update();
double xRange[2];
absFilter->GetOutput()->GetScalarRange(xRange);
vtkSmartPointer<vtkImageShiftScale> xShiftScale =
vtkSmartPointer<vtkImageShiftScale>::New();
xShiftScale->SetOutputScalarTypeToUnsignedChar();//强制类型转换 方便显示
xShiftScale->SetScale(255 / xRange[1]);//设置属性
xShiftScale->SetInputConnection(absFilter->GetOutputPort());
xShiftScale->Update();
//提取Y向边缘成分
vtkSmartPointer<vtkImageExtractComponents> ySobel =
vtkSmartPointer<vtkImageExtractComponents>::New();
ySobel->SetComponents(1);
ySobel->SetInputConnection(sobelFilter->GetOutputPort());
ySobel->Update();
vtkSmartPointer<vtkImageMathematics> absYsobel =
vtkSmartPointer<vtkImageMathematics>::New();
absYsobel->SetOperationToAbsoluteValue();
absYsobel->SetInputConnection(ySobel->GetOutputPort());
absYsobel->Update();
double yRange[2];
absYsobel->GetOutput()->GetScalarRange(yRange);
vtkSmartPointer<vtkImageShiftScale> yShiftScale =
vtkSmartPointer<vtkImageShiftScale>::New();
yShiftScale->SetOutputScalarTypeToUnsignedChar();
yShiftScale->SetScale(255 / yRange[1]);
yShiftScale->SetInputConnection(absYsobel->GetOutputPort());
yShiftScale->Update();
vtkSmartPointer<vtkImageActor> origActor =
vtkSmartPointer<vtkImageActor>::New();
origActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> xSobelActor =
vtkSmartPointer<vtkImageActor>::New();
xSobelActor->SetInputData(xShiftScale->GetOutput());
vtkSmartPointer<vtkImageActor> ySobelActor =
vtkSmartPointer<vtkImageActor>::New();
ySobelActor->SetInputData(yShiftScale->GetOutput());
double origView[4] = { 0, 0, 0.33, 1 };
double xSobelView[4] = { 0.33, 0, 0.66, 1 };
double ySobelView[4] = { 0.66, 0, 1, 1 };
vtkSmartPointer<vtkRenderer> origRender =
vtkSmartPointer<vtkRenderer>::New();
origRender->SetViewport(origView);
origRender->AddActor(origActor);
origRender->ResetCamera();
origRender->SetBackground(1, 0, 0);
vtkSmartPointer<vtkRenderer> xSobelRender =
vtkSmartPointer<vtkRenderer>::New();
xSobelRender->SetViewport(xSobelView);
xSobelRender->AddActor(xSobelActor);
xSobelRender->ResetCamera();
xSobelRender->SetBackground(0, 1, 0);
vtkSmartPointer<vtkRenderer> ySobelRender =
vtkSmartPointer<vtkRenderer>::New();
ySobelRender->SetViewport(ySobelView);
ySobelRender->AddActor(ySobelActor);
ySobelRender->ResetCamera();
ySobelRender->SetBackground(0, 0, 1);
//
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(origRender);
rw->AddRenderer(xSobelRender);
rw->AddRenderer(ySobelRender);
rw->SetSize(960, 320);
rw->SetWindowName("Edge by Soebl");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
Canny算子
Canny算子是John Canny于20世纪80年代提出的一种多级边缘检测算法。Canny研究了最优边缘的特性,即检测到的边缘要尽可能跟实际的边缘接近并尽可能的多,同时,要尽量降低噪声对边缘检测的干扰。
Canny算子计算步骤如下:
1)对源图像进行高斯平滑以消除图像中噪声;
2)采用差分法近似计算图像每一个像素的梯度,并计算梯度的模值和方向;
3)对梯度进行"非极大抑制":图像边缘点梯度值通常在梯度方向是极大值,因此检测边缘需要将非极大值赋值0来抑制非边缘点。检测方法就是在一个局部窗口内,如果中心像素点的梯度不比梯度方向上相邻两个像素值大,那么该中心像素点梯度值赋0;
4)双阈值法检测边缘和连接边缘。取两个梯度阈值high和low,将梯度图像中小于high的像素赋0得到边缘图像L1,该图像能够接近图像边缘但是可能会存在间断点;将梯度图像中小于low的像素赋0得到边缘图像L2,该图中受噪声影响比较大,但是边缘信息更多。在连接边缘时,以L1为基础,对非零点进行边缘跟踪,如果追踪过程中出现中断,则从L2对应像素点及其邻域来寻找可以连接的边缘,直至结束。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkImageShiftScale.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageGaussianSmooth.h>
#include <vtkImageGradient.h>
#include <vtkImageMagnitude.h>
#include <vtkImageNonMaximumSuppression.h>
#include <vtkImageConstantPad.h>
#include <vtkImageToStructuredPoints.h>
#include <vtkLinkEdgels.h>
#include <vtkThreshold.h>
#include <vtkGeometryFilter.h>
#include <vtkSubPixelPositionEdgels.h>
#include <vtkCamera.h>
#include <vtkProperty.h>
#include <vtkStripper.h>
#include <vtkPolyDataMapper.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena-gray.jpg");
reader->Update();
//图片类型转换
vtkSmartPointer<vtkImageCast> ic =
vtkSmartPointer<vtkImageCast>::New();
ic->SetOutputScalarTypeToFloat();
ic->SetInputConnection(reader->GetOutputPort());
ic->Update();
//高斯算法实现图片平滑
vtkSmartPointer<vtkImageGaussianSmooth> gs =
vtkSmartPointer<vtkImageGaussianSmooth>::New();
gs->SetInputConnection(ic->GetOutputPort());
gs->SetDimensionality(2);
gs->SetRadiusFactors(1, 1, 0);
gs->Update();
vtkSmartPointer<vtkImageGradient> imgGradient =
vtkSmartPointer<vtkImageGradient>::New();
imgGradient->SetInputConnection(gs->GetOutputPort());
imgGradient->SetDimensionality(2);
imgGradient->Update();
vtkSmartPointer<vtkImageMagnitude> imgMagnitude =
vtkSmartPointer<vtkImageMagnitude>::New();
imgMagnitude->SetInputConnection(imgGradient->GetOutputPort());
imgMagnitude->Update();
vtkSmartPointer<vtkImageNonMaximumSuppression> nonMax =
vtkSmartPointer<vtkImageNonMaximumSuppression>::New();
nonMax->SetMagnitudeInputData(imgMagnitude->GetOutput());
nonMax->SetVectorInputData(imgGradient->GetOutput());
nonMax->SetDimensionality(2);
nonMax->Update();
vtkSmartPointer<vtkImageConstantPad> pad =
vtkSmartPointer<vtkImageConstantPad>::New();
pad->SetInputConnection(imgGradient->GetOutputPort());
pad->SetOutputNumberOfScalarComponents(3);
pad->SetConstant(0);
pad->Update();
vtkSmartPointer<vtkImageToStructuredPoints> i2sp1 =
vtkSmartPointer<vtkImageToStructuredPoints>::New();
i2sp1->SetInputConnection(nonMax->GetOutputPort());
i2sp1->SetVectorInputData(pad->GetOutput());
i2sp1->Update();
vtkSmartPointer<vtkLinkEdgels> imgLink =
vtkSmartPointer<vtkLinkEdgels>::New();
imgLink->SetInputData(i2sp1->GetOutput());
imgLink->SetGradientThreshold(2);
imgLink->Update();
vtkSmartPointer<vtkThreshold> thresholdEdgels =
vtkSmartPointer<vtkThreshold>::New();
thresholdEdgels->SetInputConnection(imgLink->GetOutputPort());
thresholdEdgels->SetUpperThreshold(10); //老版本用的是ThresholdByUpper
thresholdEdgels->AllScalarsOff();
thresholdEdgels->Update();
vtkSmartPointer<vtkGeometryFilter> gf =
vtkSmartPointer<vtkGeometryFilter>::New();
gf->SetInputConnection(thresholdEdgels->GetOutputPort());
vtkSmartPointer<vtkImageToStructuredPoints> i2sp =
vtkSmartPointer<vtkImageToStructuredPoints>::New();
i2sp->SetInputConnection(imgMagnitude->GetOutputPort());
i2sp->SetVectorInputData(pad->GetOutput());
i2sp->Update();
vtkSmartPointer<vtkSubPixelPositionEdgels> spe =
vtkSmartPointer<vtkSubPixelPositionEdgels>::New();
spe->SetInputConnection(gf->GetOutputPort());
spe->SetGradMapsData(i2sp->GetStructuredPointsOutput());
vtkSmartPointer<vtkStripper> strip =
vtkSmartPointer<vtkStripper>::New();
strip->SetInputConnection(spe->GetOutputPort());
vtkSmartPointer<vtkPolyDataMapper> dsm =
vtkSmartPointer<vtkPolyDataMapper>::New();
dsm->SetInputConnection(strip->GetOutputPort());
dsm->ScalarVisibilityOff();
vtkSmartPointer<vtkActor> planeActor =
vtkSmartPointer<vtkActor>::New();
planeActor->SetMapper(dsm);
planeActor->GetProperty()->SetAmbient(1.0);
planeActor->GetProperty()->SetDiffuse(0.0);
planeActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
vtkSmartPointer<vtkImageActor> originalActor =
vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
double originalViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
double gradviewport[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> gradRenderer =
vtkSmartPointer<vtkRenderer>::New();
gradRenderer->SetViewport(gradviewport);
gradRenderer->AddActor(planeActor);
gradRenderer->ResetCamera();
gradRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->SetSize(900, 300);
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradRenderer);
renderWindow->Render();
renderWindow->SetWindowName("CannyExample");
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;
}
运行结果
这里需要注意的是 ,一定不要忘了用Update()方法来更新输出数据,我一开始参考的程序没有加上,一直报错:
ERROR: In vtkLinkEdgels.cxx, line 68 vtkLinkEdgels (000001A6372DB720): No data to transform (or wrong data type)!
ERROR: In vtkSubPixelPositionEdgels.cxx, line 71 vtkSubPixelPositionEdgels (000001A638F6A840): No data to fit!
Laplace算子
拉普拉斯算子是一个二阶边缘算子,即梯度的散度。拉普拉斯算子的实现也是通过模板实现。常用的拉普拉斯模板定义如下:
0 | -1 | 0 |
-1 | 4 | -1 |
0 | -1 | 0 |
-1 | -1 | -1 |
-1 | 8 | -1 |
-1 | -1 | -1 |
拉普拉斯算子计算图像的二阶导数,对于图像噪声比较敏感。拉普拉斯算子的结果为标量,表示边缘的宽度。但是它常产生双像素宽边缘,而且不能提供方向信息,因此较少直接用于边缘检测。
在VTK中由vtkImageLaplacian实现。vtkImageLaplacian输入和输出数据都是vtkImageData,与梯度算子不同,该filter的输出图像像素为标量。函数SetDimensionality用于设置输入图像的维数,默认为2维。计算完毕后,利用vtkImageShiftScale将图像的数据范围变换至0-255之间。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageData.h>
#include <vtkImageLaplacian.h>
#include <vtkImageShiftScale.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main(int argc, char* argv[])
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageLaplacian> lapFilter =
vtkSmartPointer<vtkImageLaplacian>::New();
lapFilter->SetInputConnection(reader->GetOutputPort());
lapFilter->SetDimensionality(2);
double range[2];
lapFilter->GetOutput()->GetScalarRange(range);
vtkSmartPointer<vtkImageShiftScale> ShiftScale =
vtkSmartPointer<vtkImageShiftScale>::New();
ShiftScale->SetOutputScalarTypeToUnsignedChar();
ShiftScale->SetScale(255 / (range[1] - range[0]));
ShiftScale->SetShift(-range[0]);
ShiftScale->SetInputConnection(lapFilter->GetOutputPort());
ShiftScale->Update();
vtkSmartPointer<vtkImageActor> originalActor =
vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> gradActor =
vtkSmartPointer<vtkImageActor>::New();
gradActor->SetInputData(ShiftScale->GetOutput());
double originalViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
double gradviewport[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> gradRenderer =
vtkSmartPointer<vtkRenderer>::New();
gradRenderer->SetViewport(gradviewport);
gradRenderer->AddActor(gradActor);
gradRenderer->ResetCamera();
gradRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(originalRenderer);
rw->AddRenderer(gradRenderer);
rw->SetSize(640, 320);
rw->Render();
rw->SetWindowName("Edge by Laplacian");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
4.图像平滑
图像平滑常用于图像的预处理中,如计算梯度时先对图像进行平滑处理,可以减少噪声对梯度的影响。图像平滑一般是通过模板卷积运算实现。模板可以看做是一个大小为nxn的小图像,例如3x3,5x5等等,模板的每个像素都对应一个系数值。模板卷积运算的过程是首先将模板中心依次与图像每个像素重合,通过模板各个系数与图像对应像素相乘来计算模板对应像素的加权平均值,最后将运算结果赋给图像中模板中心对应的像素。
均值滤波
均值滤波是一种经常用到的平滑方法,其对应的模板各个像素的值为1。在VTK中没有直接实现均值滤波的类,但是我们可以通过图像卷积运算来实现。卷积运算通过vtkImageConvolve类实现。通过vtkImageConvolve类,只需要设置相应的卷积模板,便可以实现多种空域图像滤波。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h> //图像数据类型转换为计算类型
#include <vtkImageData.h>
#include <vtkImageConvolve.h> //图像卷积运行
#include <vtkImageShiftScale.h> //设置像素值范围
//#include <vtkImageMandelbrotSource.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkImageMandelbrotSource.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageCast> originalCastFilter =
vtkSmartPointer<vtkImageCast>::New();
originalCastFilter->SetInputConnection(reader->GetOutputPort()); //建管线
originalCastFilter->SetOutputScalarTypeToFloat(); //设置属性
originalCastFilter->Update();
vtkSmartPointer<vtkImageConvolve> convolveFilter =
vtkSmartPointer<vtkImageConvolve>::New();
convolveFilter->SetInputConnection(originalCastFilter->GetOutputPort()); //建管线
double kernel[25] = {
0.045, 0.045, 0.045, 0.045, 0.045,
0.045, 0.045, 0.045, 0.045, 0.045,
0.045, 0.045, 0.045, 0.045, 0.045,
0.045, 0.045, 0.045, 0.045, 0.045,
0.045, 0.045, 0.045, 0.045, 0.045
};
convolveFilter->SetKernel5x5(kernel);
convolveFilter->Update();
vtkSmartPointer<vtkImageCast> convCastFilter =
vtkSmartPointer<vtkImageCast>::New();
convCastFilter->SetInputData(convolveFilter->GetOutput());
convCastFilter->SetOutputScalarTypeToUnsignedChar(); //转换为图像数据
convCastFilter->Update();
///
vtkSmartPointer<vtkImageActor> originalActor =
vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> convolvedActor =
vtkSmartPointer<vtkImageActor>::New();
convolvedActor->SetInputData(convCastFilter->GetOutput());
double leftViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
double rightViewport[4] = { 0.5, 0.0, 1.0, 1.0 };
vtkSmartPointer<vtkRenderer> originalRenderer =
vtkSmartPointer<vtkRenderer>::New();
originalRenderer->SetViewport(leftViewport);
originalRenderer->AddActor(originalActor);
originalRenderer->SetBackground(1.0, 1.0, 1.0);
originalRenderer->ResetCamera();
vtkSmartPointer<vtkRenderer> convolvedRenderer =
vtkSmartPointer<vtkRenderer>::New();
convolvedRenderer->SetViewport(rightViewport);
convolvedRenderer->AddActor(convolvedActor);
convolvedRenderer->SetBackground(1.0, 1.0, 1.0);
convolvedRenderer->ResetCamera();
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();;
rw->AddRenderer(originalRenderer);
rw->AddRenderer(convolvedRenderer);
rw->SetSize(640, 320);
rw->Render();
rw->SetWindowName("Smooth by MeanFilter");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
高斯滤波
高斯平滑的原理类似于均值滤波。均值滤波模板的系数都是一样的,而高斯平滑则是需要根据像素与模板中心的距离来定义权重。权重的计算方法是采用高斯分布,离中心越远,权重越小。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageData.h>
#include <vtkImageGaussianSmooth.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
//#include <vtkImageEllipsoidSource.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena.jpg");
reader->Update();
vtkSmartPointer<vtkImageGaussianSmooth> gaussianSmoothFilter =
vtkSmartPointer<vtkImageGaussianSmooth>::New();
gaussianSmoothFilter->SetInputConnection(reader->GetOutputPort());
gaussianSmoothFilter->SetDimensionality(2);
gaussianSmoothFilter->SetRadiusFactor(5); //设置模板范围
gaussianSmoothFilter->SetStandardDeviation(3);//正态分布/高斯分布标准差
gaussianSmoothFilter->Update();
vtkSmartPointer<vtkImageActor> originalActor =
vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> smoothedActor =
vtkSmartPointer<vtkImageActor>::New();
smoothedActor->SetInputData(gaussianSmoothFilter->GetOutput());
double originalViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
double smoothedViewport[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, 0, 0);
vtkSmartPointer<vtkRenderer> gradientMagnitudeRenderer =
vtkSmartPointer<vtkRenderer>::New();
gradientMagnitudeRenderer->SetViewport(smoothedViewport);
gradientMagnitudeRenderer->AddActor(smoothedActor);
gradientMagnitudeRenderer->ResetCamera();
gradientMagnitudeRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(originalRenderer);
rw->AddRenderer(gradientMagnitudeRenderer);
rw->SetSize(640, 320);
rw->SetWindowName("Smooth by Gaussian");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
中值滤波
vtkImageHybridMedian2D实现了对二维图像的中值滤波。其实现原理是,采用一个5x5的模板,逐次将模板中心对应于图像的每个像素上,将模板图像覆盖的像素的中值作为当前像素的输出值。vtkImageHybridMedian2D类使用非常简单,不需要用户设置任何参数。该方法能够有效的保持图像边缘,并对于椒盐噪声有较好的抑制作用。对于三维图像,则使用vtkImageHybridMedian3D类。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkPNGReader.h>
#include <vtkImageData.h>
#include <vtkImageCast.h>
#include <vtkImageHybridMedian2D.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main(int argc, char* argv[])
{
vtkSmartPointer<vtkPNGReader> reader =
vtkSmartPointer<vtkPNGReader>::New();
reader->SetFileName("D:\lena-noise1.png");
reader->Update();
vtkSmartPointer<vtkImageHybridMedian2D> hybridMedian =
vtkSmartPointer<vtkImageHybridMedian2D>::New();
hybridMedian->SetInputData(reader->GetOutput());
hybridMedian->Update();
///
vtkSmartPointer<vtkImageActor> originalActor =
vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> hybridMedianActor =
vtkSmartPointer<vtkImageActor>::New();
hybridMedianActor->SetInputData(hybridMedian->GetOutput());
double originalViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
double hybridMedianViewport[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, 0, 0);
vtkSmartPointer<vtkRenderer> hybridMedianRenderer =
vtkSmartPointer<vtkRenderer>::New();
hybridMedianRenderer->SetViewport(hybridMedianViewport);
hybridMedianRenderer->AddActor(hybridMedianActor);
hybridMedianRenderer->ResetCamera();
hybridMedianRenderer->SetBackground(1.0, 1.0, 1.0);
//
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(originalRenderer);
rw->AddRenderer(hybridMedianRenderer);
rw->SetSize(640, 320);
rw->Render();
rw->SetWindowName("MedianFilterExample");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果
各向异性滤波
各向异性滤波是一种基于偏微分方程的滤波技术,建立于热量的各向异性扩散理论。各向异性滤波在图像的平坦区域选择大尺度平滑,而边缘区域则选择小尺度的平滑,在抑制噪声的同时保持了图像的边缘信息。这样很好的改善了高斯平滑方法在平滑噪声的同时,模糊了图像的重要边缘图像的问题。
在VTK中,vtkImageAnisotropicDiffusion2D(vtkImageAnisotropicDiffusion3D)实现图像各向异性扩散滤波。
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageAnisotropicDiffusion2D.h>
#include <vtkImageActor.h>
#include <vtkCamera.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main()
{
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:\lena.jpg");
vtkSmartPointer<vtkImageAnisotropicDiffusion2D> diffusion =
vtkSmartPointer<vtkImageAnisotropicDiffusion2D>::New();
diffusion->SetInputConnection(reader->GetOutputPort());
diffusion->SetNumberOfIterations(100);
diffusion->SetDiffusionThreshold(5); //小于该阈值扩散
diffusion->Update();
vtkSmartPointer<vtkImageActor> originalActor =
vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> diffusionActor =
vtkSmartPointer<vtkImageActor>::New();
diffusionActor->SetInputData(diffusion->GetOutput());
double leftViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
double rightViewport[4] = { 0.5, 0.0, 1.0, 1.0 };
vtkSmartPointer<vtkCamera> camera =
vtkSmartPointer<vtkCamera>::New();
vtkSmartPointer<vtkRenderer> leftRenderer =
vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->AddActor(originalActor);
leftRenderer->SetBackground(1.0, 0, 0);
leftRenderer->SetActiveCamera(camera);
leftRenderer->ResetCamera();
vtkSmartPointer<vtkRenderer> rightRenderer =
vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->SetBackground(1.0, 1.0, 1.0);
rightRenderer->AddActor(diffusionActor);
rightRenderer->SetActiveCamera(camera);
vtkSmartPointer<vtkRenderWindow> rw =
vtkSmartPointer<vtkRenderWindow>::New();
rw->AddRenderer(leftRenderer);
rw->AddRenderer(rightRenderer);
rw->SetSize(640, 320);
rw->SetWindowName("Smooth by AnistropicFilter");
vtkSmartPointer<vtkRenderWindowInteractor> rwi =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style =
vtkSmartPointer<vtkInteractorStyleImage>::New();
rwi->SetInteractorStyle(style);
rwi->SetRenderWindow(rw);
rwi->Initialize();
rwi->Start();
return 0;
}
运行结果