OpenCV学习 基础图像操作(十一):Canny边缘检测

原文链接:http://www.cse.iitd.ac.in/~pkalra/csl783/canny1986.pdf

简介

经典的Canny边缘检测算法通常都是从高斯模糊开始,到基于双阈值实现边缘连接结束。但是在实际工程应用中,考虑到输入图像都是彩色图像,最终边缘连接之后的图像要二值化输出显示,所以完整的Canny边缘检测算法实现步骤如下:

  • 1.      彩色图像转换为灰度图像
  • 2.      对图像进行高斯模糊
  • 3.      计算图像梯度,根据梯度计算图像边缘幅值与角度
  • 4.      非最大信号压制处理(边缘细化)
  • 5.      双阈值边缘连接处理
  • 6.      二值化图像输出结果

算法详解

step1:高斯平滑滤波

  滤波是为了去除噪声,选用高斯滤波也是因为在众多噪声滤波器中,高斯表现最好(表现怎么定义的?最好好到什么程度?),你也可以试试其他滤波器如均值滤波、中值滤波等等。一个大小为(2k+1)x(2k+1)的高斯滤波器核(核一般都是奇数尺寸的)的生成方程式由下式给出:

   

  下面是一个sigma = 1.4,尺寸为3x3的高斯卷积核的例子,注意矩阵求和值为1(归一化):

  举个例子:若图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的亮度值为:

   其中*为卷积符号,sum表示矩阵中所有元素相加求和,简单说,就是滤波后的每个像素值=其原像素中心值及其相邻像素的加权求和。图像卷积是图像处理中非常重要且广泛使用的操作,一定要理解好。

其中高斯卷积核的大小将影响Canny检测器的性能。尺寸越大,去噪能力越强,因此噪声越少,但图片越模糊,canny检测算法抗噪声能力越强,但模糊的副作用也会导致定位精度不高,一般情况下,推荐尺寸5*5,3*3也行。

step2: 计算梯度强度和方向

边缘的最重要的特征是灰度值剧烈变化,如果把灰度值看成二元函数值,那么灰度值的变化可以用二元函数的”导数“(或者称为梯度)来描述。由于图像是离散数据,导数可以用差分值来表示,差分在实际工程中就是灰度差,说人话就是两个像素的差值。一个像素点有8邻域,那么分上下左右斜对角,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。算子是以图像卷积的形式来计算梯度,比如Roberts,Prewitt,Sobel等,这里选用Sobel算子来计算二维图像在x轴和y轴的差分值(这些数字的由来?),将下面两个模板与原图进行卷积,得出x和y轴的差分值图,最后计算该点的梯度G和方向θ

  计算梯度的模和方向属于高等数学部分的内容,如果不理解应该补习一下数学基本功,图像处理经常会用到这个概念。

step3:非极大值抑制

sobel算子检测出来的边缘太粗了,我们需要抑制那些梯度不够大的像素点,只保留最大的梯度,从而达到瘦边的目的。这些梯度不够大的像素点很可能是某一条边缘的过渡点。按照高数上二位函数的极大值的定义,即对点(x0,y0)的某个邻域内所有(x,y)都有f(x,y)≤(f(x0,y0),则称f在(x0,y0)具有一个极大值,极大值为f(x0,y0)。简单方案是判断一个像素点的8邻域与中心像素谁更大,但这很容易筛选出噪声,因此我们需要用梯度和梯度方向来辅助确定。

      如下图所示,中心像素C的梯度方向是蓝色直线,那么只需比较中心点C与dTmp1和dTmp2的大小即可。由于这两个点的像素不知道,假设像素变化是连续的,就可以用g1、g2和g3、g4进行线性插值估计。设g1的幅值M(g1),g2的幅值M(g2),则M(dtmp1)=w*M(g2)+(1-w)*M(g1)  ,其w=distance(dtmp1,g2)/distance(g1,g2)  。也就是利用g1和g2到dTmp1的距离作为权重,来估计dTmp1的值。w在程序中可以表示为tan(θ)来表示,具体又分为四种情况(下面右图)讨论。

step4:用双阈值算法检测和连接边缘

    双阈值法非常简单,我们假设两类边缘:经过非极大值抑制之后的边缘点中,梯度值超过T1的称为强边缘,梯度值小于T1大于T2的称为弱边缘,梯度小于T2的不是边缘。可以肯定的是,强边缘必然是边缘点,因此必须将T1设置的足够高,以要求像素点的梯度值足够大(变化足够剧烈),而弱边缘可能是边缘,也可能是噪声,如何判断呢?当弱边缘的周围8邻域有强边缘点存在时,就将该弱边缘点变成强边缘点,以此来实现对强边缘的补充。实际中人们发现T1:T2=2:1的比例效果比较好,其中T1可以人为指定,也可以设计算法来自适应的指定,比如定义梯度直方图的前30%的分界线为T1,我实现的是人为指定阈值。检查8邻域的方法叫边缘滞后跟踪,连接边缘的办法还有区域生长法等等。

API简介

Canny(gray_src, dst, threshold1, threshold2, 3, false);
//gray_src:输入灰度图像
//dst:输出边缘图像
//threshold1:强边缘阈值,即梯度大于该值的点都保留
//threshold2:若边缘阈值,即梯度小于该值的点都舍弃,梯度大于该值且小于强边缘阈值的点向强边缘连通
//3  : 梯度算子的大小,默认使用为sobel
//false: x与y方向梯度融合是否使用L2范数

代码实践

#include <iostream>
#include <math.h>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
Mat src,dst, gray_src;
int threshold1 = 125;
int threshold2 = 225;
char OUTPUT[] = "OUTPUT_WINDOS";
int max_threshold = 255;
void CallBack_Threshold(int, void*);

int main(int argc, char* argv[])
{
	//src = imread("src.jpg");
	Mat src = imread("cat.png");
	if (!src.data)
	{
		cout << "cannot open image" << endl;
		return -1;
	}

	Mat gray_src;
	cvtColor(src, gray_src, COLOR_RGB2GRAY);
	GaussianBlur(gray_src, gray_src, Size(3, 3), 0, 0);
	namedWindow("OUTPUT_WINDOS", WINDOW_AUTOSIZE);

	while (1)
	{
		cout << " 输入两个阈值:" << endl;
		cin >> threshold1 >> threshold2;
		if (threshold1 > max_threshold)threshold1 = max_threshold;
		if (threshold2 > threshold1)threshold2 = threshold1;
		Canny(gray_src, dst, threshold1, threshold2, 3, false);
		imshow("OUTPUT_WINDOS", dst);
		waitKey(10);
	}
	

	
	return 0;
}


猜你喜欢

转载自blog.csdn.net/fan1102958151/article/details/107357875
今日推荐