利用坎尼边缘检测算子进行边缘检测的原理及OpenCV的代码实现

Canny算子是John Canny在1986年发表的论文中首次提出的边缘检测算子,该算子检测性能比较好,应用广泛。

  1. 最优边缘检测的三个主要评价标准是:
  • 低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
  • 高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
  • 最小响应: 图像中的边缘只能标识一次。

坎尼算子进行边缘检测的原理和步骤如下:

⑴消除噪声。边缘检测的算法主要是基于图像强度的一阶和二阶微分操作,但导数通常对噪声很敏感,边缘检测算法常常需要根据图像源的数据进行预处理操作,因此采用滤波器来改善与噪声有关的边缘检测性能,比如在进行边缘检测前,可以对原始数据先作高斯滤波处理。其实不仅对噪声,如果不做滤波平滑处理,原图片中不是边缘但是灰度变化频率较高的部分也容易被认为是边缘,这样导致了边缘检测性能的下降。

⑵计算梯度的幅度与方向 .OpenCV中的坎尼函数是使用索贝尔卷积核来计算梯度的幅度与方向的。计算出的幅度与方向作为后面提取图像边缘的原始数据

⑶非极大值抑制。非极大值抑制的目的是剔除第⑵部中计算出来的结果中的大部分非边缘点。其原理是通过像素的八邻域来判断要不要将这个像素置为边缘点,如果不置为边缘点,那么就置为背景色判断的方法如下

①判断范围是像素的八邻域,所以是局部最优判断法;

②判断的标准是如果某个像素在其八邻域内,既是最大值,梯度值也最大,那么可判断该点为像素边缘点,否则就不是。如何判断呢?

首先,梯度值的判断是很好判断的,用边缘检测微分算子得到的结果直接比较就可以了,但是最大值的判断可不是只比较其旁边的八个点哦,还要比较另外两个点,详情如下:

如果已经判断出上图中的Ç点比其旁边的8个点的像素值都大,那么接下来判断上图中dTmp1和dTmp2的值是否也小于Ç点的值,那么dTmp1和dTmp2的值怎么求呢?上图中蓝色的线条方向为ç点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即除了ç点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断ç点灰度与这两个点灰度大小即可判断ç点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明ç点不是局部极大值,那么则可以排除ç点为边缘。

⑷用滞后阈值算法求解图像边缘。上一步对边缘检测算子的结果进行了非极大值抑制,接下来我们用二值化的方法来求解图像边缘。单阈值处理边缘效果不好,所以Cannny 。算法中采用滞后阈值法求解滞后阈值法需要设置一个高阈值和一个低阈值,解后按如下法则进行:

  1. 如果某一像素位置的梯度幅值超过高阈值,则像素被保留为边缘像素;
  2. 如果某一像素位置的梯度幅值小于低阈值,则像素被排除;
  3. 如果某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。

在以上的法则中,推荐的高阈值与低阈值比在2:1到3:1之间!

通过消除噪声,计算梯度幅度与方向,非极大值抑制及用滞后阈值算法求解图像边缘四个步骤就可实现的Canny边缘检测。

精明的函数原型如下:

void Canny( InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false );

参数详解:

图像为输入图像,单通道8位;

边缘为输出图像,与输入图像同类型同尺寸;

 阈值1为滞后阈值算法的低阈值;

 阈值2为为滞后阈值算法的高阈值;

apertureSize为索贝尔算子的窗口(卷积核)阶数;

L2gradient表示是否使用L2范数来计算图像梯度幅值。

精明的边缘检测实例

  • 高斯(均值)模糊

  • 灰度转换

  • 计算梯度

  • 非极大值抑制

  • 高低阈值输出二值图像

#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat src1,gray_img ,src2,dst,finnal_img;
int thresh_value = 40;
int max_value = 255;
void canny_demo(int ,void*);
int main()
{
	src1 = imread("C:\\Users\\马迎伟\\Desktop\\heibao.jpg");
	if (src1.empty())
	{
		cout << "could not find src1" << endl;
		return -1;
	}
	namedWindow("input", CV_WINDOW_AUTOSIZE);
	imshow("input", src1);
	//canny  高斯模糊-->灰度转换-->(计算梯度-->非最大信号抑制-->高低阈值输出二值图像) canny实现
	GaussianBlur(src1,src2,Size(3,3),0,0);
	cvtColor(src2,gray_img,CV_BGR2GRAY);
	namedWindow("output",CV_WINDOW_AUTOSIZE);
	createTrackbar("tiaojie","output",&thresh_value,max_value,canny_demo);
	canny_demo(0,0);
	waitKey(0);
	return 0;
}
void canny_demo(int ,void*)
{
	Canny(gray_img,dst,thresh_value,thresh_value*2,3,false);
	//imshow("output",~dst);
	finnal_img.create(dst.size(),dst.type());
	src1.copyTo(finnal_img,dst);
	imshow("output",finnal_img);
}