图像处理的边缘检测

2013-06-05 10:20:42 Augusdi 阅读数 96258

       不同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:缘和物体间的边界并不等同,边缘指的是图像中像素的值有突变的地方,而物体间的边界指的是现实场景中的存在于物体之间的边界。有可能有边缘的地方并非边界,也有可能边界的地方并无边缘,因为现实世界中的物体是三维的,而图像只具有二维信息,从三维到二维的投影成像不可避免的会丢失一部分信息;另外,成像过程中的光照和噪声也是不可避免的重要因素。正是因为这些原因,基于边缘的图像分割仍然是当前图像研究中的世界级难题,目前研究者正在试图在边缘提取中加入高层的语义信息。

        在实际的图像分割中,往往只用到一阶和二阶导数,虽然,原理上,可以用更高阶的导数,但是,因为噪声的影响,在纯粹二阶的导数操作中就会出现对噪声的敏感现象,三阶以上的导数信息往往失去了应用价值。二阶导数还可以说明灰度突变的类型。在有些情况下,如灰度变化均匀的图像,只利用一阶导数可能找不到边界,此时二阶导数就能提供很有用的信息。二阶导数对噪声也比较敏感,解决的方法是先对图像进行平滑滤波,消除部分噪声,再进行边缘检测。不过,利用二阶导数信息的算法是基于过零检测的,因此得到的边缘点数比较少,有利于后继的处理和识别工作。

      各种算子的存在就是对这种导数分割原理进行的实例化计算,是为了在计算过程中直接使用的一种计算单位。


1.Sobel算子

        其主要用于边缘检测,在技术上它是以离散型的差分算子,用来运算图像亮度函数的梯度的近似值, Sobel算子是典型的基于一阶导数的边缘检测算子,由于该算子中引入了类似局部平均的运算,因此对噪声具有平滑作用,能很好的消除噪声的影响。Sobel算子对于象素的位置的影响做了加权,与Prewitt算子、Roberts算子相比因此效果更好。

       Sobel算子包含两组3x3的矩阵,分别为横向及纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。实际使用中,常用如下两个模板来检测图像边缘。

                       

检测水平边沿 横向模板 :           检测垂直平边沿 纵向模板:

图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小。

                                                                             

然后可用以下公式计算梯度方向。

                                                                           


在以上例子中,如果以上的角度Θ等于零,即代表图像该处拥有纵向边缘,左方较右方暗。

缺点是Sobel算子并没有将图像的主题与背景严格地区分开来,换言之就是Sobel算子并没有基于图像灰度进行处理,由于Sobel算子并没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。


2. Isotropic Sobel算子

        Sobel算子另一种形式是(Isotropic Sobel)算子,加权平均算子,权值反比于邻点与中心点的距离,当沿不同方向检测边缘时梯度幅度一致,就是通常所说的各向同性Sobel(Isotropic Sobel)算子。模板也有两个,一个是检测水平边沿的 ,另一个是检测垂直平边沿的 。各向同性Sobel算子和普通Sobel算子相比,它的位置加权系数更为准确,在检测不同方向的边沿时梯度的幅度一致。


3. Roberts算子

罗伯茨算子、Roberts算子是一种最简单的算子,是一种利用局部差分算子寻找边缘的算子,他采用对角线方向相邻两象素之差近似梯度幅值检测边缘。检测垂直边缘的效果好于斜向边缘,定位精度高,对噪声敏感,无法抑制噪声的影响。1963年,Roberts提出了这种寻找边缘的算子。
Roberts边缘算子是一个2x2的模板,采用的是对角方向相邻的两个像素之差。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。适用于边缘明显且噪声较少的图像分割。Roberts边缘检测算子是一种利用局部差分算子寻找边缘的算子,Robert算子图像处理后结果边缘不是很平滑。经分析,由于Robert算子通常会在图像边缘附近的区域内产生较宽的响应,故采用上述算子检测的边缘图像常需做细化处理,边缘定位的精度不是很高。


4. Prewitt算子

        Prewitt算子是一种一阶微分算子的边缘检测,利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用 。其原理是在图像空间利用两个方向模板与图像进行邻域卷积来完成的,这两个方向模板一个检测水平边缘,一个检测垂直边缘。

 对数字图像f(x,y),Prewitt算子的定义如下:

G(i)=|[f(i-1,j-1)+f(i-1,j)+f(i-1,j+1)]-[f(i+1,j-1)+f(i+1,j)+f(i+1,j+1)]|
G(j)=|[f(i-1,j+1)+f(i,j+1)+f(i+1,j+1)]-[f(i-1,j-1)+f(i,j-1)+f(i+1,j-1)]|
则 P(i,j)=max[G(i),G(j)]或 P(i,j)=G(i)+G(j)
经典Prewitt算子认为:凡灰度新值大于或等于阈值的像素点都是边缘点。即选择适当的阈值T,若P(i,j)≥T,则(i,j)为边缘点,P(i,j)为边缘图像。这种判定是欠合理的,会造成边缘点的误判,因为许多噪声点的灰度值也很大,而且对于幅值较小的边缘点,其边缘反而丢失了。

Prewitt算子对噪声有抑制作用,抑制噪声的原理是通过像素平均,但是像素平均相当于对图像的低通滤波,所以Prewitt算子对边缘的定位不如Roberts算子。

因为平均能减少或消除噪声,Prewitt梯度算子法就是先求平均,再求差分来求梯度。水平和垂直梯度模板分别为:

检测水平边沿 横向模板                 检测垂直平边沿 纵向模板:

该算子与Sobel算子类似,只是权值有所变化,但两者实现起来功能还是有差距的,据经验得知Sobel要比Prewitt更能准确检测图像边缘。


5.Laplacian算子

         Laplace算子是一种各向同性算子,二阶微分算子,在只关心边缘的位置而不考虑其周围的象素灰度差值时比较合适。Laplace算子对孤立象素的响应要比对边缘或线的响应要更强烈,因此只适用于无噪声图象。存在噪声情况下,使用Laplacian算子检测边缘之前需要先进行低通滤波。所以,通常的分割算法都是把Laplacian算子和平滑算子结合起来生成一个新的模板。

拉普拉斯算子也是最简单的各向同性微分算子,具有旋转不变性。一个二维图像函数的拉普拉斯变换是各向同性的二阶导数,定义

                                                                           

了更适合于数字图像处理,将拉式算子表示为离散形式:

另外,拉普拉斯算子还可以表示成模板的形式,如下图所示,


离散拉普拉斯算子的模板:, 其扩展模板: 。


      拉式算子用来改善因扩散效应的模糊特别有效,因为它符合降制模型。扩散效应是成像过程中经常发生的现象。

      Laplacian算子一般不以其原始形式用于边缘检测,因为其作为一个二阶导数,Laplacian算子对噪声具有无法接受的敏感性;同时其幅值产生算边缘,这是复杂的分割不希望有的结果;最后Laplacian算子不能检测边缘的方向;所以Laplacian在分割中所起的作用包括:(1)利用它的零交叉性质进行边缘定位;(2)确定一个像素是在一条边缘暗的一面还是亮的一面;一般使用的是高斯型拉普拉斯算子(Laplacian of a Gaussian,LoG),由于二阶导数是线性运算,利用LoG卷积一幅图像与首先使用高斯型平滑函数卷积改图像,然后计算所得结果的拉普拉斯是一样的。所以在LoG公式中使用高斯函数的目的就是对图像进行平滑处理,使用Laplacian算子的目的是提供一幅用零交叉确定边缘位置的图像;图像的平滑处理减少了噪声的影响并且它的主要作用还是抵消由Laplacian算子的二阶导数引起的逐渐增加的噪声影响。

 


6.Canny算子

      该算子功能比前面几种都要好,但是它实现起来较为麻烦,Canny算子是一个具有滤波,增强,检测的多阶段的优化算子,在进行处理前,Canny算子先利用高斯平滑滤波器来平滑图像以除去噪声,Canny分割算法采用一阶偏导的有限差分来计算梯度幅值和方向,在处理过程中,Canny算子还将经过一个非极大值抑制的过程,最后Canny算子还采用两个阈值来连接边缘。

Canny边缘检测算法

step1: 用高斯滤波器平滑图象;

step2: 用一阶偏导的有限差分来计算梯度的幅值和方向;

step3: 对梯度幅值进行非极大值抑制

step4: 用双阈值算法检测和连接边缘

详解:http://www.cnblogs.com/cfantaisie/archive/2011/06/05/2073168.html


(1)图象边缘检测必须满足两个条件:一能有效地抑制噪声;二必须尽量精确确定边缘的位置。

(2)根据对信噪比与定位乘积进行测度,得到最优化逼近算子。这就是Canny边缘检测算子。

(3)类似与Marr(LoG)边缘检测方法,也属于先平滑后求导数的方法。


2018-12-20 20:55:06 HHH_ANS 阅读数 4590

1 - 引言

在图像识别中,如果可以将图像感兴趣的物体或区别分割出来,无疑可以增加我们图像识别的准确率,传统的数字图像处理中的分割方法多数基于灰度值的两个基本性质

  • 不连续性、
    以灰度突变为基础分割一副图像,比如图像的边缘
  • 相似性
    根据一组预定义的准则将一副图像分割为相似的区域。阈值处理、区域生长、区域分裂和区域聚合都是这类方法的例子。

2 - 点、线和边缘检测基础

虽然许多检测算法都被opencv封装成函数可以直接调用,但是理解其背后的理论依据可以更好地帮助我们理解和改进算法

2.1 背景知识

  1. 一阶导数通常在图像中产生较粗的边缘;
  2. 二阶导数对精细细线,如细线、孤立点和噪声有较强的响应;
  3. 二阶导数在灰度斜坡和灰度台阶过渡出会产生双边响应;
  4. 二阶导数的符号可以用于确定边缘的过渡是从亮到暗还是从暗到亮

用于计算图像中每个像素位置处的一阶导数和二阶导数可选择方法是使用空间滤波器
R=w1z1+w2z2++w9z9=k=19wkzkR = w_1z_1+w_2z_2+\dots +w_9z_9=\sum_{k=1}^9w_kz_k

在这里插入图片描述

2.1 - 孤立点的检测

