1、原理

图象边缘就是图像颜色快速变化的位置,对于灰度图像来说,也就是灰度值有明显变化的位置。图像边缘信息主要集中在高频段,图像锐化或检测边缘实质就是高通滤波。数值微分可以求变化率,在图像上离散值求梯度,图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Robert算子(交叉差分),Sobel算子,二阶拉普拉斯算子等等,是基于寻找梯度强度。

Canny 边缘检测算法是John F. Canny 于1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的 最优算法, 最优边缘检测的三个主要评价标准是:

低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。

高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。

最小响应: 图像中的边缘只能标识一次。

Canny算子求边缘点具体算法步骤如下:

1. 用高斯滤波器平滑图像.

2. 用一阶偏导有限差分计算梯度幅值和方向.

3. 对梯度幅值进行非极大值抑制.

4. 用双阈值算法检测和连接边缘.

2、实现步骤

2.1、消除噪声

使用高斯平滑滤波器卷积降噪。下面显示了一个 size = 5 的高斯内核示例:

2.2、计算梯度幅值和方向

按照Sobel滤波器的步骤,计算水平和垂直方向的差分Gx和Gy:

在vs中可以看到sobel像素值和形状:

梯度幅值和方向为:

梯度方向近似到四个可能角度之一(一般 0, 45, 90, 135)。

2.3、非极大值抑制

非极大值抑制是指寻找像素点局部最大值。sobel算子检测出来的边缘太粗了,我们需要抑制那些梯度不够大的像素点,只保留最大的梯度,从而达到瘦边的目的。沿着梯度方向,比较它前面和后面的梯度值,梯度不够大的像素点很可能是某一条边缘的过渡点,排除非边缘像素,最后保留了一些细线。

在John Canny提出的Canny算子的论文中,非最大值抑制就只是在0、90、45、135四个梯度方向上进行的,每个像素点梯度方向按照相近程度用这四个方向来代替。梯度向量的每个四分之一圆被45°线分成两种情况,一种情况是倾向于水平,另一种倾向于竖直,一共 8 个方向。这种情况下,非最大值抑制所比较的相邻两个像素就是:

1)  0:左边 和 右边

2) 45:右上 和 左下

3) 90:上边 和 下边

4)135:左上 和 右下

这样做的好处是简单,但是这种简化的方法无法达到最好的效果,因为自然图像中的边缘梯度方向不一定是沿着这四个方向的,即梯度方向的线并没有落在8邻域坐标点上。因此,就有很大的必要进行插值,找出在一个像素点上最能吻合其所在梯度方向的两侧的像素值。

如果|gx|>|gy|,这说明该点的梯度方向更靠近X轴方向,所以g2和g4则在C的左右,我们可以用下面来说明这两种情况(方向相同和方向不同):

可以使用插值计算出真实梯度值:

其中,插值计算方式为:dTemp1 = weight*g1 + (1-weight)*g2; dTemp2 = weight*g3 + (1-weight)*g4;

Matlab使用非常有技巧的方式来计算方向,如下不仅做了dx、dy的大小判断还做了方向的判定。

  1. witch direction
  2. case
  3. idx = find((iy<= & ix>-iy) | (iy>= & ix<-iy));
  4. case
  5. idx = find((ix> & -iy>=ix) | (ix< & -iy<=ix));
  6. case
  7. idx = find((ix<= & ix>iy) | (ix>= & ix<iy));
  8. case
  9. idx = find((iy< & ix<=iy) | (iy> & ix>=iy));
  10. end

2.4、双阈值检测和区域连通

最后一步,Canny 使用了滞后阈值,滞后阈值需要两个阈值(高阈值和低阈值)。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。Canny 推荐的 高:低 阈值比在 2:1 到3:1之间。

3、代码实现

3.1 计算梯度

  1. /*
  2. * Sobel 梯度计算
  3. */
  4. Mat gradients(Mat &img, Mat &sobel)
  5. {
  6. int W = img.cols;
  7. int H = img.rows;
  8.  
  9. Mat dx = Mat_<int>(img.size());
  10. int border = (int)sobel.rows / ;
  11.  
  12. for (int r = border; r < H - border; r++)
  13. {
  14. for (int c = border; c < W - border; c++)
  15. {
  16. float tmp = ;
  17. for (int i = -border; i <= border; i++) {
  18. for (int j = -border; j <= border; j++) {
  19. tmp += (int)img.data[(r + i)*W + c + j] * sobel.at<int>(i + border, j + border);
  20. }
  21. }
  22.  
  23. dx.at<int>(r, c) = tmp;
  24. }
  25. }
  26. return dx;
  27. }

3.2计算非极大值抑制(详细推导过程见参考文献文章)

  1. /*
  2. fucntion: non-maximum suppression
  3. input:
  4. pMag: pointer to Magnitude,
  5. pGradX: gradient of x-direction
  6. pGradY: gradient of y-direction
  7. sz: size of pMag (width = size.cx, height = size.cy)
  8. limit: limitation
  9. output:
  10. pNSRst: result of non-maximum suppression
  11. */
  12. void NonMaxSuppress(int *pMag, int * pGradX, int *pGradY, Size sz, int *pNSRst)
  13. {
  14. long x, y;
  15. int nPos;
  16. // the component of the gradient
  17. int gx, gy;
  18. // the temp varialbe
  19. int g1, g2, g3, g4;
  20. double weight;
  21. double dTemp, dTemp1, dTemp2;
  22. //设置图像边缘为不可能的分界点
  23. for (x = ; x < sz.width; x++)
  24. {
  25. pNSRst[x] = ;
  26. pNSRst[(sz.height - )*sz.width + x] = ;
  27. }
  28. for (y = ; y < sz.height; y++)
  29. {
  30. pNSRst[y*sz.width] = ;
  31. pNSRst[y*sz.width + sz.width - ] = ;
  32. }
  33.  
  34. for (y = ; y < sz.height - ; y++)
  35. {
  36. for (x = ; x < sz.width - ; x++)
  37. {
  38. nPos = y * sz.width + x;
  39. // if pMag[nPos]==0, then nPos is not the edge point
  40. if (pMag[nPos] == )
  41. {
  42. pNSRst[nPos] = ;
  43. }
  44. else
  45. {
  46. // the gradient of current point
  47. dTemp = pMag[nPos];
  48. // x,y 方向导数
  49. gx = pGradX[nPos];
  50. gy = pGradY[nPos];
  51. //如果方向导数y分量比x分量大,说明导数方向趋向于y分量
  52. if (abs(gy) > abs(gx))
  53. {
  54. // calculate the factor of interplation
  55. weight = fabs(gx) / fabs(gy);
  56. g2 = pMag[nPos - sz.width]; // 上一行
  57. g4 = pMag[nPos + sz.width]; // 下一行
  58. //如果x,y两个方向导数的符号相同
  59. //C 为当前像素,与g1-g4 的位置关系为:
  60. //g1 g2
  61. // C
  62. // g4 g3
  63. if (gx*gy > )
  64. {
  65. g1 = pMag[nPos - sz.width - ];
  66. g3 = pMag[nPos + sz.width + ];
  67. }
  68. //如果x,y两个方向的方向导数方向相反
  69. //C是当前像素,与g1-g4的关系为:
  70. // g2 g1
  71. // C
  72. // g3 g4
  73. else
  74. {
  75. g1 = pMag[nPos - sz.width + ];
  76. g3 = pMag[nPos + sz.width - ];
  77. }
  78. }
  79. else
  80. {
  81. //插值比例
  82. weight = fabs(gy) / fabs(gx);
  83. g2 = pMag[nPos + ]; //后一列
  84. g4 = pMag[nPos - ]; // 前一列
  85. //如果x,y两个方向的方向导数符号相同
  86. //当前像素C与 g1-g4的关系为
  87. // g3
  88. // g4 C g2
  89. // g1
  90. if (gx * gy > )
  91. {
  92. g1 = pMag[nPos + sz.width + ];
  93. g3 = pMag[nPos - sz.width - ];
  94. }
  95.  
  96. //如果x,y两个方向导数的方向相反
  97. // C与g1-g4的关系为
  98. // g1
  99. // g4 C g2
  100. // g3
  101. else
  102. {
  103. g1 = pMag[nPos - sz.width + ];
  104. g3 = pMag[nPos + sz.width - ];
  105. }
  106. }
  107.  
  108. dTemp1 = weight * g1 + ( - weight)*g2;
  109. dTemp2 = weight * g3 + ( - weight)*g4;
  110. if(dTemp )
  111. //当前像素的梯度是局部的最大值
  112. //该点可能是边界点
  113. if (dTemp >= dTemp1 && dTemp >= dTemp2)
  114. {
  115. pNSRst[nPos] = dTemp;
  116. }
  117. else
  118. {
  119. //不可能是边界点
  120. pNSRst[nPos] = ;
  121. }
  122. }
  123. }
  124. }
  125. }

