Canny边缘查找的实现
Canny边缘检测的实现
Canny边缘检测是最好用的边缘查找,它的实现也不是很复杂,我们分四个阶段来进行分析。
1、高斯模糊,这里不多说,难度不大。
2、卷积
将图像同能够得到x方向的梯度和y方向的梯度的核进行卷积,得出Gx和Gy以及梯度方向。这是整个程序的开端,虽然简单,但是很重要。
这里,我们介绍可以被作为核的算子:
这里我们可以看到,这样的算子有一个特点:进行边缘检测的矩阵的各个元素之和为0。
在我们的Canny实现中,我们采用另外一个简单的算子:
这里我们需要将x方向和y方向上的梯度转换为灰度,再根据公式来获取最后的梯度大小。
这里的内容与之前的Sobel边缘检测并没有什么不同,但是,后面的内容应该说是比较有趣了。
Canny算法之所以说是最有效的边缘检测算法,其中的特点:
(1) 低错误率:意味着对存在的边缘有着很好的检测
(2) 好的定位:将侦测到的边缘像素与真实边缘像素之间的距离最小化
(3) 最小响应:对一个边缘只有一个侦测响应
这些特点对于边缘检测完成后的后续步骤的更好实现有一定的保障。
如何实现这些特点?我们应该采取以下措施:
3、进行非极大抑制
我们知道,在梯度方向上变化率最大,因此我们可以对已经过卷积的并且求出梯度的图像中进行最大值的查找:对于图像中的任意点A,得出A的梯度以及梯度方向。因为我们已经得知在梯度方向上梯度的变化率最大,所以我们能够轻易的在这个方向中找到最大的点。由于我们在图像处理这个条件下,所以要将梯度方向离散化,这里,根据点A的8个邻域,我们将梯度方向离散为四个大的区域,8个小区。因为我们已经知道最大值就在这里,而且非极大抑制的作用就是将边缘细化,所以将点A的梯度同其他两点(8个邻域,离散后的梯度方向过两个点)进行比较,如果grad(A)最大,则保持grad(A)不变,否则将其设为0。
4、双阈值法
我们进行了非极大抑制之后,再进行阈值操作,当grad(A)>th时,将grad(A)=255否则grad(A)=0。经过这一步之后,我们发现图像的边缘并不连续,这对于我们之后的图像处理并没有任何用处。因此,我们必须做一些操作来使边缘连续。这里我们提出了双阈值法。这个方法能够将真边缘保留下来,剔除假边缘。这里的真边缘有两部分组成:强边缘与弱边缘。强边缘就是那些grad(A)>th的点所构成,弱边缘的定义如下:设有点B,在图像中存在符合(1) grad(B)>th1并且grad(B)<=th2(2)B点位于真边缘的某点C的邻域中;其中th1为小阈值,th2为大阈值。
对这个方法的实现,我们可以通过使用图的相关遍历法则进行。在下面的代码中,我们是用的是图的广度优先遍历。深度优先遍历也是可以的,但是应当注意的是,由于图像中的像素数量较多,容易引起栈溢出,因此在使用递归遍历时,需要多加注意。
代码如下:
卷积代码
void getValue(Mat in,int **kernel,int pos_heigh,int pos_width,double&t) { int temp_width_kernel_start,temp_width_kernel_end; int temp_heigh_kernel_start,temp_heigh_kernel_end; if(pos_width>=pos_archor_width) temp_width_kernel_start=0; else temp_width_kernel_start=pos_archor_width-pos_width; if(mat_width-1-pos_width>=kernel_width-1-pos_archor_width) temp_width_kernel_end=kernel_width-1; else temp_width_kernel_end=mat_width-1-pos_width+pos_archor_width; if(pos_heigh>=pos_archor_heigh) temp_heigh_kernel_start=0; else temp_heigh_kernel_start=pos_archor_heigh-pos_heigh; if(mat_heigh-1-pos_heigh>=kernel_heigh-1-pos_archor_heigh) temp_heigh_kernel_end=kernel_heigh-1; else temp_heigh_kernel_end=mat_heigh-1-pos_heigh+pos_archor_heigh; double all1=0; double all2=0; double all3=0; int x=0; Vec3b vec; //cout<<in.depth(); for(int i=temp_heigh_kernel_start;i<=temp_heigh_kernel_end;++i) { int y=0; for(int j=temp_width_kernel_start;j<=temp_width_kernel_end;++j) { vec=in.at<Vec3b>((pos_heigh-(pos_archor_heigh-temp_heigh_kernel_start)+x),(pos_width-(pos_archor_width-temp_width_kernel_start)+y)); all1+=kernel[i][j]*(int)vec[0]; all2+=kernel[i][j]*(int)vec[1]; all3+=kernel[i][j]*(int)vec[2]; y++; } x++; } t=0.114*all1+0.587*all2+0.299*all3; }
非极大抑制:
void dec(Mat in,double **angel) { int z1=0; int z2=0; int z3=0; int z4=0; for(int i=0;i!=in.rows;++i) { for(int j=0;j!=in.cols;++j) { if((angel[i][j]>67.5&&angel[i][j]<=112.5)||(angel[i][j]>-112.5&&angel[i][j]<=-67.5)) { z1++; if(i+1<in.rows) { if(in.at<uchar>(i,j)<=in.at<uchar>(i+1,j)) in.at<uchar>(i,j)=0; } if(i-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i-1,j)) in.at<uchar>(i,j)=0; } } if((angel[i][j]>112.5&&angel[i][j]<=157.5)||(angel[i][j]>-67.5&&angel[i][j]<=-22.5)) { z2++; if(i-1>=0&&j-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i-1,j-1)) in.at<uchar>(i,j)=0; } if(i+1<in.rows&&j+1<in.cols) { if(in.at<uchar>(i,j)<=in.at<uchar>(i+1,j+1)) in.at<uchar>(i,j)=0; } } if((angel[i][j]>157.5&&angel[i][j]<=180)||(angel[i][j]>-22.5&&angel[i][j]<=22.5)||(angel[i][j]>-180&&angel[i][j]<=-157.5)) { z3++; if(j-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i,j-1)) in.at<uchar>(i,j)=0; } if(j+1<in.cols) { if(in.at<uchar>(i,j)<=in.at<uchar>(i,j+1)) in.at<uchar>(i,j)=0; } } if((angel[i][j]>-157.5&&angel[i][j]<=-112.5)||(angel[i][j]>22.5&&angel[i][j]<67.5)) { z4++; if(i+1<in.rows&&j-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i+1,j-1)) in.at<uchar>(i,j)=0; } if(i-1>=0&&j+1<in.cols) { if(in.at<uchar>(i,j)<=in.at<uchar>(i-1,j+1)) in.at<uchar>(i,j)=0; } } } } }
双阈值
void canny_yuzhi(Mat in,int min,int max) { int count=0; queue<Point> q; for(int i=0;i!=in.rows;++i) { for(int j=0;j!=in.cols;++j) { if(in.at<uchar>(i,j)>max) { in.at<uchar>(i,j)=255; q.push(Point(i,j)); } if(in.at<uchar>(i,j)<min) in.at<uchar>(i,j)=0; } } imshow("3",in); while(!q.empty()) { Point pt=q.front(); in.at<uchar>(pt.x,pt.y)=255; if(pos_can(pt.x-1,pt.y-1,in.rows,in.cols)&&value_can(in,pt.x-1,pt.y-1,min,max)) { q.push(Point(pt.x-1,pt.y-1)); count++; } if(pos_can(pt.x-1,pt.y,in.rows,in.cols)&&value_can(in,pt.x-1,pt.y,min,max)) { q.push(Point(pt.x-1,pt.y)); count++; } if(pos_can(pt.x-1,pt.y+1,in.rows,in.cols)&&value_can(in,pt.x-1,pt.y+1,min,max)) { q.push(Point(pt.x-1,pt.y+1)); count++; } if(pos_can(pt.x,pt.y-1,in.rows,in.cols)&&value_can(in,pt.x,pt.y-1,min,max)) { q.push(Point(pt.x,pt.y-1)); count++; } if(pos_can(pt.x,pt.y+1,in.rows,in.cols)&&value_can(in,pt.x,pt.y+1,min,max)) { q.push(Point(pt.x,pt.y+1)); count++; } if(pos_can(pt.x+1,pt.y-1,in.rows,in.cols)&&value_can(in,pt.x+1,pt.y-1,min,max)) { q.push(Point(pt.x+1,pt.y-1)); count++; } if(pos_can(pt.x+1,pt.y,in.rows,in.cols)&&value_can(in,pt.x+1,pt.y,min,max)) { q.push(Point(pt.x+1,pt.y)); count++; } if(pos_can(pt.x+1,pt.y+1,in.rows,in.cols)&&value_can(in,pt.x+1,pt.y+1,min,max)) { q.push(Point(pt.x+1,pt.y+1)); count++; } q.pop(); } }