点的检测以二阶导数为基础。这意味可以着使用拉普拉斯模板(详情见空间域滤波基础

如果在某点该处模板的响应的绝对值超过了一个指定的阈值,那么我们` 说在模板中心位置(x,y)处的该点已被检测到了。在输入图像中,这样的点被标注为1,而所有其他点则被标注为0,从而产生一副二值图像。

g(x,y)={1R(x,y)T0 其他 g(x,y)=\begin{cases} 1& | R(x,y)| \geq T\\ 0 & \text{ 其他 } \end{cases}

其中,g是输出图像,T是一个非负的阈值,R由上式给出。

2.2 - 线检测

复杂度更高的检测是线检测,对于线检测,可以预期二阶导数将导致更强的响应,并产生比一阶导数更细的线。这样对于线检测,我们也可以使用拉普拉斯模板,记住,二阶导数的双线效应必须做适当的处理。
在这里插入图片描述

2.4 边缘模型

边缘检测是基于灰度突变来分割图像最常用的方法。我们从介绍一些边缘建模的方法开始,然后讨论一些边缘检测的方法。
在这里插入图片描述

实际中,数字图像都存在被模糊且带有噪声的边缘,模糊程度主要取决于聚焦机理中的兼职,而噪声水平主要取决于成像系统的电子元件。在这种情况下,边缘被建模为一个更接近灰度斜坡的剖面。

在这里插入图片描述

并且我们可以得出结论:一阶导数的幅度可用于检测图像中的某个点是否存在一个边缘,二阶导数可以用于确定一个边缘像素位于该边缘的暗的一侧还是亮的一侧。

那么这是理想情况下的图片边缘,如果图片有噪声的话,其边缘函数则为

在这里插入图片描述

微弱的可见噪声对检测边缘所用的两个关键导数的严重影响的这一事实,是我们应记住的一个重要问题。特别地,在类似于我们刚刚讨论的水平的噪声很可能存在的应用中,使用导数之前的图像平滑处理是应该认真考虑的问题。

因此边缘检测的三个基本步骤

  1. 为降噪对图像进行平滑处理
  2. 边缘点的检测
  3. 边缘定位

2.4.1 - 基本的边缘检测算子

Roberts算子
Roberts算子以求对角像素之差为基础,该算子用于识别对角线方向的边缘:
gx=(z9z5)gy=(z8z6)g_x=(z_9-z_5)和g_y=(z_8-z_6)
在这里插入图片描述

Prewitt算子
Prewitt算子使用以z5z_5为中心的3x3领域对gxg_xgyg_y的近似如下式所示
gx=(z7+z8+z9)(z1+z2+z3)g_x=(z_7+z_8+z_9)-(z_1+z_2+z_3)
gy=(z3+z6+z9)(z1+z4+z7)g_y=(z_3+z_6+z_9)-(z_1+z_4+z_7)
模板如下图:
在这里插入图片描述

Sobel算子
Sobel算子使用以z5z_5为中心的3x3领域对gxg_xgyg_y的近似如下式所示:
gx=(z7+2z8+z9)(z1+2z2+z3)g_x=(z_7+2z_8+z_9)-(z_1+2z_2+z_3)
gy=(z3+2z6+z9)(z1+2z4+z7)g_y=(z_3+2z_6+z_9)-(z_1+2_z4+z_7)

Sobel模板能较好地抑制(平滑)噪声地特性使得它更为可取,因为在处理导数时噪声抑制是一个重要地问题。 在这里插入图片描述

检测对角边缘的Prewitt和Sobel模板

在这里插入图片描述

在这里插入图片描述

3 - 成熟先进的边缘检测技术

3.1 - Marr-Hildreth边缘检测器

① 概念背景:
最早的成功地尝试将更高级的分析结合到边缘检测处理之一应归功与Marr和Hildreth[1980]。

Marr和Hildreth证明了:

  1. 灰度边缘与图像尺寸无关,因此他们的检测要求使用不同尺寸的算子;
  2. 灰度的突然变化会在一阶导数中引起波峰或波谷,或在二阶导数中等效地引起零交叉

这些概念建议,用于边缘检测的算子应有两个显著的特点。

  • 第一个和最重要的特点是它应该是一个能计算图像中每一点处的一阶导数或二阶导数的数字近似的微分算子。
  • 第二个是它应能被“调整”以便在任何期望的尺寸上起作用
    因此,大的算子也可以用于检测模糊边缘,小的算子也可以用于检测锐度集中的精细细节。

② 高斯拉普拉斯(LoG):
Marr和Hildreth证明了:满足这些最令人满意的算子是滤波器2G\triangledown^2G,2\triangledown^2是拉普拉斯算子(2/x2+2/y2\partial^2/\partial x^2+\partial^2/\partial y^2),而G是标准差为σ\sigma的二维高斯函数
G(x,y)=ex2+y22σ2G(x,y)=e^{-\frac{x^2+y^2}{2\sigma^2}}
为求2G\triangledown^2G的表达式,我们执行如下微分:
2G(x,y)=2G(x,y)x2+2G(x,y)y2=x[xσ2ex2+y22σ2]+y[yσ2ex2+y22σ2]\triangledown^2G(x,y)=\frac{\partial^2G(x,y)}{\partial x^2}+\frac{\partial^2G(x,y)}{\partial y^2}=\frac{\partial}{\partial x}[\frac{-x}{\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}]+\frac{\partial}{\partial y}[\frac{-y}{\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}]

整理各项后给出如下最终表达式:
2G(x,y)=[x2+y22σ2σ4]ex2+y22σ2\triangledown^2G(x,y)=[\frac{x^2+y^2-2\sigma^2}{\sigma^4}]e^{-\frac{x^2+y^2}{2\sigma^2}}

该表达式成为高斯拉普拉斯(LoG)

在这里插入图片描述

③ Marr-Hildreth算法
Marr-Hildreth算法由LOG滤波器与一副输入图像f(x,y)卷积组成,即
g(x,y)=[2G(x,y)]f(x,y)g(x,y)=[\triangledown^2G(x,y)]\bigstar f(x,y)
然后找寻g(x,y)的零交叉来确定f(x,y)中边缘的位置。因为这些都是线性操作,故可以写为
KaTeX parse error: Expected 'EOF', got '\bigstarf' at position 30: …ledown^2[G(x,y)\̲b̲i̲g̲s̲t̲a̲r̲f̲(x,y)]

它指出我们可以先使用一个高斯滤波器来平滑图像,然后计算该结果的拉普拉斯。

Marr-Hildreth算法小结

  1. 用一个G(x,y)取样得到的nxn的高斯高斯低通滤波器对输入图像滤波。
  2. 计算由第一步得到的图像的拉普拉斯
  3. 找到步骤2所有图像的零交叉

零交叉是Marr-Hildreth边缘检测方法的关键特征,实现简单,并且通常能给出好的结果。

import numpy as np
import matplotlib.pyplot as plt
import cv2

def edgesMarrHildreth(img, sigma):
    """
        finds the edges using MarrHildreth edge detection method...
        :param im : input image
        :param sigma : sigma is the std-deviation and refers to the spread of gaussian
        :return:
        a binary edge image...
    """
    size = int(2 * (np.ceil(3 * sigma)) + 1)

    x, y = np.meshgrid(np.arange(-size / 2 + 1, size / 2 + 1), np.arange(-size / 2 + 1, size / 2 + 1))

    normal = 1 / (2.0 * np.pi * sigma ** 2)

    kernel = ((x ** 2 + y ** 2 - (2.0 * sigma ** 2)) / sigma ** 4) * np.exp(
        -(x ** 2 + y ** 2) / (2.0 * sigma ** 2)) / normal  # LoG filter

    kern_size = kernel.shape[0]
    log = np.zeros_like(img, dtype=float)

    # applying filter
    for i in range(img.shape[0] - (kern_size - 1)):
        for j in range(img.shape[1] - (kern_size - 1)):
            window = img[i:i + kern_size, j:j + kern_size] * kernel
            log[i, j] = np.sum(window)

    log = log.astype(np.int64, copy=False)

    zero_crossing = np.zeros_like(log)

    # computing zero crossing
    for i in range(log.shape[0] - (kern_size - 1)):
        for j in range(log.shape[1] - (kern_size - 1)):
            if log[i][j] == 0:
                if (log[i][j - 1] < 0 and log[i][j + 1] > 0) or (log[i][j - 1] < 0 and log[i][j + 1] < 0) or (
                        log[i - 1][j] < 0 and log[i + 1][j] > 0) or (log[i - 1][j] > 0 and log[i + 1][j] < 0):
                    zero_crossing[i][j] = 255
            if log[i][j] < 0:
                if (log[i][j - 1] > 0) or (log[i][j + 1] > 0) or (log[i - 1][j] > 0) or (log[i + 1][j] > 0):
                    zero_crossing[i][j] = 255

                # plotting images
    fig = plt.figure()
    a = fig.add_subplot(1, 2, 1)
    imgplot = plt.imshow(log, cmap='gray')
    a.set_title('Laplacian of Gaussian')
    a = fig.add_subplot(1, 2, 2)
    imgplot = plt.imshow(zero_crossing, cmap='gray')
    string = 'Zero Crossing sigma = '
    string += (str(sigma))
    a.set_title(string)
    plt.show()

    return zero_crossing

img = cv2.imread('images/17.jpg',0)
img = edgesMarrHildreth(img,4)

在这里插入图片描述

(可以看到这个算法检测出了图像的边缘,但是有很多地方都是不连续的,那么之后我们会介绍如何检测边界和边缘连接)

3.2 - 坎尼边缘检测(Canny)

虽然其算法更为复杂,但是Canny边缘检测是迄今为止讨论过的边缘检测器中最为优秀的,Canny基于三个基本目标:

  1. 低错误率。所有边缘都应被找到,并且应该没有伪相应,也就是检测到的边缘必须尽可能是真是的边缘
  2. 边缘点应被很好的定位。已定位边缘必须尽可能接近真实边缘。也就是由检测器标记为边缘的点和真实边缘的中心之间的距离应该最小
  3. 单一的边缘点响应。对于真实的边缘点,检测器仅应返回一个点。也就是真是边缘周围的局部最大数应该是最小的。这意味着在仅存一个单一边缘点到额位置,检测器不应指出多个边缘像素。

Canny工作的本质是,从数学上表达前面的三个准则,并试图找到这些表达式的最佳解,通常这是很困难的,但是我们可以使用高斯近似得出最优解:首先使用一个环形二维高斯函数平滑图像,计算结果的梯度,然后使用梯度幅度和方向来估计每一点的边缘强度与方向

第一步
令f(x,y)表示输入图像,G(x,y)表示高斯函数:
G(x,y)=ex2+y22σ2G(x,y)=e^{-\frac{x^2+y^2}{2\sigma^2}}

我们用G和f卷积形成一幅平滑的图像fs(x,y)f_s(x,y)
fs(x,y)=G(x,y)f(x,y)f_s(x,y)=G(x,y)\bigstar f(x,y)

第二步
接下来计算结果的梯度幅度和方向:
M(x,y)=gx2+gy2M(x,y)=\sqrt {g_x^2+g_y^2}
α(x,y)=arctan[gygx]\alpha(x,y)=arctan[\frac{g_y}{g_x}]

第三步
细化边缘,使用非最大抑制:

  1. 寻找最接近α(x,y)\alpha(x,y)的方向dkd_k
  2. M(x,y)M(x,y)的值至少小于沿dkd_k的两个零邻居之一,零gN(x,y)=0g_N(x,y)=0(抑制);否则令gN(x,y)=M(x,y)g_N(x,y) = M(x,y),得到最大非抑制后的图像gN(x,y)g_N(x,y)

第四步
最后操作时对gN(x,y)g_N(x,y)进行阈值处理,以便减少伪边缘点,Canny算法使用两个阈值:低阈值TLT_L和高阈值THT_H(Canny建议高低阈值比为2:1或3:1)

gNH={gN(x,y)TH}g_{NH}=\left \{ g_N(x,y)\geq T_H\right\}
gNL={gN(x,y)TL}g_{NL}=\left \{ g_N(x,y)\geq T_L\right\}
gNH=gNL(x,y)gNH(x,y)g_{NH}= g_{NL}(x,y)-g_{NH}(x,y)

gNH(x,y)g_{NH}(x,y)gNL(x,y)g_{NL}(x,y)的非零像素可分别视为“强”和“弱”边缘像素。其中gNH(x,y)g_{NH}(x,y)为边缘点,gNL(x,y)g_{NL}(x,y)为候选点,对于候选点,如果与边缘点邻近,就标记为边缘点。

具体步骤如下:

  1. gNH(x,y)g_{NH}(x,y)中定位一下个未被访问的边缘像素p
  2. gNL(x,y)g_{NL}(x,y)中与p是8邻接的像素标记为有效边缘像素
  3. gNH(x,y)g_{NH}(x,y)中的所有非零像素已被访问,则跳到步骤4,否走返回步骤1
  4. gNL(x,y)g_{NL}(x,y)中未标记为有效边缘像素的所有像素置零
    在这一过程的末尾,将来自gNL(x,y)g_{NL}(x,y)的所有非零像素附近到gNH(x,y)g_{NH}(x,y),用Canny算子形成最终的输出图像。

那么我们来总结一下Canny算法
Canny算法步骤

  1. 用一个高斯滤波器平滑输入图像
  2. 计算梯度幅度图像和角度图像
  3. 对梯度幅度图像应用非最大抑制
  4. 用双阈值处理和连接分析来检测并连接边缘(这相对Marr-Hildreth优化了边缘的连接使得检测的边缘更加完整)

OpenCV将这一算法封装成了一个函数

def Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None): # real signature unknown; restored from __doc__
    """
    Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges
    .   @brief Finds edges in an image using the Canny algorithm @cite Canny86 .
    .   
    .   The function finds edges in the input image and marks them in the output map edges using the
    .   Canny algorithm. The smallest value between threshold1 and threshold2 is used for edge linking. The
    .   largest value is used to find initial segments of strong edges. See
    .   <http://en.wikipedia.org/wiki/Canny_edge_detector>
    .   
    .   @param image 8-bit input image.
    .   @param edges output edge map; single channels 8-bit image, which has the same size as image .
    .   @param threshold1 first threshold for the hysteresis procedure.
    .   @param threshold2 second threshold for the hysteresis procedure.
    .   @param apertureSize aperture size for the Sobel operator.
    .   @param L2gradient a flag, indicating whether a more accurate \f$L_2\f$ norm
    .   \f$=\sqrt{(dI/dx)^2 + (dI/dy)^2}\f$ should be used to calculate the image gradient magnitude (
    .   L2gradient=true ), or whether the default \f$L_1\f$ norm \f$=|dI/dx|+|dI/dy|\f$ is enough (
    .   L2gradient=false ).

必要参数:

  • 第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;
  • 第二个参数是阈值1;
  • 第三个参数是阈值2。

函数返回一副二值图,其中包含检测出的边缘。

使用
Canny函数的使用很简单,只需指定最大和最小阈值即可。如下:

# coding=utf-8
import cv2
import numpy as np

img = cv2.imread("images/17.jpg", 0)
cv2.imshow('img',img)
img = cv2.GaussianBlur(img, (3, 3), 0)
canny = cv2.Canny(img, 50, 150)

cv2.imshow('Canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

可以看到,Canny算法明显提取边缘的效果要优于Marr-Hildreth算法,对边缘的连接也做的更好。

2018-04-03 16:41:15 u012197749 阅读数 11343

一、边缘检测算子类别 

     常见边缘检测算子:Roberts 、Sobel 、Prewitt、Laplacian、Log/Marr、Canny、Kirsch、Nevitia

二、一阶微分算子:Roberts 、Sobel 、Prewitt

        Robert算子是第一个边缘检测算子,提出者Lawrence Roberts in 1963。

        Sobel边缘算子,当年作者并没有公开发表过论文,仅仅是在一次博士生课题讨论会(1968)上提出("A 3x3 Isotropic Gradient Operator for Image Processing"),后在1973年出版的一本专著("Pattern Classification and Scene Analysis")的脚注里作为注释出现和公开的。提出者Irwin Sobel。

        Prewitt算子来自J.M.S. Prewitt "Object Enhancement and Extraction" in "Picture processing and Psychopictorics", Academic Press,1970。

        我们看这三种边缘检测算子模板及写成差分的形式



 Roberts算子



Sobel算子



Prewitt算子

图 4 一阶微分算子

       如何计算边缘幅值与方向?以Sobel算子为例。3*3 Sobel两个方向的算子在图像上滑动,模板与其覆盖的图像3*3区域9个像素进行卷积,求和后得到此方向的边缘检测幅值。





f(x,y)为图像,Gx和Gy分别是水平和竖直方向算子的卷积结果,G则是最终得到的边缘幅值,θ值则是边缘方向。当然G的计算有时简化为

  

或者

 

求幅值时是有多种选择的,一般根据具体应用选择用水平还是竖直或是两个方向同时检测。

        另外,需要说明的是,Sobel算子还有一种变种,是各向同性Sobel算子,其模板为



图 5 各向同性Sobel算子

Sobel各向同性算子的权值比普通Sobel算子的权值更准确。为什么?模板的权值是离中心位置越远则权值(看绝对值)影响越小,如上图,把模板看成是9个小正方形,小正方形边长为1,则虚线三角形的斜边长为,下直角边长为1,则如果(0,0)位置权值绝对值大小为1,则按照距离关系,位置(1,0)处的权值绝对值大小应该为才是准确的。

三、 二阶微分算子:Laplacian、Log/Marr

        拉普拉斯算子来自拉普拉斯变换,而Log算子又称Marr算子,由David Courtnay Marr和Ellen Hildreth(1980)共同提出,计算神经学创始人Marr在1980年正式发表论文时,因换白血病早逝,后面设立Marr奖以此纪念其贡献,现在每两年的ICCV(与ECCV,CVPR并称计算机视觉三大顶级会议)会评出一个Marr奖。这两种算子模板如下:


Laplacian算子(两种模板)


Log算子

图 6 二阶微分算子


拉普拉斯算子数学公式是

写成差分形式为


        Log边缘检测则是先进行高斯滤波再进行拉普拉斯算子检测,然后找过零点来确定边缘位置,很多时候我们只是知道Log 5*5模板如上图所示,但是具体是怎么得到的?下面进行推导。

二维高斯公式是


按拉普拉斯算子公式求x,y方向的二阶偏导后为


这里x,y不能看成模板位置,应看成是模板其他位置到中心位置的距离。那么写成


这里x0,y0就是模板中心位置,x,y是模板其他位置,对于5*5模板,则x0=2,y0 = 2,那对于模板中(0,0)位置的权值,即把x= 0,y= 0,x0= 2,y0 = 2带入上式,另= 1,得到约等于0.0175,这样得到


通过取整变符号,且模板总和为0,得到图6所示的模板。

        另外,这里模板大小是如何取的?通常高斯分布中,在(-3,3)的范围内就覆盖了绝大部分区域,所以模板大小一般取dim = 1 + 6(在SIFT特征中,其中的高斯模糊也是这样取),dim如果为小数,则取不小于dim的最小整数,当然实际使用时没有这么严格,如上面我们取=1时,模板大小取5*5。那同一个尺寸的模板中的权值调整就是的变化得到的,变化到一定程度,模板尺寸大小改变(这个是个人理解,欢迎拍砖羡慕)。

四、非微分边缘检测算子:Canny

       Canny边缘检测大家应该很熟悉,这里列出步骤,并给出一个详细介绍的链接Canny算子

    1.      彩色图像转换为灰度图像
    2.      对图像进行高斯模糊
    3.      计算图像梯度,根据梯度计算图像边缘幅值与角度(这里其实用到了微分边缘检测算子来计算梯度幅值方向)
    4.      非最大信号压制处理(边缘细化)
    5.      双阈值边缘连接处理

    6.      二值化图像输出结果

五、方向算子Kirsch(8个3*3模板),Nevitia (12个5*5模板)

        这两个算子是利用多个方向的子模板进行分别计算,最后取幅值最大的那个为最终边缘幅值,方向即最大幅值对应的那个方向。

六、各边缘检测算子对比


参考文献:

1、http://blog.csdn.net/xiaojiegege123456/article/details/7714863

2、http://blog.csdn.net/yanmy2012/article/details/8110316

3、http://blog.csdn.net/langb2014/article/details/45667921

4、https://blog.csdn.net/tigerda/article/details/61192943

2011-11-01 20:36:10 xiajun07061225 阅读数 55519

Canny边缘检测算法一直是边缘检测的经典算法。下面详细介绍Canny边缘检测算法的原理以及编程实现。

Canny边缘检测基本原理
(1)图象边缘检测必须满足两个条件:一能有效地抑制噪声;二必须尽量精确确定边缘的位置。
 (2)根据对信噪比与定位乘积进行测度,得到最优化逼近算子。这就是Canny边缘检测算子。
 (3)类似与Marr(LoG)边缘检测方法,也属于先平滑后求导数的方法。

Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:
(1)好的检测 - 算法能够尽可能多地标识出图像中的实际边缘。
(2)好的定位 - 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近。
(3)最小响应 - 图像中的边缘只能标识一次,并且可能存在的图像雜訊不应标识为边缘。

Canny边缘检测算法的步骤

(1)去噪

任何边缘检测算法都不可能在未经处理的原始数据上很好地處理,所以第一步是对原始数据与高斯 mask 作卷积,得到的图像与原始图像相比有些轻微的模糊(blurred)。这样,单独的一个像素雜訊在经过高斯平滑的图像上变得几乎没有影响。

(2)用一阶偏导的有限差分来计算梯度的幅值和方向。

(3)对梯度幅值进行非极大值抑制。

仅仅得到全局的梯度并不足以确定边缘,因此为确定边缘,必须保留局部梯度最大的点,而抑制非极大值。(non-maxima suppression,NMS)
解决方法:利用梯度的方向。


四个扇区的标号为0到3,对应3*3邻域的四种可能组合。在每一点上,邻域的中心象素M与沿着梯度线的两个象素相比。如果M的梯度值不比沿梯度线的两个相邻象素梯度值大,则令M=0。

(4)用双阈值算法检测和连接边缘。

减少假边缘段数量的典型方法是对N[i,j]使用一个阈值。将低于阈值的所有值赋零值。但问题是如何选取阈值?
 解决方法:双阈值算法。双阈值算法对非极大值抑制图象作用两个阈值τ1和τ2,且2τ1≈τ2,从而可以得到两个阈值边缘图象N1[i,j]和N2[i,j]。由于N2[i,j]使用高阈值得到,因而含有很少的假边缘,但有间断(不闭合)。双阈值法要在N2[i,j]中把边缘连接成轮廓,当到达轮廓的端点时,该算法就在N1[i,j]的8邻点位置寻找可以连接到轮廓上的边缘,这样,算法不断地在N1[i,j]中收集边缘,直到将N2[i,j]连接起来为止。

在连接边缘的时候,用数组模拟队列的实现。以进行8-连通域搜索。

更详细的资料请参考维基百科:http://zh.wikipedia.org/wiki/Canny%E7%AE%97%E5%AD%90

下面是我编程实现的Canny边缘检测代码,如有错误,请大家包涵、指正:

I = imread('rice.png');
I = double(I);
[height,width] = size(I);
J = I;

conv = zeros(5,5);%高斯卷积核
sigma = 1;%方差
sigma_2 = sigma * sigma;%临时变量
sum = 0;
for i = 1:5
    for j = 1:5
        conv(i,j) = exp((-(i - 3) * (i - 3) - (j - 3) * (j - 3)) / (2 * sigma_2)) / (2 * 3.14 * sigma_2);%高斯公式
        sum = sum + conv(i,j);
    end
end
conv = conv./sum;%标准化

%对图像实施高斯滤波
for i = 1:height
    for j = 1:width
        sum = 0;%临时变量
        for k = 1:5
            for m = 1:5
                if (i - 3 + k) > 0 && (i - 3 + k) <= height && (j - 3 + m) > 0 && (j - 3 + m) < width
                    sum = sum + conv(k,m) * I(i - 3 + k,j - 3 + m);
                end
            end
        end
        J(i,j) = sum;
    end
end
figure,imshow(J,[])
title('高斯滤波后的结果')
%求梯度
dx = zeros(height,width);%x方向梯度
dy = zeros(height,width);%y方向梯度
d = zeros(height,width);
for i = 1:height - 1
    for j = 1:width - 1
        dx(i,j) = J(i,j + 1) - J(i,j);
        dy(i,j) = J(i + 1,j) - J(i,j);
        d(i,j) = sqrt(dx(i,j) * dx(i,j) + dy(i,j) * dy(i,j));
    end
end
figure,imshow(d,[])
title('求梯度后的结果')

%局部非极大值抑制
K = d;%记录进行非极大值抑制后的梯度
%设置图像边缘为不可能的边缘点
for j = 1:width
    K(1,j) = 0;
end
for j = 1:width
    K(height,j) = 0;
end
for i = 2:width - 1
    K(i,1) = 0;
end
for i = 2:width - 1
    K(i,width) = 0;
end

for i = 2:height - 1
    for j = 2:width - 1
        %当前像素点的梯度值为0,则一定不是边缘点
        if d(i,j) == 0
            K(i,j) = 0;
        else
            gradX = dx(i,j);%当前点x方向导数
            gradY = dy(i,j);%当前点y方向导数
            gradTemp = d(i,j);%当前点梯度
            %如果Y方向幅度值较大
            if abs(gradY) > abs(gradX)
                weight = abs(gradX) / abs(gradY);%权重
                grad2 = d(i - 1,j);
                grad4 = d(i + 1,j);
                %如果x、y方向导数符号相同
                %像素点位置关系
                %g1 g2
                %   C
                %   g4 g3
                if gradX * gradY > 0
                    grad1 = d(i - 1,j - 1);
                    grad3 = d(i + 1,j + 1);
                else
                    %如果x、y方向导数符号反
                    %像素点位置关系
                    %   g2 g1
                    %   C
                    %g3 g4
                    grad1 = d(i - 1,j + 1);
                    grad3 = d(i + 1,j - 1);
                end
            %如果X方向幅度值较大
            else
                weight = abs(gradY) / abs(gradX);%权重
                grad2 = d(i,j - 1);
                grad4 = d(i,j + 1);
                %如果x、y方向导数符号相同
                %像素点位置关系
                %g3
                %g4 C g2
                %     g1
                if gradX * gradY > 0
                    grad1 = d(i + 1,j + 1);
                    grad3 = d(i - 1,j - 1);
                else
                    %如果x、y方向导数符号反
                    %像素点位置关系
                    %     g1
                    %g4 C g2
                    %g3
                    grad1 = d(i - 1,j + 1);
                    grad3 = d(i + 1,j - 1);
                end
            end
            %利用grad1-grad4对梯度进行插值
            gradTemp1 = weight * grad1 + (1 - weight) * grad2;
            gradTemp2 = weight * grad3 + (1 - weight) * grad4;
            %当前像素的梯度是局部的最大值,可能是边缘点
            if gradTemp >= gradTemp1 && gradTemp >= gradTemp2
                K(i,j) = gradTemp;
            else
                %不可能是边缘点
                K(i,j) = 0;
            end
        end
    end
end
figure,imshow(K,[])
title('非极大值抑制后的结果')

%定义双阈值:EP_MIN、EP_MAX,且EP_MAX = 2 * EP_MIN
EP_MIN = 12;
EP_MAX = EP_MIN * 2;
EdgeLarge = zeros(height,width);%记录真边缘
EdgeBetween = zeros(height,width);%记录可能的边缘点
for i = 1:height
    for j = 1:width
        if K(i,j) >= EP_MAX%小于小阈值,不可能为边缘点
            EdgeLarge(i,j) = K(i,j);
        else if K(i,j) >= EP_MIN
                EdgeBetween(i,j) = K(i,j);
            end
        end
    end
end
%把EdgeLarge的边缘连成连续的轮廓
MAXSIZE = 999999;
Queue = zeros(MAXSIZE,2);%用数组模拟队列
front = 1;%队头
rear = 1;%队尾
edge = zeros(height,width);
for i = 1:height
    for j = 1:width
        if EdgeLarge(i,j) > 0
            %强点入队
            Queue(rear,1) = i;
            Queue(rear,2) = j;
            rear = rear + 1;
            edge(i,j) = EdgeLarge(i,j);
            EdgeLarge(i,j) = 0;%避免重复计算
        end
        while front ~= rear%队不空
            %队头出队
            temp_i = Queue(front,1);
            temp_j = Queue(front,2);
            front = front + 1;
            %8-连通域寻找可能的边缘点
            %左上方
            if EdgeBetween(temp_i - 1,temp_j - 1) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i - 1,temp_j - 1) = K(temp_i - 1,temp_j - 1);
                EdgeBetween(temp_i - 1,temp_j - 1) = 0;%避免重复计算
                %入队
                Queue(rear,1) = temp_i - 1;
                Queue(rear,2) = temp_j - 1;
                rear = rear + 1;
            end
            %正上方
            if EdgeBetween(temp_i - 1,temp_j) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i - 1,temp_j) = K(temp_i - 1,temp_j);
                EdgeBetween(temp_i - 1,temp_j) = 0;
                %入队
                Queue(rear,1) = temp_i - 1;
                Queue(rear,2) = temp_j;
                rear = rear + 1;
            end
            %右上方
            if EdgeBetween(temp_i - 1,temp_j + 1) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i - 1,temp_j + 1) = K(temp_i - 1,temp_j + 1);
                EdgeBetween(temp_i - 1,temp_j + 1) = 0;
                %入队
                Queue(rear,1) = temp_i - 1;
                Queue(rear,2) = temp_j + 1;
                rear = rear + 1;
            end
            %正左方
            if EdgeBetween(temp_i,temp_j - 1) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i,temp_j - 1) = K(temp_i,temp_j - 1);
                EdgeBetween(temp_i,temp_j - 1) = 0;
                %入队
                Queue(rear,1) = temp_i;
                Queue(rear,2) = temp_j - 1;
                rear = rear + 1;
            end
            %正右方
            if EdgeBetween(temp_i,temp_j + 1) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i,temp_j + 1) = K(temp_i,temp_j + 1);
                EdgeBetween(temp_i,temp_j + 1) = 0;
                %入队
                Queue(rear,1) = temp_i;
                Queue(rear,2) = temp_j + 1;
                rear = rear + 1;
            end
            %左下方
            if EdgeBetween(temp_i + 1,temp_j - 1) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i + 1,temp_j - 1) = K(temp_i + 1,temp_j - 1);
                EdgeBetween(temp_i + 1,temp_j - 1) = 0;
                %入队
                Queue(rear,1) = temp_i + 1;
                Queue(rear,2) = temp_j - 1;
                rear = rear + 1;
            end
            %正下方
            if EdgeBetween(temp_i + 1,temp_j) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i + 1,temp_j) = K(temp_i + 1,temp_j);
                EdgeBetween(temp_i + 1,temp_j) = 0;
                %入队
                Queue(rear,1) = temp_i + 1;
                Queue(rear,2) = temp_j;
                rear = rear + 1;
            end
            %右下方
            if EdgeBetween(temp_i + 1,temp_j + 1) > 0%把在强点周围的弱点变为强点
                EdgeLarge(temp_i + 1,temp_j + 1) = K(temp_i + 1,temp_j + 1);
                EdgeBetween(temp_i + 1,temp_j + 1) = 0;
                %入队
                Queue(rear,1) = temp_i + 1;
                Queue(rear,2) = temp_j + 1;
                rear = rear + 1;
            end
        end
        %下面2行用于观察程序运行的状况
        i
        j
    end
end

figure,imshow(edge,[])
title('双阈值后的结果')

对图片rice.png进行处理后的结果如下:



2018-04-26 22:56:53 qq_31804159 阅读数 11198

我的程序效果:

 

边缘检测算法是图像处理中最为基本的问题。其目的是标志图像出亮度变化明显的点,从而反映出图像中重要变化。

 

先介绍一下Sobel算子

Sobel 算子是像素图像边缘检测中最重要的算子之一,该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如下图,Gx和Gy分别是在横向及纵向的灰度偏导的近似值。(:对于一个彩色图要先把它转换为灰度图)

关于卷积可以看一下这个博客:https://blog.csdn.net/chaipp0607/article/details/72236892?locationNum=9&fps=1

 

对于每一个点我们可以获得两个方向的梯度,我们可以通过下面这个公式算出梯度的估计值。

我们定义一个阈值Gmax(这里定义Gmax = 150),如果G比Gmax大可以认为该点是一个边界值.则设置这个点为白色否则该店为黑色。这样我们就得到了通过边缘检测的图像。

 

代码实现:

1.彩色图转为灰度图


QImage LeadToGrey(const QImage &source)
{

    int w = source.width();
    int h = source.height();
    QImage gray(w,h,QImage::Format_RGB32);

    for( int i = 0; i< h; i++){

        for(int j = 0; j < w; j++){
            QRgb pixel = source.pixel(j,i);

            int grey = qGray(pixel);
            QRgb graypixel = qRgb(grey,grey,grey);
            gray.setPixel(j,i,graypixel);

        }
    }
    return gray;
}


2.边缘检测代码:


Image LeadToEdge(QImage source)
{
    int w = source.width();
    int h = source.height();

    QImage Edge(w,h,QImage::Format_RGB32);

    for( int i = 0; i< h; i++){
   //卷积操作
        for(int j = 0; j < w; j++){
            double Gx =  (-1)* QColor(source.pixel(getIndex(j-1,w),getIndex(i-1,h))).red()
                        +(-2)*QColor(source.pixel(getIndex(j,w),getIndex(i-1,h))).red()
                        +(-1)*QColor(source.pixel(getIndex(j+1,w),getIndex(i-1,h))).red()
                        +QColor(source.pixel(getIndex(j-1,w),getIndex(i+1,h))).red()
                        +2*QColor(source.pixel(getIndex(j,w),getIndex(i+1,h))).red()
                        +QColor(source.pixel(getIndex(j+1,w),getIndex(i+1,h))).red();

            double Gy =  QColor(source.pixel(getIndex(j-1,w),getIndex(i-1,h))).red()
                    +(2)*QColor(source.pixel(getIndex(j-1,w),getIndex(i,h))).red()
                    +(1)*QColor(source.pixel(getIndex(j-1,w),getIndex(i+1,h))).red()
                    +(-1)*QColor(source.pixel(getIndex(j+1,w),getIndex(i-1,h))).red()
                    +(-2)*QColor(source.pixel(getIndex(j+1,w),getIndex(i,h))).red()
                    +(-1)*QColor(source.pixel(getIndex(j+1,w),getIndex(i+1,h))).red();

           double G = sqrt(Gx*Gx+Gy*Gy);

            QRgb pixel;
            if(G>Gmax)
              pixel = qRgb(255,255,255);
            else
              pixel = qRgb(0,0,0);
            Edge.setPixel(j,i,pixel);
        }
    }
    return Edge;
}