6  空间滤波

如果你把工业相机对准一块静止的、被均匀照亮的灰板,连续采集两帧图像并相减,你会发现差值并不是零:每个像素都在一个小范围内随机跳动。这就是图像噪声(image noise)。它来自传感器的暗电流与读出电路、来自为弥补照度不足而调高的模拟增益、来自电源纹波引起的照明波动,有时还来自镜头或保护玻璃上的灰尘。在实验室里这些扰动也许无伤大雅,但在产线上,噪声会直接撼动后续算法的根基:边缘定位会在噪声处产生虚假响应,阈值分割会冒出成片的碎斑,亚像素测量的重复精度会随之恶化。因此,空间滤波(spatial filtering)几乎总是定位与测量之前的第一道工序——在像素邻域内做一次恰当的”平均”,把随机扰动压下去,同时尽量保住我们真正关心的结构。

本章使用一幅合成的测试场景来贯穿所有实验:均匀的亮背景上有一个暗矩形(提供阶跃边缘),上方还有一条仅 2 个像素宽的细亮线(模拟细小结构,例如一道划痕或一根引线)。图 6.1 展示了原始场景,以及叠加两种典型噪声后的样子。

(a) 原始场景
(b) 高斯噪声
(c) 椒盐噪声
图 6.1: 合成测试场景及两种典型噪声。(a) 干净场景:亮背景、暗矩形与一条 2 px 宽的细亮线;(b) 叠加标准差 \(\sigma=18\) 的高斯噪声,整幅图像呈现细密的颗粒感;(c) 叠加 4% 的椒盐噪声,散布着孤立的纯黑与纯白坏点,其余像素完好。

6.1 图像噪声模型

要选对滤波器,先要弄清楚噪声长什么样。机器视觉中最常见的模型是加性高斯噪声(additive Gaussian noise):观测图像 \(g\) 等于真实图像 \(f\) 加上一个随机扰动,

\[ g[n,m] = f[n,m] + \eta[n,m], \qquad \eta \sim \mathcal{N}(0, \sigma^2). \]

每个像素都被污染,但污染量通常不大——大部分扰动落在 \(\pm 2\sigma\) 以内。它的物理来源是传感器的暗电流噪声、读出噪声以及放大增益带来的电子噪声:这些因素是大量微小独立扰动的叠加,按中心极限定理,其总和趋于高斯分布。图 6.1 (b) 正是这种情形——图像处处”起毛”,但没有哪个像素被彻底破坏。

另一类常见噪声是椒盐噪声(salt-and-pepper noise),又称脉冲噪声(impulse noise):少数像素被随机替换成最小值(“胡椒”,0)或最大值(“盐”,255),其余像素完全不受影响。它的来源包括传感器坏点、相机到工控机之间的传输误码,以及某些强反光表面上镜面反射造成的孤立过曝点。图 6.1 (c) 中那些散落的黑白小点就是典型的椒盐噪声——注意被污染的像素与真实值毫无关系,误差幅度可以高达整个灰度范围。

为什么两种噪声需要不同的滤波器?高斯噪声是”人人都错一点点”,把邻域平均起来即可让误差互相抵消;椒盐噪声是”少数人错得离谱”,平均反而会把离谱的值扩散给邻居,正确的做法是把离群值剔除——这正是中值滤波所做的事。

这两种模型的差别绝不只是数学趣味:它直接决定了滤波器的选择。对高斯噪声而言,每个像素的观测值都”大致正确”,把邻域内的值平均起来,独立的零均值误差会相互抵消,估计随之改善——线性滤波是天然的选择。对椒盐噪声而言,被污染的像素携带的不是”带误差的信息”而是纯粹的垃圾,把垃圾纳入平均只会污染原本干净的邻居;我们需要的是能识别并丢弃离群值的非线性手段。本章后面的实验会反复印证这一点。

6.2 线性滤波与卷积

线性空间滤波的统一数学形式是卷积(convolution)。给定输入图像 \(f\) 和一个小的权重模板——称为卷积核(kernel)\(h\),输出图像为

\[ g[n,m] = \sum_{k,l} f[n-k,\, m-l]\, h[k,l]. \]

直观地说,输出在 \((n,m)\) 处的值,是以该点为中心的一小块输入邻域与核权重的加权和。核决定了滤波器的全部性格。

最朴素的核是均值滤波(mean filter)核:一个 \(K\times K\) 的窗口,每个权重都等于 \(1/K^2\)。例如 \(3\times 3\) 均值核为

\[ h = \frac{1}{9} \begin{bmatrix} 1 & 1 & 1\\ 1 & 1 & 1\\ 1 & 1 & 1 \end{bmatrix}. \]

注意系数前的归一化因子:核所有权重之和称为滤波器的直流增益(DC gain)。把权重归一化使其和为 1,意味着滤波一幅恒定灰度的图像会原样输出——图像的平均亮度不被改变。这一点在测量场合尤其重要:如果滤波顺手把整体灰度抬高或压低了,后续依赖灰度阈值或灰度插值的算法都会被牵连。

均值核对窗口内所有像素一视同仁,而高斯滤波(Gaussian filter)按距离递减地分配权重——离中心越远的像素,对输出的影响越小:

\[ h[n,m] = \frac{1}{2\pi\sigma^2}\exp\!\Big(-\frac{n^2+m^2}{2\sigma^2}\Big). \]

参数 \(\sigma\) 控制平滑的空间尺度:\(\sigma\) 越大,参与平均的有效邻域越宽,去噪越强,但图像也越模糊。与均值核相比,高斯核的频率响应单调下降、没有旁瓣振铃,平滑效果更”干净”,因此是线性去噪的默认选择。在离散实现中,我们对连续高斯函数采样后再除以采样值之和,以保证直流增益恰好为 1。

实践中还有两个工程要点。第一,核尺寸与 \(\sigma\) 要匹配。高斯函数理论上无限延伸,但在 \(3\sigma\) 之外幅值仅约为中心值的 1%,故通常按 \(K \approx 6\sigma\)(取最近的奇数)截断:\(\sigma=1.2\)\(7\times 7\) 左右的核就足够了。核取得太小会把高斯”削肩”,太大则白白浪费计算。第二,高斯核是可分离的(separable):二维高斯恰好等于一维水平高斯与一维垂直高斯的乘积,因此一次二维卷积可以拆成先做一遍水平一维卷积、再做一遍垂直一维卷积,结果完全相同。

可分离性的收益:\(K\times K\) 的二维核每个像素要做 \(O(K^2)\) 次乘加,拆成两个一维核后只需 \(O(2K)\) 次。\(K=7\) 时是 49 比 14,\(K=15\) 时是 225 比 30——核越大省得越多。商用视觉库的高斯滤波内部几乎都这样实现。

6.3 非线性滤波

线性滤波有一个绕不过去的局限:它无法区分”噪声”与”边缘”——两者都是高频成分,要压制前者就必然钝化后者。非线性滤波器跳出了加权平均的框架,从而获得了线性方法做不到的能力。

中值滤波(median filter)是其中最重要的一员。它不做加权和,而是把窗口内的像素值排序,取中位数作为输出:

对输出图像的每个像素 (n, m):
    取以 (n, m) 为中心的 K×K 邻域内全部像素值
    将这些值从小到大排序
    输出 ← 排序后位于正中间的那个值(中位数)

中位数对离群值天然免疫:窗口里混入几个 0 或 255 的坏点,只要它们占不到半数,排序后就被挤到队列两端,根本轮不到当输出。这正是椒盐噪声的克星。同时,中值滤波跨过一条阶跃边缘时,窗口内多数像素来自边缘的某一侧,输出直接取那一侧的代表值——边缘保持锐利,不会像线性滤波那样被拖出一条过渡带。它的代价我们留到实验一节再揭示。

双边滤波(bilateral filter)(Tomasi 和 Manduchi 1998) 则把”保边”的思想引入了加权平均的框架。它给每个邻居 \(\mathbf q\) 分配的权重是两个高斯因子的乘积:

\[ g[\mathbf p] = \frac{1}{W_{\mathbf p}} \sum_{\mathbf q \in S} G_{\sigma_s}\!\big(\|\mathbf p-\mathbf q\|\big)\; G_{\sigma_r}\!\big(|f[\mathbf p]-f[\mathbf q]|\big)\; f[\mathbf q], \]

其中 \(W_{\mathbf p}\) 是权重之和(保证直流增益为 1),\(G_{\sigma_s}\) 是空间域高斯,\(G_{\sigma_r}\) 是灰度域(range)高斯。直觉非常清楚:一个邻居要想对输出有发言权,必须同时满足两个条件——离得近(空间高斯给高权)且长得像(灰度与中心像素接近,灰度高斯给高权)。在平坦区域,邻居们灰度相近,双边滤波退化为普通高斯平滑,噪声被有效抑制;在边缘附近,对侧像素灰度差大,灰度高斯把它们的权重压到近零,平均只在边缘同侧进行——边缘因此原封不动。\(\sigma_r\) 划定了”自己人”的界线:灰度差小于约 \(2\sigma_r\!\sim\!3\sigma_r\) 的邻居参与平均,超过的被视为”另一侧”而排除。

