3、图像去雨算法(传统方法):基于滤波的去雨、基于稀疏编码的去雨、基于先验模型的去雨

各位同学,咱们今天聊聊传统图像去雨。别一听「传统」就觉得过时,我跟你讲,这些方法在算力受限的嵌入式平台上,至今仍是主力。我自己在早期做ADAS项目时,就靠这些方法扛过了不少雨天路测。

图像去雨,说白了就是要把画面里的雨条纹给「擦掉」。雨条纹在图像里是什么?是高频噪声,但又比普通噪声有规律——它有方向性,有亮度变化。抓住这个特点,传统方法就能下手了。

3.1 基于滤波的去雨

滤波去雨,思路最直接。雨是高频成分,那我用低通滤波器把高频干掉不就行了?嗯,理论上没错,但实际操作起来坑不少。

3.1.1 中值滤波

中值滤波对椒盐噪声效果很好,雨条纹其实有点像拉长的椒盐噪声。我早期试过用3x3的中值滤波核,效果嘛……雨是淡了,但图像也糊了。尤其是车道线边缘,变得模模糊糊。

为什么会这样?因为中值滤波不分青红皂白,把边缘和雨纹一起平滑了。你想想看,车道线边缘也是高频,跟雨纹在频域上有重叠。

我的经验: 中值滤波适合雨量较小、背景纹理简单的场景。如果雨很大,建议先做一次雨纹检测,只对检测到的雨区域做滤波,别全图无差别攻击。

3.1.2 双边滤波

双边滤波就聪明多了。它不光考虑像素距离,还考虑像素值的差异。边缘处像素值变化大,权重就低,所以边缘能保住。

我记得有一次在高速公路上测试,雨不算大但很密。用双边滤波处理后,护栏和标线都还清晰,就是雨纹残留了一些「尾巴」。后来我调了调空间域sigma和值域sigma的参数,效果才上来。

// 伪代码:双边滤波去雨
for each pixel (i, j):
    sum_weight = 0
    sum_value = 0
    for each neighbor (k, l) in window:
        d = sqrt((i-k)^2 + (j-l)^2)          // 空间距离
        v = abs(I(i,j) - I(k,l))             // 像素差异
        w = exp(-d^2/(2*sigma_d^2)) * exp(-v^2/(2*sigma_r^2))
        sum_weight += w
        sum_value += w * I(k,l)
    I_out(i,j) = sum_value / sum_weight
注意: 双边滤波的参数调起来很费劲。sigma_d 控制空间范围,sigma_r 控制值域敏感度。sigma_r 设太大,跟普通高斯滤波没区别;设太小,雨纹去不干净。我建议你先固定 sigma_d=5,然后从 0.1 开始试 sigma_r。

3.1.3 导向滤波

导向滤波是我个人比较喜欢的一种。它需要一张引导图,可以是原图本身,也可以是原图的灰度版本。导向滤波假设输出是引导图的线性变换,这样能很好地保留边缘。

我在项目中用过导向滤波做去雨的前处理——先用它把图像分成基础层和细节层,雨纹主要在细节层,然后对细节层做阈值处理。效果比直接滤波好不少。

3.2 基于稀疏编码的去雨

稀疏编码,这个名字听起来高大上,其实核心思想很简单:图像可以用少量「原子」的线性组合来表示。雨纹也有自己的原子,背景也有自己的原子。把它们分开,就能去雨了。

3.2.1 基本原理

具体怎么做呢?先准备一堆无雨的干净图像块,训练出一个背景字典。再准备一堆雨纹图像块,训练出一个雨纹字典。然后对带雨图像做稀疏分解,把雨纹部分的系数置零,再用背景字典重建。

嗯,这里要注意:两个字典的区分度是关键。如果雨纹字典里混进了背景纹理,重建时就会把背景也干掉。

核心公式: 给定带雨图像 y,求解 min ||x||_0 s.t. y = D*x,其中 D 是联合字典 [D_bg, D_rain],x 是稀疏系数。去雨后图像 = D_bg * x_bg。

3.2.2 我踩过的坑

我曾经试过用稀疏编码去雨,结果发现处理后的图像有块状伪影。为什么?因为图像块是独立处理的,块与块之间不一致。后来我加了重叠块和平均策略,伪影才消失。

另外,字典训练需要大量数据。我当时从网上扒了1000张无雨图像和500张雨纹图像,训练了一整夜。效果还行,但泛化能力一般——换一种雨型就不太灵了。

方法 优点 缺点
稀疏编码去雨 对特定雨型效果好,可解释性强 计算量大,泛化性差,有块效应
滤波去雨 速度快,实现简单 容易过平滑,细节丢失

3.3 基于先验模型的去雨

先验模型,就是利用我们对雨纹的「先验知识」来指导去雨。比如我们知道雨纹是稀疏的、有方向的、亮度比背景高等等。把这些知识写成数学约束,然后求解一个优化问题。

3.3.1 雨纹的稀疏先验

雨纹在图像中占的面积其实很小,大部分区域是干净的背景。所以雨纹是稀疏的。我们可以用 L0 或 L1 范数来约束雨纹的稀疏性。

我记得有一篇经典论文,把去雨建模成:

min ||I - B - R||^2 + λ1 * ||∇B||_1 + λ2 * ||R||_1

其中 I 是带雨图像,B 是背景,R 是雨纹。第一项保证重建 fidelity,第二项让背景平滑(TV 正则化),第三项让雨纹稀疏。

3.3.2 方向先验

雨纹还有一个特点:方向大致一致,通常是斜向的。这个先验可以用方向滤波器来捕捉。比如用 Gabor 滤波器组,提取特定方向的响应,然后只对那个方向的成分做处理。

我在实际项目中试过,方向先验对小雨和中雨效果不错。但遇到大风天,雨纹方向乱飘,这个先验就不太靠谱了。

避坑指南: 我曾经把方向先验设得太死,结果把斜向的树枝也当雨纹去掉了。后来我加了一个「雨纹置信度」图,只有同时满足「方向一致」和「亮度偏高」的像素才被认为是雨纹。这样误检少了很多。

3.3.3 亮度先验

雨纹的亮度通常比背景高,尤其是在暗色区域(比如柏油路面)。这个先验可以写成:雨纹像素值 > 0。结合稀疏先验和方向先验,三个约束一起上,效果就比较稳了。

不过要注意,雨滴在强光下可能过曝,变成白色一片,这时候亮度先验反而会失效。嗯,这种情况我建议先做一次高光检测,把过曝区域单独处理。

3.4 三种方法的对比与选择

说了这么多,到底该用哪种?我个人的建议是:

  • 算力有限、实时性要求高:选滤波方法。导向滤波优先,双边滤波次之。
  • 离线处理、对质量要求高:选稀疏编码或先验模型。先验模型更灵活,但调参麻烦。
  • 雨型多变、场景复杂:先验模型更鲁棒。你可以把多个先验组合起来用。

最后说一句,传统方法虽然不如深度学习那么「智能」,但胜在可控、可解释、无数据依赖。在自动驾驶的感知系统中,我通常把传统方法作为深度学习的补充——深度学习处理大部分场景,传统方法兜底处理极端情况。两者结合,才是王道。