canny 边缘监测¶
一、Canny 边缘检测的原理¶
良好的检测率:算法能够尽可能多地标识出图像中的实际边缘,不漏检。 良好的定位性能:标识出的边缘要与实际图像中的实际边缘尽可能接近。 最小响应:图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘
为了实现这个目标,Canny 算法包含以下四个关键步骤:¶
1、高斯滤波(去噪)¶
边缘检测非常容易受到图像噪声的影响。因此,第一步是使用高斯滤波器对图像进行平滑处理,以去除噪声。
高斯滤波器是一个低通滤波器,它通过一个卷积核与图像进行卷积,模糊图像并减少噪声。卷积核的大小和标准差(σ)决定了模糊的程度。 公式:
$G(x,y) = \frac{1}{2\pi\sigma^2}e^{- (x^2+y^2)/2\sigma^2}$
2、计算梯度强度和方向¶
使用 Sobel、Scharr 等算子(Canny 内部使用 Sobel)来计算图像在水平(Gx)和垂直(Gy)方向上的梯度。
根据 Gx 和 Gy,可以计算出每个像素的梯度幅度(强度)和方向:
梯度方向总是垂直于边缘。梯度幅度越大,该点是边缘的可能性就越高
梯度幅度(Gradient Magnitude):
$G = \sqrt{{G^2~x}+{G^2~y}}$
为了简化计算,有时也会使用:
$G = {|{G~x}|+|{G~y}|}$
梯度方向(Gradient Direction):
$\theta = arctan2({G~x},{G~y})$
3、非极大值抑制(NMS - Non-Maximum Suppression)¶
在得到梯度幅度和方向后,图像中可能存在许多“粗”边缘。非极大值抑制的目的是“瘦边”,即只保留局部梯度最大的点,抑制所有其他非极大值的点。
具体做法:
对于每一个像素,检查其梯度方向上的两个相邻像素(例如,一个像素的梯度方向是水平的,则检查其左右两个像素)。 * 如果当前像素的梯度幅度是这三个点中最大的,则保留其幅度值。 * 否则,将其幅度抑制为 0(即不是边缘)。 经过这一步,输出的是一个“细线”的二值图像,其中只包含了最可能是边缘的像素。
4、双阈值检测与边缘连接¶
!!! note 这是 Canny 算法的最后一步,用于确定哪些是真正的边缘,哪些不是。
双阈值(Hysteresis Thresholding):
设置两个阈值:高阈值(maxVal) 和 低阈值(minVal)。
根据像素的梯度幅度进行判断:
强边缘:如果梯度幅度 > 高阈值,则认定其为确定的边缘。
弱边缘:如果梯度幅度介于低阈值和高阈值之间,则认定其为可能的边缘,需要进一步检查。
非边缘:如果梯度幅度 < 低阈值,则直接抑制。
边缘连接(通过滞后现象): 对于被标记为“弱边缘”的像素,检查它是否与任何一个“强边缘”像素相连接(8邻域内)。 如果连接,则认为这个弱边缘也是真实边缘的一部分,并将其保留。 如果不连接,则将其抑制。
这种方法可以有效地排除由噪声引起的假边缘,同时确保真实的弱边缘(如模糊的、不连续的部分)能够被正确地连接起来。
二、opencv中的cv2.canny()函数使用¶
函数原型¶
更常用的简化形式是:参数详解¶
image: 输入图像,必须是单通道的8位图像(通常是灰度图)。threshold1(minVal): Canny算法第四步中的低阈值。threshold2(maxVal): Canny算法第四步中的高阈值。maxVal和minVal的比值通常在 2:1 到 3:1 之间。例如,一个常用的起始值是(100, 200)。
apertureSize(可选): Sobel算子用于计算梯度的孔径大小(卷积核尺寸)。默认是 3。可以是 3, 5, 7。L2gradient(可选): 一个布尔值,用于计算梯度幅度。False(默认): 使用更简单的 \(|G_x| + |G_y|\)。True: 使用更精确的 \(\sqrt{G_x^2 + G_y^2}\)(L2范数)。
返回值¶
edges: 输出图像,一个单通道的二值图像(黑白图),其中白色像素表示检测到的边缘,黑色表示背景。
三、代码示例¶
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 1. 读取图像并转换为灰度图
img = cv2.imread('your_image.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 应用Canny边缘检测
# 参数:输入图像,低阈值,高阈值
edges = cv2.Canny(gray, 100, 200) # 也可以直接读取灰度图 cv2.Canny(img, 100, 200)
# 3. 显示结果
cv2.imshow('IMG',edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
高级示例:使用滑动条动态调整阈值
这个示例非常有助于理解 minVal和 maxVal参数的影响
import cv2
def nothing(x):
# 调函数,用于createTrackbar
print(x) # x为滑条当前值
# 读取图像并转为灰度图
img = cv2.imread('your_image.jpg', cv2.IMREAD_GRAYSCALE)
# 创建一个窗口和滑动条
cv2.namedWindow('Canny Edges')
cv2.createTrackbar('Min Threshold', 'Canny Edges', 0, 500, nothing)
cv2.createTrackbar('Max Threshold', 'Canny Edges', 100, 500, nothing)
while True:
# 获取滑动条的当前位置
min_val = cv2.getTrackbarPos('Min Threshold', 'Canny Edges')
max_val = cv2.getTrackbarPos('Max Threshold', 'Canny Edges')
# 应用Canny检测
edges = cv2.Canny(img, min_val, max_val)
# 显示结果
cv2.imshow('Canny Edges', edges)
# 按ESC键退出循环
k = cv2.waitKey(1) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
总结¶
| 方面 | 描述 |
|---|---|
| 核心思想 | 通过多阶段算法(滤波->梯度->NMS->双阈值)来最优地检测图像边缘 |
| 关键参数 | minVal (低阈值) 和 maxVal (高阈值)。调整它们可以控制边缘的细节程度。比值通常在 2:1 到 3:1 |
| 优点 | 抗噪声能力强,检测出的边缘连接性好、更细、更完整 |
| 缺点 | 阈值需要手动调整,对于不同的图像可能需要不同的参数 |
| 适用场景 | 任何需要精确、连续边缘的计算机视觉任务,如物体识别、图像分割、三维重建等 |