《OpenCV 轻松入门 面向Python》 学习笔记
Canny边缘检测基础
函数原型:
edges = cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
参数:
- edges:计算得到的边缘图像
- image:原始图像
- threshold1:处理过程中的第一个阈值(minVal)
- threshold2:处理过程中的第 二个阈值(maxVal)
- apertureSize:Sobel 算子的孔径大小
- L2gradient:计算图像梯度幅度的标识。 默认值是False,使用L1范数进行计算(直接将两个方向导数的绝对值相加);如果为True,使用更精确的L2范数进行计算(两个方向的导数的平方和再开方)。
Canny 边缘检测分为以下几个步骤:
- 去噪。噪声会影响边缘检测的准确性,因此首先要将噪声过滤掉。
- 计算梯度的幅度与方向。
- 非极大值抑制。适当的让边缘“变瘦”
- 确定边缘。 使用双阈值算法确定最终的边缘信息
1. 去噪 (应用高斯滤波去除噪声)
在图像中,一般会存在一些纹理较弱的非边缘区域。这些纹理较弱的非边缘就是噪声,为了避免噪声的干扰,避免检测到错误的边缘信息,我们采用高斯滤波去除图像中的噪声。
2. 计算梯度 (根据梯度的方向)
在计算梯度时,边缘检测算子会返回水平方向的梯度 G x G_x Gx 和 垂直方向的梯度 G y G_y Gy。在这里,我们不仅关注梯度的大小,还关注梯度的方向。
-
梯度幅度:
- 若参数 L2gradient=False,采用L1范数计算梯度幅度: G = ∣ G x ∣ + ∣ G y ∣ G = |G_x| + |G_y| G=∣Gx∣+∣Gy∣
- 若参数 L2gradient= True,采用L2范数计算梯度幅度: G = G x 2 + G y 2 G = \sqrt{G_x^2 + G_y^2} G=Gx2+Gy2
-
梯度方向: θ = a t a n 2 ( G x , G y ) \theta =atan2(G_x, G_y) θ=atan2(Gx,Gy),得到的梯度角度就近取值为:上,下,左,右,左上,左下,右上,右下 8个方向。
梯度的方向与边缘的方向是垂直的。
3. 非极大值抑制
在获取了梯度的幅度与方向后,采用非极大值抑制的方法,遍历图中的像素点,去除所有非边缘的点。
在具体实现过程中,逐一遍历像素点,判断该像素点 是否是周围(在3x3的邻域范围内,与沿着其对应的梯度方向的两个像素相比) 的最大值:
- 如果该点是正/负梯度方向上的局部最大值,则保留该点。 “正/负梯度方向上”理解为:上和下是同一个梯度方向上的正/负方向,左和右是同一个梯度方向上的正/负方向,左上和右下是同一个梯度方向上的正/负方向,右上和左下是同一个梯度方向上的正/负方向。
- 如果不是,则抑制该点,归零处理
以图示例说明 1:
中心点的梯度幅度为8, 方向为向下,所以沿着其梯度方向(向上和向下)的两个像素为其上方(梯度幅度值为2,梯度方向为向下)和 其下方(梯度幅度值为2,梯度方向为向上)的两个像素。比较结果,中心像素为最大值,保留该点。
以图示例说明 1:
”正/负梯度方向上“指的是与自己方向相同,以及与自己相反方向的梯度方向,黑色背景的像素点都是垂直方向梯度(向上,向下)方向上(水平边缘)的局部最大值。
经过上述处理后,同一个方向上的若干个边缘点,基本上只保留了一个,因此实现了边缘细化的目的。
4. 应用双阈值确定边缘
完成上述步骤后,图像内的强边缘已经在当前获取的边缘图像内,但是,一些虚边缘可能也在边缘图像内。这些虚边缘可能是真实边缘,也可能是由于噪声所产生的。对于后者,我们必须要将其剔除。
设定两个阈值(函数参数),其中一个为高阈值maxVal,另一个为低阈值minVal,根据当前边缘像素的梯度值与这两个阈值的关系,判断边缘的属性:
(1)如果当前边缘像素的梯度值大于或等于maxVal,则将当前边缘像素标记为强边缘
(2)如果当前边缘像素的梯度值介于maxVal和minVal之间,则将当前边缘像素标记为虚边缘
(3)如果当前边缘像素的梯度值小于或等于minVal,则抑制当前边缘像素
上述过程中,我们得到的虚边缘需要做进一步处理:
(4)如果该虚边缘与强边缘有连接,则将该边缘处理为边缘
(5)如果该虚边缘与强边缘无连接,则抑制该边缘
举例:
import cv2
import numpy as np
image = cv2.imread("/Users/manmi/Desktop/lena.bmp", 0)
result_1 = cv2.Canny(image, 128, 200)
result_2 = cv2.Canny(image, 32, 128)
cv2.imshow('image', image)
cv2.imshow('result_1', result_1)
cv2.imshow('result_2', result_2)
cv2.waitKey()
cv2.destroyAllWindows()
输出为: