边缘检测:基础算子到高级边缘提取【计算机视觉】
- [边缘检测(Edge Detection)](#边缘检测(Edge Detection))
-
- Ⅰ、引言
- Ⅱ、边缘检测的数学基础
-
- 一、图像的数学表示
- 二、边缘的数学定义(微分的直接应用)
-
- [1. 一维连续信号的边缘](#1. 一维连续信号的边缘)
- [2. 二维连续图像的边缘](#2. 二维连续图像的边缘)
- 三、从微分到差分(关键步骤)
-
- [1. 一阶微分的离散近似(核心)](#1. 一阶微分的离散近似(核心))
- [2. 二阶微分的离散近似](#2. 二阶微分的离散近似)
- 四、从差分走向卷积(为什么所有算子都用卷积?)
-
- [1. 一阶微分 → 差分 → 卷积核](#1. 一阶微分 → 差分 → 卷积核)
- [2. 二阶微分 → 二阶差分 → 卷积核](#2. 二阶微分 → 二阶差分 → 卷积核)
- 五、梯度与边缘检测(微分的最终应用)
- 六、二阶微分与边缘检测(Laplacian)
- Ⅲ、基于梯度的一阶算子
-
- [一、Roberts 算子](#一、Roberts 算子)
- [二、Prewitt / Sobel 算子(合并讲解)](#二、Prewitt / Sobel 算子(合并讲解))
- [Ⅳ、Laplacian 算子(基于梯度的二阶算子)](#Ⅳ、Laplacian 算子(基于梯度的二阶算子))
-
- 一、数学逻辑
-
- [1. 数学公式](#1. 数学公式)
- [2. 把离散公式写成卷积核(得到模板)](#2. 把离散公式写成卷积核(得到模板))
- [3. 例题](#3. 例题)
- 二、代码实现
- 三、对Laplacian算子与一阶算子的总结
- Ⅴ、Canny边缘检测
-
- 一、Canny算法的数学本质
- 二、核心步骤的数学公式与推导
-
- [1. 高斯滤波(噪声抑制)](#1. 高斯滤波(噪声抑制))
- [2. 一阶梯度计算(边缘强度与方向)](#2. 一阶梯度计算(边缘强度与方向))
- [3. 非极大值抑制(边缘细化)](#3. 非极大值抑制(边缘细化))
- [4. 双阈值判决(边缘连接与噪声剔除)](#4. 双阈值判决(边缘连接与噪声剔除))
- 三、单像素邻域的Canny边缘检测计算
-
- [1. 计算x/y方向梯度(Sobel核卷积)](#1. 计算x/y方向梯度(Sobel核卷积))
- [2. 计算梯度大小与梯度方向](#2. 计算梯度大小与梯度方向)
- [3. 非极大值抑制(边缘细化)](#3. 非极大值抑制(边缘细化))
- [4. 双阈值判决(最终边缘判定)](#4. 双阈值判决(最终边缘判定))
- 四、代码实现
- 五、cv2.Canny()函数
-
-
- [1. 函数原型](#1. 函数原型)
- [2. 核心参数说明](#2. 核心参数说明)
- [3. 关键注意事项](#3. 关键注意事项)
-
- [六、Canny 与 Sobel 边缘检测对比](#六、Canny 与 Sobel 边缘检测对比)
- Ⅵ、总结
边缘检测(Edge Detection)
边缘检测是让计算机"看见"物体轮廓的核心技术,也是入门计算机视觉的第一步。
注意:本文所有代码均可导入 Jupyter Notebook 直接运行
Ⅰ、引言
简单来说,边缘检测就是在图像里找"边界线"------比如从一张照片里抠出杯子、书本的轮廓。
先认识5种最常用的边缘检测工具,难度从易到难:
| 算法名称 | 核心特点(小白版) | 定位 |
|---|---|---|
| Roberts 算子 | 最简单的找边方法,易受噪点干扰 | 入门 |
| Prewitt 算子 | 比Roberts稳一点的基础找边法 | 入门 |
| Sobel 算子 | 实际应用最多的基础找边工具 | 常用 |
| Laplacian 算子 | 找更细的边缘,但怕噪点 | 进阶 |
| Canny 算法 | 找边最精准的"全能选手" | 高级 |
这5种方法覆盖了从"随手找边"到"精准找边"的全场景,接下来我们逐个拆解~
Ⅱ、边缘检测的数学基础
一、图像的数学表示
图像在数学上是一个二维离散函数: f ( x , y ) f(x, y) f(x,y)
其中:
- x , y x, y x,y 为像素坐标(离散)
- f ( x , y ) f(x, y) f(x,y) 为灰度值(离散)
但数学中的微分、导数是针对连续函数定义的,因此我们需要先把图像看作连续函数,再讨论如何离散化。
二、边缘的数学定义(微分的直接应用)
边缘 = 灰度值剧烈变化的位置。
数学上,"变化剧烈"用导数描述。
1. 一维连续信号的边缘
对连续函数 f ( x ) f(x) f(x):
- 一阶导数大 → 变化快 → 可能是边缘
- 二阶导数过零点 → 变化率由正变负 → 精确边缘位置
即:
边缘位置 ≈ 一阶导数极大值
边缘位置 = 二阶导数过零点
这是边缘检测的数学起点。
2. 二维连续图像的边缘
图像是二维函数 f ( x , y ) f(x, y) f(x,y),变化率由偏导数描述:
∂ f ∂ x , ∂ f ∂ y \frac{\partial f}{\partial x}, \quad \frac{\partial f}{\partial y} ∂x∂f,∂y∂f
这两个偏导数组成梯度向量:
∇ f = ( ∂ f ∂ x , ∂ f ∂ y ) \nabla f = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right) ∇f=(∂x∂f,∂y∂f)
梯度的大小表示变化强度:
∥ ∇ f ∥ = ( ∂ f ∂ x ) 2 + ( ∂ f ∂ y ) 2 \|\nabla f\| = \sqrt{\left(\frac{\partial f}{\partial x}\right)^2 + \left(\frac{\partial f}{\partial y}\right)^2} ∥∇f∥=(∂x∂f)2+(∂y∂f)2
梯度方向指向变化最快的方向。
所以:边缘 = 梯度大的地方。
这是所有一阶边缘检测算子的数学基础。
三、从微分到差分(关键步骤)
图像是离散的,无法直接计算连续导数,因此需要用差分近似微分。
1. 一阶微分的离散近似(核心)
连续形式:
d f d x = lim h → 0 f ( x + h ) − f ( x ) h \frac{df}{dx} = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h} dxdf=h→0limhf(x+h)−f(x)
离散图像中 h = 1 h=1 h=1(像素间距),因此:
d f d x ≈ f ( x + 1 ) − f ( x ) \frac{df}{dx} \approx f(x+1) - f(x) dxdf≈f(x+1)−f(x)
这就是前向差分。
同理:
∂ f ∂ x ≈ f ( x + 1 , y ) − f ( x , y ) \frac{\partial f}{\partial x} \approx f(x+1, y) - f(x, y) ∂x∂f≈f(x+1,y)−f(x,y)
∂ f ∂ y ≈ f ( x , y + 1 ) − f ( x , y ) \frac{\partial f}{\partial y} \approx f(x, y+1) - f(x, y) ∂y∂f≈f(x,y+1)−f(x,y)
这是所有一阶算子(Roberts、Prewitt、Sobel)的根本来源。
2. 二阶微分的离散近似
连续形式:
d 2 f d x 2 = lim h → 0 f ( x + h ) − 2 f ( x ) + f ( x − h ) h 2 \frac{d^2f}{dx^2} = \lim_{h \to 0} \frac{f(x+h) - 2f(x) + f(x-h)}{h^2} dx2d2f=h→0limh2f(x+h)−2f(x)+f(x−h)
离散化( h = 1 h=1 h=1):
d 2 f d x 2 ≈ f ( x + 1 ) − 2 f ( x ) + f ( x − 1 ) \frac{d^2f}{dx^2} \approx f(x+1) - 2f(x) + f(x-1) dx2d2f≈f(x+1)−2f(x)+f(x−1)
这就是 Laplacian 的数学基础。
四、从差分走向卷积(为什么所有算子都用卷积?)
差分是线性运算,而线性运算在离散信号处理中可以表示为卷积。
例如一维一阶差分:
f ( x + 1 ) − f ( x ) = f ( x ) ∗ − 1 , 1 f(x+1) - f(x) = f(x) * -1, 1 f(x+1)−f(x)=f(x)∗−1,1
二维图像的差分同样可以写成卷积核。
1. 一阶微分 → 差分 → 卷积核
例如 x 方向一阶差分:
∂ f ∂ x ≈ f ( x + 1 , y ) − f ( x − 1 , y ) \frac{\partial f}{\partial x} \approx f(x+1,y) - f(x-1,y) ∂x∂f≈f(x+1,y)−f(x−1,y)
对应的卷积核:
− 1 0 1 \] \\begin{bmatrix} -1 \& 0 \& 1 \\end{bmatrix} \[−101
这正是 Prewitt/Sobel 的基础。
2. 二阶微分 → 二阶差分 → 卷积核
x 方向二阶差分:
∂ 2 f ∂ x 2 ≈ f ( x + 1 ) − 2 f ( x ) + f ( x − 1 ) \frac{\partial^2 f}{\partial x^2} \approx f(x+1) - 2f(x) + f(x-1) ∂x2∂2f≈f(x+1)−2f(x)+f(x−1)
对应的卷积核:
1 − 2 1 \] \\begin{bmatrix} 1 \& -2 \& 1 \\end{bmatrix} \[1−21
扩展到二维得到 Laplacian:
0 1 0 1 − 4 1 0 1 0 \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix} 0101−41010
五、梯度与边缘检测(微分的最终应用)
所有一阶算子都在计算梯度:
∇ f = ( ∂ f ∂ x , ∂ f ∂ y ) \nabla f = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right) ∇f=(∂x∂f,∂y∂f)
边缘强度:
E = G x 2 + G y 2 E = \sqrt{G_x^2 + G_y^2} E=Gx2+Gy2
其中:
- G x G_x Gx 是 x 方向卷积结果(近似 ∂f/∂x)
- G y G_y Gy 是 y 方向卷积结果(近似 ∂f/∂y)
这就是为什么一阶算子要用卷积:
卷积 = 离散化的微分运算。
六、二阶微分与边缘检测(Laplacian)
二阶微分检测边缘的依据是:
- 一阶微分极大值 → 二阶微分过零点
因此: ∇ 2 f = 0 \nabla^2 f = 0 ∇2f=0的位置就是边缘。
Laplacian 的卷积核正是二阶差分的离散形式。
Ⅲ、基于梯度的一阶算子
一、Roberts 算子
1. 数学逻辑
(1)数学公式
用 2×2 大小的"小格子"计算图像灰度的差异,差异大的地方就是边缘。
- 把图像看成无数个像素点,每个点有个"灰度值"(0=黑,255=白);
- 用两个 2×2 模板(卷积核)分别算水平+对角线 、垂直+对角线 的灰度差:
- 模板1(Gx): 1 0 0 − 1 \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} 100−1 (算右下-左上的差)
- 模板2(Gy): 0 1 − 1 0 \begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix} 0−110 (算右上-左下的差)
- 最终边缘强度 = G x 2 + G y 2 \sqrt{Gx^2 + Gy^2} Gx2+Gy2 (简单理解:把两个方向的差异"合并")
就像数方格,用相邻4个点的灰度相减,差越大越像边缘,缺点是太敏感,一点点噪点都会误判。
(2)例题
假设有一个 2×2 的图像像素块(每个数值为像素灰度值,范围 0-255): 100 120 110 130 \begin{bmatrix} 100 & 120 \\ 110 & 130 \end{bmatrix} 100110120130使用 Roberts 算子计算该区域的边缘强度。
-
步骤1:计算 Gx(右下-左上方向灰度差)
Roberts 算子 Gx 模板:
G x = 1 0 0 − 1 Gx = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} Gx=100−1计算逻辑:像素点 × 模板对应位置数值,再求和
G x = ( 100 × 1 ) + ( 120 × 0 ) + ( 110 × 0 ) + ( 130 × − 1 ) = 100 − 130 = − 30 Gx = (100×1) + (120×0) + (110×0) + (130×-1) = 100 - 130 = -30 Gx=(100×1)+(120×0)+(110×0)+(130×−1)=100−130=−30 -
步骤2:计算 Gy(右上-左下方向灰度差)
Roberts 算子 Gy 模板:
G y = 0 1 − 1 0 Gy = \begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix} Gy=0−110计算逻辑:像素点 × 模板对应位置数值,再求和
G y = ( 100 × 0 ) + ( 120 × 1 ) + ( 110 × − 1 ) + ( 130 × 0 ) = 120 − 110 = 10 Gy = (100×0) + (120×1) + (110×-1) + (130×0) = 120 - 110 = 10 Gy=(100×0)+(120×1)+(110×−1)+(130×0)=120−110=10 -
步骤3:计算最终边缘强度
边缘强度公式(勾股定理合并两个方向的差异):
边缘强度 = G x 2 + G y 2 \text{边缘强度} = \sqrt{Gx^2 + Gy^2} 边缘强度=Gx2+Gy2代入数值计算:
边缘强度 = ( − 30 ) 2 + 10 2 = 900 + 100 = 1000 ≈ 31.62 \text{边缘强度} = \sqrt{(-30)^2 + 10^2} = \sqrt{900 + 100} = \sqrt{1000} ≈ 31.62 边缘强度=(−30)2+102 =900+100 =1000 ≈31.62
结论
该 2×2 像素区域的边缘强度约为 31.62,数值越大代表该位置的边缘越明显。
2. 代码实现
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
img = np.random.randint(0, 255, (500, 500), dtype=np.uint8)
python
kernel_x = np.array([[1, 0], [0, -1]], dtype=np.float32)
kernel_y = np.array([[0, 1], [-1, 0]], dtype=np.float32)
gx = cv2.filter2D(img, cv2.CV_64F, kernel_x)
gy = cv2.filter2D(img, cv2.CV_64F, kernel_y)
edge_strength = np.sqrt(gx**2 + gy**2)
edge_strength = cv2.normalize(edge_strength, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
- 先定义 Roberts 算子 X(右下 - 左上)、Y(右上 - 左下)两个方向的 2×2 检测模板(卷积核)
- 通过
cv2.filter2D用 64 位浮点型精度对整张图像做卷积计算,得到每个像素点两个方向的灰度差值(gx、gy) - 再利用勾股定理(
np.sqrt(gx**2 + gy**2))合并两个方向的差值,得到像素的边缘强度 - 最后通过
cv2.normalize将边缘强度值归一化到图像显示的 0-255 范围,并转为 8 位无符号整数,确保边缘检测结果能正常可视化。
python
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('原图')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(edge_strength, cmap='gray')
plt.title('Roberts算子边缘检测')
plt.axis('off')
plt.show()

二、Prewitt / Sobel 算子(合并讲解)
1. 数学逻辑
(1)数学公式
Prewitt 和 Sobel 都是用 3×3 模板计算图像的水平和垂直灰度差,区别仅在于:
Sobel 给中间行/列的像素加了权重 2,让中心像素更重要。
两者的模板可以统一写成:
| 算子 | 水平模板 Gx(检测垂直边缘) | 垂直模板 Gy(检测水平边缘) |
|---|---|---|
| Prewitt | − 1 0 1 − 1 0 1 − 1 0 1 \begin{bmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{bmatrix} −1−1−1000111 | − 1 − 1 − 1 0 0 0 1 1 1 \begin{bmatrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{bmatrix} −101−101−101 |
| Sobel | − 1 0 1 − 2 0 2 − 1 0 1 \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} −1−2−1000121 | − 1 − 2 − 1 0 0 0 1 2 1 \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} −101−202−101 |
计算方式完全相同:
- Gx = 模板与图像 3×3 区域的卷积
- Gy = 模板与图像 3×3 区域的卷积
- 最终边缘强度 = G x 2 + G y 2 \sqrt{Gx^2 + Gy^2} Gx2+Gy2
可以理解为:
- Prewitt:所有像素"一人一票",平均计算灰度差
- Sobel:中间像素"两票",更重视中心,更准确、更抗噪
(2)例题(Prewitt 与 Sobel 对比)
使用同一个 3×3 像素块:
50 60 70 40 50 60 30 40 50 \begin{bmatrix} 50 & 60 & 70 \\ 40 & 50 & 60 \\ 30 & 40 & 50 \end{bmatrix} 504030605040706050
① Prewitt 计算
Gx = (-50 + 70) + (-40 + 60) + (-30 + 50) = 20 + 20 + 20 = 60
Gy = (-50 -60 -70) + (30 + 40 + 50) = -180 + 120 = -60
边缘强度 = 60 2 + ( − 60 ) 2 = 7200 ≈ 84.85 \sqrt{60^2 + (-60)^2} = \sqrt{7200} ≈ 84.85 602+(−60)2 =7200 ≈84.85
② Sobel 计算
Gx = (-50 + 70) + 2*(-40 + 60) + (-30 + 50) = 20 + 40 + 20 = 80
Gy = (-50 -260 -70) + (30 + 240 + 50) = -240 + 160 = -80
边缘强度 = 80 2 + ( − 80 ) 2 = 12800 ≈ 113.14 \sqrt{80^2 + (-80)^2} = \sqrt{12800} ≈ 113.14 802+(−80)2 =12800 ≈113.14
结论
- 两个算子都能有效检测边缘;
- Sobel 因为中间像素加权,对边缘的响应更强,精度更高,是工程中最常用的基础边缘检测算子;
- Prewitt 更简单,但精度和抗噪性略弱。
2.Roberts、Prewitt、Sobel 边缘检测算子对比实验总结
(1)实验目的
对比分析 Roberts(2×2 模板)、Prewitt(3×3 等权模板)、Sobel(3×3 加权模板)三类一阶梯度边缘检测算子在无噪声和椒盐噪声场景下的检测效果,验证模板尺寸、像素加权策略对算子抗噪性和边缘检测精度的影响。
(2)实验流程
- 图像预处理:读取灰度图像(无本地图像时自动生成 500×500 随机灰度测试图),添加比例为 0.02 的椒盐噪声,构建「原图 + 加噪图」双测试集;
- 算子实现:基于卷积原理实现三类算子,通过对应模板计算像素水平/垂直方向灰度差,结合勾股定理合并边缘强度,并将结果归一化至 0-255 灰度范围;
- 可视化对比:采用 4×2 竖版布局展示检测结果,每行左右对比「原图/加噪图」下同一算子的效果,直观呈现噪声对不同算子的影响。
首先给我们的测试图片添加椒盐噪声
python
def add_salt_pepper_noise(img, noise_ratio=0.02):
noisy_img = img.copy()
noise_num = int(noise_ratio * img.size)
for _ in range(noise_num//2):
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
noisy_img[x, y] = 255
for _ in range(noise_num//2):
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
noisy_img[x, y] = 0
return noisy_img
实现三种算子函数
python
def edge_detect_roberts(img):
"""Roberts算子边缘检测"""
kernel_x = np.array([[1, 0], [0, -1]], dtype=np.float32)
kernel_y = np.array([[0, 1], [-1, 0]], dtype=np.float32)
gx = cv2.filter2D(img, cv2.CV_64F, kernel_x)
gy = cv2.filter2D(img, cv2.CV_64F, kernel_y)
edge = np.sqrt(gx**2 + gy**2)
edge = cv2.normalize(edge, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
return edge
def edge_detect_prewitt(img):
"""Prewitt算子边缘检测"""
kernel_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=np.float32)
kernel_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=np.float32)
gx = cv2.filter2D(img, cv2.CV_64F, kernel_x)
gy = cv2.filter2D(img, cv2.CV_64F, kernel_y)
edge = np.sqrt(gx**2 + gy**2)
edge = cv2.normalize(edge, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
return edge
def edge_detect_sobel(img):
"""Sobel算子边缘检测"""
kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32)
kernel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=np.float32)
gx = cv2.filter2D(img, cv2.CV_64F, kernel_x)
gy = cv2.filter2D(img, cv2.CV_64F, kernel_y)
edge = np.sqrt(gx**2 + gy**2)
edge = cv2.normalize(edge, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
return edge
读取图片+图像处理
python
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
img = np.random.randint(0, 255, (500, 500), dtype=np.uint8)
img_noisy = add_salt_pepper_noise(img, noise_ratio=0.02)
# 原图检测
edge_roberts = edge_detect_roberts(img)
edge_prewitt = edge_detect_prewitt(img)
edge_sobel = edge_detect_sobel(img)
# 加噪图检测
edge_roberts_noisy = edge_detect_roberts(img_noisy)
edge_prewitt_noisy = edge_detect_prewitt(img_noisy)
edge_sobel_noisy = edge_detect_sobel(img_noisy)
可视化
python
plt.figure(figsize=(10, 16))
plt.subplot(4, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('1. 原图', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 2)
plt.imshow(img_noisy, cmap='gray')
plt.title('2. 加椒盐噪声图像', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 3)
plt.imshow(edge_roberts, cmap='gray')
plt.title('3. Roberts(原图)', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 4)
plt.imshow(edge_roberts_noisy, cmap='gray')
plt.title('4. Roberts(加噪)', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 5)
plt.imshow(edge_prewitt, cmap='gray')
plt.title('5. Prewitt(原图)', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 6)
plt.imshow(edge_prewitt_noisy, cmap='gray')
plt.title('6. Prewitt(加噪)', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 7)
plt.imshow(edge_sobel, cmap='gray')
plt.title('7. Sobel(原图)', fontsize=11)
plt.axis('off')
plt.subplot(4, 2, 8)
plt.imshow(edge_sobel_noisy, cmap='gray')
plt.title('8. Sobel(加噪)', fontsize=11)
plt.axis('off')
plt.tight_layout()
plt.subplots_adjust(wspace=0.1, hspace=0.2)
plt.show()


(3)实验结果
| 算子类型 | 原图检测效果 | 加椒盐噪声后检测效果 | 核心特性 |
|---|---|---|---|
| Roberts(2×2) | 边缘细碎,计算速度最快 | 伪边缘泛滥,真实边缘被掩盖 | 对噪声极度敏感,仅适用于无噪场景 |
| Prewitt(3×3) | 边缘响应平缓,无明显毛刺 | 伪边缘较多,抗噪性略优于 Roberts | 3×3 等权模板具备基础平滑效果 |
| Sobel(3×3) | 边缘清晰,响应强度高于 Prewitt | 伪边缘少,真实边缘保留完整 | 中间像素加权,抗噪性与精度最优 |
(4)结论
- 模板尺寸决定基础抗噪性:3×3 模板(Prewitt/Sobel)因参考更多像素点,抗噪性显著优于 2×2 模板的 Roberts 算子;
- 加权策略提升检测精度:Sobel 算子通过给中间像素赋予 2 倍权重,强化了核心像素对边缘的贡献,在保持检测速度的同时,精度和抗噪性均优于等权的 Prewitt 算子;
- 工程应用建议:Roberts 仅适用于无噪、对速度要求极高的场景;Prewitt 可作为过渡型算子使用;Sobel 兼顾速度、精度与抗噪性,是工程中边缘检测的首选基础算子。
Ⅳ、Laplacian 算子(基于梯度的二阶算子)
一、数学逻辑
Laplacian 算子的数学基础是二阶微分 。
它的核心思想是:
- 一阶微分(梯度)检测"灰度变化快"的位置
- 二阶微分检测"灰度变化率的变化",即"变化的加速度"
- 二阶微分过零点 = 边缘的精确位置
1. 数学公式
对二维连续图像 f ( x , y ) f(x, y) f(x,y),Laplacian 定义为:
∇ 2 f = ∂ 2 f ∂ x 2 + ∂ 2 f ∂ y 2 \nabla^2 f = \frac{\partial^2 f}{\partial x^2} + \frac{\partial^2 f}{\partial y^2} ∇2f=∂x2∂2f+∂y2∂2f
它是 x 方向二阶偏导数 + y 方向二阶偏导数 。
二阶偏导数的定义是:
∂ 2 f ∂ x 2 = lim h → 0 f ( x + h , y ) − 2 f ( x , y ) + f ( x − h , y ) h 2 \frac{\partial^2 f}{\partial x^2} = \lim_{h \to 0} \frac{f(x+h,y) - 2f(x,y) + f(x-h,y)}{h^2} ∂x2∂2f=h→0limh2f(x+h,y)−2f(x,y)+f(x−h,y)
同理:
∂ 2 f ∂ y 2 = lim h → 0 f ( x , y + h ) − 2 f ( x , y ) + f ( x , y − h ) h 2 \frac{\partial^2 f}{\partial y^2} = \lim_{h \to 0} \frac{f(x,y+h) - 2f(x,y) + f(x,y-h)}{h^2} ∂y2∂2f=h→0limh2f(x,y+h)−2f(x,y)+f(x,y−h)
图像是离散的,像素间距 h = 1 h=1 h=1,因此:
∂ 2 f ∂ x 2 ≈ f ( x + 1 , y ) − 2 f ( x , y ) + f ( x − 1 , y ) \frac{\partial^2 f}{\partial x^2} \approx f(x+1,y) - 2f(x,y) + f(x-1,y) ∂x2∂2f≈f(x+1,y)−2f(x,y)+f(x−1,y)
∂ 2 f ∂ y 2 ≈ f ( x , y + 1 ) − 2 f ( x , y ) + f ( x , y − 1 ) \frac{\partial^2 f}{\partial y^2} \approx f(x,y+1) - 2f(x,y) + f(x,y-1) ∂y2∂2f≈f(x,y+1)−2f(x,y)+f(x,y−1)
这两个式子就是二阶差分。
把它们相加得到离散的 Laplacian:
∇ 2 f = f ( x + 1 , y ) + f ( x − 1 , y ) − 2 f ( x , y ) + f ( x , y + 1 ) + f ( x , y − 1 ) − 2 f ( x , y ) = f ( x + 1 , y ) + f ( x − 1 , y ) + f ( x , y + 1 ) + f ( x , y − 1 ) − 4 f ( x , y ) \begin{aligned} \nabla^2 f &= f(x+1,y) + f(x-1,y) - 2f(x,y) \\ &+ f(x,y+1) + f(x,y-1) - 2f(x,y) \\ &= f(x+1,y) + f(x-1,y) + f(x,y+1) + f(x,y-1) - 4f(x,y) \end{aligned} ∇2f=f(x+1,y)+f(x−1,y)−2f(x,y)+f(x,y+1)+f(x,y−1)−2f(x,y)=f(x+1,y)+f(x−1,y)+f(x,y+1)+f(x,y−1)−4f(x,y)
这就是 Laplacian 的离散公式。
2. 把离散公式写成卷积核(得到模板)
上面的离散公式可以写成一个 3×3 模板:
0 1 0 1 − 4 1 0 1 0 \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix} 0101−41010
因为:
- 上下左右四个方向的像素系数为 +1
- 中心像素系数为 -4
- 对角线像素系数为 0(未考虑)
卷积运算就是:
输出 = ∑ i = − 1 1 ∑ j = − 1 1 f ( x + i , y + j ) ⋅ kernel ( i , j ) \text{输出} = \sum_{i=-1}^{1}\sum_{j=-1}^{1} f(x+i,y+j) \cdot \text{kernel}(i,j) 输出=i=−1∑1j=−1∑1f(x+i,y+j)⋅kernel(i,j)
这与我们的离散公式完全等价。
3. 例题
假设有一个 3×3 图像块:
50 60 50 60 80 60 50 60 50 \begin{bmatrix} 50 & 60 & 50 \\ 60 & 80 & 60 \\ 50 & 60 & 50 \end{bmatrix} 506050608060506050
我们用 Laplacian 模板计算中心像素 80 的响应。
计算过程:
response = ( 60 + 60 + 60 + 60 ) − 4 × 80 = 240 − 320 = − 80 \begin{aligned} \text{response} &= (60 + 60 + 60 + 60) - 4 \times 80 \\ &= 240 - 320 \\ &= -80 \end{aligned} response=(60+60+60+60)−4×80=240−320=−80
取绝对值得到边缘强度:
∣ response ∣ = 80 |\text{response}| = 80 ∣response∣=80
说明该位置灰度变化剧烈,存在边缘。
二、代码实现
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
添加椒盐噪声
python
def add_salt_pepper_noise(img, noise_ratio=0.02):
noisy_img = img.copy()
noise_num = int(noise_ratio * img.size)
for _ in range(noise_num//2):
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
noisy_img[x, y] = 255
for _ in range(noise_num//2):
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
noisy_img[x, y] = 0
return noisy_img
生成加噪图像,以及去噪图
python
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
img = np.zeros((500, 500), dtype=np.uint8)
cv2.rectangle(img, (100, 100), (400, 400), 200, -1)
cv2.circle(img, (250, 250), 100, 150, -1)
img_noisy = add_salt_pepper_noise(img, noise_ratio=0.05)
# 椒盐噪声用中值滤波去噪
img_noisy_smooth = cv2.medianBlur(img_noisy, 3)
Laplacian算子边缘检测(核心逻辑)
python
laplacian_kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]], dtype=np.float32)
# 原图
img_float = img.astype(np.float64)
laplacian_origin = cv2.filter2D(img_float, cv2.CV_64F, laplacian_kernel)
laplacian_origin = np.abs(laplacian_origin)
laplacian_origin = cv2.normalize(laplacian_origin, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# 加噪图
img_noisy_float = img_noisy.astype(np.float64)
laplacian_noisy = cv2.filter2D(img_noisy_float, cv2.CV_64F, laplacian_kernel)
laplacian_noisy = np.abs(laplacian_noisy)
laplacian_noisy = cv2.normalize(laplacian_noisy, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# 加噪图先中值滤波再 Laplacian
img_noisy_smooth_float = img_noisy_smooth.astype(np.float64)
laplacian_noisy_smooth = cv2.filter2D(img_noisy_smooth_float, cv2.CV_64F, laplacian_kernel)
laplacian_noisy_smooth = np.abs(laplacian_noisy_smooth)
laplacian_noisy_smooth = cv2.normalize(laplacian_noisy_smooth, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# 增强对比度
laplacian_origin = cv2.convertScaleAbs(laplacian_origin, alpha=3)
laplacian_noisy = cv2.convertScaleAbs(laplacian_noisy, alpha=3)
laplacian_noisy_smooth = cv2.convertScaleAbs(laplacian_noisy_smooth, alpha=3)
可视化
python
plt.figure(figsize=(10, 12))
plt.subplot(3, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('1. 原图', fontsize=11)
plt.axis('off')
plt.subplot(3, 2, 2)
plt.imshow(img_noisy, cmap='gray')
plt.title('2. 加椒盐噪声(比例0.05)', fontsize=11)
plt.axis('off')
plt.subplot(3, 2, 3)
plt.imshow(laplacian_origin, cmap='gray')
plt.title('3. Laplacian(原图)', fontsize=11)
plt.axis('off')
plt.subplot(3, 2, 4)
plt.imshow(laplacian_noisy, cmap='gray')
plt.title('4. Laplacian(加噪直接测→全是噪声)', fontsize=11)
plt.axis('off')
plt.subplot(3, 2, 5)
plt.imshow(img_noisy_smooth, cmap='gray')
plt.title('5. 加噪图先中值滤波', fontsize=11)
plt.axis('off')
plt.subplot(3, 2, 6)
plt.imshow(laplacian_noisy_smooth, cmap='gray')
plt.title('6. Laplacian(中值滤波后→清晰边缘)', fontsize=11)
plt.axis('off')
plt.tight_layout()
plt.subplots_adjust(wspace=0.1, hspace=0.2)
plt.show()


三、对Laplacian算子与一阶算子的总结
| 对比维度 | 一阶算子(Roberts/Prewitt/Sobel) | Laplacian算子 |
|---|---|---|
| 数学基础 | 一阶偏导数(梯度运算) | 二阶偏导数(拉普拉斯运算) |
| 检测目标 | 灰度变化的"速率"(相邻像素灰度差) | 灰度变化率的"变化"(灰度曲率) |
| 边缘形态 | 粗、亮、连续,对比度高 | 细、弱、仅保留核心,对比度低 |
| 方向特性 | 需分x/y方向计算,非各向同性 | 各向同性,一次检测所有方向边缘 |
| 抗噪性能 | 天然抗噪,对椒盐/高斯噪声抵抗力强 | 极度敏感,二阶微分放大噪声 |
| 预处理要求 | 无需额外处理,直接检测 | 必须滤波(椒盐→中值/高斯→高斯) |
| 边缘定位精度 | 较粗略 | 精准(仅响应边缘过零点) |
| 单独使用性 | 可独立使用,效果直观 | 极少单独使用,需配合其他操作 |
| 典型适用场景 | 实时检测、噪声环境、基础边缘提取 | 边缘精确定位、特征提取 |
- 一阶算子是"实用性优先"的边缘检测方案,胜在抗噪性强、实现简单,适合大部分工程化的基础边缘提取场景;
- Laplacian 算子是"精度优先"的边缘检测方案,胜在定位精准、各向同性,短板是抗噪性差,需依赖预处理;
- 实际应用中,两者常结合使用(如用 Laplacian 对一阶算子检测的边缘做细化),兼顾抗噪性与定位精度。
Ⅴ、Canny边缘检测
一、Canny算法的数学本质
Canny边缘检测不是单一算子,而是基于一阶微分的多步骤优化算法,核心目标是在"抑制噪声"与"精准定位边缘"之间取得最优平衡,其数学框架围绕"高斯平滑→一阶梯度计算→极值筛选→阈值判决"四层逻辑展开。
二、核心步骤的数学公式与推导
1. 高斯滤波(噪声抑制)
(1)高斯核数学表达式
Canny算法首先用二维高斯核对原始图像 I ( x , y ) I(x,y) I(x,y)做卷积平滑,高斯核的数学定义为:
G ( x , y ; σ ) = 1 2 π σ 2 exp ( − x 2 + y 2 2 σ 2 ) G(x,y;\sigma) = \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)
- σ \sigma σ:高斯标准差(Canny经典取值 σ = 1.4 \sigma=1.4 σ=1.4),决定平滑强度;
- x , y x,y x,y:高斯核内像素相对于中心的坐标(如3×3核的 x , y ∈ { − 1 , 0 , 1 } x,y \in \{-1,0,1\} x,y∈{−1,0,1})。
(2)平滑图像计算
滤波后的平滑图像 I σ ( x , y ) I_\sigma(x,y) Iσ(x,y)为原始图像与高斯核的二维卷积:
I σ ( x , y ) = I ( x , y ) ∗ G ( x , y ; σ ) I_\sigma(x,y) = I(x,y) * G(x,y;\sigma) Iσ(x,y)=I(x,y)∗G(x,y;σ)
- ∗ * ∗:二维卷积运算符;
- 作用:削弱图像中微小的灰度突变(噪声),避免后续梯度计算对噪声过度响应。
2. 一阶梯度计算(边缘强度与方向)
对平滑后的图像 I σ ( x , y ) I_\sigma(x,y) Iσ(x,y),计算一阶偏导数得到梯度(边缘的核心数学表征):
(1)x/y方向梯度(离散近似)
Canny采用Sobel核作为一阶偏导数的离散近似,计算x(水平)、y(垂直)方向梯度:
G x = ∂ I σ ∂ x = I σ ∗ − 1 0 1 − 2 0 2 − 1 0 1 G_x = \frac{\partial I_\sigma}{\partial x} = I_\sigma * \begin{bmatrix}-1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1\end{bmatrix} Gx=∂x∂Iσ=Iσ∗ −1−2−1000121
G y = ∂ I σ ∂ y = I σ ∗ − 1 − 2 − 1 0 0 0 1 2 1 G_y = \frac{\partial I_\sigma}{\partial y} = I_\sigma * \begin{bmatrix}-1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1\end{bmatrix} Gy=∂y∂Iσ=Iσ∗ −101−202−101
(2)梯度大小(边缘强度)
梯度大小表征像素点的边缘显著程度,公式为:
G ( x , y ) = G x 2 ( x , y ) + G y 2 ( x , y ) G(x,y) = \sqrt{G_x^2(x,y) + G_y^2(x,y)} G(x,y)=Gx2(x,y)+Gy2(x,y)
- 工程优化:为提升计算速度,可近似为 G ( x , y ) = ∣ G x ( x , y ) ∣ + ∣ G y ( x , y ) ∣ G(x,y) = |G_x(x,y)| + |G_y(x,y)| G(x,y)=∣Gx(x,y)∣+∣Gy(x,y)∣
(3)梯度方向(边缘走向)
梯度方向垂直于边缘方向,数学定义为:
θ ( x , y ) = arctan ( G y ( x , y ) G x ( x , y ) ) \theta(x,y) = \arctan\left(\frac{G_y(x,y)}{G_x(x,y)}\right) θ(x,y)=arctan(Gx(x,y)Gy(x,y))
- 取值范围: 0 ∘ , 180 ∘ 0\^\\circ, 180\^\\circ 0∘,180∘(仅关注相对方向,无正负歧义);
- 量化处理:实际计算中会将 θ \theta θ归为4个主方向( 0 ∘ 、 45 ∘ 、 90 ∘ 、 135 ∘ 0^\circ、45^\circ、90^\circ、135^\circ 0∘、45∘、90∘、135∘),简化后续极值筛选。
3. 非极大值抑制(边缘细化)
这是Canny算法实现"单像素宽度边缘"的核心,数学本质是沿梯度方向的局部最大值筛选:
(1)核心规则
对任意像素 ( x , y ) (x,y) (x,y),沿其梯度方向 θ ( x , y ) \theta(x,y) θ(x,y)选取两个邻域像素 P 1 P_1 P1、 P 2 P_2 P2,满足:
- 若 G ( x , y ) > G ( P 1 ) G(x,y) > G(P_1) G(x,y)>G(P1) 且 G ( x , y ) > G ( P 2 ) G(x,y) > G(P_2) G(x,y)>G(P2) → 该像素为梯度方向上的局部最大值,保留(判定为边缘);
- 否则 → 该像素为非最大值,抑制(置0,剔除)。
(2)方向匹配的邻域选取(4方向示例)
| 梯度方向 θ \theta θ | 邻域像素 P 1 P_1 P1、 P 2 P_2 P2位置 |
|---|---|
| 0 ∘ 0^\circ 0∘(水平) | 像素 ( x , y − 1 ) (x,y-1) (x,y−1)、 ( x , y + 1 ) (x,y+1) (x,y+1)(左、右) |
| 45 ∘ 45^\circ 45∘(对角线) | 像素 ( x − 1 , y − 1 ) (x-1,y-1) (x−1,y−1)、 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)(左上、右下) |
| 90 ∘ 90^\circ 90∘(垂直) | 像素 ( x − 1 , y ) (x-1,y) (x−1,y)、 ( x + 1 , y ) (x+1,y) (x+1,y)(上、下) |
| 135 ∘ 135^\circ 135∘(反对角线) | 像素 ( x − 1 , y + 1 ) (x-1,y+1) (x−1,y+1)、 ( x + 1 , y − 1 ) (x+1,y-1) (x+1,y−1)(右上、左下) |
4. 双阈值判决(边缘连接与噪声剔除)
通过高低两个阈值 T h i g h T_{high} Thigh、 T l o w T_{low} Tlow(经典比例 T h i g h : T l o w = 2 : 1 T_{high}:T_{low}=2:1 Thigh:Tlow=2:1)筛选有效边缘,数学规则为:
- 强边缘:若 G ( x , y ) ≥ T h i g h G(x,y) \geq T_{high} G(x,y)≥Thigh → 直接保留,判定为确定边缘;
- 弱边缘:若 T l o w < G ( x , y ) < T h i g h T_{low} < G(x,y) < T_{high} Tlow<G(x,y)<Thigh → 仅当该像素与强边缘连通时保留;
- 非边缘:若 G ( x , y ) ≤ T l o w G(x,y) \leq T_{low} G(x,y)≤Tlow → 直接剔除(置0)。
- 数学本质:用阈值分割结合连通性分析,平衡"边缘完整性"与"噪声剔除",避免单一阈值导致的边缘断裂或噪声残留。
三、单像素邻域的Canny边缘检测计算
题目背景:已知某3×3图像块(已完成高斯滤波, σ = 1.4 \sigma=1.4 σ=1.4)的灰度值如公式所示,计算中心像素 ( 1 , 1 ) (1,1) (1,1)是否为边缘像素。
I σ = 100 100 100 100 150 200 100 100 100 I_\sigma = \begin{bmatrix} 100 & 100 & 100 \\ 100 & 150 & 200 \\ 100 & 100 & 100 \end{bmatrix} Iσ= 100100100100150100100200100
- 解题要求:按Canny算法核心步骤完成计算,最终判定中心像素是否为边缘(双阈值取 T h i g h = 30 T_{high}=30 Thigh=30、 T l o w = 15 T_{low}=15 Tlow=15)。
1. 计算x/y方向梯度(Sobel核卷积)
- (1)Sobel_x核定义:
S o b e l x = − 1 0 1 − 2 0 2 − 1 0 1 Sobel_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} Sobelx= −1−2−1000121 - (2)中心像素 G x G_x Gx卷积计算:
G x ( 1 , 1 ) = ( − 1 × 100 ) + ( 0 × 100 ) + ( 1 × 100 ) + ( − 2 × 100 ) + ( 0 × 150 ) + ( 2 × 200 ) + ( − 1 × 100 ) + ( 0 × 100 ) + ( 1 × 100 ) = ( − 100 + 0 + 100 ) + ( − 200 + 0 + 400 ) + ( − 100 + 0 + 100 ) = 0 + 200 + 0 = 200 \begin{align*} G_x(1,1) &= (-1×100)+(0×100)+(1×100) + (-2×100)+(0×150)+(2×200) + (-1×100)+(0×100)+(1×100) \\ &= (-100+0+100) + (-200+0+400) + (-100+0+100) \\ &= 0 + 200 + 0 = 200 \end{align*} Gx(1,1)=(−1×100)+(0×100)+(1×100)+(−2×100)+(0×150)+(2×200)+(−1×100)+(0×100)+(1×100)=(−100+0+100)+(−200+0+400)+(−100+0+100)=0+200+0=200 - (3)Sobel_y核定义:
S o b e l y = − 1 − 2 − 1 0 0 0 1 2 1 Sobel_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix} Sobely= −101−202−101 - (4)中心像素 G y G_y Gy卷积计算:
G y ( 1 , 1 ) = ( − 1 × 100 ) + ( − 2 × 100 ) + ( − 1 × 100 ) + ( 0 × 100 ) + ( 0 × 150 ) + ( 0 × 200 ) + ( 1 × 100 ) + ( 2 × 100 ) + ( 1 × 100 ) = ( − 100 − 200 − 100 ) + ( 0 + 0 + 0 ) + ( 100 + 200 + 100 ) = − 400 + 0 + 400 = 0 \begin{align*} G_y(1,1) &= (-1×100)+(-2×100)+(-1×100) + (0×100)+(0×150)+(0×200) + (1×100)+(2×100)+(1×100) \\ &= (-100-200-100) + (0+0+0) + (100+200+100) \\ &= -400 + 0 + 400 = 0 \end{align*} Gy(1,1)=(−1×100)+(−2×100)+(−1×100)+(0×100)+(0×150)+(0×200)+(1×100)+(2×100)+(1×100)=(−100−200−100)+(0+0+0)+(100+200+100)=−400+0+400=0
2. 计算梯度大小与梯度方向
- (1)梯度大小 G G G(边缘强度):
G ( 1 , 1 ) = G x 2 ( 1 , 1 ) + G y 2 ( 1 , 1 ) = 200 2 + 0 2 = 200 G(1,1) = \sqrt{G_x^2(1,1) + G_y^2(1,1)} = \sqrt{200^2 + 0^2} = 200 G(1,1)=Gx2(1,1)+Gy2(1,1) =2002+02 =200 - (2)梯度方向 θ \theta θ(边缘走向):
θ ( 1 , 1 ) = arctan ( G y ( 1 , 1 ) G x ( 1 , 1 ) ) = arctan ( 0 ) = 0 ∘ ( 水平方向 ) \theta(1,1) = \arctan\left(\frac{G_y(1,1)}{G_x(1,1)}\right) = \arctan(0) = 0^\circ \ (\text{水平方向}) θ(1,1)=arctan(Gx(1,1)Gy(1,1))=arctan(0)=0∘ (水平方向)
3. 非极大值抑制(边缘细化)
- 梯度方向为 0 ∘ 0^\circ 0∘(水平),选取中心像素水平方向邻域:左像素 ( 1 , 0 ) (1,0) (1,0)、右像素 ( 1 , 2 ) (1,2) (1,2);
- 邻域像素梯度大小: G ( 1 , 0 ) = 0 G(1,0)=0 G(1,0)=0, G ( 1 , 2 ) = 0 G(1,2)=0 G(1,2)=0;
- 判定: G ( 1 , 1 ) = 200 > G ( 1 , 0 ) G(1,1)=200 > G(1,0) G(1,1)=200>G(1,0)且 200 > G ( 1 , 2 ) 200 > G(1,2) 200>G(1,2),为梯度方向局部最大值,保留该像素。
4. 双阈值判决(最终边缘判定)
- 双阈值定义: T h i g h = 30 T_{high}=30 Thigh=30, T l o w = 15 T_{low}=15 Tlow=15;
- 判定规则: G ( 1 , 1 ) = 200 > T h i g h = 30 G(1,1)=200 > T_{high}=30 G(1,1)=200>Thigh=30,属于强边缘像素;
- 最终结论:中心像素 ( 1 , 1 ) (1,1) (1,1)为有效边缘像素。
四、代码实现
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
IMG_PATH = "test.jpg"
LOW_THRESH = 50
HIGH_THRESH = 150
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
img = cv2.imread(IMG_PATH)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
实现Sobel
python
def sobel_with_threshold(gray, low_thresh, high_thresh):
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel_mag = np.hypot(sobel_x, sobel_y)
sobel_mag = (sobel_mag / sobel_mag.max()) * 255
sobel_result = np.zeros_like(sobel_mag, dtype=np.uint8)
sobel_result[(sobel_mag >= low_thresh) & (sobel_mag <= high_thresh)] = 255
return sobel_result
计算Sobel/Canny
python
sobel_result = sobel_with_threshold(gray, LOW_THRESH, HIGH_THRESH)
canny_original = cv2.Canny(gray, LOW_THRESH, HIGH_THRESH)
优化Canny
python
def optimize_canny(gray, low_coeff=0.2, high_coeff=0.4, morph_size=2):
# 1. 自适应阈值(核心:通过系数调控纹理过滤强度)
mean = np.mean(gray)
std = np.std(gray)
low = int(max(0, mean - low_coeff * std)) # 低阈值系数越大,过滤越狠
high = int(min(255, mean + high_coeff * std))# 高阈值系数越大,过滤越狠
# 2. Canny+形态学:去除残留纹理
canny = cv2.Canny(gray, low, high)
canny = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, np.ones((morph_size, morph_size), np.uint8))
return canny, low, high
# 调用(无滤波,仅阈值+形态学优化)
canny_optimized, opt_low, opt_high = optimize_canny(gray)
可视化
python
plt.figure(figsize=(20, 8))
plt.subplot(2, 2, 1)
plt.imshow(gray, cmap='gray')
plt.title('1. 灰度原图(含背景细碎纹理)', fontsize=11)
plt.axis('off')
plt.subplot(2, 2, 2)
plt.imshow(sobel_result, cmap='gray')
plt.title(f'2. Sobel边缘检测(阈值:{LOW_THRESH}/{HIGH_THRESH})', fontsize=11)
plt.axis('off')
plt.subplot(2, 2, 3)
plt.imshow(canny_original, cmap='gray')
plt.title(f'3. 原始Canny(阈值:{LOW_THRESH}/{HIGH_THRESH})', fontsize=11)
plt.axis('off')
plt.subplot(2, 2, 4)
plt.imshow(canny_optimized, cmap='gray')
plt.title(f'4. 优化后Canny(阈值:{opt_low}/{opt_high})', fontsize=11)
plt.axis('off')
plt.tight_layout()
plt.show()

五、cv2.Canny()函数
1. 函数原型
python
cv2.Canny(image, threshold1, threshold2, apertureSize=None, L2gradient=None)
2. 核心参数说明
| 参数名 | 核心作用 | 常用取值 |
|---|---|---|
| image | 输入图像,必须为单通道灰度图(uint8类型) | 灰度图变量(如gray) |
| threshold1 | 低阈值,双阈值判决的下限,用于判定弱边缘 | 50/80(建议为高阈值1/2~1/3) |
| threshold2 | 高阈值,双阈值判决的上限,用于判定强边缘 | 150/200 |
| apertureSize | Sobel卷积核大小,用于梯度计算 | 3(默认值,可选3/5/7) |
| L2gradient | 梯度计算方式:True=L2范数(√(Gx²+Gy²)),False=L1范数( | Gx |
3. 关键注意事项
- 低阈值过低:背景细碎纹理会被大量检测,干扰核心边缘;
- 高阈值过高:核心边缘细节会丢失,仅保留强边缘;
- 卷积核过大:梯度计算精度降低,边缘定位会偏移。
六、Canny 与 Sobel 边缘检测对比
| 对比维度 | Sobel算子 | Canny算法 |
|---|---|---|
| 核心定位 | 快速计算边缘强度 | 精准提取完整边缘 |
| 边缘形态 | 宽条带,定位精度低 | 单像素,定位精度极高 |
| 背景抗干扰性 | 强(忽略细碎纹理) | 弱(易提取背景纹理) |
| 灵敏度 | 低(丢失弱边缘) | 高(保留弱边缘) |
| 运行效率 | 极高(仅卷积运算) | 中等(多步骤优化) |
| 适用场景 | 实时检测、纹理多场景 | 高精度提取、背景简洁场景 |
- Sobel:主打"快+抗干扰",适合实时、纹理多、低精度需求场景;
- Canny:主打"准+完整",适合高精度、背景简洁场景,需优化来降低背景纹理干扰;
- 取舍原则:精度优先选Canny(需优化),效率/抗干扰优先选Sobel。
Ⅵ、总结
| 检测方法 | 核心原理 | 边缘特征 | 抗背景纹理干扰性 | 精度/效率 | 适用场景 |
|---|---|---|---|---|---|
| Roberts算子 | 2×2模板计算对角线方向差分,一阶微分算子 | 边缘定位较准,但对噪声敏感,边缘易断裂 | 弱(无降噪步骤) | 精度中等、效率极高 | 低噪声、高对比度的简单场景 |
| Prewitt算子 | 3×3模板计算水平/垂直方向差分,一阶微分算子 | 边缘宽且平滑,对噪声抑制优于Roberts,但定位精度略低 | 中(通过模板平均抑制轻微噪声) | 精度中等、效率高 | 噪声中等、需平滑边缘的场景 |
| Sobel算子 | 3×3带权重模板计算梯度,一阶微分算子 | 边缘宽,对噪声抑制优于Prewitt,定位精度中等 | 强(权重模板抑制高频噪声) | 精度中等、效率极高 | 实时检测、背景纹理多、低精度需求场景 |
| Laplacian算子 | 二阶微分算子,检测边缘变化率 | 边缘定位精准,但对噪声极敏感,易产生伪边缘 | 极弱(二阶放大噪声) | 精度高、效率中等 | 低噪声、需精准定位边缘细节的场景(通常需配合高斯滤波) |
| Canny算法 | 高斯滤波→梯度计算→非极大值抑制→双阈值判决+连通性分析 | 单像素边缘,定位精度极高,边缘完整且连续性好 | 中-弱(高灵敏度易提取纹理,需额外优化) | 精度极高、效率中等 | 背景简洁、需高精度单像素边缘的工业级场景 |
核心结论
- 从效率到精度排序:Roberts > Sobel > Prewitt > Laplacian > Canny
- 抗噪声能力排序:Sobel > Prewitt > Canny > Roberts > Laplacian
- 场景选择建议
- 追求极致速度:Roberts、Sobel
- 平衡抗噪与效率:Prewitt
- 需精准边缘但噪声可控:Laplacian(配合高斯滤波)
- 工业级高精度需求:Canny(需优化抗纹理干扰)