6.4 实验对比

现在让四种滤波器同台竞技。先看高斯噪声(图 6.1 (b))下的表现,四种滤波结果见 图 6.2

(a) 5×5 均值滤波
(b) 5×5 高斯滤波(\(\sigma=1.2\)
(c) 5×5 中值滤波
(d) 双边滤波(\(d=9\), \(\sigma_s=3\), \(\sigma_r=30\)
图 6.2: 高斯噪声图像经四种滤波器处理的结果。均值与高斯滤波有效压噪但边缘和细线均被钝化;中值滤波保住了矩形边缘,细亮线却几乎消失;双边滤波在压噪的同时把阶跃边缘和细亮线都保得最好。

均值与高斯滤波都明显压低了颗粒感,验证了”平均抵消独立误差”的原理;代价同样明显——矩形边缘被拖出一圈灰色过渡带,那条 2 px 的细亮线也变暗、变粗了,因为它的亮度被摊给了周围的背景像素。两者相比,相同尺寸下高斯滤波的边缘退化稍轻,这是其权重向中心集中的结果。中值滤波把矩形边缘保持得相当锐利,但请注意细亮线:它几乎被抹掉了——这个现象我们马上单独讨论。表现最均衡的是双边滤波:背景噪声被抚平,矩形边缘干脆利落,细亮线也完好保留,因为线与背景的灰度差(约 70 个灰度级)远超 \(\sigma_r=30\) 的”自己人”门槛,线上像素只与线上像素平均。对高斯噪声而言,双边滤波是保边去噪的最佳选择。

换成椒盐噪声(图 6.1 (c)),结论几乎完全反转,见 图 6.3

(a) 5×5 均值滤波
(b) 5×5 高斯滤波(\(\sigma=1.2\)
(c) 5×5 中值滤波
(d) 双边滤波(\(d=9\), \(\sigma_s=3\), \(\sigma_r=30\)
图 6.3: 椒盐噪声图像经四种滤波器处理的结果。均值与高斯滤波把脉冲”抹开”成灰斑;中值滤波几乎完全清除了椒盐噪声、边缘依旧锐利,但细亮线也被一并清除;双边滤波对椒盐噪声几乎无效,孤立坏点被当作”边缘”保护了下来。

均值与高斯滤波的结果令人失望:脉冲没有消失,而是被”抹开”了——每个 0 或 255 的坏点把自己的极端值分摊给整个窗口,变成一个个直径约 5 px 的浅色或深色软斑,整幅图像反而显得脏兮兮的。中值滤波则展现出压倒性优势:黑白坏点被悉数剔除,背景恢复均匀,矩形边缘依旧锐利,结果干净得近乎原图。这正是 小节 6.1 末尾论断的直观印证——对离群值,要剔除,不要平均。

最值得记住的是双边滤波的表现:它对椒盐噪声几乎无效。对照 图 6.3 (d)图 6.1 (c),坏点几乎原封不动地留在原地。原因并不神秘,却常被误解:一个值为 255 的盐点与周围灰度约 180 的背景相差 75 个灰度级,远超 \(\sigma_r=30\) 的相似性门槛——于是在双边滤波眼里,这个坏点是一块”与众不同的小结构”,邻居们都不是它的”自己人”,它只与自己平均,自然保持原值。双边滤波的保边机制恰好成了脉冲噪声的保护伞。遇到椒盐噪声,请直接选中值滤波。

不过中值滤波也有自己的账单。再仔细对照 图 6.1 (a) 的原图看 图 6.3 (c)图 6.2 (c):那条 2 px 宽的细亮线在两幅中值结果里都消失了。道理与坏点被剔除一模一样——在 5×5 的窗口里,细线最多贡献 \(2\times 5=10\) 个像素,不足 25 个像素的半数,排序后永远占不到中位,于是被当成”较大的噪声”清除了。中值滤波保边,但不保细结构:凡是宽度小于窗口半宽的线状或点状结构,无论是噪声还是真实目标,一律被抹除。由此得出一条硬性约束:中值核的尺寸必须小于需要保留的最小结构宽度(的两倍)——若细线宽 2 px,3×3 的中值核尚可保它一命,5×5 就会把它连同噪声一起带走。滤波器不认识”缺陷”与”噪声”,它只认识尺寸。

6.5 SciVision 实现

本章四种滤波器在 SciVision SDK 中由 SCIMV::SciSvFilter 类提供,调用方式如下:

SCIMV::SciSvFilter f;
SciImage dst;
f.Mean(src, roi, &dst, 5, 5);
f.Gaussian(src, roi, &dst, 5, 5, 1.2, 1.2);
f.Median(src, roi, &dst, 5, 5);
// Bilateral(核直径9, 空间σ=3.0, 灰度σ=30.0):灰度差超过≈3σ的邻居权重趋零,因而保边
f.Bilateral(src, roi, &dst, 9, 3.0, 30.0);

各接口的公共参数中,src 为输入图像,roi 限定处理区域(产线程序通常只滤波检测区域以节省时间),dst 接收输出。滤波器专有参数的含义如下。

  • Mean / MediankernelSizeXkernelSizeY(上例均为 5)分别是窗口的宽与高,须为奇数以保证窗口有中心;两者可以不同,例如用 (9, 3) 的扁窗口对水平条纹噪声做方向性抑制。
  • Gaussian 在窗口尺寸之外还接受 sigmaXsigmaY(上例均为 1.2),即水平与垂直方向的高斯标准差。记住 小节 6.2 的匹配法则:窗口边长宜约等于 \(6\sigma\),上例 \(\sigma=1.2\) 配 5×5 属于略紧的截断,工程上可接受。
  • Bilateral 的三个参数依次是 kernelSize(邻域直径,上例为 9)、space_sigma(空间域 \(\sigma_s\),控制”离得近”的尺度,上例 3.0 与直径 9 满足 \(d\approx 3\sigma_s\) 的常用配比)和 color_sigma(灰度域 \(\sigma_r\),控制”长得像”的门槛,上例 30.0 意味着灰度差超过约 90 的邻居权重趋零,因而 70 级以上的阶跃边缘被几乎完整保留)。

生成本章全部实验图像的完整可运行工程位于 code/spatial_filtering/,读者可以修改噪声强度与滤波参数自行复现。

工业案例:金属件划痕检测的预处理

某金属冲压件检测项目中,喷砂工件表面形成大量孤立高亮反光点,统计特性接近椒盐噪声。初期用高斯滤波做预处理,反光点被抹成浅灰软斑,与划痕难以区分,误检率高;改用 3×3 中值滤波后,反光点被干净剔除,划痕边缘保持锐利,误检率大幅下降。后续”优化”将中值核扩大到 7×7,产线随即漏检细划痕——划痕宽度仅 2~3 px,小于窗口半数占比,被当成噪声移除,这正是本章细亮线实验的现场翻版。经验法则:核取噪声颗粒直径的 2~3 倍,且必须小于最小缺陷宽度的两倍;两者冲突时优先保缺陷,残余噪声交给后续面积筛选处理。

6.6 小结

本章的要点可以浓缩为以下几条。

  • 先认噪声,再选滤波器。高斯噪声是”人人小错”,适合平均类的线性滤波;椒盐噪声是”少数大错”,必须用中值类的剔除手段。
  • 线性滤波的核应归一化为直流增益 1,以保持图像平均亮度不变;高斯核按 \(K\approx 6\sigma\) 截断,并利用可分离性把计算量从 \(O(K^2)\) 降到 \(O(2K)\)
  • 双边滤波 = 空间邻近 × 灰度相似的双重加权,是高斯噪声下保边去噪的最佳选择;但孤立脉冲会被它当作”边缘”保护起来,对椒盐噪声几乎无效。
  • 中值滤波保边但不保细结构:宽度不足窗口半数占比的细线与小点会被连同噪声一起抹除,核尺寸必须小于需要保留的最小结构宽度的两倍。
  • 滤波参数是测量精度的一部分:核的尺寸、\(\sigma\) 的大小都应根据噪声尺度与最小目标尺寸来定,而不是”越大越干净”。

空间滤波的系统理论(线性滤波、统计排序滤波及其与频域的联系)可参阅 Gonzalez 与 Woods 的经典教材 (Gonzalez 和 Woods 2018);双边滤波的原始构造见 Tomasi 与 Manduchi 的论文 (Tomasi 和 Manduchi 1998),本章正文已多次引用;各向异性扩散这一保边平滑的代表性进阶方法,源头是 Perona 与 Malik 的工作 (Perona 和 Malik 1990)。关于平滑滤波器在工业检测流水线中更系统的讨论,可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)