5.4.3 边缘检测-canny算子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kateyabc/article/details/83627606

Canny算子是John Canny于20世纪80年代提出的一种多级边缘检测算法。John Canny研究了最优边缘的特性,即检测到的边缘要尽可能跟实际的边缘接近并尽可能的多,同时,要尽量降低噪声对边缘检测的干扰。其计算步骤如下
1)对源图像进行高斯平滑以消除图像中噪声
2)采用差分法近似计算图像每一个像素的梯度,并计算梯度的模值和方向
3)对梯度进行"非极大抑制":图像边缘点梯度值通常在梯度方向是极大值,因此检测边缘需要将非极大值赋值0来抑制非边缘点。检测方法就是在一个局部窗口内,如果中心像素点的梯度不比梯度方向上相邻两个像素值大,那么该中心像素点梯度值赋0。
4)双阈值法检测边缘和连接边缘。取两个梯度阈值high和low,将梯度图像中小于high的像素赋0得到边缘图像I1,该图像能够接近图像边缘但是可能会存在间断点;将梯度图像中小于low的像素赋0得到边缘图像I2,该图中受噪声影响比较大,但是边缘信息更多。在连接边缘时,以I1为基础,对非零点进行边缘跟踪,如果追踪过程中出现中断,则从I2对应像素点及其邻域来寻找可以连接的边缘,直至结束。
以上是Canny算子的计算步骤。
在VTK中没有实现一个专门的类来做Canny边缘检测。
但是我们可以根据以上步骤来实现:

#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageData.h>
#include <vtkImageShiftScale.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
#include <vtkImageCast.h> //图像数据类型转换
#include <vtkImageGaussianSmooth.h>
#include <vtkImageGradient.h>
#include <vtkImageMagnitude.h>
#include <vtkImageNonMaximumSuppression.h> //将图像中的非局部峰值设置为0
#include <vtkImageConstantPad.h>//增加图像的大小
#include <vtkImageToStructuredPoints.h>//将vtkImageData格式转换为规则点集
#include <vtkLinkEdgels.h> //根据点的相邻关系连接成连续的折线Polyline
#include <vtkThreshold.h> 
#include <vtkGeometryFilter.h> //数据转换为几何数据,输出类型为vtkPolyData
#include <vtkSubPixelPositionEdgels.h> 
#include <vtkCamera.h>
#include <vtkProperty.h>
#include <vtkStripper.h> //将输入的多边形生成三角形带或者折线段
#include <vtkPolyDataMapper.h>

int main(int argc, char* argv[])
{
	vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New();
	reader->SetFileName("data\\lena-gray.jpg");
	reader->Update();

	vtkSmartPointer<vtkImageCast> ic = vtkSmartPointer<vtkImageCast>::New();
	ic->SetOutputScalarTypeToFloat();
	ic->SetInputConnection(reader->GetOutputPort());

	vtkSmartPointer<vtkImageGaussianSmooth> gs = vtkSmartPointer<vtkImageGaussianSmooth>::New();
	gs->SetInputConnection(ic->GetOutputPort());
	gs->SetDimensionality(2); //设置要计算梯度的维度
	gs->SetRadiusFactors(1, 1, 0);

	vtkSmartPointer<vtkImageGradient> imgGradient = vtkSmartPointer<vtkImageGradient>::New();
	imgGradient->SetInputConnection(gs->GetOutputPort());
	imgGradient->SetDimensionality(2);

	vtkSmartPointer<vtkImageMagnitude> imgMagnitude = vtkSmartPointer<vtkImageMagnitude>::New();
	imgMagnitude->SetInputConnection(imgGradient->GetOutputPort());

	vtkSmartPointer<vtkImageNonMaximumSuppression> nonMax = vtkSmartPointer<vtkImageNonMaximumSuppression>::New();
	nonMax->SetMagnitudeInputData(imgMagnitude->GetOutput());
	nonMax->SetVectorInputData(imgGradient->GetOutput());
	nonMax->SetDimensionality(2);

	vtkSmartPointer<vtkImageConstantPad> pad = vtkSmartPointer<vtkImageConstantPad>::New();
	pad->SetInputConnection(imgGradient->GetOutputPort());
	pad->SetOutputNumberOfScalarComponents(3);//用于设置输出图像的像素数据组分个数
	pad->SetConstant(0); //设置输出图像中扩大的区域像素值

	vtkSmartPointer<vtkImageToStructuredPoints> i2sp1 = vtkSmartPointer<vtkImageToStructuredPoints>::New();//将vtkImageData格式转换为规则点集
	i2sp1->SetInputConnection(nonMax->GetOutputPort());
	i2sp1->SetVectorInputData(pad->GetOutput());

	vtkSmartPointer<vtkLinkEdgels> imgLink = vtkSmartPointer<vtkLinkEdgels>::New(); //根据点的相邻关系连接成连续的折线Polyline
	imgLink->SetInputData(i2sp1->GetOutput());
	imgLink->SetGradientThreshold(2);

	vtkSmartPointer<vtkThreshold> thresholdEdgels = vtkSmartPointer<vtkThreshold>::New(); 
	thresholdEdgels->SetInputConnection(imgLink->GetOutputPort());
	thresholdEdgels->ThresholdByUpper(10); //用于获取输入任意类型数据的满足阈值条件的单元数据
	thresholdEdgels->AllScalarsOff();

	vtkSmartPointer<vtkGeometryFilter> gf = vtkSmartPointer<vtkGeometryFilter>::New(); //将数据转换为几何数据,输出类型为vtkPolyData
	gf->SetInputConnection(thresholdEdgels->GetOutputPort());

	vtkSmartPointer<vtkImageToStructuredPoints> i2sp = vtkSmartPointer<vtkImageToStructuredPoints>::New();
	i2sp->SetInputConnection(imgMagnitude->GetOutputPort());
	i2sp->SetVectorInputData(pad->GetOutput());

	//接收一系列连续曲线及其对应的梯度系信息作为输入,利用梯度信息来调整曲线位置
	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;
}

