4. 图像预处理(二):高斯滤波、中值滤波、均值滤波、双边滤波、图像锐化
各位同学,欢迎来到预处理第二讲。
上一节我们聊了灰度化和直方图均衡化,说白了就是让图像“看起来更清楚”。但现实中的车牌图像,往往伴随着各种噪声——摄像头传感器噪点、光照不均、甚至雨雪天气的干扰。这时候,滤波就该上场了。
我个人习惯把滤波理解为“给图像做美容”。不同的滤波器,就像不同的护肤品,有的负责去痘印(噪声),有的负责提亮轮廓(锐化)。今天我们就来逐一拆解。
4.1 均值滤波:最简单的平滑方式
均值滤波的原理非常直观:用一个窗口(比如3x3)在图像上滑动,取窗口内所有像素的平均值,替换中心像素的值。
说白了,就是“和稀泥”。
核心公式:
g(x,y) = (1 / (m × n)) × Σ f(i,j)
其中 m×n 是核的大小,f(i,j) 是邻域内的像素值。
嗯,这里要注意:均值滤波虽然简单,但副作用也很明显——它会模糊边缘。我在做车牌定位时,如果直接用均值滤波,车牌字符的边缘会变得模糊,导致后续分割困难。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('car_plate.jpg', cv2.IMREAD_GRAYSCALE)
# 均值滤波,核大小为5x5
blurred = cv2.blur(img, (5, 5))
# 或者用方框滤波(归一化)
box_filter = cv2.boxFilter(img, -1, (5, 5), normalize=True)
我的经验:均值滤波适合处理高斯噪声,但对椒盐噪声效果很差。而且核越大,图像越模糊。一般车牌预处理中,核大小不要超过5x5,否则字符会糊成一团。
4.2 高斯滤波:更自然的平滑
高斯滤波和均值滤波类似,但权重不同。它根据高斯分布给窗口内的像素分配权重——离中心越近,权重越大;越远,权重越小。
为什么会这样?因为自然图像中,相邻像素的相关性更强。高斯滤波更符合这个规律,所以平滑效果更自然。
高斯核示例(3x3,σ=0.8):
| 0.057 | 0.125 | 0.057 |
| 0.125 | 0.273 | 0.125 |
| 0.057 | 0.125 | 0.057 |
所有权重之和为1,中心像素权重最大。
# 高斯滤波,核大小5x5,标准差1.0
gaussian = cv2.GaussianBlur(img, (5, 5), 1.0)
# 如果sigma设为0,OpenCV会自动根据核大小计算
gaussian_auto = cv2.GaussianBlur(img, (5, 5), 0)
我记得有一次做夜间车牌识别,图像噪点特别多。均值滤波搞不定,高斯滤波一上,效果立竿见影。但要注意,高斯滤波对椒盐噪声的抑制能力依然有限。
避坑指南:高斯滤波的核大小必须是奇数。我曾经因为用了偶数核,程序直接报错,排查了半天才发现是这个问题。
4.3 中值滤波:椒盐噪声的克星
中值滤波的原理很简单:取窗口内所有像素的中位数,替换中心像素。
你想想看,如果图像中有孤立的亮点或暗点(椒盐噪声),均值滤波会把它们“扩散”到周围,而中值滤波直接把它们剔除掉。因为中位数不受极端值影响。
# 中值滤波,核大小为5
median = cv2.medianBlur(img, 5)
中值滤波对椒盐噪声的效果,可以说是“立竿见影”。我在处理老旧摄像头拍摄的车牌时,经常遇到密密麻麻的椒盐噪声,中值滤波一上,画面瞬间干净了。
我的习惯:车牌预处理中,我通常会先用一次中值滤波(核大小3或5),再用高斯滤波做精细平滑。这个组合拳效果很好。
但中值滤波也有缺点:计算量大,尤其是核比较大的时候。而且它对高斯噪声的效果不如高斯滤波。
4.4 双边滤波:保边去噪的利器
前面几种滤波都会模糊边缘。但车牌识别中,边缘信息至关重要——字符的轮廓全靠边缘来区分。
双边滤波就是为了解决这个问题而生的。它同时考虑两个因素:
- 空间距离:离中心越近,权重越大(和高斯滤波一样)
- 像素值差异:像素值差异越大,权重越小(边缘处像素值变化大,所以权重小)
说白了,双边滤波在平滑的同时,会“保护”边缘不被模糊。
# 双边滤波
# d:邻域直径
# sigmaColor:颜色空间标准差
# sigmaSpace:空间坐标标准差
bilateral = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
参数调优建议:
- d:一般设为5-9,太大计算量剧增
- sigmaColor:值越大,颜色差异大的像素也会被平滑(边缘保护减弱)
- sigmaSpace:值越大,空间上更远的像素也会影响当前像素
我曾经在车牌去雾场景中用过双边滤波,效果出奇的好。但要注意,双边滤波的计算速度很慢,实时处理时慎用。
注意:双边滤波的参数调起来比较玄学。我建议先固定d=9,然后调整sigmaColor和sigmaSpace,观察边缘保留效果。如果边缘模糊了,就减小sigmaColor。
4.5 图像锐化:让字符更清晰
滤波之后,图像平滑了,但可能也变模糊了。这时候需要锐化来“拉回”边缘的清晰度。
锐化的原理很简单:提取图像的边缘信息,然后叠加回原图。
锐化公式:
sharpened = original + α × (original - blurred)
其中 α 是锐化强度,blurred 是平滑后的图像。
说白了,就是“原图加上细节”。
# 方法一:使用拉普拉斯算子
laplacian = cv2.Laplacian(img, cv2.CV_64F)
sharpened_laplacian = cv2.convertScaleAbs(img - laplacian)
# 方法二:使用自定义核
kernel_sharpen = np.array([
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
])
sharpened_custom = cv2.filter2D(img, -1, kernel_sharpen)
# 方法三:非锐化掩模(Unsharp Masking)
blurred = cv2.GaussianBlur(img, (0, 0), 3)
sharpened_um = cv2.addWeighted(img, 1.5, blurred, -0.5, 0)
我的推荐:非锐化掩模(Unsharp Masking)效果最可控。调整alpha和beta值,可以精细控制锐化强度。我一般用alpha=1.5,beta=-0.5。
锐化过度会怎样?会出现“光晕”现象,边缘处出现白边或黑边。嗯,这个要把握好度。
4.6 实战组合:车牌预处理流水线
在实际项目中,我不会只用一种滤波。下面是我常用的预处理流水线:
def preprocess_plate(img):
# 1. 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 中值滤波去椒盐噪声
median = cv2.medianBlur(gray, 3)
# 3. 高斯滤波去高斯噪声
gaussian = cv2.GaussianBlur(median, (3, 3), 0)
# 4. 锐化增强字符边缘
blurred = cv2.GaussianBlur(gaussian, (0, 0), 3)
sharpened = cv2.addWeighted(gaussian, 1.5, blurred, -0.5, 0)
return sharpened
总结一下各滤波器的适用场景:
| 滤波器 | 适用噪声 | 边缘保留 | 计算速度 | 推荐场景 |
|---|---|---|---|---|
| 均值滤波 | 高斯噪声 | 差 | 快 | 简单预处理 |
| 高斯滤波 | 高斯噪声 | 较差 | 快 | 通用平滑 |
| 中值滤波 | 椒盐噪声 | 较好 | 中等 | 去噪首选 |
| 双边滤波 | 各类噪声 | 优秀 | 慢 | 保边去噪 |
| 锐化 | 不适用 | 增强边缘 | 快 | 字符增强 |
好了,这一节的内容就到这里。下一节我们会聊聊形态学操作——膨胀、腐蚀、开闭运算,这些在车牌字符分割中非常有用。
记住,滤波没有银弹。不同的场景用不同的滤波器,甚至组合使用,才是工程实践中的正确姿势。