Canny 边缘检测算法原理详解

Canny 边缘检测算法是一种经典的图像边缘检测方法,由 John F. Canny 在 1986 年提出。该算法旨在以最优方式检测图像边缘,其核心目标是实现 低误检率、良好的定位性和最少的响应次数。Canny 算法包含五个主要步骤,下面将详细介绍每个步骤的原理和相关公式推导。


一、算法步骤概述

  1. 噪声抑制(高斯平滑)
  2. 计算梯度(使用 Sobel 或类似算子)
  3. 非极大值抑制(NMS)
  4. 双阈值检测
  5. 边缘连接(滞后阈值)

二、详细步骤与公式推导

1. 高斯滤波(去噪)

图像中存在噪声会干扰边缘检测,因此第一步是用高斯滤波器平滑图像。

公式:
高斯核函数(二维):

G ( x , y ) = 1 2 π σ 2 exp ⁡ ( − x 2 + y 2 2 σ 2 ) G(x, y) = \frac{1}{2\pi \sigma^2} \exp\left(-\frac{x^2 + y^2}{2\sigma^2}\right) G(x,y)=2πσ21exp(2σ2x2+y2)

将图像 I ( x , y ) I(x, y) I(x,y) 与高斯核进行卷积:

I s ( x , y ) = I ( x , y ) ∗ G ( x , y ) I_s(x, y) = I(x, y) * G(x, y) Is(x,y)=I(x,y)G(x,y)


2. 梯度计算(边缘强度与方向)

使用 Sobel 算子或 Prewitt 算子计算图像中每个像素的梯度。

常用 Sobel 算子:

  • 水平方向:

    G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} Gx= 121000121

  • 垂直方向:

    G y = [ − 1 − 2 − 1 0 0 0 1 2 1 ] G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} Gy= 101202101

梯度幅值和方向:

M ( x , y ) = G x 2 + G y 2 M(x, y) = \sqrt{G_x^2 + G_y^2} M(x,y)=Gx2+Gy2

θ ( x , y ) = arctan ⁡ ( G y G x ) \theta(x, y) = \arctan\left(\frac{G_y}{G_x}\right) θ(x,y)=arctan(GxGy)


3. 非极大值抑制(Non-Maximum Suppression)

目的是精确定位边缘,仅保留局部最大值点。

Canny 边缘检测算法中的 非极大值抑制(Non-Maximum Suppression, NMS) 是使边缘细化(thin)为单像素宽度的关键步骤,目的是消除在梯度方向上不是真正边缘的像素,从而得到更加精确的边界定位。


3.1. 背景回顾

在使用 Sobel 或其他梯度算子计算出图像每个像素的梯度幅值 M ( x , y ) M(x, y) M(x,y) 和方向 θ ( x , y ) \theta(x, y) θ(x,y) 后,很多像素的梯度虽然大,但并不一定位于真实边缘的“局部最大”处。

非极大值抑制的目标
只保留梯度方向上“局部最大的像素”,并将其余像素置零。


3.2. 基本原理

对每个像素点:

  1. 获取其梯度幅值 M ( x , y ) M(x, y) M(x,y) 和方向 θ ( x , y ) \theta(x, y) θ(x,y)
  2. 沿着 θ \theta θ 方向,找其前后两个邻接像素的幅值
  3. 如果当前点的幅值不是沿该方向上的最大值,则将其抑制(设为 0)

3.3. 梯度方向量化(4 个主方向)

由于图像是离散的,梯度方向不能按连续角度进行比较,因此通常将方向 θ \theta θ(单位为角度)量化为以下 4 个方向:

方向范围(角度)量化方向(近似)比较点位置
0° ± 22.5°水平 (0°)左 / 右 像素
45° ± 22.5°斜向 (45°)左下 / 右上 像素
90° ± 22.5°垂直 (90°)上 / 下 像素
135° ± 22.5°斜向 (135°)左上 / 右下 像素

3.4. 伪代码算法步骤
for each pixel (x, y):
    compute gradient magnitude M(x, y) and direction θ(x, y)
    quantize θ into one of 0°, 45°, 90°, 135°

    compare M(x, y) with M1 and M2 (interpolated or neighbor points in θ direction)

    if M(x, y) >= M1 and M(x, y) >= M2:
        keep it
    else:
        suppress (set to 0)

其中 M 1 M1 M1 M 2 M2 M2 是沿梯度方向的两个邻域像素的幅值。


3.5. 示例图解
  ↑ θ=90°
  |
M1 M0 M2       → 若 M0 ≥ M1 且 M0 ≥ M2,保留 M0,否则设为 0
 ↖ 斜方向 θ=135°
M1   M2
   M0

3.6. 数学描述

设当前像素为 ( x , y ) (x, y) (x,y),其梯度方向为单位向量 d ⃗ = ( cos ⁡ θ , sin ⁡ θ ) \vec{d} = (\cos \theta, \sin \theta) d =(cosθ,sinθ)

取两个点:

  • M 1 = M ( x + d x , y + d y ) M_1 = M(x + d_x, y + d_y) M1=M(x+dx,y+dy)
  • M 2 = M ( x − d x , y − d y ) M_2 = M(x - d_x, y - d_y) M2=M(xdx,ydy)

若:

M ( x , y ) ≥ M 1  且  M ( x , y ) ≥ M 2 M(x, y) \geq M_1 \text{ 且 } M(x, y) \geq M_2 M(x,y)M1  M(x,y)M2

则保留,否则抑制。

(注意:M1、M2 可通过双线性插值获取)


