python Canny边缘检测算法的实现


Posted in Python onApril 24, 2020

图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波。我们知道微分运算是求信号的变化率,具有加强高频分量的作用。在空域运算中来说,对图像的锐化就是计算微分。对于数字图像的离散信号,微分运算就变成计算差分或梯度。图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Robert算子(交叉差分),Sobel算子等等,是基于寻找梯度强度。拉普拉斯算子(二阶差分)是基于过零点检测。通过计算梯度,设置阀值,得到边缘图像。

Canny边缘检测算子是一种多级检测算法。1986年由John F. Canny提出,同时提出了边缘检测的三大准则:

  • 低错误率的边缘检测:检测算法应该精确地找到图像中的尽可能多的边缘,尽可能的减少漏检和误检。
  • 最优定位:检测的边缘点应该精确地定位于边缘的中心。
  • 图像中的任意边缘应该只被标记一次,同时图像噪声不应产生伪边缘。

Canny算法出现以后一直是作为一种标准的边缘检测算法,此后也出现了各种基于Canny算法的改进算法。时至今日,Canny算法及其各种变种依旧是一种优秀的边缘检测算法。而且除非前提条件很适合,你很难找到一种边缘检测算子能显著地比Canny算子做的更好。

关于各种差分算子,还有Canny算子的简单介绍,这里就不罗嗦了,网上都可以找得到。直接进入Canny算法的实现。Canny算法分为以下几步。

1. 高斯模糊。

这一步很简单,类似于LoG算子(Laplacian of Gaussian)作高斯模糊一样,主要作用就是去除噪声。因为噪声也集中于高频信号,很容易被识别为伪边缘。应用高斯模糊去除噪声,降低伪边缘的识别。但是由于图像边缘信息也是高频信号,高斯模糊的半径选择很重要,过大的半径很容易让一些弱边缘检测不到。

python Canny边缘检测算法的实现

2. 计算梯度幅值和方向。

图像的边缘可以指向不同方向,因此经典Canny算法用了四个梯度算子来分别计算水平,垂直和对角线方向的梯度。但是通常都不用四个梯度算子来分别计算四个方向。常用的边缘差分算子(如Rober,Prewitt,Sobel)计算水平和垂直方向的差分Gx和Gy。这样就可以如下计算梯度模和方向:

python Canny边缘检测算法的实现

梯度角度 θ 范围从弧度 -π 到 π,然后把它近似到四个方向,分别代表水平,垂直和两个对角线方向(0°,45°,90°,135°)。可以以±iπ/8(i=1,3,5,7)分割,落在每个区域的梯度角给一个特定值,代表四个方向之一。

这里我选择Sobel算子计算梯度。Sobel算法很简单,到处都可以找到,就不列出代码来了。相对于其他边缘算子,Sobel算子得出来的边缘粗大明亮。

python Canny边缘检测算法的实现

下图是对上面半径2的高斯模糊图像L通道(HSL)应用Sobel算子的梯度模图,没有施加任何阀值。

python Canny边缘检测算法的实现

Sobel算子,无阀值

3. 非最大值抑制。

非最大值抑制是一种边缘细化方法。通常得出来的梯度边缘不止一个像素宽,而是多个像素宽。就像我们所说Sobel算子得出来的边缘粗大而明亮,从上面Lena图的Sobel结果可以看得出来。因此这样的梯度图还是很“模糊”。而准则3要求,边缘只有一个精确的点宽度。非最大值抑制能帮助保留局部最大梯度而抑制所有其他梯度值。这意味着只保留了梯度变化中最锐利的位置。算法如下:

  • 比较当前点的梯度强度和正负梯度方向点的梯度强度。
  • 如果当前点的梯度强度和同方向的其他点的梯度强度相比较是最大,保留其值,否则抑制,即设为0。比如当前点的方向指向正上方90°方向,那它需要和垂直方向,它的正上方和正下方的像素比较。

注意,方向的正负是不起作用的,比如东南方向和西北方向是一样的,都认为是对角线的一个方向。前面我们把梯度方向近似到水平,垂直和两个对角线四个方向,所以每个像素根据自身方向在这四个方向之一进行比较,决定是否保留。这一部分的代码也很简单,列出如下。pModule,pDirection分别记录了上一步梯度模值和梯度方向。

pmoddrow = pModule + Width + 1; 
pdirdrow = pDirection + Width + 1;
pstrongdrow = pStrong + Width + 1;
for (i = 1; i < Hend - 1; i++)
{
pstrongd = pstrongdrow;

pmodd = pmoddrow;

pdird = pdirdrow;

for (j = 1; j < Wend - 1; j++)


{
       switch (*pdird)
      {
      case 0:    // x direction
      case 4:
        if (*pmodd > *(pmodd - 1) && *pmodd > *(pmodd + 1))
          *pstrongd = 255;
        break;
      case 1:    // northeast-southwest direction. Notice the data order on y direction of bmp data
      case 5:
        if (*pmodd > *(pmodd + Width + 1) && *pmodd > *(pmodd - Width - 1))
          *pstrongd = 255;
        break;
      case 2:    // y direction
      case 6:
        if (*pmodd > *(pmodd - Width) && *pmodd > *(pmodd + Width))
          *pstrongd = 255;
        break;
      case 3:    // northwest-southeast direction. Notice the data order on y direction of bmp data
      case 7:
        if (*pmodd > *(pmodd + Width - 1) && *pmodd > *(pmodd - Width + 1))
          *pstrongd = 255;
        break;
      default:
        ASSERT(0);
        break;
      }
      pstrongd++;
      pmodd++;
      pdird++;
  }
  pstrongdrow += Width;
  pmoddrow += Width;
  pdirdrow += Width;
}

下图是非最大值抑制的结果。可见边缘宽度已经大大减小。但是这个图像中因为没有应用任何阀值,还含有大量小梯度模值的点,也就是图中很暗的地方。下面,阀值要上场了。

python Canny边缘检测算法的实现

非最大值抑制结果

4. 双阀值。

一般的边缘检测算法用一个阀值来滤除噪声或颜色变化引起的小的梯度值,而保留大的梯度值。Canny算法应用双阀值,即一个高阀值和一个低阀值来区分边缘像素。如果边缘像素点梯度值大于高阀值,则被认为是强边缘点。如果边缘梯度值小于高阀值,大于低阀值,则标记为弱边缘点。小于低阀值的点则被抑制掉。这一步算法很简单。

5. 滞后边界跟踪。

至此,强边缘点可以认为是真的边缘。弱边缘点则可能是真的边缘,也可能是噪声或颜色变化引起的。为得到精确的结果,后者引起的弱边缘点应该去掉。通常认为真实边缘引起的弱边缘点和强边缘点是连通的,而由噪声引起的弱边缘点则不会。所谓的滞后边界跟踪算法检查一个弱边缘点的8连通领域像素,只要有强边缘点存在,那么这个弱边缘点被认为是真的边缘保留下来。

这个算法搜索所有连通的弱边缘,如果一条连通的弱边缘的任何一个点和强边缘点连通,则保留这条弱边缘,否则抑制这条弱边缘。搜索时可以用广度优先或者深度优先算法,我在这里实现了应该是最容易的深度优先算法。一次连通一条边缘的深度优先算法如下:

  • 准备一个栈s,一个队列q,设连通指示变量connected为假。从图像的第一个点开始,进入2。
  • 如果这个点是弱边界点并且没有被标记,把它标记,并把它作为第一个元素放入栈s中,同时把它放入记录连通曲线的队列q,进入3。如果这个点不是弱边界或者已经被标记过,到图像的下一个点,重复2。
  • 从栈s中取出一个元素,查找它的8像素领域。如果一个领域像素是弱边界并且没有被标记过,把这个领域像素标记,并加入栈s中,同时加入队列q。同时查找领域对应的强边界图,如果有一个像素是强边界,表示这条弱边界曲线和强边界联通,设置connected为真。重复3直到栈中没有元素了。如果connected为假,则依次从队列q中取出每个元素,清空标记。如果connected为真,保留标记。
  • 清空队列q,设置connected为假,移动到图像的下一个点,回到2。
pmoddrow = pModule + Width + 1; 
pdirdrow = pDirection + Width + 1;
pstrongdrow = pStrong + Width + 1;
for (i = 1; i < Hend - 1; i++)
{
pstrongd = pstrongdrow;

pmodd = pmoddrow;

pdird = pdirdrow;

for (j = 1; j < Wend - 1; j++)


{
       switch (*pdird)
      {
      case 0:    // x direction
      case 4:
        if (*pmodd > *(pmodd - 1) && *pmodd > *(pmodd + 1))
          *pstrongd = 255;
        break;
      case 1:    // northeast-southwest direction. Notice the data order on y direction of bmp data
      case 5:
        if (*pmodd > *(pmodd + Width + 1) && *pmodd > *(pmodd - Width - 1))
          *pstrongd = 255;
        break;
      case 2:    // y direction
      case 6:
        if (*pmodd > *(pmodd - Width) && *pmodd > *(pmodd + Width))
          *pstrongd = 255;
        break;
      case 3:    // northwest-southeast direction. Notice the data order on y direction of bmp data
      case 7:
        if (*pmodd > *(pmodd + Width - 1) && *pmodd > *(pmodd - Width + 1))
          *pstrongd = 255;
        break;
      default:
        ASSERT(0);
        break;
      }
      pstrongd++;
      pmodd++;
      pdird++;
  }
  pstrongdrow += Width;
  pmoddrow += Width;
  pdirdrow += Width;
}

下面是对Lena图计算Canny边缘检测的梯度模图和二值化图,高斯半径2,高阀值100,低阀值50。

python Canny边缘检测算法的实现   python Canny边缘检测算法的实现

Canny检测梯度模图                        Canny检测梯度二值图

