这两年我一直在折腾夜间图像恢复这条线,尤其是多天气场景。白天的去雨、去雾、去雪模型已经很成熟,开源代码一抓一大把,但时间切到晚上,亮度掉下来,光晕、噪声、色彩偏移全部冒出来,雨雪雾又会跟这些光照问题纠缠在一起,问题就彻底变了味。所谓的多天气夜间图像恢复,目标就是在这种低照度、复杂光源、多退化叠加的环境下,恢复出一张干净、清晰、颜色真实的图像,让监控画面里的车牌能看清,让自动驾驶的夜间感知模块不用靠运气,也让手机夜拍的照片真正具备可用性。
这个方向上能聊的东西非常多:物理模型怎么重新推导、训练数据从哪来、网络结构是走多分支还是单主干、损失函数怎么避免越训越灰、部署时怎么不踩量化和ISP的坑。这篇内容我会从问题建模开始,一路聊到数据集搭建、模型选型、损失设计、实验复盘的完整思路,中间穿插我自己踩过的不少坑。对刚接触多天气夜间恢复的开发者来说,这算一份可以直接避坑的路线图。
1. 为什么夜间多天气图像恢复是个“难啃的硬骨头”
1.1 单任务模型到了晚上就不灵
白天图像恢复的思路本质上依赖一个前提:场景中的光照相对均匀,退化因素可以单独建模。去雨模型学的是雨线形态,去雾模型学的是大气散射系数,去雪模型学的是雪花轨迹和反光。这些模型在白天数据集上表现确实能打,但放到夜间场景,几乎等同于拿着白天的经验去做黑夜的判断题。
原因并不复杂。夜间图像的光照不是均匀分布的,路灯、车灯、店铺招牌、月光可能同时出现在画面里,形成强烈的局部高亮和暗部区域。雨线在灯光照射下会拉出长条光带,雪花会变成高光斑块,雾也不再是均匀的白色薄纱,而是贴着光源形成的不规则团状散射。再加上传感器为了补偿低照度会拉高ISO,噪声会变得非常明显。白天那套以透射率为核心的物理模型,在夜间基本失去了成立的条件。
我自己实测过一个白天表现很好的去雨网络,拿夜间雨景图跑一遍,输出画面整体偏灰、暗部细节被吞掉、雨线只是变淡并没有真正消失,还额外产生了很多伪纹理。原因是模型把所有低亮度区域都当成需要增强的阴影,结果把手部的纹理刷成了水印。所以,夜间恢复不能简单套用白天模型,必须针对夜间的光照分布和传感器特性重新设计。
1.2 多天气叠加让问题从“单一退化”变成“联合退化”
如果只是单纯的夜暗场景,难度还在可控范围内,毕竟低光增强有大量成熟方案。但一旦把雨、雪、雾这些天气因素同时放进来,情况就完全不同了。雨是近景大尺寸运动模糊加上远处的细密条纹,雪是飘忽不定的反光颗粒加上局部遮挡,雾是大范围低频散射加上亮度分布不均。这几个因素叠加到低照度图像上,并不是简单的加性关系。
可以这样理解:白天的天气退化像“眼镜脏了”,擦干净就完事;夜间的天气退化像“在关了灯的房间里戴着脏眼镜,还被人拿着手电筒往眼睛里照”。你不仅看不清楚东西,还得应付刺眼的光晕、忽明忽暗的过曝区域,以及无处不在的传感器底噪。多天气叠加意味着恢复模型要同时处理高强度噪声、非均匀光照和结构化天气退化,而且这三者之间存在相互影响。雨线区域的光晕会让去噪算法误把高光当噪声,雪花反射会把暗部细节藏在过曝区域后面,雾气又会让整个画面的对比度统计完全偏移。
1.3 应用场景多,问题足够迫切
研究这个方向的人越来越多,根本原因是需求太强烈。安防监控需要全天候工作,夜间下雨时如果无法看清车牌和人脸,很多溯源场景就会失效。自动驾驶的视觉感知在夜间本来就捉襟见肘,再叠加雨雪天气,车道线、行人、障碍物的特征会严重衰减。移动影像设备就更不用说了,用户晚上拍一张雨天街景,期望的是干净通透的画面,而不是一张布满噪声和雨丝的照片。
这个技术落地后的价值非常直接:监控系统可以全天候稳定运行,辅助驾驶的夜间感知能多一层保障,普通用户也愿意为了更干净的照片体验买单。这也是为什么多天气夜间图像恢复不只是论文里的指标游戏,而是一个真正有行业驱动力的方向。
2. 先建模再谈恢复:夜间多天气成像模型怎么推
2.1 白天大气散射模型为什么不能直接套用
白天图像恢复绕不开大气散射模型,经典形式是:观测图像 = 场景反射光 × 透射率 + 大气光 × (1 − 透射率)。这个模型假设大气光是一个全局常量,透射率随深度均匀衰减。在白天且天气均匀的情况下,这个假设基本成立,所以去雾算法能够通过估计透射率和大气光来反解出清晰图像。
夜间的问题在于全局大气光这个假设完全被打破。夜间的环境光来自多个方向、多个色温、多种强度的独立光源,路灯是暖黄色、店铺招牌是冷白色、车灯是点状高亮。把它们压缩成一个全局大气光常量,物理上就不成立。更麻烦的是,光源本身会直接进入相机,形成光晕和镜头内反射。光晕的半径和强度跟光源距离、镜头镀膜、光圈形状都有关系,这部分在白天模型里根本没有对应项。
所以我在构建夜间恢复方案时,第一件事就是放弃全局大气光的假设,把光照项从常量改成逐像素的空间变化场。
2.2 一种可用的夜间多天气退化建模方式
既然标准的白天模型不成立,就需要构造一个更适合夜间多天气的退化表示。我常用来指导网络设计的是一个分解式模型,大致形式是:
夜间观测图 = 光照场 ⊙ 干净反射图 ⊙ 透射率 + 天气散射分量 + 结构化天气残差 + 噪声
这个式子的每一部分都对应一个物理来源。光照场描述路灯、车灯造成的非均匀亮度分布;干净反射图是我们最终希望恢复的清晰内容;透射率保留了大雾天气下的深度衰减特性;天气散射分量对应雾和雨雪造成的局部泛光;结构化天气残差用来单独建模雨线、雪花这类有明确形态特征的退化;噪声则对应高ISO带来的传感器底噪。
把退化拆成这几个分量,最大的好处是网络设计有了明确的“任务分配”依据。我可以让一个分支去估计光照场,让另一个分支去估计结构化天气残差,再让一个分支处理噪声和散射。每个分支的监督信号都来自同一个物理过程的不同侧面,相互不冲突,训练起来比直接让网络端到端地猜测“干净图”要稳定得多。
2.3 为什么“先增强再去雨”的两段式方案会被放弃
很多人第一反应是,既然退化复杂,那就先做低光增强,再对增强后的图像去做雨去雾。这个思路听起来顺理成章,但实操中几乎必翻车。原因在于误差累计和任务干扰。低光增强会把暗部细节强行提亮,同步放大了噪声,也改变了雨线、雪花的局部对比度。等增强完再去做天气恢复,后一级模型要处理的是被人为拉伸过的噪声和伪纹理,天气退化本身的形态特征反而被弱化了。
另外一个隐患是两段式模型无法端到端优化。低光增强阶段犯的错误,第二阶段根本无法纠正,只能被动接受。我做过对比实验,同样的训练数据,两段式方案的PSNR总是比联合建模低2dB以上,而且输出的图像容易出现局部过曝和偏灰的“夹生感”。这也是我坚决走联合建模路线的核心原因:让网络同时感知光照分布和天气退化,在同一个优化目标下协调两者的处理方式,远比串行各管一段更可靠。
3. 数据从哪来:多天气夜间数据集的搭建套路
3.1 合成数据:用渲染引擎生成可控的多天气样本
夜间多天气恢复最大的痛点是数据。真实世界里,想同时采集夜间、下雨、下雪、起雾、干净场景的清晰配对数据,几乎是不可能的任务。没有对照的清晰GT,监督学习就无从谈起。现实的做法是靠合成数据打底,再用真实数据做微调。
合成数据的核心工具是3D渲染引擎。准备一批包含街道、建筑、车辆、行人的夜间3D场景,在场景里分别加入雨天粒子、雪花粒子、雾气体积和不同的光源组合,然后渲染出“干净场景”和“退化场景”的图像对。关键是渲染参数要开得足够大:雨量、雪量、雾浓度、光源色温、ISO噪声强度都应该有独立的参数控制。这样才能生成“雨+雾+暗光”“雪+暗光”“纯雾”等各种组合,覆盖真实场景里的退化分布。
雨线和雪花的合成有个容易踩的细节:不能只把雨做成半透明的白色斜线。真实雨滴在灯光下会有运动模糊和折射效果,近景雨滴还应该带有失焦的大圆斑,远景雨滴才是细短条纹。如果只用简单的线段来模拟雨,训练出来的模型对真实雨景基本无效。
3.2 真实数据与伪标签的取舍
合成数据只能做到“看起来像真实”,跟真实传感器成像之间始终存在域差距。所以一定要补充真实夜间多天气数据。真实数据没有GT怎么办?常见方案有这么几种,我都试过,各有取舍。
第一种是双相机短长曝光法。用两台相机在同一视角下同时采集短曝光和长曝光画面,短曝光能“冻结”雨丝运动形成相对干净的参考图,长曝光则得到包含雨丝和噪声的退化图。这种方法得到的配对比较真实,但设备贵、场景难找,而且夜间长曝光本身会产生运动模糊,参考图并不完全干净。
第二种是连拍取中值法。用三脚架在同一场景连续拍摄多张,取时间中位数作为伪GT。雨丝和噪声在时间维度上是随机的,中位数滤波能抑制一部分退化,但这个办法只适合轻度天气,大雪或大雨时每张图都严重退化,中值挽救不了什么。
第三种是离线恢复模型生成伪标签。先用一个表现不错的单任务恢复网络对真实夜间多天气图像做处理,把输出当作监督标签。这个方案最方便,但风险也最大,模型会学到“修复痕迹”。我实际试过用伪标签训练,一开始指标涨得快,后面发现模型学会了把暗部刷成雾状的灰,就是因为伪标签本身带有这个偏差。解决方案是只保留模型中置信度较高的区域,把那些亮度异常、边缘模糊的像素mask掉,再参与训练。
3.3 数据组合与采样策略
收齐了合成和真实数据,还要面对一个很现实的问题:怎么把不同类型的天气数据混合起来训练。很多人直接把雨、雪、雾、干净四类数据混在一起,随机打乱喂给网络,结果训练出来的模型有点四不像:雨天雪天都抹得平,但什么天气都处理不干净。
更好的做法是主导天气采样策略。每一个训练批次里,先随机选一个主导天气类别,让这个类别的数据占批次的70%,其余天气各占10%。这样可以保证网络在一个批次内主要学习当前天气的特定退化模式,同时又能看到其他天气样本,不至于遗忘。训练中期再加入少量干净低光图像,防止模型把所有低光场景都强行加上“恢复操作”,导致干净图被过度处理。
我还会做一个“跨天气组合增强”:把同一场景的干净反射图分别配上雨、雪、雾和不同噪声等级,生成四组退化程度差异明显的样本对。这相当于人为制造了域随机化,让模型更关注退化本身的结构差异,而不是死记场景内容。
4. 模型结构怎么选:分工、融合与Transformer
4.1 多分支并行:让每个退化因子有自己的处理通道
模型结构的选择决定了多天气夜间恢复的天花板。目前主流路线可以分成三类:单网络通吃、多分支并联、渐进式分解。我先把对比放在下面。
| 结构方案 | 核心思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 单网络通吃 | 输入退化图,端到端输出修复图 | 实现简单、参数少、训练方便 | 退化因子相互干扰,暗部和光晕往往处理不平衡 | 轻度退化、单一天气为主 |
| 多分支并联 | 光照估计、天气结构、噪声清理各自独立分支,最后融合重建 | 物理意义清晰、可针对不同退化单独调优、便于消融 | 参数量大、分支间平衡难控制、训练工程复杂度高 | 多天气叠加、对质量要求高 |
| 渐进式分解 | 先剥离天气退化、再恢复光照,分阶段输出中间结果 | 步骤可解释、中间监督信号明确 | 阶段误差累计、推理耗时增加 | 部署资源充足、需要中间可视化 |
从我的实践经验来看,单网络通吃适合快速验证想法,真正要拿指标、做落地,还是多分支并联更可靠。它让网络内部形成了一条自然的“分工流水线”,光照分支不会强行去擦雨线,天气分支不会自作主张调整亮度,最后融合阶段再做整合。这样更符合夜间多天气退化的物理过程,优化起来目标也更清晰。
4.2 为什么Transformer开始成为夜间恢复的主干
CNN在图像恢复里统治了很多年,但夜间多天气场景有一个天然适合Transformer的特性:光晕、雨线、大范围雾气这些退化因素在空间上跨度很大。比如一个路灯造成的镜头光晕可能横跨小半个画面,远处的雾气会同时影响相距很远的多个区域。CNN的感受野是逐步堆叠出来的,想同时感知这些远距离相关,需要网络非常深,代价很大。
Transformer的全局注意力机制可以天然地建立长距离依赖,对光晕和雾气的整体估计非常有效。实践中完全用纯Transformer也不行。夜间恢复数据集没那么大,Transformer又特别吃数据,训练不够时反而比CNN更容易过拟合,输出会出现奇怪的重影。
我最常用的结构是CNN+Transformer混合。主干保持UNet式的编码解码结构,在最底层替换为几个Swin风格的全局注意力模块,让网络既保持CNN的局部先验,又获得全局建模能力。实际跑下来,混合结构在合成夜间雨雾数据集上比纯CNN高了0.8dB左右,比纯Transformer稳不少。
4.3 一个可以抄的Baseline参考方案
说了这么多,我直接给出一个我实际验证过的基础配置,你可以当作起点来用。整体流程分成四个部分:
第一,多尺度特征提取。输入夜间退化图像后,先用三个并行下采样卷积分支获取1x、1/2、1/4分辨率特征,用于后续融合。
第二,光照感知模块。这个模块接收多尺度特征,通过通道注意力机制估算一个逐像素的光照增益图,然后对特征做亮度校正。它本质上执行的是“哪里亮、哪里暗、亮多少、暗多少”的估计,让后续结构恢复不会在暗部盲目加亮。
第三,天气退化估计分支。这是整个网络的核心,采用一个从粗到细的编码解码器,分别预测透射率图、天气散射项和雨雪结构化残差。三个预测结果分辨率逐步上采样,每一步都跟主分支特征做乘法或加法融合。
第四,融合重建模块。光照校正后的特征、天气退化估计的结果、原始的跳跃连接特征一起进入重建模块,最终输出残差图,加到输入图上得到恢复结果。
这个结构的好处是每个模块都有明确的物理含义,你做消融实验时非常方便。我自己的模拟项目X就是在这个架构上调出来的,后面实验复盘部分会提到具体的收益分配。
5. 损失函数与训练细节:那些坑我替你踩过了
5.1 最终损失函数怎么定
损失函数直接决定恢复结果的主观质量。我只用纯L1或MSE是远远不够的,MSE会让输出变得平滑、缺少纹理细节,L1虽然稍好一点,但单独用还是会出现“塑料感”。我更常用的组合是四部分叠加:像素损失、感知损失、频域损失和可选的对抗损失。
像素损失是主损失,我用L1而不是L2,因为它对异常值的惩罚更温和,夜间图像里有大量过曝区域,L2会被这些高亮像素带偏。感知损失用预训练的分类网络提取特征做比对,能让纹理结构和语义内容更接近真实图像。但这里有个大坑:预训练网络通常是在白天自然图像上训练的,直接拿它的特征来约束夜间输出,反而会把夜色里的真实光照统计当作误差来惩罚。所以我在实践里会用大量夜间干净图像对感知网络做一次微调,再参与损失计算,效果差别很大。
频域损失是我个人非常推荐加的一项。对夜间恢复结果做FFT变换,然后约束频域幅度谱的差异。它能有效保留图像的锐利边缘和细密纹理,同时抑制那种整体模糊、只在空间域看PSNR还挺高的“假恢复”。
对抗损失适合在训练末期加入,用来提升视觉真实性,但它训练不稳定,如果项目周期紧,可以先不加。以下是我在模拟项目里用的参考权重:L1系数1.0、感知损失0.05、频域损失0.1、对抗损失0.01。当然这个不能照搬,要根据你的数据量微调。
5.2 训练不稳定、不收敛的排查方法
多天气夜间恢复模型几乎一定会遇到训练不稳定。最常见的症状是:损失曲线前几百步掉得很快,然后突然反弹,接着产生周期性波动,或者干脆卡住不动。
我踩过最深的坑是不同损失函数之间的尺度不匹配。有一次感知损失的数值是L1的20倍,梯度方向被感知损失完全主导,结果网络死命去调整纹理结构,亮度一直偏灰。后来我把每个损失的数值统计打印出来,发现尺度差了一个数量级,立刻用损失权重重校准,训练就恢复正常了。所以建议你在训练初期先冻结所有权重,跑几百步,观察每个损失的数值范围,再做针对性缩放。
另外还有一个要注意的点,GAN模式崩溃。对抗损失加入后,如果判别器训练得比生成器好太多,生成器很容易陷入输出同一张图的崩溃状态。我的解决办法是分阶段训练:第一阶段只用像素损失、感知损失和频域损失来训生成器,稳定后再用极低的学习率开启判别器;同时给判别器加梯度惩罚,防止它过于强势。
5.3 实验设计:消融要做哪些
做消融实验不是为了交差,而是为了搞清楚每一个模块到底贡献了什么。我的思路是围绕三个问题展开。
第一个问题:光照感知模块到底有没有用。做法是去掉光照感知分支,让其他部分保持不变,再对比PSNR、SSIM和主观图。你会发现去掉之后暗部细节明显变差,整体曝光也不均衡,这说明显式建模光照分布确实有价值。
第二个问题:多天气联合建模优于分任务训练吗。做法是训练一个只在雨天数据上优化的模型,拿去测雪天,再跟联合训练的模型做对比。这一步能直观看到联合建模时的特征共享是否真实有效。
第三个问题:频域损失对高频细节的影响。把频域损失去掉,输出图会变得更平滑,视觉上的质感会下降,但PSNR可能变化不大。这说明PSNR并不能完全反映感知质量,因此每个消融实验都要同时汇报PSNR、SSIM和LPIPS,主观对比图也不能少。
6. 实验复盘与工程落地
6.1 一个模拟项目的实验复盘
我在模拟项目X上完整跑过多天气夜间图像恢复的全流程。数据集由合成部分和真实部分组成,合成部分约2万对,真实部分约500对,包含夜间街道、高速公路、停车场三类场景,天气覆盖雨、雪、雾三类。
基线模型是前面提到的CNN+Transformer混合结构,初版PSNR只有23.8dB。随后我按三步改动逐步迭代,效果差异如下:
| 模型版本 | 关键改动 | PSNR | SSIM |
|---|---|---|---|
| Baseline | CNN+Transformer混合,只有L1损失 | 23.8dB | 0.77 |
| +光照感知 | 增加光照感知模块,亮度校正能力增强 | 25.1dB | 0.80 |
| +频域损失 | 引入FFT幅度损失,细节纹理保持显著改善 | 25.7dB | 0.82 |
| +真实微调 | 加入真实夜间多天气数据进行域适应 | 26.1dB | 0.83 |
从结果可以清楚看到,光照感知模块带来的收益最大,频域损失次之,真实数据微调则是跨越合成到真实鸿沟的最后一环。这里也印证了我前面的判断:夜间恢复的瓶颈更多在光照建模和数据分布,而不是单纯堆网络深度。
6.2 部署时的三个不能忽视的细节
模型训练好了,真正部署上机会有更多隐藏问题。
第一个是量化掉点。FP16推理一般没问题,但你要是为了性能做INT8量化,PSNR可能掉2~3dB。夜间图像本身暗部信息量少,低比特量化会进一步压缩暗部动态范围。建议在量化校准阶段加入大量暗光图像,并且对暗部通道用更大的量化步长区间。
第二个是ISP二次处理。很多相机硬件会对接出图像再做白平衡、去马赛克、降噪。模型输出的图像如果你直接展示不经过ISP,颜色可能完全不对;但经过ISP后再展示,ISP又可能把模型增强过的区域再次调整,导致过曝或偏色。最省事的做法是把模型的输出放到与输入相同的ISP处理流程里,而不是让它另起炉灶。
第三个是视频时序闪烁。单帧恢复模型放在视频流里,经常会出现亮度忽明忽暗、雨线处理结果前后帧不一致的问题。解决思路是把视频帧切块后输入,让连续帧之间通过光流对齐补充时序信息;或者在训练时随机抽取相邻两帧,把帧间特征的一致性作为额外约束加入损失项。
6.3 常见问题速查表
做多天气夜间恢复的过程中,我积累了一些高频问题的排查思路,整理成一张表供快速查阅。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 输出整体偏灰、对比度低 | 感知损失主导,网络偏向语义而忽略光照凸显 | 加大L1权重,加入直方图匹配损失或光照对抗判别 |
| 雨线没有彻底清除 | 合成雨量不够或雨线渲染过于简单 | 增强雨线的近景大尺寸样本,调整雨线合成方式,加大雨密度 |
| 雪花被处理成白色斑块 | 模型把雪花的高光误判为光源 | 增加雪花轨迹的监督信号,让天气分支单独学习雪花结构 |
| 画面闪烁剧烈 | 帧间恢复不一致,缺少时序约束 | 使用多帧时序一致性损失,或推理时对连续帧做时域滤波 |
| 暗部细节全黑 | 光照增益过大,低亮度区域被裁切 | 降低光照分支增益上限,把输出映射改为softplus形式 |
| INT8推理严重掉点 | 暗部量化步长过大,低亮度信息丢失 | 量化校准集合以夜间暗光为主,保留暗部量化范围 |
我自己做这个项目最深的一点体会是:多天气夜间图像恢复这件事,真正难的不是把PSNR刷上去,而是让模型在真实夜间场景里稳定地输出可信、可用的画面。合成数据带来的虚假安全感、感知损失造成的灰度偏移、部署时的ISP二次处理,每一个坑都值得花时间认真对待。
如果让我给刚入坑的朋友一句建议:先别急着换最新的Transformer大刀阔斧改架构,把夜间成像的物理过程拆清楚,把数据分布理明白,再决定网络每个模块负责什么、损失函数约束什么。方向对了,后面的一切都会顺很多。