边缘检测:基础算子到高级边缘提取【计算机视觉】
- [边缘检测(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 -2*60 -70) + (30 + 2*40 + 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)实验流程
1. **图像预处理**:读取灰度图像(无本地图像时自动生成 500×500 随机灰度测试图),添加比例为 0.02 的椒盐噪声,构建「原图 + 加噪图」双测试集;
2. **算子实现**:基于卷积原理实现三类算子,通过对应模板计算像素水平/垂直方向灰度差,结合勾股定理合并边缘强度,并将结果归一化至 0-255 灰度范围;
3. **可视化对比**:采用 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)结论
1. **模板尺寸决定基础抗噪性**:3×3 模板(Prewitt/Sobel)因参考更多像素点,抗噪性显著优于 2×2 模板的 Roberts 算子;
2. **加权策略提升检测精度**:Sobel 算子通过给中间像素赋予 2 倍权重,强化了核心像素对边缘的贡献,在保持检测速度的同时,精度和抗噪性均优于等权的 Prewitt 算子;
3. **工程应用建议**: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方向计算,非各向同性 | 各向同性,一次检测所有方向边缘 |
| 抗噪性能 | 天然抗噪,对椒盐/高斯噪声抵抗力强 | 极度敏感,二阶微分放大噪声 |
| 预处理要求 | 无需额外处理,直接检测 | 必须滤波(椒盐→中值/高斯→高斯) |
| 边缘定位精度 | 较粗略 | 精准(仅响应边缘过零点) |
| 单独使用性 | 可独立使用,效果直观 | 极少单独使用,需配合其他操作 |
| 典型适用场景 | 实时检测、噪声环境、基础边缘提取 | 边缘精确定位、特征提取 |
1. 一阶算子是"实用性优先"的边缘检测方案,胜在抗噪性强、实现简单,适合大部分工程化的基础边缘提取场景;
2. Laplacian 算子是"精度优先"的边缘检测方案,胜在定位精准、各向同性,短板是抗噪性差,需依赖预处理;
3. 实际应用中,两者常结合使用(如用 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)筛选有效边缘,数学规则为:
1. 强边缘:若 G ( x , y ) ≥ T h i g h G(x,y) \\geq T_{high} G(x,y)≥Thigh → 直接保留,判定为确定边缘;
2. 弱边缘:若 T l o w \< G ( x , y ) \< T h i g h T_{low} \< G(x,y) \< T_{high} Tlow\