OpenCV 从零学习 · 第 5 讲:图像滤波与平滑
照片总有噪点——暗光拍摄的颗粒、扫描件的脏点、传输中的杂讯。这一讲学 4 种最常用的"平滑/滤波"函数。它们的核心差别只有一句话:用周围哪些像素、按什么权重,给当前像素重新算一个值。不同算法"怎么算"差别很大:有的糊整张图、有的专治黑白点、有的去噪还保住边缘。我会用同一张带噪图做对比,让你看清楚该选谁。
0. 先分清两种噪声(决定你用哪种滤波)
| 噪声类型 | 长什么样 | 该用什么 |
|---|---|---|
| 高斯噪声 | 整张图细密颗粒、亮度轻微抖动 | 均值 / 高斯滤波 |
| 椒盐噪声(脉冲) | 随机出现的纯黑/纯白孤立点 | 中值滤波 ★ |
1. cv2.blur —— 均值滤波(最简单)
函数签名
dst = cv2.blur(src, ksize)
# ksize: 核大小 (宽, 高), 如 (5, 5); 越大越平滑也越糊
原理:把当前像素和它周围 ksize 窗口内的所有像素取平均值替换自己。
场景案例 A:监控截图轻微噪点先压一下
整体细密颗粒用均值先稳一稳,代价是图会变"肉"一点:
整体细密颗粒用均值先稳一稳,代价是图会变"肉"一点:
smooth = cv2.blur(img, (5, 5))
坑:均值把"边缘"也一起平均了 → 整张图变糊;而且它对椒盐黑点无效——黑点被周围平均成"灰斑",依然看得见。
2. cv2.boxFilter —— 盒式滤波(了解即可)
cv2.boxFilter(src, ddepth, ksize, normalize=True)
当 normalize=True 时,它和 cv2.blur 完全等价(就是做平均)。normalize=False 时不除以像素数、直接求和,uint8 会溢出成乱码,一般不用。记住"cv2.blur 就是归一化的盒式滤波"即可。
3. cv2.GaussianBlur —— 高斯滤波(最常用)
函数签名
dst = cv2.GaussianBlur(src, ksize, sigmaX)
# ksize : (宽, 高), 必须是正奇数! 如 (3,3) (5,5)
# sigmaX: 高斯核在 X 方向的标准差; 填 0 则由 ksize 自动推算
原理:也是取周围像素平均,但离中心越近权重越大(按高斯分布),所以结果比均值更自然、不那么"平"。
新手必踩坑:
ksize 两个维度都必须是奇数。传 (4,4) 会直接报错 "ksize ... must be positive odd"。
场景案例 B:边缘检测前的标准预处理
在第 7 讲 Canny 找边缘之前,先高斯去噪能大幅减少"假边缘"。这是 OpenCV 官方流程的固定搭配:
在第 7 讲 Canny 找边缘之前,先高斯去噪能大幅减少"假边缘"。这是 OpenCV 官方流程的固定搭配:
denoised = cv2.GaussianBlur(img, (5, 5), 0) # sigmaX=0 → 由 (5,5) 自动算标准差
场景案例 C:人像磨皮的前奏
高斯模糊后的图叠加回原图(高斯金字塔/双边)可做人脸磨皮,比均值更不容易把五官抹平。
高斯模糊后的图叠加回原图(高斯金字塔/双边)可做人脸磨皮,比均值更不容易把五官抹平。
4. cv2.medianBlur —— 中值滤波(椒盐噪声克星)
函数签名
dst = cv2.medianBlur(src, ksize)
# ksize: 单个奇数整数, 如 3 / 5 / 7 (注意: 不是元组!)
原理:把窗口内所有像素值排序取中间值替换中心像素。因为黑/白孤立点的数值是极值,排序后落在两头,中值直接把它们"踢出",因此能消除椒盐点,同时基本不模糊边缘。
场景案例 D:扫描件/老照片的脏点、雪花点
这类"随机黑白点"用均值/高斯只会糊成灰斑,中值能干净利落去掉:
这类"随机黑白点"用均值/高斯只会糊成灰斑,中值能干净利落去掉:
clean = cv2.medianBlur(img, 5) # ksize 是单个奇数, 不是 (5,5)
坑:
medianBlur 的 ksize 是整数不是元组。写成 medianBlur(img, (5,5)) 会报错。
5. cv2.bilateralFilter —— 双边滤波(去噪又保边)
函数签名
dst = cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)
# d : 邻域直径; 越大越慢, 一般 ≤ 9 (大图设 5 即可)
# sigmaColor: 颜色差多大算"同一类"; 越小越只平滑很接近的颜色
# sigmaSpace: 空间上多远算邻居; 越大越远的像素也参与平滑
原理:同时考虑两个权重——①空间近不近(和普通高斯一样);②颜色像不像。跨边缘时两侧颜色差很大,第②个权重就很小,于是边缘不被平滑,达到"去噪同时保边"。
场景案例 E:人像磨皮 / 美颜
皮肤上的噪点被磨掉,但眼睛、嘴、轮廓这些边缘保留——这是磨皮算法的核心思想:
皮肤上的噪点被磨掉,但眼睛、嘴、轮廓这些边缘保留——这是磨皮算法的核心思想:
skin = cv2.bilateralFilter(face, 9, 75, 75)
坑:双边滤波比前面几种慢很多(每个像素都要算颜色相似度)。大图别把
d 设太大,否则卡死。
6. 对比实验:同一张椒盐噪图,三种滤波差多少
下面这段代码可复现——造一张 5% 椒盐点的图,再分别用三种滤波,肉眼就能看出中值的优势:
import numpy as np, cv2
# 造椒盐噪声: 随机 5% 像素变成纯白点
sp = img.copy()
mask = np.random.rand(*img.shape[:2]) < 0.05
sp[mask] = 255
blur_c = cv2.blur(sp, (5, 5)) # 黑/白点被糊成灰斑, 仍在
gauss_c = cv2.GaussianBlur(sp, (5, 5), 0) # 同样糊成灰斑
median_c = cv2.medianBlur(sp, 5) # 中值: 黑白点直接消失, 边缘还在
# 若是高斯噪声(细密颗粒), 则反过来: 中值帮助不大, 高斯更合适
g = img.astype(np.float32)
g += np.random.normal(0, 25, g.shape) # 叠加高斯噪声, 标准差 25
g = np.clip(g, 0, 255).astype(np.uint8)
g_gauss = cv2.GaussianBlur(g, (5, 5), 0) # 合适
g_median = cv2.medianBlur(g, 5) # 对高斯噪声帮助有限
| 滤波 | 去高斯噪声 | 去椒盐噪声 | 保边 | 速度 |
|---|---|---|---|---|
| blur 均值 | 一般 | 差 | 差(糊) | 快 |
| GaussianBlur 高斯 | 好 | 一般 | 差(糊) | 快 |
| medianBlur 中值 | 一般 | 最好 | 好 | 中 |
| bilateral 双边 | 好 | 好 | 最好 | 慢 |
7. 本讲要点清单
- 均值
cv2.blur(src, (w,h)):简单平均,会糊整图、对椒盐点无效。 boxFilter归一化后等价于 blur,一般用不到。- 高斯
cv2.GaussianBlur(src, (w,h), sigmaX):ksize 必须奇数;去高斯噪声首选,也是边缘检测前预处理标配。 - 中值
cv2.medianBlur(src, ksize):ksize 是整数不是元组;椒盐噪声克星,去点又保边。 - 双边
cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace):去噪且保边(磨皮核心),但慢,d 别太大。 - 选型口诀:高斯噪声→高斯;椒盐点→中值;既要去噪又要保边→双边。
✅ 下一讲预告:去噪之后怎么把目标"抠成黑白"?第 6 讲:图像阈值化(全局 / 自适应 / OTSU)。