3.3双阈值检测和边缘连接

  1. void duble_threshold(Mat &pMag, Mat &pThreadImg, float threshold)
  2. {
  3. double maxv;
  4. int * img_ptr = pMag.ptr<int>();
  5. uchar * dst_ptr = pThreadImg.ptr<uchar>();
  6. minMaxLoc(pMag, , &maxv, , );
  7. cout << "max" << maxv << endl;
  8.  
  9. int TL = 0.333 * threshold *maxv; // 1/3 of TH
  10. int TH = threshold *maxv;
  11. int w = pMag.cols;
  12. int h = pMag.rows;
  13.  
  14. for (int r = ; r < pMag.rows; r++)
  15. {
  16. for (int c = ; c < pMag.cols; c++)
  17. {
  18. int tmp = img_ptr[r*w + c];
  19. if (tmp < TL) {
  20. dst_ptr[r*w + c] = ;
  21. }
  22. else if (tmp >= TH) {
  23. dst_ptr[r*w + c] = ;
  24. }
  25. else {
  26. bool connect = false;
  27. for(int i=-; i<= && connect == false; i++)
  28. for (int j = -; j <= && connect == false; j++)
  29. {
  30. if (img_ptr[r + i, c + j] >= TH)
  31. {
  32. dst_ptr[r*w + c] = ;
  33. connect = true;
  34. break;
  35. }
  36. else dst_ptr[r*w + c] = ;
  37. }
  38. }
  39. }
  40. }
  41. }

4、测试结论

测试1:左侧是原图,右侧是进行了sobel梯度计算和非极大值抑制后的图。

可见右图,在企鹅轮廓内部还有孤立的点,放大后如下图。

使用双阈值限定后如下图,内部点消失了。

测试2:选择合适的阈值,图像中心的白色噪点可以消除。

测试3:

如下图,图2的双阈值计算梯度后最大梯度360,图3使用0.5倍高阈值,轮廓不连贯,可见阈值过高。改为0.2倍高阈值,结果如图4,改善了轮廓缺失问题。

5、参考文献

1、《数字图像处理与机器视觉》,第二版。 张铮、徐超、任淑霞、韩海玲等编著。

2、Canny 边缘检测

http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.html

3、Sobel算子的数学基础

http://blog.sciencenet.cn/blog-425437-1139187.html

4、Canny边缘检测

https://www.cnblogs.com/mmmmc/p/10524640.html

5、Canny算子中的非极大值抑制(Non-Maximum Suppression)分析

https://blog.csdn.net/kezunhai/article/details/11620357

6、一种改进非极大值抑制的Canny边缘检测算法

https://www.doc88.com/p-5174766661571.html

个人博客,转载请注明。

https://www.cnblogs.com/pingwen/p/12489703.html

