5.4 边缘检测
图像中不连续的灰度值会产生边缘,图像的边缘检测是基于边界的图像分割方法的基础。分水岭算法通常是分割原图的梯度图像,而梯度实际上也反映图像的边缘信息
5.4.1 梯度算子
梯度算子对应于图像的一阶导数。图像的一阶导数一般是通过差分运算来近似的。VTK中可以用vtkImageCradient类计算图像梯度。
///****************************************************/
///* Examples/Chap05/5.4_ImageGradientExample.cpp */
///****************************************************/
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageGradient.h>
#include <vtkImageMagnitude.h>
#include <vtkImageShiftScale.h>
#include <vtkImageData.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main(int argn, char* argv[]) {
// 读入灰度图像
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D://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<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradRenderer);
renderWindow->SetSize(640, 320);
renderWindow->Render();
renderWindow->SetWindowName("ImageGradientExample");
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;
}
梯度是矢量,不能直接显示。使用vtkImageMagnitude对象来计算梯度矢量的2-范数,既矢量的模。利用vtkImageShiftScale将图像的数据范围调整到「0 255」然后显示。注意:彩色图像不能直接用来计算梯度,需要先转换为灰度图像。
索贝尔(Sobel)算子也是一种常用的梯度算子。采用3x3模板。计算时模板在图像上移动,并且在每个位置上计算对应中心像素的梯度值。VTK中用vtkSobel2D计算图像的Sobel算子。
///****************************************************/
///* Examples/Chap05/5.4_SobelExample.cpp */
///****************************************************/
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageSobel2D.h>
#include <vtkImageExtractComponents.h>
#include <vtkImageMathematics.h>
#include <vtkImageShiftScale.h>
#include <vtkImageData.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main(int argn, char* argv[]) {
// 读入灰度图像
vtkSmartPointer<vtkJPEGReader> reader =
vtkSmartPointer<vtkJPEGReader>::New();
reader->SetFileName("D://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<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->SetSize(1200, 300);
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(xRenderer);
renderWindow->AddRenderer(yRenderer);
renderWindow->Render();
renderWindow->SetWindowName("SobelExample");
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;
}
5.4.2 Canny算子
5.4.3 拉普拉斯算子
拉普拉斯算子是一个二阶边缘算子,既梯度的散度。
///****************************************************/
///* Examples/Chap05/5.4_ImageGradientExample.cpp */
///****************************************************/
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageLaplacian.h>
#include <vtkImageShiftScale.h>
#include <vtkImageData.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
int main(int argn, 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> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(gradRenderer);
renderWindow->SetSize(640, 320);
renderWindow->Render();
renderWindow->SetWindowName("LaplacianExample");
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;
}
5.5 图像平滑
图像平滑常用于图像的预处理中,如计算梯度时先对图像进行平滑处理,可以减少噪声对梯度的影响。图像平滑一般是通过模板卷积运算实现。
卷积运算过程:先将模板中心依次与图像每个像素重合,然后通过模板各个系数与图像对应像素相乘来计算模板对应像素的加权平均值。最后将运算结果赋给图像中模板中心对应的像素。
5.5.1 均值滤波
均值滤波是一种常用的平滑方法,其对应的模板各个像素的值相同且和为1.
在VTK中没有直接实现均值滤波的类,但是可以通过图像卷积运算来实现均值滤波。
卷积运算通过vtkImageConvolve类实现。
/**********************************************************************
文件名: 5.5_MeanFilterExample.cpp
Copyright (c) 张晓东, 罗火灵. All rights reserved.
更多信息请访问:
http://www.vtkchina.org (VTK中国)
http://blog.csdn.net/www_doling_net (东灵工作室)
**********************************************************************/
#include <vtkSmartPointer.h>
#include <vtkImageData.h>
#include <vtkImageConvolve.h>
#include <vtkImageMandelbrotSource.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
#include <vtkRenderer.h>
#include <vtkImageActor.h>
#include <vtkImageCast.h>
#include <vtkJPEGReader.h>
#include <vtkImageShiftScale.h>
//测试图像:../data/lena-gray.jpg
int main(int argc, char* argv[])
{
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.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<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();;
renderWindow->AddRenderer(originalRenderer);
renderWindow->AddRenderer(convolvedRenderer);
renderWindow->SetSize(640, 320);
renderWindow->Render();
renderWindow->SetWindowName("MeanFilterExample");
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;
}
5.5.2 高斯平滑
高斯平滑的原理类似于均值滤波。
高斯平滑需要根据像素与模板中心的距离来定义权重。
权重的计算方法是采用高斯分布,离中心越远,权重越小。