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使用MySQLdb for Python操作数据库教程
Oct 11 Python
python搜索指定目录的方法
Apr 29 Python
深入了解Python数据类型之列表
Jun 24 Python
Python实现遍历目录的方法【测试可用】
Mar 22 Python
python实现报表自动化详解
Nov 16 Python
Python如何处理大数据?3个技巧效率提升攻略(推荐)
Apr 15 Python
python爬取盘搜的有效链接实现代码
Jul 20 Python
Pytorch 计算误判率,计算准确率,计算召回率的例子
Jan 18 Python
用Python生成HTML表格的方法示例
Mar 06 Python
Python xlwt模块使用代码实例
Jun 10 Python
浅析python连接数据库的重要事项
Feb 22 Python
Python可变集合和不可变集合的构造方法大全
Dec 06 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
锁定年轻人的双倍活力 星巴克推出星倍醇即饮浓咖啡
2021/03/03 咖啡文化
PHP页面跳转操作实例分析(header方法)
2016/09/28 PHP
Yii2框架制作RESTful风格的API快速入门教程
2016/11/08 PHP
ThinkPHP 3.2.3实现加减乘除图片验证码
2018/12/05 PHP
jQuery get和post 方法传值注意事项
2009/11/03 Javascript
跨域表单提交状态的变相判断代码
2009/11/12 Javascript
jquery.validate使用攻略 第三部
2010/07/01 Javascript
jquery队列queue与原生模仿其实现方法分享
2014/03/25 Javascript
Jquery的each里用return true或false代替break或continue
2014/05/21 Javascript
jquery用data方法获取某个元素上的事件
2014/06/23 Javascript
Angular2+如何去除url中的#号详解
2017/12/20 Javascript
AutoJs实现刷宝短视频的思路详解
2020/05/22 Javascript
Vue实现附件上传功能
2020/05/28 Javascript
[02:20]DOTA2亚洲邀请赛 IG战队出场宣传片
2015/02/07 DOTA
Python格式化css文件的方法
2015/03/10 Python
Python多线程编程(三):threading.Thread类的重要函数和方法
2015/04/05 Python
Python3实现的字典遍历操作详解
2018/04/18 Python
Python基本socket通信控制操作示例
2019/01/30 Python
Python代码使用 Pyftpdlib实现FTP服务器功能
2019/07/22 Python
python3中的eval和exec的区别与联系
2019/10/10 Python
Python列表元素常见操作简单示例
2019/10/25 Python
在Python中使用MySQL--PyMySQL的基本使用方法
2019/11/19 Python
详解Python调用系统命令的六种方法
2021/01/28 Python
python Autopep8实现按PEP8风格自动排版Python代码
2021/03/02 Python
塑料制成的可水洗的编织平底鞋和鞋子:Rothy’s
2018/09/16 全球购物
Liu Jo西班牙官网:意大利服装品牌
2019/09/11 全球购物
bonprix荷兰网上商店:便宜的服装、鞋子和家居用品
2020/07/04 全球购物
C语言如何决定使用那种整数类型
2016/11/26 面试题
财产公证书格式
2014/04/10 职场文书
目标责任书范文
2014/04/14 职场文书
作风转变心得体会
2014/09/02 职场文书
领导干部学习“三严三实”思想汇报
2014/09/15 职场文书
走群众路线学习心得体会
2014/10/31 职场文书
2016大学生入党积极分子心得体会
2016/01/06 职场文书
Django集成富文本编辑器summernote的实现步骤
2021/05/31 Python
MySQL系列之一 MariaDB-server安装
2021/07/02 MySQL