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标准库之itertools库的使用方法
Sep 07 Python
Python实现购物系统(示例讲解)
Sep 13 Python
python查询mysql,返回json的实例
Mar 26 Python
Python格式化日期时间操作示例
Jun 28 Python
python对list中的每个元素进行某种操作的方法
Jun 29 Python
Windows下将Python文件打包成.EXE可执行文件的方法
Aug 03 Python
使用TensorFlow实现SVM
Sep 06 Python
浅谈pandas用groupby后对层级索引levels的处理方法
Nov 06 Python
PIL图像处理模块paste方法简单使用详解
Jul 17 Python
tensorflow基于CNN实战mnist手写识别(小白必看)
Jul 20 Python
python实现mask矩阵示例(根据列表所给元素)
Jul 30 Python
python如何查找列表中元素的位置
May 30 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
phpadmin如何导入导出大数据文件及php.ini参数修改
2013/02/18 PHP
php判断电脑访问、手机访问的例子
2014/05/10 PHP
合并ThinkPHP配置文件以消除代码冗余的实现方法
2014/07/22 PHP
详解PHP数组赋值方法
2015/11/07 PHP
php结合mysql与mysqli扩展处理事务的方法
2016/06/29 PHP
PHP精确到毫秒秒杀倒计时实例详解
2019/03/14 PHP
PHP中quotemeta()函数的用法讲解
2019/04/04 PHP
微信公众平台开发教程②微信端分享功能图文详解
2019/04/10 PHP
Yii2框架操作数据库的方法分析【以mysql为例】
2019/05/27 PHP
可以将word转成html的js代码
2010/04/11 Javascript
jQuery UI Dialog控件中的表单无法正常提交的解决方法
2010/12/19 Javascript
js计算德州扑克牌面值的方法
2015/03/04 Javascript
javascript创建cookie、读取cookie
2016/03/31 Javascript
基于jQuery实现点击列表加载更多效果
2016/05/31 Javascript
详谈$.data()的用法和作用
2017/02/13 Javascript
JavaScript正则获取地址栏中参数的方法
2017/03/02 Javascript
深入掌握 react的 setState的工作机制
2017/09/27 Javascript
基于angular-utils-ui-breadcrumbs使用心得(分享)
2017/11/03 Javascript
Nodejs调用Dll模块的方法
2018/09/17 NodeJs
JavaScript实现学生在线做题计时器功能
2018/12/05 Javascript
vue中img src 动态加载本地json的图片路径写法
2019/04/25 Javascript
Vue侦测相关api的实现方法
2019/05/22 Javascript
Vue + Elementui实现多标签页共存的方法
2019/06/12 Javascript
vue中使用百度脑图kityminder-core二次开发的实现
2019/09/26 Javascript
vue改变循环遍历后的数据实例
2019/11/07 Javascript
javascrpt密码强度校验函数详解
2020/03/18 Javascript
Vue组件通信$attrs、$listeners实现原理解析
2020/09/03 Javascript
vue 二维码长按保存和复制内容操作
2020/09/22 Javascript
Django打印出在数据库中执行的语句问题
2019/07/25 Python
pytorch实现MNIST手写体识别
2020/02/14 Python
海量信息软件测试笔试题
2015/08/08 面试题
活动倡议书范文
2014/05/13 职场文书
局领导领导班子四风对照检查材料
2014/09/27 职场文书
幼儿园六一儿童节主持词
2015/06/30 职场文书
python 遍历磁盘目录的三种方法
2021/04/02 Python
C#连接ORACLE出现乱码问题的解决方法
2021/10/05 Oracle