python Canny边缘检测算法的实现

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

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

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

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

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

1. 高斯模糊。

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

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

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

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

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

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

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;
}

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

非最大值抑制结果

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。

   

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

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

   

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

   

Sobel梯度模图                          Sobel梯度二值图

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

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

(0)

相关推荐

  • 使用OpenCV-python3实现滑动条更新图像的Canny边缘检测功能

    import cv2 from matplotlib import pyplot as plt import numpy as np img= cv2.imread('39.jpg')#加载图片 cv2.namedWindow('Canny edge detect')#设置窗口,cv2.WINDOW_NORMAL表示窗口大小可自动调节 cv2.namedWindow('Original Image',cv2.WINDOW_NORMAL) cv2.namedWindow('Canny edgeIm

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

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

  • 详解Python中图像边缘检测算法的实现

    目录 写在前面 1.一阶微分算子 1.1 Prewitt算子 1.2 Sobel算子 2.二阶微分算子 2.1 Laplace算子 2.2 LoG算子 3.Canny边缘检测 写在前面 从本节开始,计算机视觉教程进入第三章节——图像特征提取.在本章,你会见到一张简简单单的图片中蕴含着这么多你没注意到的细节特征,而这些特征将会在今后更高级的应用中发挥着极其重要的作用.本文讲解基础特征之一——图像边缘. 本文采用面向对象设计,定义了一个边缘检测类EdgeDetect,使图像边缘检测算法的应用更简洁,

  • 应用OpenCV和Python进行SIFT算法的实现详解

    应用OpenCV和Python进行SIFT算法的实现 如下图为进行测试的gakki101和gakki102,分别验证基于BFmatcher.FlannBasedMatcher等的SIFT算法,对比其优劣.为体现出匹配效果对于旋转特性的优势,将图gakki101做成具有旋转特性的效果. 基于BFmatcher的SIFT实现 BFmatcher(Brute-Force Matching)暴力匹配,应用BFMatcher.knnMatch( )函数来进行核心的匹配,knnMatch(k-nearest

  • python常用排序算法的实现代码

    这篇文章主要介绍了python常用排序算法的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 排序是计算机语言需要实现的基本算法之一,有序的数据结构会带来效率上的极大提升. 1.插入排序 插入排序默认当前被插入的序列是有序的,新元素插入到应该插入的位置,使得新序列仍然有序. def insertion_sort(old_list): n=len(old_list) k=0 for i in range(1,n): temp=old_lis

  • 详解Python OpenCV图像分割算法的实现

    目录 前言 1.图像二值化 2.自适应阈值分割算法 3.Otsu阈值分割算法 4.基于轮廓的字符分离 4.1轮廓检测 4.2轮廓绘制 4.3包围框获取 4.4矩形绘制 前言 图像分割是指根据灰度.色彩.空间纹理.几何形状等特征把图像划分成若干个互不相交的区域. 最简单的图像分割就是将物体从背景中分割出来 1.图像二值化 cv2.threshold是opencv-python中的图像二值化方法,可以实现简单的分割功能. retval, dst = cv2.threshold(src, thresh

  • python选择排序算法的实现代码

    1.算法:对于一组关键字{K1,K2,-,Kn}, 首先从K1,K2,-,Kn中选择最小值,假如它是 Kz,则将Kz与 K1对换:然后从K2,K3,- ,Kn中选择最小值 Kz,再将Kz与K2对换.如此进行选择和调换n-2趟,第(n-1)趟,从Kn-1.Kn中选择最小值 Kz将Kz与Kn-1对换,最后剩下的就是该序列中的最大值,一个由小到大的有序序列就这样形成. 2.python 选择排序代码: 复制代码 代码如下: def selection_sort(list2):    for i in

  • 基于ID3决策树算法的实现(Python版)

    实例如下: # -*- coding:utf-8 -*- from numpy import * import numpy as np import pandas as pd from math import log import operator #计算数据集的香农熵 def calcShannonEnt(dataSet): numEntries=len(dataSet) labelCounts={} #给所有可能分类创建字典 for featVec in dataSet: currentLa

  • python 基于卡方值分箱算法的实现示例

    原理很简单,初始分20箱或更多,先确保每箱中都含有0,1标签,对不包含0,1标签的箱向前合并,计算各箱卡方值,对卡方值最小的箱向后合并,代码如下 import pandas as pd import numpy as np import scipy from scipy import stats def chi_bin(DF,var,target,binnum=5,maxcut=20): ''' DF:data var:variable target:target / label binnum:

  • python实现canny边缘检测

    canny边缘检测原理 canny边缘检测共有5部分组成,下边我会分别来介绍. 1 高斯模糊(略) 2 计算梯度幅值和方向. 可选用的模板:soble算子.Prewitt算子.Roberts模板等等; 一般采用soble算子,OpenCV也是如此,利用soble水平和垂直算子与输入图像卷积计算dx.dy: 进一步可以得到图像梯度的幅值: 为了简化计算,幅值也可以作如下近似: 角度为: 如下图表示了中心点的梯度向量.方位角以及边缘方向(任一点的边缘与梯度向量正交) : θ = θm = arcta

  • python 贪心算法的实现

    贪心算法 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解. 贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关. 基本思路 思想 贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解.每一步只考虑一个数据,他的选取应该满足局部优化的条件.若

随机推荐