一、边缘检测
1、梯度算子
梯度算子对应于图像一阶导数。图像一阶导数一般是通过差分运算来近似的。VTK中可以用 vtklmageGradient 类计算图像梯度。
注意:图像梯度是一个矢量,具有方向和大小,因此vtkImageGradient的计算结果是一个梯度场,即每个像素值都是一个梯度矢量。显示梯度图像时需要计算每个像素点的梯度大小,即模值。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageGradient.h>
#include <vtkImageMagnitude.h>
#include <vtkImageShiftScale.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageGradient> gradientFilter = vtkSmartPointer<vtkImageGradient>::New();
gradientFilter->SetInputConnection(reader->GetOutputPort());
gradientFilter->SetDimensionality(2);
vtkSmartPointer<vtkImageMagnitude> magnitudeFilter = vtkSmartPointer<vtkImageMagnitude>::New();
magnitudeFilter->SetInputConnection(gradientFilter->GetOutputPort());
magnitudeFilter->Update();
double range[2];
magnitudeFilter->GetOutput()->GetScalarRange(range);
vtkSmartPointer<vtkImageShiftScale> ShiftScale = vtkSmartPointer<vtkImageShiftScale>::New();
ShiftScale->SetOutputScalarTypeToUnsignedChar();
ShiftScale->SetScale( 255 / range[1] );
ShiftScale->SetInputConnection(magnitudeFilter->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<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640,320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("ImageGradientExample");
w.show();
return a.exec();
}
效果如下:
索贝尔(Sobel)算子也是一种常用的梯度算子。其计算稍微复杂些,采用3x3 的模板。计算时模板在图像上移动,并在每个位置上计算对应中心像素的梯度值。
VTK中用 vtkSobel2D计算图像的 Sobel算子:
利用 Sobel 算子计算图像的梯度图像示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageSobel2D.h>
#include <vtkImageExtractComponents.h>
#include <vtkImageMathematics.h>
#include <vtkImageShiftScale.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageSobel2D> sobelFilter = vtkSmartPointer<vtkImageSobel2D>::New();
sobelFilter->SetInputConnection(reader->GetOutputPort());
vtkSmartPointer<vtkImageExtractComponents> extractXFilter = vtkSmartPointer<vtkImageExtractComponents>::New();
extractXFilter->SetComponents(0);
extractXFilter->SetInputConnection(sobelFilter->GetOutputPort());
extractXFilter->Update();
double xRange[2];
extractXFilter->GetOutput()->GetScalarRange(xRange);
vtkSmartPointer<vtkImageMathematics> xImageAbs = vtkSmartPointer<vtkImageMathematics>::New();
xImageAbs->SetOperationToAbsoluteValue();
xImageAbs->SetInputConnection(extractXFilter->GetOutputPort());
xImageAbs->Update();
vtkSmartPointer<vtkImageShiftScale> xShiftScale = vtkSmartPointer<vtkImageShiftScale>::New();
xShiftScale->SetOutputScalarTypeToUnsignedChar();
xShiftScale->SetScale( 255 / xRange[1] );
xShiftScale->SetInputConnection(xImageAbs->GetOutputPort());
xShiftScale->Update();
vtkSmartPointer<vtkImageExtractComponents> extractYFilter = vtkSmartPointer<vtkImageExtractComponents>::New();
extractYFilter->SetComponents(1);
extractYFilter->SetInputConnection(sobelFilter->GetOutputPort());
extractYFilter->Update();
double yRange[2];
extractYFilter->GetOutput()->GetScalarRange(yRange);
vtkSmartPointer<vtkImageMathematics> yImageAbs = vtkSmartPointer<vtkImageMathematics>::New();
yImageAbs->SetOperationToAbsoluteValue();
yImageAbs->SetInputConnection(extractYFilter->GetOutputPort());
yImageAbs->Update();
vtkSmartPointer<vtkImageShiftScale> yShiftScale = vtkSmartPointer<vtkImageShiftScale>::New();
yShiftScale->SetOutputScalarTypeToUnsignedChar();
yShiftScale->SetScale( 255 / yRange[1] );
yShiftScale->SetInputConnection(yImageAbs->GetOutputPort());
yShiftScale->Update();
vtkSmartPointer<vtkImageActor> originalActor = vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> xActor = vtkSmartPointer<vtkImageActor>::New();
xActor->SetInputData(xShiftScale->GetOutput());
vtkSmartPointer<vtkImageActor> yActor = vtkSmartPointer<vtkImageActor>::New();
yActor->SetInputData(yShiftScale->GetOutput());
double originalViewport[4] = {0.0, 0.0, 0.33, 1.0};
double xViewport[4] = {0.33, 0.0, 0.66, 1.0};
double yViewport[4] = {0.66, 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> xRenderer = vtkSmartPointer<vtkRenderer>::New();
xRenderer->SetViewport(xViewport);
xRenderer->AddActor(xActor);
xRenderer->ResetCamera();
xRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> yRenderer = vtkSmartPointer<vtkRenderer>::New();
yRenderer->SetViewport(yViewport);
yRenderer->AddActor(yActor);
yRenderer->ResetCamera();
yRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(xRenderer);
renderWindow->AddRenderer(yRenderer);
QVTKOpenGLNativeWidget w;
w.resize(900,300);
w.setRenderWindow(renderWindow);
w.setWindowTitle("SobelExample");
w.show();
return a.exec();
}
效果如下:
2、Canny算子
Canny 算子是 John Canny 于20 世纪 80年代提出的一种多级边缘检测算法。John Canny研究了最优边缘的特性,即检测到的边缘要尽可能接近实际的边缘,同时,要尽量降低噪声对边缘检测的干扰。其计算步骤如下:
1)对源图像进行高斯平滑以消除图像中的噪声。
2)采用差分法近似计算图像每一个像素的梯度,并计算梯度的值和方向。
3)对梯度进行“非极大值抑制”。
4)双闽值法检测边缘和连接边缘。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageGaussianSmooth.h>
#include <vtkImageGradient.h>
#include <vtkImageShiftScale.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 <vtkStripper.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/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->SetInputData(ic->GetOutput());
gs->SetDimensionality(2);
gs->SetRadiusFactors(1, 1, 0);
gs->Update();
//计算图像的梯度和模值
vtkSmartPointer<vtkImageGradient> imgGradient = vtkSmartPointer<vtkImageGradient>::New();
imgGradient->SetInputData(gs->GetOutput());
imgGradient->SetDimensionality(2);
imgGradient->Update();
vtkSmartPointer<vtkImageMagnitude> imgMagnitude = vtkSmartPointer<vtkImageMagnitude>::New();
imgMagnitude->SetInputData(imgGradient->GetOutput());
imgMagnitude->Update();
//vtkImageNonMaximumSuppression将图片中的非局部峰值设置为0,输入和输出类型都是ImageData
//其中输入有两个,模型图像和向量图像,一个典型的应用就是输入梯度模值图像梯度图像对梯度做非极大值抑制
vtkSmartPointer<vtkImageNonMaximumSuppression> nonMax = vtkSmartPointer<vtkImageNonMaximumSuppression>::New();
nonMax->SetMagnitudeInputData(imgMagnitude->GetOutput());
nonMax->SetVectorInputData(imgGradient->GetOutput());
nonMax->SetDimensionality(2);
nonMax->Update();
//vtkImageConstantPad增加图像的大小,将梯度图像像素的组分修改为3,方便下面vtkImageToStructuredPoints使用。
vtkSmartPointer<vtkImageConstantPad> pad = vtkSmartPointer<vtkImageConstantPad>::New();
pad->SetInputConnection(imgGradient->GetOutputPort());
pad->SetOutputNumberOfScalarComponents(3);
pad->SetConstant(0);
pad->Update();
//vtkImageToStructuredPoints将VTKImageData格式转换为规则点集。该类的输入类型VTKImageData,
//另外还有一个可选的RGB三组分向量图像输入;输入类型是VTKStructuredData类型,导航输入向量图像时,向量图像像素数据回转为输入图像的对应点
vtkSmartPointer<vtkImageToStructuredPoints> i2sp1 = vtkSmartPointer<vtkImageToStructuredPoints>::New();
i2sp1->SetInputData(nonMax->GetOutput());
i2sp1->SetVectorInputData(pad->GetOutput());
i2sp1->Update();
//vtkLinkEdgels类根据点的相邻关系连接成连续的折线Polyline。其内部阈值变量GradientThreshold,
//可以用来排除输入点中梯度值小于该阈值的点。当使用vtkLinkEdgels进行Canny算子的双阈值边缘检测时,GradientThreshold可以用作较小的阈值。设置该阈值的函数是SetGradientThreshold(2)。
vtkSmartPointer<vtkLinkEdgels> imgLink = vtkSmartPointer<vtkLinkEdgels>::New();
imgLink->SetInputData(i2sp1->GetOutput());
imgLink->SetGradientThreshold(2);
imgLink->Update();
vtkSmartPointer<vtkThreshold> thresholdEdgels = vtkSmartPointer<vtkThreshold>::New();
thresholdEdgels->SetInputData(imgLink->GetOutput());
thresholdEdgels->SetUpperThreshold(10);
thresholdEdgels->AllScalarsOff();
thresholdEdgels->Update();
vtkSmartPointer<vtkGeometryFilter> gf = vtkSmartPointer<vtkGeometryFilter>::New();
gf->SetInputConnection(thresholdEdgels->GetOutputPort());
vtkSmartPointer<vtkImageToStructuredPoints> i2sp = vtkSmartPointer<vtkImageToStructuredPoints>::New();
i2sp->SetInputData(imgMagnitude->GetOutput());
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<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradRenderer);
QVTKOpenGLNativeWidget w;
w.resize(900,300);
w.setRenderWindow(renderWindow);
w.setWindowTitle("CannyExample");
w.show();
return a.exec();
}
效果如下:
3、拉普拉斯算子
拉普拉斯算子是一个二阶边缘算子,即梯度的散度。拉普拉斯算子也是通过模板实现。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageGaussianSmooth.h>
#include <vtkImageLaplacian.h>
#include <vtkImageShiftScale.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/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<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640,320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("LaplacianExample");
w.show();
return a.exec();
}
效果如下:
由于拉普拉斯算子对噪声比较敏感,为了减少噪声影响,可以先对图像做高斯滤波平滑再计算拉普拉斯算子,称为LoG算子(LaplacianofGaussian)。
二、图像平滑
图像平滑常用于图像的预处理中,如计算梯度时先对图像进行平滑处理,可以减少噪声对梯度的影响。图像平滑一般是通过模板卷积运算实现。模板可以看作一个大小为nxn的小图像,例如 3x3、5x5 等,模板的每个像素都对应一个系数值。模板卷积运算的过程先是将模板中心依次与图像每个像素重合,然后通过模板各个系数与图像对应像素相乘来计算模板对应像素的加权平均值,最后将运算结果赋给图像中模板中心对应的像素。
1、均值滤波
均值滤波是一种常用的平滑方法,其对应的模板各个像素的值相同且和为1。在 VTK中没有直接实现均值滤波的类,但是可以通过图像卷积运算来实现均值滤波。卷积运算通过vtkImageConvolve 类实现。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageConvolve.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/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.04,0.04,0.04,0.04,0.04,
0.04,0.04,0.04,0.04,0.04,
0.04,0.04,0.04,0.04,0.04,
0.04,0.04,0.04,0.04,0.04,
0.04,0.04,0.04,0.04,0.04 };
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<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(convolvedRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640,320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("MeanFilterExample");
w.show();
return a.exec();
}
运行效果:
2、高斯平滑
高斯平滑的原理类似于均值滤波。均值滤波模板的系数都是一样的,而高斯平滑则是需要根据像素与模板中心的距离来定义权重。权重的计算方法是采用高斯分布,即离中心越远,权重越小。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageGaussianSmooth.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.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, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> gradientMagnitudeRenderer = vtkSmartPointer<vtkRenderer>::New();
gradientMagnitudeRenderer->SetViewport(smoothedViewport);
gradientMagnitudeRenderer->AddActor(smoothedActor);
gradientMagnitudeRenderer->ResetCamera();
gradientMagnitudeRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradientMagnitudeRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640,320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("GaussianFilterExample");
w.show();
return a.exec();
}
运行效果:
3、中值滤波
VTK 中用类 vtkmageHybridMedian2D 来实现二维图像的中值滤波。其实现原理是采用一个5x5 的模板,逐次将模板中心对应到图像的每个像素上,将模板图像覆盖的像素的中值作为当前像素的输出值。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageHybridMedian2D.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-noise.jpg");
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, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> hybridMedianRenderer = vtkSmartPointer<vtkRenderer>::New();
hybridMedianRenderer->SetViewport(hybridMedianViewport);
hybridMedianRenderer->AddActor(hybridMedianActor);
hybridMedianRenderer->ResetCamera();
hybridMedianRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(hybridMedianRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640,320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("MedianFilterExample");
w.show();
return a.exec();
}
运行效果:
vtkImageHybridMedian2D类的使用,不需要用户设置任何参数。该方法能够有效保持图像边缘,并对椒盐噪声有较好的抑制作用。若要实现三维图像的中值滤波,则使用vtkImageHybridMedian3D类。
4、各向异性滤波
高斯平滑方法在平滑噪声的同时模糊了图像的重要边缘信息。各向异性滤波是一种基于偏微分方程的滤波技术,建立于热量的各向异性扩散理论。各向异性滤波在图像的平坦区域选择大尺度平滑,而在边缘区域则选择小尺度的平滑,在抑制噪声的同时也保持了图像的边缘信息。
VTK中用vtkImageAnisotropicDiffusion2D 类和 vtkImageAnisotropicDiffusion3D 类分别实现对二维和三维图像的各向异性扩散滤波。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageAnisotropicDiffusion2D.h>
#include <vtkCamera.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageAnisotropicDiffusion2D> diffusion = vtkSmartPointer<vtkImageAnisotropicDiffusion2D>::New();
diffusion->SetInputConnection(reader->GetOutputPort());
diffusion->SetNumberOfIterations(10);
diffusion->SetDiffusionThreshold(20);
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, 1.0, 1.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<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640,320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("AnistropicFilteringExample");
w.show();
return a.exec();
}
运行效果:
三、频域处理
1、快速傅里叶变换
快速傅里叶变换(Fast Fourier Transform,FFT)是根据离散傅里叶变换的奇、偶、虚、实等特性,对离散傅里叶变换的算法进行改进获得的。FFT是可逆的,其逆变换为RFFT。FFT在数字图像处理中有着广泛的应用,例如数字图像频域滤波、去噪、增强等。目前,在 VTK中FFT和 RFFT这两种变换都已经实现,对应的类分别为vkmageFFT和vtkImageRFFT。
vtkmageFFT和 vtkImageRFFT 的输入为实数或复数数据,输出均为复数数据。因此,vtklmageFFT与 vtklmageRFFT的输出结果不能直接显示,因为 VTK 会将其当作彩色图像显示,需要通过 vtkImageExtractComponents 类提取某一组分进行图像显示。
示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageFFT.h>
#include <vtkImageExtractComponents.h>
#include <vtkImageShiftScale.h>
#include <vtkImageRFFT.h>
#include <vtkImageCast.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageFFT> fftFilter = vtkSmartPointer<vtkImageFFT>::New();
fftFilter->SetInputConnection(reader->GetOutputPort());
fftFilter->SetDimensionality(2);
fftFilter->Update();
vtkSmartPointer<vtkImageExtractComponents> fftExtractReal = vtkSmartPointer<vtkImageExtractComponents>::New();
fftExtractReal->SetInputConnection(fftFilter->GetOutputPort());
fftExtractReal->SetComponents(0);
double range[2];
fftExtractReal->GetOutput()->GetScalarRange(range);
vtkSmartPointer<vtkImageShiftScale> ShiftScale = vtkSmartPointer<vtkImageShiftScale>::New();
ShiftScale->SetOutputScalarTypeToUnsignedChar();
ShiftScale->SetScale( 255.0 / (range[1]-range[0]) );
ShiftScale->SetShift(-range[0]);
ShiftScale->SetInputConnection(fftExtractReal->GetOutputPort());
ShiftScale->Update();
vtkSmartPointer<vtkImageRFFT> rfftFilter = vtkSmartPointer<vtkImageRFFT>::New();
rfftFilter->SetInputConnection(fftFilter->GetOutputPort());
rfftFilter->SetDimensionality(2);
rfftFilter->Update();
vtkSmartPointer<vtkImageExtractComponents> ifftExtractReal = vtkSmartPointer<vtkImageExtractComponents>::New();
ifftExtractReal->SetInputConnection(rfftFilter->GetOutputPort());
ifftExtractReal->SetComponents(0);
vtkSmartPointer<vtkImageCast> rfftCastFilter = vtkSmartPointer<vtkImageCast>::New();
rfftCastFilter->SetInputConnection(ifftExtractReal->GetOutputPort());
rfftCastFilter->SetOutputScalarTypeToUnsignedChar();
rfftCastFilter->Update();
vtkSmartPointer<vtkImageActor> originalActor = vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> fftActor = vtkSmartPointer<vtkImageActor>::New();
fftActor->SetInputData(ShiftScale->GetOutput());
vtkSmartPointer<vtkImageActor> rfftActor = vtkSmartPointer<vtkImageActor>::New();
rfftActor->SetInputData(rfftCastFilter->GetOutput());
double originalViewport[4] = {0.0, 0.0, 0.33, 1.0};
double fftViewport[4] = {0.33, 0.0, 0.66, 1.0};
double rfftViewport[4] = {0.66, 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> fftRenderer = vtkSmartPointer<vtkRenderer>::New();
fftRenderer->SetViewport(fftViewport);
fftRenderer->AddActor(fftActor);
fftRenderer->ResetCamera();
fftRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderer> rfftRenderer = vtkSmartPointer<vtkRenderer>::New();
rfftRenderer->SetViewport(rfftViewport);
rfftRenderer->AddActor(rfftActor);
rfftRenderer->ResetCamera();
rfftRenderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(fftRenderer);
renderWindow->AddRenderer(rfftRenderer);
QVTKOpenGLNativeWidget w;
w.resize(900,300);
w.setRenderWindow(renderWindow);
w.setWindowTitle("FFTAndRFFTExample");
w.show();
return a.exec();
}
运行效果:
2、低通滤波
低通滤波是将频域图像中的高频部分滤除而保留低频部分。图像的边缘和噪声对应于频域图像中的高频部分,而低通滤波的作用即是减弱这部分的能量,从而达到图像平滑去噪的目的。
巴特沃斯低通滤波器示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageFFT.h>
#include <vtkImageExtractComponents.h>
#include <vtkImageButterworthLowPass.h>
#include <vtkImageRFFT.h>
#include <vtkImageCast.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageFFT> fftFilter = vtkSmartPointer<vtkImageFFT>::New();
fftFilter->SetInputConnection(reader->GetOutputPort());
fftFilter->Update();
vtkSmartPointer<vtkImageButterworthLowPass> lowPassFilter = vtkSmartPointer<vtkImageButterworthLowPass>::New();
lowPassFilter->SetInputConnection(fftFilter->GetOutputPort());
lowPassFilter->SetXCutOff(0.05);
lowPassFilter->SetYCutOff(0.05);
lowPassFilter->Update();
vtkSmartPointer<vtkImageRFFT> rfftFilter = vtkSmartPointer<vtkImageRFFT>::New();
rfftFilter->SetInputConnection(lowPassFilter->GetOutputPort());
rfftFilter->Update();
vtkSmartPointer<vtkImageExtractComponents> ifftExtractReal = vtkSmartPointer<vtkImageExtractComponents>::New();
ifftExtractReal->SetInputConnection(rfftFilter->GetOutputPort());
ifftExtractReal->SetComponents(0);
vtkSmartPointer<vtkImageCast> castFilter = vtkSmartPointer<vtkImageCast>::New();
castFilter->SetInputConnection(ifftExtractReal->GetOutputPort());
castFilter->SetOutputScalarTypeToUnsignedChar();
castFilter->Update();
vtkSmartPointer<vtkImageActor> originalActor = vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> erodedActor = vtkSmartPointer<vtkImageActor>::New();
erodedActor->SetInputData(castFilter->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> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->SetBackground(1.0, 1.0, 1.0);
leftRenderer->AddActor(originalActor);
leftRenderer->ResetCamera();
vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->SetBackground(1.0, 1.0, 1.0);
rightRenderer->AddActor(erodedActor);
rightRenderer->ResetCamera();
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("ButterworthLowPassExample");
w.show();
return a.exec();
}
效果如下:
3、高通滤波
高通滤波与低通滤波正好相反,是让频域图像的高频部分通过而抑制低频部分。图像的边缘对应高频分量,因此高通滤波的效果是图像锐化。
巴特沃斯高通滤波器示例代码:
#include <QApplication>
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkJPEGReader.h>
#include <vtkImageFFT.h>
#include <vtkImageExtractComponents.h>
#include <vtkImageButterworthHighPass.h>
#include <vtkImageRFFT.h>
#include <vtkImageCast.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D:/data/lena-gray.jpg");
reader->Update();
vtkSmartPointer<vtkImageFFT> fftFilter = vtkSmartPointer<vtkImageFFT>::New();
fftFilter->SetInputConnection(reader->GetOutputPort());
fftFilter->Update();
vtkSmartPointer<vtkImageButterworthHighPass> highPassFilter = vtkSmartPointer<vtkImageButterworthHighPass>::New();
highPassFilter->SetInputConnection(fftFilter->GetOutputPort());
highPassFilter->SetXCutOff(0.1);
highPassFilter->SetYCutOff(0.1);
highPassFilter->Update();
vtkSmartPointer<vtkImageRFFT> rfftFilter = vtkSmartPointer<vtkImageRFFT>::New();
rfftFilter->SetInputConnection(highPassFilter->GetOutputPort());
rfftFilter->Update();
vtkSmartPointer<vtkImageExtractComponents> ifftExtractReal = vtkSmartPointer<vtkImageExtractComponents>::New();
ifftExtractReal->SetInputConnection(rfftFilter->GetOutputPort());
ifftExtractReal->SetComponents(0);
vtkSmartPointer<vtkImageCast> castFilter = vtkSmartPointer<vtkImageCast>::New();
castFilter->SetInputConnection(ifftExtractReal->GetOutputPort());
castFilter->SetOutputScalarTypeToUnsignedChar();
castFilter->Update();
vtkSmartPointer<vtkImageActor> originalActor = vtkSmartPointer<vtkImageActor>::New();
originalActor->SetInputData(reader->GetOutput());
vtkSmartPointer<vtkImageActor> erodedActor = vtkSmartPointer<vtkImageActor>::New();
erodedActor->SetInputData(castFilter->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> leftRenderer = vtkSmartPointer<vtkRenderer>::New();
leftRenderer->SetViewport(leftViewport);
leftRenderer->SetBackground(1.0, 1.0, 1.0);
leftRenderer->AddActor(originalActor);
leftRenderer->ResetCamera();
vtkSmartPointer<vtkRenderer> rightRenderer = vtkSmartPointer<vtkRenderer>::New();
rightRenderer->SetViewport(rightViewport);
rightRenderer->SetBackground(1.0, 1.0, 1.0);
rightRenderer->AddActor(erodedActor);
rightRenderer->ResetCamera();
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderWindow->AddRenderer(leftRenderer);
renderWindow->AddRenderer(rightRenderer);
QVTKOpenGLNativeWidget w;
w.resize(640, 320);
w.setRenderWindow(renderWindow);
w.setWindowTitle("ButterworthHighPassExample");
w.show();
return a.exec();
}
运行效果: