Canny边缘检测是最好用的边缘查找,它的实现也不是很复杂,咱们分四个阶段来进行分析。算法
一、高斯模糊,这里很少说,难度不大。spa
二、卷积code
将图像同可以获得x方向的梯度和y方向的梯度的核进行卷积,得出Gx和Gy以及梯度方向。这是整个程序的开端,虽然简单,可是很重要。blog
这里,咱们介绍能够被做为核的算子:递归
这里咱们能够看到,这样的算子有一个特色:进行边缘检测的矩阵的各个元素之和为0。get
在咱们的Canny实现中,咱们采用另一个简单的算子:图像处理
这里咱们须要将x方向和y方向上的梯度转换为灰度,再根据公式来获取最后的梯度大小。class
这里的内容与以前的Sobel边缘检测并无什么不一样,可是,后面的内容应该说是比较有趣了。遍历
Canny算法之因此说是最有效的边缘检测算法,其中的特色:程序
(1) 低错误率:意味着对存在的边缘有着很好的检测
(2) 好的定位:将侦测到的边缘像素与真实边缘像素之间的距离最小化
(3) 最小响应:对一个边缘只有一个侦测响应
这些特色对于边缘检测完成后的后续步骤的更好实现有必定的保障。
如何实现这些特色?咱们应该采起如下措施:
三、进行非极大抑制
咱们知道,在梯度方向上变化率最大,所以咱们能够对已通过卷积的而且求出梯度的图像中进行最大值的查找:对于图像中的任意点A,得出A的梯度以及梯度方向。由于咱们已经得知在梯度方向上梯度的变化率最大,因此咱们可以轻易的在这个方向中找到最大的点。因为咱们在图像处理这个条件下,因此要将梯度方向离散化,这里,根据点A的8个邻域,咱们将梯度方向离散为四个大的区域,8个小区。由于咱们已经知道最大值就在这里,并且非极大抑制的做用就是将边缘细化,因此将点A的梯度同其余两点(8个邻域,离散后的梯度方向过两个点)进行比较,若是grad(A)最大,则保持grad(A)不变,不然将其设为0。
四、双阈值法
咱们进行了非极大抑制以后,再进行阈值操做,当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(); } }