3.7. 总结作用
优点描述
边缘细化保留边缘响应的精确位置,宽边变细边
去除虚假边缘剔除非局部最大响应的像素,减少伪边缘
为边缘连接打基础只有局部最强点才能触发连接传播

4. 双阈值检测(Hysteresis Thresholding)

使用两个阈值:

  • 高阈值 T H T_H TH:强边缘
  • 低阈值 T L T_L TL:弱边缘

分类:

  • M ( x , y ) > T H M(x, y) > T_H M(x,y)>TH:强边缘(保留)
  • T L < M ( x , y ) ≤ T H T_L < M(x, y) \le T_H TL<M(x,y)TH:弱边缘(待定)
  • M ( x , y ) ≤ T L M(x, y) \le T_L M(x,y)TL:非边缘(剔除)

5. 边缘连接(滞后处理)

将弱边缘像素与其邻域的强边缘进行连接(8邻域检查),只有与强边缘相连的弱边缘才保留为最终边缘。

Canny 边缘检测算法中的边缘连接(Edge Tracking by Hysteresis,又称滞后阈值处理)是算法中非常关键的一步,它的作用是将图像中真实存在但不够强烈的“弱边缘”正确地识别出来,并排除孤立的噪声响应。


5.1、为什么需要边缘连接?

在非极大值抑制之后,我们得到了细化的边缘,但其中包含两种类型的边缘响应:

  • 强边缘点:响应值 > 高阈值( T H T_H TH),这些是可靠的边缘。
  • 弱边缘点:介于 T L T_L TL(低阈值)与 T H T_H TH 之间的响应,不确定是否是边缘。
  • 非边缘点:响应值 ≤ T L T_L TL,可直接舍弃。

问题是:某些真实边缘由于纹理或光照不均,在某些区域响应值可能较低而被归为“弱边缘”,如果直接丢弃,就会造成边缘断裂。


5.2、边缘连接的核心思想

一个弱边缘点,如果它的 8 邻域中至少有一个强边缘点,那么它被认为是边缘的一部分并保留;否则丢弃。

这是一个 递归或 BFS(广度优先搜索)式 的连接过程。


5.3、算法步骤
  1. 从所有强边缘像素出发。

  2. 对每个强边缘点的 8 邻域像素:

    • 如果邻域中有弱边缘点,且未被标记为边缘:

      • 将它标记为边缘。
      • 把这个点加入强边缘集合(继续向外扩张)。
  3. 继续这个过程直到没有更多可以连接的弱边缘点。

  4. 所有没有连接上的弱边缘点被丢弃。


5.4、图示说明
   T_H = 高阈值
   T_L = 低阈值

   强边缘点(> T_H):立即标记为边缘
   弱边缘点(T_L < val <= T_H):待观察
   非边缘点(≤ T_L):剔除

   连接判断:
     强点: o
     弱点: x
     其他: .

   示例局部区域(假设中间是强边缘):
       . x .
       x o x
       . x .

   中心 o 是强边缘,x 是弱边缘 → 所有 x 都被连接标记为边缘

5.5、数学抽象

设梯度幅值图为 M ( x , y ) M(x, y) M(x,y),定义集合:

  • E s = { ( x , y ) ∣ M ( x , y ) > T H } E_s = \{(x, y) \mid M(x, y) > T_H\} Es={(x,y)M(x,y)>TH}:强边缘点集合
  • E w = { ( x , y ) ∣ T L < M ( x , y ) ≤ T H } E_w = \{(x, y) \mid T_L < M(x, y) \le T_H\} Ew={(x,y)TL<M(x,y)TH}:弱边缘点集合

最终保留的边缘点集合 E E E 为:

E = E s ∪ { ( x , y ) ∈ E w ∣ ∃ ( x ′ , y ′ ) ∈ N 8 ( x , y ) ,   ( x ′ , y ′ ) ∈ E s } E = E_s \cup \left\{(x, y) \in E_w \mid \exists (x', y') \in \mathcal{N}_8(x, y),\ (x', y') \in E_s \right\} E=Es{(x,y)Ew(x,y)N8(x,y), (x,y)Es}

其中 N 8 ( x , y ) \mathcal{N}_8(x, y) N8(x,y) 表示点 ( x , y ) (x, y) (x,y) 的 8 邻域。


三、Canny 算法的优势

  • 抗噪性能强(高斯滤波)
  • 定位准确(非极大值抑制)
  • 不会重复响应边缘(通过滞后双阈值处理)

四、Canny 算法 C++ 实现(基于 OpenCV)

#include <opencv2/opencv.hpp>
#include <iostream>

int main() {
    // 加载图像
    cv::Mat src = cv::imread("sample.jpg", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        std::cerr << "无法加载图像!" << std::endl;
        return -1;
    }

    // 1. 高斯滤波去噪
    cv::Mat blurred;
    cv::GaussianBlur(src, blurred, cv::Size(5, 5), 1.4);

    // 2~5. 使用 OpenCV 的 Canny 封装函数
    cv::Mat edges;
    double low_thresh = 50, high_thresh = 150;
    cv::Canny(blurred, edges, low_thresh, high_thresh);

    // 显示结果
    cv::imshow("Original", src);
    cv::imshow("Canny Edges", edges);
    cv::waitKey(0);

    return 0;
}

参数调节建议

参数说明
low_thresh低阈值:边缘保留灵敏度高
high_thresh高阈值:边缘更稳定、抗噪强
GaussianBlur kernel size核越大,越模糊,边缘越干净

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

点云SLAM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值