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.0570.1250.057
0.1250.2730.125
0.0570.1250.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

总结一下各滤波器的适用场景:

滤波器 适用噪声 边缘保留 计算速度 推荐场景
均值滤波 高斯噪声 简单预处理
高斯滤波 高斯噪声 较差 通用平滑
中值滤波 椒盐噪声 较好 中等 去噪首选
双边滤波 各类噪声 优秀 保边去噪
锐化 不适用 增强边缘 字符增强

好了,这一节的内容就到这里。下一节我们会聊聊形态学操作——膨胀、腐蚀、开闭运算,这些在车牌字符分割中非常有用。

记住,滤波没有银弹。不同的场景用不同的滤波器,甚至组合使用,才是工程实践中的正确姿势。