计算机视觉(十一):边缘检测Canny
Canny 边缘检测的原理
Canny 算法由 John F. Canny 在 1986 年提出,其设计理念是寻找一个最优的边缘检测器,其核心思想可以概括为以下三个准则:
- 低错误率:尽可能多地检测出真实的边缘,同时减少错误地检测到非边缘的情况。
- 高定位精度:检测到的边缘点应该尽可能准确地靠近真实边缘的中心位置。
- 单边缘响应:对于一个真实的边缘,算法只应给出一个响应,避免检测到多个重叠的边缘。
为了实现这三个准则,Canny 算法分五步执行:
第一步:高斯模糊(Gaussian Blurring)
在进行任何边缘检测之前,首先要对图像进行平滑处理,以去除噪声。Canny 算法使用高斯滤波器来平滑图像。高斯滤波是一种加权平均,它使用一个高斯函数来定义权重,中心点的权重最大,离中心越远的像素权重越小。这一步的目的是确保后续的梯度计算不会被图像中的随机噪声所干扰。
第二步:计算梯度幅值和方向(Gradient Magnitude and Direction)
平滑后的图像会进行梯度计算。梯度可以理解为像素值在水平和垂直方向上的变化率。梯度幅值表示该点像素变化的剧烈程度,即“边缘的强度”,而梯度方向则表示变化的方向。
Canny 算法通常使用 Sobel 算子来计算图像在 x 和 y 方向的梯度 (G_x 和 G_y)。
-
梯度幅值 (Magnitude):G =
-
梯度方向 (Direction):
梯度幅值越大,表示该点的边缘越强。
第三步:非极大值抑制(Non-maximum Suppression)
这一步是 Canny 算法的关键。在计算出梯度幅值后,我们得到的是许多“粗糙”的边缘,其中一个真实的边缘可能对应了多个像素点。非极大值抑制的目标是将这些粗糙的边缘“细化”为单一的像素宽。
算法会遍历所有像素点,并检查该点的梯度幅值是否在其梯度方向上的邻域内达到最大。如果不是,就将该点视为非边缘点并将其幅值设为零。例如,如果一个点的梯度方向是水平的,算法会比较它与左边和右边邻点的梯度幅值,如果它不是最大的,则它不是真正的边缘点。通过这个过程,我们能够消除许多由于梯度值高但并非边缘中心而产生的假边缘。
第四步:双阈值处理(Double Thresholding)
非极大值抑制后,图像中仍然存在许多边缘,有些是真实的,有些可能是噪声。Canny 算法使用两个阈值(高阈值 high_threshold
和低阈值 low_threshold
)来区分它们。
- 强边缘(Strong Edges):梯度幅值大于
high_threshold
的像素点被立即标记为边缘。 - 弱边缘(Weak Edges):梯度幅值介于
low_threshold
和high_threshold
之间的像素点被标记为潜在的边缘。 - 非边缘(Non-edges):梯度幅值小于
low_threshold
的像素点被直接丢弃。
这个步骤有效地将所有边缘分成了三类,为下一步的边缘连接做准备。
第五步:边缘连接(Hysteresis Thresholding)
这一步的目标是将弱边缘点连接到强边缘上。算法会遍历所有的弱边缘点,如果某个弱边缘点与任何一个强边缘点是相邻的,那么它也会被视为一个真实的边缘。这种方法可以有效地保留那些因噪声等原因导致梯度值稍低的真实边缘,同时丢弃那些孤立的、可能是噪声引起的弱边缘点。
最终,所有被标记为强边缘或通过连接保留的弱边缘,就构成了最终的边缘图像。
OpenCV 中的 Canny 边缘检测
核心函数
edges = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
参数详解:
1. image
- 类型:
numpy.ndarray
- 说明: 这是输入图像。Canny 算法通常在单通道的灰度图像上效果最好。如果你传入彩色图像,函数内部会先将其转换为灰度图。为了获得更好的控制和性能,建议在调用
Canny()
之前手动将图像转换为灰度。
2. threshold1
(低阈值)
- 类型:
int
或float
- 说明: 这是 Canny 算法中双阈值处理的低阈值。任何梯度幅值小于这个值的像素点都会被立即丢弃,不被认为是边缘。
3. threshold2
(高阈值)
- 类型:
int
或float
- 说明: 这是 Canny 算法中双阈值处理的高阈值。任何梯度幅值大于这个值的像素点都会被立即标记为强边缘。
threshold1
和 threshold2
之间的关系是 Canny 算法的关键:
- 梯度幅值大于
threshold2
的点是强边缘。 - 梯度幅值在
threshold1
和threshold2
之间的点是弱边缘。 - 梯度幅值小于
threshold1
的点被丢弃。
弱边缘点只有在与强边缘点相连时才会被保留。这个机制(滞后阈值)使得 Canny 算法能够保留真实的边缘,同时有效地过滤掉噪声。
如何选择这两个阈值?
- 一个常见的经验法则是将
high_threshold
设置为low_threshold
的 2 到 3 倍。例如,(50, 150)
或(100, 200)
。 - 如果阈值设置得太高,你可能会错过一些真实的微弱边缘。
- 如果阈值设置得太低,你可能会得到太多由噪声引起的边缘。
- 对于不同的图像,这两个值的最佳选择会有所不同,通常需要通过实验来确定。
可选参数详解
4. apertureSize
- 类型:
int
- 说明: 这是 Sobel 算子的孔径大小。它用于计算图像梯度。这个值必须是奇数,并且在
[3, 5, 7]
之间。默认值为3
。 apertureSize
越大,Sobel 算子的核就越大,它所计算的梯度会更平滑,对噪声的抵抗能力也更强,但可能会牺牲一些边缘的细节。
5. L2gradient
- 类型:
bool
- 说明: 这是一个布尔标志,用于指定计算梯度幅值的方式。
- 如果设置为
True
,梯度幅值使用更精确的 L2 范数(欧几里得距离)计算: G=sqrtG_x2+G_y2 - 如果设置为
False
(默认值),梯度幅值使用更快的 L1 范数计算: G=∣G_x∣+∣G_y∣ - L2 范数计算更准确,但计算速度稍慢。通常情况下,L1 范数已经足够好,所以默认是
False
。
示例
import cv2
import numpy as np# 1. 加载图像
image = cv2.imread('Lenna.png')if image is None:print("错误:无法读取图像。")exit()# 2. 将图像转换为灰度图(Canny通常在灰度图上操作)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 3. 执行Canny边缘检测
# Canny函数需要三个参数:灰度图像,低阈值,高阈值
# 这里的 50 和 150 是常用的经验值,可以根据图像特性调整
edges = cv2.Canny(gray_image, 50, 150)# 4. 显示结果
cv2.imshow('Original Image', image)
cv2.imshow('Canny Edges', edges)cv2.waitKey(0)
cv2.destroyAllWindows()
执行效果: