资讯详情

资讯详情

Derain去雨实战:从合成数据到深度学习模型部署

简介Derain 是一份面向图像处理与计算机视觉学习者的 Python 去雨项目资源聚焦于消除照片中的雨滴干扰提升恶劣天气下图像的清晰度与可用性适合具备一定 Python 基础、希望了解去雨算法实现思路的开发者参考。压缩包共 4 个文件约 3.72MB包含 2 个 pdf 论文资料、1 个 py 主程序脚本和 1 个 txt 说明文档分别对应算法理论参考、核心代码实现与使用说明。资源围绕预处理、特征提取、雨滴建模与背景恢复等环节展开涉及 OpenCV、scikit-image 等常用库并可能结合卷积神经网络等深度学习方法完成雨滴分割与图像补全。已有 199 人学习读者可借此了解去雨任务的完整流程参考论文与代码对照理解算法细节并在此基础上进行复现与二次开发。1. Derain从一张雨滴糊住的行车记录图说起雨天开车前挡玻璃上挂满水珠行车记录仪拍出来的画面几乎没法看——车牌糊成一团、车道线断断续续、远处红绿灯只剩一个色块。这不是镜头脏了是雨滴在光学路径上做了两件事折射和散射。折射让背景纹理发生位移散射让局部对比度骤降。Derain去雨要解决的就是从这种被雨滴污染的单帧图像里把雨滴层剥离出来还原出接近无雨状态下的清晰背景。这个方向不是学术玩具。安防监控在梅雨季的可用率、自动驾驶视觉感知在暴雨天的鲁棒性、户外摄影的后期修复都直接吃这个能力。做 Derain 的人通常分两拨一拨走传统图像处理路线靠频域滤波和稀疏编码另一拨走深度学习路线用 CNN 或 Transformer 做端到端映射。两条路各有各的翻车点后面会逐一拆开讲。如果你手头正好有一批雨天图像要处理或者想在这个方向做点能落地的东西下面这套从数据到推理的路径可以直接抄。2. Derain 的两条技术路线选型逻辑与最小可跑方案2.1 传统方法为什么在真实雨图上容易翻车传统 Derain 的核心假设是雨滴在频域上表现为高频分量背景是低频。于是有人用低通滤波把高频砍掉有人用稀疏编码把雨滴字典和背景字典分开学。这套逻辑在合成雨图上跑得通因为合成雨滴的形态、方向、透明度都是可控的。但真实雨滴不是这样——它受风速影响会拉成斜线受焦距影响会变成大小不一的椭圆受背景亮度影响会呈现半透明到全反射的连续变化。我早期用导向滤波做过一版参数调了三天结果是把雨滴和纹理一起抹掉了。树叶边缘、建筑窗框、路面标线全被当成高频噪声处理。后来换成稀疏编码字典训练了 2000 张合成雨图在真实雨图上重建出来的背景带着明显的块效应。血泪经验是传统方法不是不能用而是它的先验假设太强强到只能处理特定形态的雨。如果你手头的数据是固定场景、固定焦距、雨滴形态单一传统方法还能凑合一旦场景切换泛化能力断崖式下跌。2.2 深度学习方案的最小闭环数据、模型、损失深度学习做 Derain最小闭环就三件事配对数据、编码器-解码器结构、像素级损失加感知损失。配对数据是最大的门槛——真实场景下你不可能同时拍到有雨和无雨的两张图。常见做法是用合成雨图训练再用真实雨图做微调。合成雨图的生成方式直接决定模型的上限。下面这段代码用 Python 生成一批基础合成雨图核心是控制雨滴的方向、长度、密度和透明度四个参数。这不是什么官方脚本是我自己反复调出来的一套参数范围能覆盖大部分中雨到大雨的场景。import cv2 import numpy as np import random def generate_rain_layer(h, w, num_drops800, angle_range(-30, 30), length_range(15, 40), thickness_range(1, 3), alpha_range(0.3, 0.7)): 生成单帧雨滴层 h, w: 图像高宽 num_drops: 雨滴数量控制密度 angle_range: 雨滴倾斜角度范围模拟风速影响 length_range: 雨滴长度范围模拟焦距和运动模糊 thickness_range: 雨滴粗细 alpha_range: 透明度范围模拟背景亮度影响 rain_layer np.zeros((h, w, 3), dtypenp.float32) for _ in range(num_drops): x random.randint(0, w - 1) y random.randint(0, h - 1) angle random.uniform(*angle_range) length random.randint(*length_range) thickness random.randint(*thickness_range) alpha random.uniform(*alpha_range) # 计算雨滴终点 rad np.deg2rad(angle) x2 int(x length * np.sin(rad)) y2 int(y length * np.cos(rad)) # 画线模拟雨滴颜色偏白 color (255, 255, 255) cv2.line(rain_layer, (x, y), (x2, y2), color, thickness) # 按透明度混合 mask np.zeros((h, w), dtypenp.float32) cv2.line(mask, (x, y), (x2, y2), 1.0, thickness) rain_layer rain_layer * (1 - mask[..., None] * alpha) \ rain_layer * mask[..., None] * alpha return rain_layer def blend_rain(clean_img, rain_layer, beta0.8): 将雨滴层叠加到干净图像上 beta: 背景保留系数越小雨越重 rain_layer rain_layer.astype(np.float32) / 255.0 clean clean_img.astype(np.float32) / 255.0 rainy clean * beta rain_layer * (1 - beta) rainy np.clip(rainy * 255, 0, 255).astype(np.uint8) return rainy # 使用示例 clean cv2.imread(clean.jpg) h, w clean.shape[:2] rain generate_rain_layer(h, w, num_drops1200, angle_range(-25, 25)) rainy blend_rain(clean, rain, beta0.75) cv2.imwrite(rainy.jpg, rainy)这段代码的逻辑很直白先随机撒线模拟雨滴再按透明度混合到干净图上。参数说明几个关键点。num_drops控制密度800 到 1500 对应中雨到大雨超过 2000 会变成暴雨背景几乎不可见。angle_range模拟风向真实场景下风速越大角度偏移越明显一般设在正负 30 度以内。beta是背景保留系数0.75 到 0.85 之间比较接近真实雨图的对比度下降程度低于 0.7 会显得太假。这套合成数据训练出来的模型在真实雨图上做微调时学习率要降到初始值的十分之一否则会把合成数据的分布特征带进去导致真实雨滴被过度平滑。模型结构上我一般用 U-Net 变体编码器用 ResNet 的前几层做特征提取解码器加跳连。损失函数用 L1 加 VGG 感知损失权重比 1:0.01。L1 保证像素级对齐感知损失防止过度平滑。训练时 batch size 设 8学习率 1e-4Adam 优化器大概 50 个 epoch 能看到收敛趋势。这些参数不是金科玉律但能让你在半天内跑出一个能看的 baseline。3. 把 Derain 跑起来数据准备、训练配置与推理部署3.1 数据集的三个来源与清洗规则Derain 的数据集来源无非三种合成雨图、真实配对雨图、非配对真实雨图。合成雨图用上一节的脚本批量生成注意控制场景多样性——室内、室外、白天、夜晚、近景、远景都要覆盖否则模型会过拟合到某类背景。真实配对雨图非常稀缺公开的几套数据集规模都不大常见做法是拿合成数据预训练再用少量真实配对数据微调。非配对真实雨图最多但需要配合 CycleGAN 这类非配对框架使用训练难度和调参成本都更高。清洗规则我踩过坑。合成雨图里如果雨滴层叠加时背景本身就有噪声模型会学到噪声和雨滴的混合模式推理时把噪声也当成雨滴处理。所以合成前要对干净图做一次降噪用非局部均值或者 BM3D 都行。真实雨图这边要剔除雨滴完全遮挡主体的样本比如雨滴正好糊在车牌上、人脸上这种样本对训练没好处反而会让模型学到错误的映射关系。我一般用简单的人工抽检抽 10% 看一眼把明显不合格的删掉。3.2 训练配置学习率、批大小与损失权重的实操取值训练 Derain 模型学习率是最容易翻车的参数。初始学习率设 1e-4 是安全区设 1e-3 大概率震荡设 1e-5 收敛太慢。用余弦退火调度每 10 个 epoch 降一次降到 1e-6 左右停。批大小受显存限制8 到 16 之间比较稳太小梯度噪声大太大泛化差。损失权重方面L1 和感知损失的比值我试过 1:0.1、1:0.01、1:0.001最后发现 1:0.01 在视觉质量和像素指标之间平衡最好。比值太高输出偏模糊比值太低输出会有伪影。下面这段是训练循环的核心配置用 PyTorch 写的关键参数都标了注释。import torch import torch.nn as nn import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR # 假设 model 是定义好的 Derain 网络 model DerainNet().cuda() criterion_l1 nn.L1Loss() criterion_perceptual PerceptualLoss().cuda() # 基于 VGG 的感知损失 # 学习率 1e-4Adam 优化器权重衰减 1e-5 防止过拟合 optimizer optim.Adam(model.parameters(), lr1e-4, weight_decay1e-5) scheduler CosineAnnealingLR(optimizer, T_max50, eta_min1e-6) # 损失权重L1 为主感知损失为辅 lambda_l1 1.0 lambda_perceptual 0.01 for epoch in range(50): model.train() for rainy, clean in dataloader: rainy, clean rainy.cuda(), clean.cuda() output model(rainy) loss_l1 criterion_l1(output, clean) loss_perceptual criterion_perceptual(output, clean) loss lambda_l1 * loss_l1 lambda_perceptual * loss_perceptual optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() # 每 5 个 epoch 存一次权重 if (epoch 1) % 5 0: torch.save(model.state_dict(), fderain_epoch_{epoch1}.pth)逻辑说明前向传播得到去雨输出分别算 L1 损失和感知损失加权求和后反向传播。参数说明T_max50对应 50 个 epoch 的余弦周期eta_min1e-6是学习率下限。权重衰减1e-5是为了防止模型记住训练集的雨滴模式。存权重的频率设 5 个 epoch 一次方便回滚到效果最好的版本。如果显存不够把批大小降到 4同时把学习率降到 5e-5否则梯度更新幅度太大容易跳过最优解。3.3 推理阶段的滑动窗口与边缘融合推理时如果图像分辨率超过训练时的输入尺寸直接缩放会丢失细节常见做法是滑动窗口切块推理再拼接。切块大小设 256×256步长设 128重叠区域用余弦窗加权融合。这样能避免块与块之间的拼接缝。下面这段是推理和拼接的代码。import torch import numpy as np import cv2 def infer_with_sliding_window(model, img, patch_size256, stride128): 滑动窗口推理余弦窗融合 img: 输入雨图HWC 格式RGB patch_size: 切块大小 stride: 滑动步长 model.eval() h, w, c img.shape output np.zeros((h, w, c), dtypenp.float32) weight np.zeros((h, w, c), dtypenp.float32) # 生成余弦窗 cos_window np.outer(np.hanning(patch_size), np.hanning(patch_size)) cos_window cos_window[..., None] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch img[y:ypatch_size, x:xpatch_size, :] patch_tensor torch.from_numpy(patch).permute(2, 0, 1).float().unsqueeze(0).cuda() / 255.0 with torch.no_grad(): pred model(patch_tensor).squeeze(0).permute(1, 2, 0).cpu().numpy() pred pred * 255.0 output[y:ypatch_size, x:xpatch_size, :] pred * cos_window weight[y:ypatch_size, x:xpatch_size, :] cos_window # 处理边缘未覆盖区域 output output / np.maximum(weight, 1e-6) output np.clip(output, 0, 255).astype(np.uint8) return output逻辑说明对每个切块做推理用余弦窗加权累加最后除以权重和。参数说明patch_size和训练时保持一致否则分布不匹配。stride设 patch_size 的一半保证重叠区域足够融合。余弦窗的作用是让块中心权重高、边缘权重低拼接时过渡自然。如果图像尺寸小于 patch_size直接整图推理不用切块。推理完记得把输出转成 uint8 再保存否则 OpenCV 写出来的图会全黑。4. Derain 避坑指南五个真实翻车现场4.1 合成数据训练真实雨图推理雨滴变成糊状现象模型在合成验证集上 PSNR 跑到 32dB拿到真实雨图上跑雨滴是没了但背景也糊了树叶变成一团绿色建筑边缘像水彩画。原因合成雨滴的透明度和形态分布和真实雨滴差距太大。合成雨滴是均匀的白色线条真实雨滴有高光、有暗边、有半透明过渡。模型学到的是“白色线条等于雨滴”遇到真实雨滴时把雨滴和背景的过渡区域也当成雨滴抹掉了。解决在合成数据里加入真实雨滴的纹理特征。具体做法是收集一批真实雨滴的局部 patch用泊松融合贴到干净图上再训练。或者用非配对框架做域适应把合成域的特征对齐到真实域。我一般会留 20% 的真实雨图做验证不参与训练只看指标趋势一旦验证集 PSNR 开始下降就停。4.2 损失函数权重没调好输出出现棋盘伪影现象推理结果放大看有规律的网格状纹理像棋盘格。原因感知损失的权重设得太高VGG 特征图的池化操作引入了棋盘效应。或者解码器用了转置卷积步长和卷积核不匹配。解决把感知损失权重降到 0.001 以下或者改用 L1 加 SSIM 的组合。解码器上采样换成最近邻插值加卷积别用转置卷积。如果伪影已经出现在推理后加一道非局部均值降噪能压下去一部分但治标不治本。4.3 滑动窗口步长太大拼接缝像刀切现象大图推理后块与块之间有明显的直线接缝像拼图没拼好。原因步长等于 patch_size没有重叠区域或者重叠区域太小余弦窗融合不够。解决步长设 patch_size 的一半余弦窗的尺寸和 patch_size 一致。如果接缝还在把步长再缩小到 patch_size 的三分之一代价是推理时间翻倍。另一个容易忽略的点是图像边缘——滑动窗口可能覆盖不到最右边和最下边需要单独处理或者把图像 padding 到 patch_size 的整数倍再推理最后裁掉 padding。4.4 学习率设太大损失震荡不收敛现象训练 loss 在 0.05 到 0.15 之间来回跳降不下去。原因初始学习率 1e-3 对 Derain 这种像素级回归任务来说太大了梯度更新幅度超过最优解的范围。解决初始学习率降到 1e-4加余弦退火。如果还震荡降到 5e-5同时把批大小翻倍用梯度累积模拟大批量。另外检查一下数据归一化输入图像要除以 255 归到 0 到 1 之间别直接拿 0 到 255 的像素值训练否则梯度量级完全不对。4.5 显存不够批大小降到 1BN 层统计量失真现象训练时 loss 正常下降推理时效果差很多尤其在小批量样本上。原因批大小太小BatchNorm 层的均值和方差估计不准训练和推理的分布不一致。解决把 BatchNorm 换成 InstanceNorm 或 GroupNorm这两个对批大小不敏感。或者用梯度累积攒 4 个 batch 再更新一次参数等效批大小变成 4。如果显存实在紧张把图像裁成 128×128 的小块训练推理时再滑窗拼接但要注意小块训练时感受野受限雨滴长度超过 40 像素的样本可能学不好。5. 进阶技巧用半监督微调把真实雨图利用率拉满真实配对雨图难搞但真实雨图本身不难搞。半监督微调的思路是先用合成数据训练一个 baseline再用少量配对真实数据做有监督微调最后用大量非配对真实数据做自监督约束。自监督的损失怎么设计常见做法是让模型对同一张雨图做两次不同增强比如随机裁剪、随机翻转然后约束两次输出的去雨结果一致。这叫一致性正则不需要干净标签。具体操作上我一般分三个阶段。第一阶段合成数据训练 50 个 epoch学习率 1e-4。第二阶段配对真实数据微调 20 个 epoch学习率降到 1e-5损失只用 L1。第三阶段非配对真实数据做一致性正则学习率 1e-6损失是 L1 加一致性损失权重比 1:0.1。一致性损失用 MSE约束两次增强输出的差异。下面这段是第三阶段的训练逻辑。# 第三阶段半监督一致性正则 model.train() for rainy in unpaired_dataloader: rainy rainy.cuda() # 两次不同增强 aug1 random_augment(rainy) # 随机裁剪翻转 aug2 random_augment(rainy) out1 model(aug1) out2 model(aug2) # 一致性损失两次输出应该一致 loss_consistency nn.MSELoss()(out1, out2) # 如果手头有少量配对数据也可以混进来算 L1 loss lambda_consistency * loss_consistency optimizer.zero_grad() loss.backward() optimizer.step()参数说明lambda_consistency设 0.1 到 0.5 之间太高会让模型输出趋于平均失去细节太低起不到正则作用。增强方式用随机裁剪到 256×256随机水平翻转别用颜色抖动颜色变化会破坏雨滴的物理特征。这个阶段不需要太多 epoch10 到 15 个就够多了容易过拟合到非配对数据的噪声上。验证方法上我习惯留三张真实雨图不参与任何训练每完成一个阶段就跑一次推理肉眼对比。指标方面PSNR 和 SSIM 在真实雨图上参考价值有限因为干净标签本身是估计出来的。更靠谱的是看下游任务的表现——比如把去雨后的图送进目标检测器看检测框的召回率有没有提升。如果去雨后检测器反而漏检更多说明去雨过程把目标特征也抹掉了得回头调损失权重。这套流程跑下来从数据准备到推理部署大概需要两到三天。硬件门槛不高单卡 8GB 显存就能跑。值不值得做取决于你的场景对雨天图像质量的敏感程度。如果只是偶尔几张图传统方法凑合能用如果是批量处理或者集成到实时系统里深度学习方案的上限明显更高。我自己踩过的最大坑是急于求成合成数据没调好就上真实数据结果返工重训了三次。后来养成习惯每换一个数据集先跑 5 个 epoch 看 loss 曲线曲线不健康就停下来查数据别硬训。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →