Python-Opencv中边缘检测中Canny算法

Python-Opencv中边缘检测中Canny算法

边缘检测

边缘检测的目的是在保留原有图像属性的情况下,显著减少图像的数据规模。目前有多种算法可以进行边缘检测,虽然Canny算法年代久远,但可以说它是边缘检测的一种标准算法,而且仍在研究中广泛使用。

Canny算法
Canny算法是边缘检测中的一种算法,对边缘算法的理解,以下内容我觉得可以很好理解,以下内容转载于一篇博客,博客地址为 https://www.cnblogs.com/techyan1990/p/7291771.html

1.Canny边缘检测算法的发展历史

Canny边缘检测于1986年由JOHN CANNY首次在论文《A Computational Approach to Edge Detection》中提出,就此拉开了Canny边缘检测算法的序幕。

Canny边缘检测是从不同视觉对象中提取有用的结构信息并大大减少要处理的数据量的一种技术,目前已广泛应用于各种计算机视觉系统。Canny发现,在不同视觉系统上对边缘检测的要求较为类似,因此,可以实现一种具有广泛应用意义的边缘检测技术。边缘检测的一般标准包括:

  1. 以低的错误率检测边缘,也即意味着需要尽可能准确的捕获图像中尽可能多的边缘。

  2. 检测到的边缘应精确定位在真实边缘的中心。

  3. 图像中给定的边缘应只被标记一次,并且在可能的情况下,图像的噪声不应产生假的边缘。

为了满足这些要求,Canny使用了变分法。Canny检测器中的最优函数使用四个指数项的和来描述,它可以由高斯函数的一阶导数来近似。

在目前常用的边缘检测方法中,Canny边缘检测算法是具有严格定义的,可以提供良好可靠检测的方法之一。由于它具有满足边缘检测的三个标准和实现过程简单的优势,成为边缘检测最流行的算法之一。

2.Canny边缘检测算法的处理流程

Canny边缘检测算法可以分为以下5个步骤:

  1. 使用高斯滤波器,以平滑图像,滤除噪声。

  2. 计算图像中每个像素点的梯度强度和方向。

  3. 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。

  4. 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。

  5. 通过抑制孤立的弱边缘最终完成边缘检测。

下面详细介绍每一步的实现思路。

2.1 高斯平滑滤波

为了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。大小为(2k+1)x(2k+1)的高斯滤波器核的生成方程式由下式给出:
Python-Opencv中边缘检测中Canny算法
下面是一个sigma = 1.4,尺寸为3x3的高斯卷积核的例子(需要注意归一化):
Python-Opencv中边缘检测中Canny算法
若图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的亮度值为:
Python-Opencv中边缘检测中Canny算法
其中*为卷积符号,sum表示矩阵中所有元素相加求和。
重要的是需要理解,高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的trade off。

2.2 计算梯度强度和方向

图像中的边缘可以指向各个方向,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。边缘检测的算子(如Roberts,Prewitt,Sobel等)返回水平Gx和垂直Gy方向的一阶导数值,由此便可以确定像素点的梯度G和方向theta 。
Python-Opencv中边缘检测中Canny算法
其中G为梯度强度, theta表示梯度方向,arctan为反正切函数。下面以Sobel算子为例讲述如何计算梯度强度和方向。

x和y方向的Sobel算子分别为:
Python-Opencv中边缘检测中Canny算法
其中Sx表示x方向的Sobel算子,用于检测y方向的边缘; Sy表示y方向的Sobel算子,用于检测x方向的边缘(边缘方向和梯度方向垂直)。在直角坐标系中,Sobel算子的方向如下图所示。
Python-Opencv中边缘检测中Canny算法
图2-1 Sobel算子的方向

若图像中一个3x3的窗口为A,要计算梯度的像素点为e,则和Sobel算子进行卷积之后,像素点e在x和y方向的梯度值分别为:
Python-Opencv中边缘检测中Canny算法
Python-Opencv中边缘检测中Canny算法

其中*为卷积符号,sum表示矩阵中所有元素相加求和。根据上面公式便可以计算出像素点e的梯度和方向。

2.3 非极大值抑制

非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:

  1. 将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。

  2. 如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。

通常为了更加精确的计算,在跨越梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度,现举例如下:

Python-Opencv中边缘检测中Canny算法
          图2-2 梯度方向分割

如图3-2所示,将梯度分为8个方向,分别为E、NE、N、NW、W、SW、S、SE,其中0代表0-45°,1代表45°-90°,2代表-90–45°,3代表-45°-0°。像素点P的梯度方向为theta,则像素点P1和P2的梯度线性插值为:
Python-Opencv中边缘检测中Canny算法
因此非极大值抑制的伪代码描写如下:
Python-Opencv中边缘检测中Canny算法
需要注意的是,如何标志方向并不重要,重要的是梯度方向的计算要和梯度算子的选取保持一致。

2.4 双阈值检测

在施加非极大值抑制之后,剩余的像素可以更准确地表示图像中的实际边缘。然而,仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。

双阈值检测的伪代码描写如下:
Python-Opencv中边缘检测中Canny算法
2.5 抑制孤立低阈值点

到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。为了获得准确的结果,应该抑制由后者引起的弱边缘。通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘点就可以保留为真实的边缘。

抑制孤立边缘点的伪代码描述如下:
Python-Opencv中边缘检测中Canny算法
代码实现

import cv2 as cv


def edge_demo(image):
    blurred = cv.GaussianBlur(image, (3,3), 0)
    gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
    # 求X方向上的梯度
    grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
    # 求y方向上的梯度
    grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
    # 将梯度值转化到8位上来
    x_grad = cv.convertScaleAbs(grad_x)
    y_grad = cv.convertScaleAbs(grad_y)
    # 将两个梯度组合起来
    src1 = cv.addWeighted(x_grad, 0.5, y_grad, 0.5, 0)
    # 组合梯度用canny算法,其中50和100为阈值 
    edge = cv.Canny(src1, 50, 100)
    cv.imshow("Canny_edge_1", edge)
    edge1 = cv.Canny(grad_x, grad_y, 10, 100)
    cv.imshow("Canny_edge_2", edge1)
    # 用边缘做掩模,进行bitwise_and位运算
    edge2 = cv.bitwise_and(image, image, mask=edge1)
    cv.imshow("bitwise_and", edge2)


src = cv.imread("girl.jpg")
cv.imshow("girl", src)
edge_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()

Python-Opencv中边缘检测中Canny算法

初学Opencv,如有错误地方和改进地方,真诚地邀请您提出来,谢谢!

本文结束…