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 相关文章推荐
使用C#配合ArcGIS Engine进行地理信息系统开发
Feb 19 Python
Python的Django框架中forms表单类的使用方法详解
Jun 21 Python
Python实现改变与矩形橡胶的线条的颜色代码示例
Jan 05 Python
python时间日期函数与利用pandas进行时间序列处理详解
Mar 13 Python
用TensorFlow实现戴明回归算法的示例
May 02 Python
Python对数据进行插值和下采样的方法
Jul 03 Python
python生成九宫格图片
Nov 19 Python
解决python3 pika之连接断开的问题
Dec 18 Python
用python实现刷点击率的示例代码
Feb 21 Python
python实现AES加密和解密
Mar 27 Python
Python 文件数据读写的具体实现
Jan 24 Python
Django Paginator分页器的使用示例
Jun 23 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
php检测apache mod_rewrite模块是否安装的方法
2015/03/14 PHP
Laravel如何友好的修改.env配置文件详解
2017/06/07 PHP
新浪刚打开页面出来的全屏广告代码
2007/04/02 Javascript
javascript之更有效率的字符串替换
2008/08/02 Javascript
ajax无刷新动态调用股票信息(改良版)
2008/11/01 Javascript
ie8 不支持new Date(2012-11-10)问题的解决方法
2013/07/31 Javascript
javascript抖动元素的小例子
2013/10/28 Javascript
JavaScript数据类型学习笔记
2016/01/25 Javascript
NodeJS中的MongoDB快速入门详细教程
2016/11/11 NodeJs
Bootstrap中datetimepicker使用小结
2016/12/28 Javascript
Vue结合SignalR实现前后端实时消息同步
2017/09/19 Javascript
js实现随机点名系统(实例讲解)
2017/10/18 Javascript
js核心基础之闭包的应用实例分析
2019/05/11 Javascript
vue本地打开build后生成的dist文件夹index.html问题
2019/09/04 Javascript
win7 x64系统中安装Scrapy的方法
2018/11/18 Python
selenium+python截图不成功的解决方法
2019/01/30 Python
Python基本数据结构之字典类型dict用法分析
2019/06/08 Python
Pytorch 实现自定义参数层的例子
2019/08/17 Python
python wav模块获取采样率 采样点声道量化位数(实例代码)
2020/01/22 Python
适合Python初学者的一些编程技巧
2020/02/12 Python
Python模拟登入的N种方式(建议收藏)
2020/05/31 Python
Python坐标轴操作及设置代码实例
2020/06/04 Python
Expected conditions模块使用方法汇总代码解析
2020/08/13 Python
浅析Python模块之间的相互引用问题
2021/02/26 Python
HTML5 placeholder(空白提示)属性介绍
2013/08/07 HTML / CSS
教师找工作推荐信
2013/11/23 职场文书
预备党员党课思想汇报
2014/01/13 职场文书
采购人员的个人自我评价
2014/01/16 职场文书
新学期校长寄语
2014/01/18 职场文书
运动会入场词60字
2014/02/15 职场文书
中学生社会实践活动总结
2014/07/03 职场文书
2014年音乐教师工作总结
2014/12/03 职场文书
十岁生日答谢词
2015/01/05 职场文书
文体活动总结
2015/02/04 职场文书
英语教师个人总结
2015/02/09 职场文书
Python进程池与进程锁之语法学习
2022/04/11 Python