该程序运行出来的结果有问题,还有待解决!

该程序比较复杂,处理边缘时将其作为几何数据来进行处理。因此涉及了部分几何数据操作的filter,这里如果不明白可以先放一下,再几何数据处理部分会做详细介绍。
程序首先读入图像,计算图像的梯度和模值。
接下来按照Canny算子的步骤进行处理。
我们详细介绍用到的相应的filter:
vtkImageNonMaximumSuppression将图像中的非局部峰值设置为0,输入和输出类型都是vtkImageData:其中输入有两个,模值图像(magnitude)和向量图像,一个典型的应用就是输入梯度模值图像和梯度向量图像对梯度做非极大值抑制。
vtkImageConstantPad增加图像的大小,其输入和输出都为vtkImageData。其中函数SetOutputNumberOfScalarComponents(3)用于设置输出图像的像素数据组分个数,函数SetConstant(0)用于设置输出图像中扩大的区域像素值。而SetOutputWholeExtent()则用于设置输出图像的范围。这里的作用是将梯度图像像素的组分修改为3,方便下面vtkImageToStructuredPoints使用。
vtkImageToStructuredPoints将vtkImageData格式转换为规则点集。该类的输入类型是vtkImageData,另外还有一个可选的RGB三组分向量图像输入;输出类型是vtkStructuredPoints,当输入向量图像时,向量图像像素数据会转为输出图像的对应点的属性。
vtkLinkEdgels类根据点的相邻关系连接成连续的折线Polyline。其内部阈值变量GradientThreshold,可以用来排除输入点中梯度值小于该阈值的点。当使用vtkLinkEdgels进行Canny算子的双阈值边缘检测时,GradientThreshold可以用作较小的阈值。设置该阈值的函数是SetGradientThreshold(2)。
vtkThreshold用于获取输入任意类型数据的满足阈值条件的单元数据。该类的输入为VTK的任意数据类型,输出数据类型是不规则网格。阈值设置有:大于阈值,小于阈值和介于两个阈值之间。内部提供了两种属性模式AttributeMode设置,即阈值比较时是采用的点属性还是单元属性,默认下是点属性。而当属性为多元数据时,还需要设置阈值比较时使用哪个组分的数据。其中提供了三种模式选择,所有组分都满足阈值条件,任意一个满足阈值条件和用户指定的组分满足阈值条件。当使用点属性数据时,如果设置了AllScalars,那么单元满足阈值条件的前提会是其所有点的属性都满足阈值条件。这里将阈值设置为10,即Canny中双阈值的较大阈值。
vtkGeometryFilter将数据转换为几何数据,输出类型为vtkPolyData。该类从vtkThreshold的输出中提取图像边缘的几何数据。
vtkSubPixelPositionEdgels接收一系列连续曲线及其对应的梯度系信息作为输入,利用梯度信息来调整曲线位置。这里对前面提取的图像边缘再根据其梯度进行调整。
vtkStripper用来将输入的多边形、三角形或者线段生成三角形带或者折线段。输入的多边形数据必须是三角形,否则不会进行带化处理。因此处理多边形数据时,可以先用vtkTriangleFilter进行三角化后再使用本类。如果输入中存在孤立点的话,也不会进行任何处理。默认情况下,该filter处理后会丢弃掉属性数据。

参考资料:

1.《The Visualization Toolkit – AnObject-Oriented Approach To 3D Graphics (4th Edition)》
2. 张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.

所用软件:vtk7.0+visual studio 2013


注:此文知识学习笔记,仅记录完整程序和实现结果,具体原理参见:

https://blog.csdn.net/www_doling_net/article/details/8541534

https://blog.csdn.net/shenziheng1/article/category/6114053/4

猜你喜欢

转载自blog.csdn.net/kateyabc/article/details/83627606