作为对比,下面是用一阶差分和Sobel算子对原图计算的结果,阀值100。由于一阶差分的梯度值相对较小,我对一阶差分的梯度值放大了一定倍数,使得它和Sobel的梯度值保持同样的水平。

python Canny边缘检测算法的实现   python Canny边缘检测算法的实现

 一阶差分梯度模图                        一阶差分梯度二值图

python Canny边缘检测算法的实现   python Canny边缘检测算法的实现

Sobel梯度模图                          Sobel梯度二值图

很明显,Canny边缘检测的效果是很显著的。相比普通的梯度算法大大抑制了噪声引起的伪边缘,而且是细化过的边缘,易于后续处理。对于对比度较低的图像,通过调节参数,Canny算法也能有很好的效果。

python Canny边缘检测算法的实现

到此这篇关于python Canny边缘检测算法的实现的文章就介绍到这了,更多相关Canny边缘检测算法内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python中wx将图标显示在右下角的脚本代码
Mar 08 Python
python实现在无须过多援引的情况下创建字典的方法
Sep 25 Python
Python实现115网盘自动下载的方法
Sep 30 Python
Python读取键盘输入的2种方法
Jun 16 Python
Python的面向对象编程方式学习笔记
Jul 12 Python
python3安装pip3(install pip3 for python 3.x)
Apr 03 Python
在Pycharm中对代码进行注释和缩进的方法详解
Jan 20 Python
numpy concatenate数组拼接方法示例介绍
May 27 Python
PyQt5固定窗口大小的方法
Jun 18 Python
python用win32gui遍历窗口并设置窗口位置的方法
Jul 26 Python
python+django+rest框架配置创建方法
Aug 31 Python
详解django使用include无法跳转的解决方法
Mar 19 Python
python实现文字版扫雷
Apr 24 #Python
离线状态下在jupyter notebook中使用plotly实例
Apr 24 #Python
python3中sys.argv的实例用法
Apr 24 #Python
VScode连接远程服务器上的jupyter notebook的实现
Apr 23 #Python
Python实现仿射密码的思路详解
Apr 23 #Python
利用matplotlib为图片上添加触发事件进行交互
Apr 23 #Python
python中matplotlib实现随鼠标滑动自动标注代码
Apr 23 #Python
You might like
7个鲜为人知却非常实用的PHP函数
2015/07/01 PHP
解析WordPress中函数钩子hook的作用及基本用法
2015/12/22 PHP
Yii2简单实现给表单添加验证码的方法
2016/07/18 PHP
Prototype Number对象 学习
2009/07/19 Javascript
jQuery 选择器、DOM操作、事件、动画
2010/11/25 Javascript
Prototype源码浅析 String部分(三)之HTML字符串处理
2012/01/15 Javascript
jquery插件开发方法(初学者)
2012/02/03 Javascript
javascript学习笔记(八) js内置对象
2012/06/19 Javascript
jQuery-Tools-overlay 使用介绍
2012/07/14 Javascript
js如何获取file控件的完整路径具体实现代码
2013/05/15 Javascript
JavaScript使用slice函数获取数组部分元素的方法
2015/04/06 Javascript
JS+JSP通过img标签调用实现静态页面访问次数统计的方法
2015/12/14 Javascript
jQuery获取父元素节点、子元素节点及兄弟元素节点的方法
2016/04/14 Javascript
javascript封装addLoadEvent实现页面同时加载执行多个函数的方法
2016/07/25 Javascript
jQuery如何封装输入框插件
2016/08/19 Javascript
JS实现iframe自适应高度的方法示例
2017/01/07 Javascript
Angular在一个页面中使用两个ng-app的方法(二)
2017/02/20 Javascript
微信小程序使用wxParse解析html的方法教程
2018/07/06 Javascript
express+vue+mongodb+session 实现注册登录功能
2018/12/06 Javascript
JavaScript中构造函数与原型链之间的关系详解
2019/02/25 Javascript
JavaScript使用百度ECharts插件绘制饼图操作示例
2019/11/26 Javascript
基于Django filter中用contains和icontains的区别(详解)
2017/12/12 Python
如何创建一个Flask项目并进行简单配置
2020/11/18 Python
python爬虫爬取图片的简单代码
2021/01/18 Python
利用Python函数实现一个万历表完整示例
2021/01/23 Python
一款纯css3实现的鼠标经过按钮特效教程
2014/11/09 HTML / CSS
UNIX文件名称有什么规定
2013/03/25 面试题
临床医学大学生求职信
2013/09/28 职场文书
幽默自我介绍演讲稿
2014/08/21 职场文书
白酒代理协议书范本
2014/10/26 职场文书
学生检讨书范文
2014/10/30 职场文书
祝福语集锦:给满月宝宝的祝福语
2019/11/20 职场文书
MySQL优化之如何写出高质量sql语句
2021/05/17 MySQL
SQL模糊查询报:ORA-00909:参数个数无效问题的解决
2021/06/21 Oracle
使用Nginx的访问日志统计PV与UV
2022/05/06 Servers
spring 项目实现限流方法示例
2022/07/15 Java/Android