Canny检测算法与实现的更多相关文章

  1. OpenCV: Canny边缘检测算法原理及其VC实现详解(转载)

    原文地址:http://blog.csdn.net/likezhaobin/article/details/6892176 原文地址:http://blog.csdn.net/likezhaobin/ ...

  2. Canny边缘检测算法原理及其VC实现详解(一)

    转自:http://blog.csdn.net/likezhaobin/article/details/6892176 图象的边缘是指图象局部区域亮度变化显著的部分,该区域的灰度剖面一般可以看作是一个 ...

  3. 一些关于Canny边缘检测算法的改进

    传统的Canny边缘检测算法是一种有效而又相对简单的算法,可以得到很好的结果(可以参考上一篇Canny边缘检测算法的实现).但是Canny算法本身也有一些缺陷,可以有改进的地方. 1. Canny边缘 ...

  4. Canny边缘检测算法的一些改进

    传统的Canny边缘检测算法是一种有效而又相对简单的算法,可以得到很好的结果(可以参考上一篇Canny边缘检测算法的实现).但是Canny算法本身也有一些缺陷,可以有改进的地方. 1. Canny边缘 ...

  5. Canny检测理解和Matlab实现

    图象的边缘是指图象局部区域亮度变化显著的部分,该区域的灰度剖面一般可以看作是一个阶跃,既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值. 1.Canny边缘检测的基本特征 (1) ...

  6. 十五 Canny边缘检测算法

    一.Canny算法介绍 Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是: 好的检测- 算法能够尽可能多地标识出图像中的实际边缘. 好的定位- 标识出的边缘要尽可能与实际图像中的实 ...

  7. 【数字图像分析】基于Python实现 Canny Edge Detection(Canny 边缘检测算法)

    Canny 边缘检测算法 Steps: 高斯滤波平滑 计算梯度大小和方向 非极大值抑制 双阈值检测和连接 代码结构: Canny Edge Detection | Gaussian_Smoothing ...

  8. Canny边缘检测算法(基于OpenCV的Java实现)

    目录 Canny边缘检测算法(基于OpenCV的Java实现) 绪论 Canny边缘检测算法的发展历史 Canny边缘检测算法的处理流程 用高斯滤波器平滑图像 彩色RGB图像转换为灰度图像 一维,二维 ...

  9. 每天进步一点点------Sobel算子(3)基于彩色图像边缘差分的运动目标检测算法

    摘  要: 针对目前常用的运动目标提取易受到噪声影响.易出现阴影和误检漏检等情况,提出了一种基于Sobel算子的彩色边缘图像检测和帧差分相结合的检测方法.首先用Sobel算子提取视频流中连续4帧图像的 ...

随机推荐

  1. HotSpot Java对象创建,内存布局以及访问方式

    内存中对象的创建.对象的结构以及访问方式. 一.对象的创建 在语言层面上,对象的创建只不过是一个new关键字而已,那么在虚拟机中又是一个怎样的过程呢? (一)判断类是否加载.虚拟机遇到一条new指令的 ...

  2. django框架基础-视图系统-长期维护

    ##################   什么是视图?     ####################### 视图: 1,一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受W ...

  3. 微软研究院张永光博士与Dilek Hakkani-Tür博士当选2014年 IEEE院士

    Hakkani-Tür博士当选2014年 IEEE院士" title="微软研究院张永光博士与Dilek Hakkani-Tür博士当选2014年 IEEE院士"> ...

  4. MySQL之数据存储引擎

    1.什么是存储引擎: 现实生活中我们用来存储数据的文件有不同的类型,每种文件类型对应各自不同的处理机制:比如处 理文本用txt类型,处理表格用excel,处理图片用png等,数据库中的表也应该有不同的 ...

  5. deeplearning.ai 构建机器学习项目 Week 1 机器学习策略 I

    这门课是讲一些分析机器学习问题的方法,如何更快速高效的优化机器学习系统,以及NG自己的工程经验和教训. 1. 正交化(Othogonalization) 设计机器学习系统时需要面对一个问题是:可以尝试 ...

  6. deeplearning.ai 神经网络和深度学习 week2 神经网络基础

    1. Logistic回归是用于二分分类的算法. 对于m个样本的训练集,我们可能会习惯于使用for循环一个个处理,但在机器学习中,是把每一个样本写成一个列向量x,然后把m个列向量拼成一个矩阵X.这个矩 ...

  7. mysqli存储过程

    <?php$link = mysqli_connect('localhost','root','','chinatupai');  $sql = "call getEmail('000 ...

  8. Java 的 ArrayList 的底层数据结构

    1. 数据结构--ArrayList源码摘要 ublic class ArrayList<E> extends AbstractList<E> implements List& ...

  9. Log4j输出的日志乱码问题

    设置日志输出编码: log4j.appender.stdout.Encoding=UTF-8

  10. haproxy笔记之四:配置文件中的关键字参考

    3.1 balance balance <algorithm> [ <arguments> ]balance url_param <param> [check_po ...