OpenCV(三十一):边缘检测Canny

基本原理

Canny 边缘检测是一种经典且常用的图像边缘提取算法,由 John Canny 在 1986 年提出。它在 OpenCV 中得到了高效实现,是结构化光、SLAM、医学影像分析与机器视觉领域中最常用的边缘检测方法之一。其目标是以最优方式识别图像中的边缘,既能准确检测真实边缘,又能保持较强的抗噪能力与定位精度。

Canny 算法之所以经典,是因为它通过数学推导给出了边缘检测的三个最优准则:

  1. 检测率高(Good Detection):尽可能找到真实边缘;
  2. 定位准确(Good Localization):检测到的边缘点应准确落在真实边缘位置;
  3. 一次响应(Minimal Response):相同边缘只产生一条响应,避免多条边缘线。

Canny 算法实现包含 5 个关键步骤:

第一步:噪声抑制(Gaussian Blur)

边缘通常对应灰度变化剧烈的区域,但噪声也会产生剧烈变化。如果不去噪,边缘会出现大量虚假响应。因此 Canny 算法的第一步是使用高斯滤波进行平滑:

复制代码
GaussianBlur(src, blur, Size(3,3), 1.0);

高斯滤波使用如下数学形式的卷积核:

σ 越大,平滑越强,但也会模糊真实边缘。

第二步:计算梯度(Sobel)

Canny 使用 Sobel 算子计算图像在水平方向与垂直方向的梯度:

OpenCV 内部相当于:

cpp 复制代码
Sobel(blur, grad_x, CV_16S, 1, 0);
Sobel(blur, grad_y, CV_16S, 0, 1);

然后得到:

梯度幅值:

梯度方向:

梯度方向说明边缘的方向,梯度越大表示边缘越强。

第三步:非极大值抑制(NMS)

未经处理的梯度图会出现宽厚的边缘带,这不符合 "一次响应" 原则。NMS 的目标是 细化边缘

  1. 根据梯度方向确定像素的比较方向(四种情况:0°、45°、90°、135°)
  2. 当前像素的梯度如果不是局部最大值,则置为 0

这种方法能有效将粗边缩减为优美细线。

第四步:双阈值检测(Double Threshold)

Canny 的创新点之一就是"双阈值",使用高低两个阈值 T1(低)、T2(高):

  • 强边缘:G > T2
  • 弱边缘:T1 < G ≤ T2
  • 非边缘:G ≤ T1

为何要这样做?

  • 边缘是连通的,一个强边缘附近往往会有梯度较弱的像素;
  • 如果只用单阈值,弱边可能被直接丢弃;
  • 双阈值允许保留"可能属于边缘"的弱像素。

第五步:边缘连接(Hysteresis)

弱边缘并不是全部保留,而是需要与强边缘连接才认为是真实边。

过程如下:

  1. 对所有强边缘保留;
  2. 对弱边缘,若与强边相邻(8 连接),则保留;
  3. 其他弱边缘舍弃。

最终得到干净、连续、不重复的边缘线。

OpenCV 中的 Canny 函数

OpenCV 在 Python 中提供的 Canny 边缘检测函数原型如下:

python 复制代码
cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=3, L2gradient=False)

1. image(输入图像)

  • 类型:uint8 单通道(灰度图)
  • 说明:如果传入的是彩色图,OpenCV 不会报错,但会自动将像素分量混合,使结果不可控。

建议永远手动转换为灰度图:

复制代码
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

2. threshold1(低阈值)

  • 用于双阈值检测中的"弱边缘"判断。
  • 越低 → 保留越多弱边缘(噪声也更多)
  • 越高 → 结果越干净,但容易漏掉细节边缘

一般经验:

复制代码
low = 0.66 * 中位数

3. threshold2(高阈值)

  • 用于判定"强边缘"
  • 边缘必须 > threshold2 才能直接保留为强边

常用经验规则:

复制代码
threshold2 ≈ 2 × threshold1

建议:

  • 图像干净 → 高阈值可放宽
  • 图像噪声大 → 高阈值应提高

4. edges(输出图像,可忽略)

一般不需要传入,由 OpenCV 创建:

复制代码
edges = cv2.Canny(image, 50, 150)

5. apertureSize(Sobel 算子卷积核尺寸)

  • 默认值:3
  • 可选:3, 5, 7

它决定了梯度计算的卷积核大小(Sobel 算子大小),影响梯度平滑程度。

apertureSize 特点
3 默认,精度好
5 边缘更平滑,适合噪声较大图像
7 更强的平滑,适合强噪声环境

6. L2gradient(梯度幅值计算方式)

  • 默认:False
  • 控制梯度幅值计算方法:

False(默认)

使用简单加和:

速度较快,在多数应用中足够。

True

使用更准确的欧几里得距离:

更精确但计算更慢。

如果你对边缘精度要求高,应启用 L2gradient:

python 复制代码
edges = cv2.Canny(gray, 50, 150, L2gradient=True)

示例

python 复制代码
import cv2
import numpy as np

# 1. 读取图像
img = cv2.imread("test.jpg")

# 检查是否读取成功
if img is None:
    raise FileNotFoundError("无法找到图像,请检查路径 test.jpg 是否存在!")

# 2. 转灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 3. 高斯滤波(Canny 内部也会处理,但建议手动提高效果)
blur = cv2.GaussianBlur(gray, (3, 3), 1)

# 4. 设置 Canny 双阈值
low_threshold = 50
high_threshold = 150

# 5. 执行 Canny 边缘检测
edges = cv2.Canny(
    blur,
    low_threshold,
    high_threshold,
    apertureSize=3,
    L2gradient=True  # 使用更精确的梯度计算
)

# 6. 显示结果
cv2.imshow("Original", img)
cv2.imshow("Gray", gray)
cv2.imshow("Canny Edges", edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

执行效果:

相关推荐
小鸡吃米…6 小时前
机器学习 - K - 中心聚类
人工智能·机器学习·聚类
好奇龙猫6 小时前
【AI学习-comfyUI学习-第三十节-第三十一节-FLUX-SD放大工作流+FLUX图生图工作流-各个部分学习】
人工智能·学习
沈浩(种子思维作者)6 小时前
真的能精准医疗吗?癌症能提前发现吗?
人工智能·python·网络安全·健康医疗·量子计算
saoys6 小时前
Opencv 学习笔记:图像掩膜操作(精准提取指定区域像素)
笔记·opencv·学习
minhuan6 小时前
大模型应用:大模型越大越好?模型参数量与效果的边际效益分析.51
人工智能·大模型参数评估·边际效益分析·大模型参数选择
Cherry的跨界思维6 小时前
28、AI测试环境搭建与全栈工具实战:从本地到云平台的完整指南
java·人工智能·vue3·ai测试·ai全栈·测试全栈·ai测试全栈
MM_MS6 小时前
Halcon变量控制类型、数据类型转换、字符串格式化、元组操作
开发语言·人工智能·深度学习·算法·目标检测·计算机视觉·视觉检测
ASF1231415sd7 小时前
【基于YOLOv10n-CSP-PTB的大豆花朵检测与识别系统详解】
人工智能·yolo·目标跟踪
水如烟7 小时前
孤能子视角:“意识“的阶段性回顾,“感质“假说
人工智能
Carl_奕然8 小时前
【数据挖掘】数据挖掘必会技能之:A/B测试
人工智能·python·数据挖掘·数据分析