博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenCV2马拉松第17圈——边缘检測(Canny边缘检測)
阅读量:6151 次
发布时间:2019-06-21

本文共 7079 字,大约阅读时间需要 23 分钟。

计算机视觉讨论群162501053
转载请注明:http://blog.csdn.net/abcd1992719g

收入囊中

  • 利用OpenCV Canny函数进行边缘检測
  • 掌握Canny算法基本理论
  • 分享Java的实现
葵花宝典
在此之前,我们先阐述一下canny检測的算法.总共分为4部分.
(1)处理噪声
一般用高斯滤波.OpenCV使用例如以下核
K = \dfrac{1}{159}\begin{bmatrix}          2 & 4 & 5 & 4 & 2 \\          4 & 9 & 12 & 9 & 4 \\          5 & 12 & 15 & 12 & 5 \\          4 & 9 & 12 & 9 & 4 \\          2 & 4 & 5 & 4 & 2                  \end{bmatrix}
(2)计算梯度幅值
先用例如以下Sobel算子计算出水平和竖直梯度
G_{x} = \begin{bmatrix}-1 & 0 & +1  \\-2 & 0 & +2  \\-1 & 0 & +1\end{bmatrix}G_{y} = \begin{bmatrix}-1 & -2 & -1  \\0 & 0 & 0  \\+1 & +2 & +1\end{bmatrix}
我在 具体介绍了Sobel算子
然后计算每一个点的梯度幅值和方向   
\begin{array}{l}G = \sqrt{ G_{x}^{2} + G_{y}^{2} } \\\theta = \arctan(\dfrac{ G_{y} }{ G_{x} })\end{array}
方向最后选4个 (0, 45, 90 or 135)
(3)非极大值抑制
(图片来自http://blog.csdn.net/likezhaobin/article/details/6892176)
以下的蓝色字体分析也来自http://blog.csdn.net/likezhaobin/article/details/6892176  很感谢
图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这不过属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检測的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所相应的灰度值置为0,这样能够剔除掉一大部分非边缘的点。
依据上图可知。要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图1中蓝色的线条方向为C点的梯度方向。这样就能够确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此。推断C点灰度与这两个点灰度大小就可以推断C点是否为其邻域内的局部最大灰度点。假设经过推断,C点灰度值小于这两个点中的任一个。那就说明C点不是局部极大值。那么则能够排除C点为边缘。把C的灰度值改为0,假设C是极大值。能够设为128。

这就是非极大值抑制的工作原理。

但实际上,我们仅仅能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在当中,要得到这两个值就须要对该两个点两端的已知灰度进行线性插值,也即依据图1中的g1和g2对dTmp1进行插值。依据g3和g4对dTmp2进行插值,这要用到其梯度方向,这是上文Canny算法中要求解梯度方向矩阵theta的原因。
我相信上面的解释很明确了。这也是为什么我们要选0。45,90,135四个方向的原因
  1.         第一种情况///  
  2.         /       g1  g2                  /  
  3.         /           C                   /  
  4.         /           g3  g4              /  
  5.         /  
  6.            
  7.         另外一种情况///  
  8.         /       g1                      /  
  9.         /       g2  C   g3              /  
  10.         /               g4              /  
  11.         /  
  12.              
  13.         第三种情况///  
  14.         /           g1  g2              /  
  15.         /           C                   /  
  16.         /       g4  g3                  /  
  17.         /  
  18.         
  19.               
  20.         第四种情况///  
  21.         /               g1              /  
  22.         /       g4  C   g2              /  
  23.         /       g3                      /  
  24.         /  
(4)双阀值检測

在上个步骤中,产生了梯度幅值, (upper and lower):

假设一个像素点的梯度值大于upper,则是边界

假设一个像素点的梯度值小于lower,则不是边界

假设介于两者之间,仅当这个点和边界点连通才会被觉得是边界点

依据高阈值得到一个边缘图像,这样一个图像含有非常少的假边缘。可是因为阈值较高。产生的图像边缘可能不闭合。为解决这样一个问题採用了另外一个低阈值。在高阈值图像中把边缘链接成轮廓,当到达轮廓的端点时。该算法会在断点的8邻域点中寻找满足低阈值的点。再依据此点收集新的边缘,直到整个图像边缘闭合。

Canny推荐upper:lower 的比例为  2:1 或者 3:1.

小提示:由于,所以最高阀值不是255而是360!还有处于[lower,upper]的点检測是否是边界肯定要放在最后一步。也就是[0,lower),(upper,360]的点都处理完再处理。这样才干判连通!

初识API
C++:
 void 
Canny
(InputArray 
image, OutputArray 
edges, double 
threshold1, double 
threshold2, int 
apertureSize=3, bool 
L2gradient=false 
)
 
  • image – 单通道8比特图像
  • edges – 输出图像。和src有同样的大小类型
  • threshold1 – 低阀值
  • threshold2 – 高阀值
  • apertureSize – Sobel算子的大小
  • L2gradient – 一个标识位,一旦设置就启用更准确的 L_2 norm =\sqrt{(dI/dx)^2 + (dI/dy)^2} 去计算图像梯度,默认使用L_1 norm =|dI/dx|+|dI/dy|
荷枪实弹
我们就来看看非常easy的例子程序吧
#include "opencv2/imgproc/imgproc.hpp"#include "opencv2/highgui/highgui.hpp"#include 
using namespace cv;/// Global variablesMat src, src_gray;Mat dst, detected_edges;int lowThreshold;int const max_lowThreshold = 100;int ratio = 3;int kernel_size = 3;const char* window_name = "Edge Map";static void CannyThreshold(int, void*){ blur( src_gray, detected_edges, Size(3,3) ); Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size ); dst = Scalar::all(0); //detected_edges是mask,仅仅有detected_edges被置上(边缘)。才会从原始彩色图像copy到dot中,所以展示的是彩色边缘 src.copyTo( dst, detected_edges); imshow( window_name, dst );}int main( int, char** argv ){ src = imread( argv[1] ); dst.create( src.size(), src.type() ); cvtColor( src, src_gray, CV_BGR2GRAY ); namedWindow( window_name, CV_WINDOW_AUTOSIZE ); createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold ); CannyThreshold(0, 0); waitKey(0); return 0;}
效果图:
举一反三
C的实现http://blog.csdn.net/likezhaobin/article/details/6892629
以下是一个Java实现,曾经是在windows上写的。如今在mac里怎么编码有问题了...
public int[][] CannyEdgeDetect(int[][] oldmat)	{				int[][] tempI = gaussFilter(oldmat); 				//Õ¨—˘ø…“‘”√≤ªÕ¨µƒºÏ≤‚∆˜/  		/    P[i,j]=(S[i,j+1]-S[i,j]+S[i+1,j+1]-S[i+1,j])/2     /  		/    Q[i,j]=(S[i,j]-S[i+1,j]+S[i,j+1]-S[i+1,j+1])/2     /  		/  		float[][] P = new float[height][width];                 //xœÚ∆´µº ˝  		float[][] Q = new float[height][width];                 //yœÚ∆´µº ˝  		int[][] M = new int[height][width];                       //Û∂»∑˘÷µ  		float[][] Theta = new float[height][width];             //Û∂»∑ΩœÚ  		//º∆À„x,y∑ΩœÚµƒ∆´µº ˝  		for(int i=0; i<(width-1); i++)  		{  			for(int j=0; j<(height-1); j++)  			{  				P[j][i] = (float)(tempI[j][Math.min(i+1, width-1)] - tempI[j][i] + tempI[Math.min(j+1, height-1)][Math.min(i+1, width-1)] - tempI[Math.min(j+1, height-1)][i])/2;  				Q[j][i] = (float)(tempI[j][i] - tempI[Math.min(j+1, height-1)][i] + tempI[j][Math.min(i+1, width-1)] - tempI[Math.min(j+1, height-1)][Math.min(i+1, width-1)])/2; 							}  		}  		//º∆À„Û∂»∑˘÷µ∫ÕÛ∂»µƒ∑ΩœÚ  		for(int i=0; i
=90)&&(Theta[j][i]<135)) || ((Theta[j][i]>=270)&&(Theta[j][i]<315))) { //∏˘æ›–±¬ ∫ÕÀƒ∏ˆ÷–º‰÷µΩ¯––≤Â÷µ«ÛΩ‚ g1 = M[j-1][i-1]; g2 = M[j-1][i]; g3 = M[j+1][i]; g4 = M[j+1][i+1]; dWeight = Math.abs(P[j][i])/Math.abs(Q[j][i]); //∑¥’˝«– dTmp1 = g1*dWeight+g2*(1-dWeight); dTmp2 = g4*dWeight+g3*(1-dWeight); } µ⁄∂˛÷÷«Èøˆ/// / g1 / / g2 C g3 / / g4 / / else if( ((Theta[j][i]>=135)&&(Theta[j][i]<180)) || ((Theta[j][i]>=315)&&(Theta[j][i]<360))) { g1 = M[j-1][i-1]; g2 = M[j][i-1]; g3 = M[j][i+1]; g4 = M[j+1][i+1]; dWeight = Math.abs(Q[j][i])/Math.abs(P[j][i]); //’˝«– dTmp1 = g2*dWeight+g1*(1-dWeight); dTmp2 = g4*dWeight+g3*(1-dWeight); } µ⁄»˝÷÷«Èøˆ/// / g1 g2 / / C / / g4 g3 / / else if( ((Theta[j][i]>=45)&&(Theta[j][i]<90)) || ((Theta[j][i]>=225)&&(Theta[j][i]<270))) { g1 = M[j-1][i]; g2 = M[j-1][i+1]; g3 = M[j+1][i]; g4 = M[j+1][i-1]; dWeight = Math.abs(P[j][i])/Math.abs(Q[j][i]); //∑¥’˝«– dTmp1 = g2*dWeight+g1*(1-dWeight); dTmp2 = g3*dWeight+g4*(1-dWeight); } µ⁄Àƒ÷÷«Èøˆ/// / g1 / / g4 C g2 / / g3 / / else if( ((Theta[j][i]>=0)&&(Theta[j][i]<45)) || ((Theta[j][i]>=180)&&(Theta[j][i]<225))) { g1 = M[j-1][i+1]; g2 = M[j][i+1]; g3 = M[j+1][i-1]; g4 = M[j][i-1]; dWeight = Math.abs(Q[j][i])/Math.abs(P[j][i]); //’˝«– dTmp1 = g1*dWeight+g2*(1-dWeight); dTmp2 = g3*dWeight+g4*(1-dWeight); } } //Ω¯––æ÷≤ø◊Ó¥Û÷µ≈–∂œ£¨≤¢–¥»ÎºÏ≤‚Ω·π˚ if((M[j][i]>=dTmp1) && (M[j][i]>=dTmp2)) N[j][i] = 128; else N[j][i] = 0; //System.out.println(N[j][i]); } } //À´∑ß÷µºÏ≤‚ µœ÷ int []nHist = new int[1024]; int nEdgeNum; //ø…ƒ‹±flΩÁ ˝ int nMaxMag = 0; //◊Ó¥ÛÛ∂» ˝ int nHighCount; //Õ≥º∆÷±∑ΩÕº for(int i=0;i<1024;i++) nHist[i] = 0; for(int i=0; i

初识API
你可能感兴趣的文章
设计模式:组合模式(Composite Pattern)
查看>>
ContentValues 和HashTable区别
查看>>
LogicalDOC 6.6.2 发布,文档管理系统
查看>>
给PowerShell脚本传递参数
查看>>
实战2——Hadoop的日志分析
查看>>
利用FIFO进行文件拷贝一例
查看>>
Ecshop安装过程中的的问题:cls_image::gd_version()和不支持JPEG
查看>>
resmgr:cpu quantum等待事件
查看>>
JDK
查看>>
开源BT磁力搜索引擎收集
查看>>
redis 超时失效key 的监听触发
查看>>
分区默认segment大小变化(64k—>8M)
查看>>
Oracle安装部署之 timesten install on redhat6.5
查看>>
对soc-audio体系snd_soc_machine和snd_soc_dai_link简单理解
查看>>
android 常用时间格式转换代码
查看>>
Ruby 面向对象知识详解
查看>>
【转】Visual Studio——多字节编码与Unicode码
查看>>
简单对象访问协议(Simple Object Access Protocol),PHP调用SOAP过程中的种种问题;php的soap无故出错的真凶:wsdl缓存...
查看>>
CMD 切换管理员权限
查看>>
Appium Android Bootstrap源代码分析之简单介绍
查看>>