Skip to content

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()函数使用

函数原型

edges = cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None)
更常用的简化形式是:
edges = cv2.Canny(image, minVal, maxVal, apertureSize=3, L2gradient=False)

参数详解

  1. image: 输入图像,必须是单通道的8位图像(通常是灰度图)。
  2. threshold1 (minVal): Canny算法第四步中的低阈值
  3. threshold2 (maxVal): Canny算法第四步中的高阈值
    • maxValminVal 的比值通常在 2:13:1 之间。例如,一个常用的起始值是 (100, 200)
  4. apertureSize (可选): Sobel算子用于计算梯度的孔径大小(卷积核尺寸)。默认是 3。可以是 3, 5, 7。
  5. 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
优点 抗噪声能力强,检测出的边缘连接性好、更细、更完整
缺点 阈值需要手动调整,对于不同的图像可能需要不同的参数
适用场景 任何需要精确、连续边缘的计算机视觉任务,如物体识别、图像分割、三维重建等