资讯动态

融合物理先验与域自适应的恶劣天气目标检测方法

发布时间:2026/10/16 4:29:14 来源:尧图企业网站定制
做自动驾驶感知或者户外监控的朋友肯定都体会过这种落差模型在晴天数据上跑得好好的一到雨天、雾天直接翻车行人检测器的置信度哗啦往下掉远处车辆几乎全部漏报。我一开始也以为只是数据不够就往训练集里堆雨雾图结果模型在真实天气下依然不稳定。后来才想明白恶劣天气条件下的域偏移不是模糊一点那么简单它有非常明确的物理成因——大气散射、雨线方向、光照衰减这些都会在图像上留下有规律的痕迹。与其让模型自己硬扛不如把手头的物理先验显式送进去再配合域自适应把源域清晰天气和目标域雾天雨天的特征拉到一起。这篇文章我会完整拆解我做的基于先验的域自适应目标检测方案从雾天雨天的物理模型讲起到暗通道、透射率、雨纹特征的工程提取再到三个层级的域对齐介入方式最后是一套可以上手的完整训练流程和我踩过的坑。适合正在做恶劣天气感知、无人车视觉、或者想给自己的检测器加天气鲁棒性的同学参考。1. 雾天雨天检测崩掉的根本原因域偏移里藏着物理退化这一层1.1 退化模型决定了天气干扰不是均匀的噪声很多人把雾天检测问题简单理解为图像对比度低给检测器换一个更强的数据增强就完事。但雾的物理退化是空间非均匀的。大气散射模型里面传感器接收到的图像可以拆成两部分来自物体的直接衰减部分和来自大气光的散射部分。物体距离越远透射率越低远处的行人车辆几乎被大气光完全淹没。这种距离相关的退化导致检测器在近处还行、远处直接崩单纯做对比度增强根本救不回来。雨的情况更复杂。雨滴下落会形成方向性一致的线条背景被雨幕削弱同时雨线在空间上其实是局部遮挡而不是全局模糊。简单用一个高斯模糊或者加噪模拟出来的雨天图和真实雨天的特征分布完全不是一回事。这就解释了为什么用合成数据训练的检测器一到真实雨夜就失灵——你模拟的不对模型学到的域差也就不对。1.2 目标检测里域偏移为什么会比分类更严重我最早试过直接把图像分类领域的域自适应方法搬过来效果差得离谱。原因是检测任务比分类任务敏感得多。分类只需要一个全局的语义判别而检测要同时完成三个子任务给目标一个置信度、预测一个框、再归到正确类别。雾雨造成的边界模糊直接影响锚框回归的质量置信度也会漂移NMS阶段还可能把真实目标当噪声滤掉。最直观的例子是远处的行人。在清晰天气下检测器可以靠轮廓和肤色特征找到他在雾天他的轮廓和背景的边界没了再加上透射率低、对比度差特征图上的响应被严重稀释。这个目标在特征空间里不是变小了而是被平移到了源域特征分布之外。域自适应要解决的就是这个分布的平移和形变。1.3 为什么先复原再检测不是最优路线一定有人会说那先接一个去雾网络或者去雨网络把图处理干净再喂给检测器不就完事了。我一开始也是这么干的后来发现三个问题。第一复原网络的目标函数是让整幅图像的重建误差最小而检测任务只关心目标区域有没有足够的判别性信息。这两个目标不一致复原网络很可能会花大量参数去还原天空、地面这类检测器根本不在意的区域而对远程目标的还原完全不充分。第二复原过程会引入新的伪影最常见的是去雾之后的Halo效应、去雨之后的残纹这些伪影在检测器看来可能就是新的目标或干扰纹理。第三端到端推理链路拉长额外引入一个全分辨率编解码网络时延和显存开销都不可忽视部署上非常难受。所以我后来确定的路线是不复原而是把物理先验当作提示信息嵌进检测器的特征提取过程同时用域自适应让源域和目标域在特征空间对齐。2. 先验的选择与提取暗通道、透射率、雨纹特征的工程化处理2.1 暗通道先验用一张图估计哪里雾最重做雾的先验最经典的是暗通道先验。它基于一个观察在清晰无雾图像中每个局部小区域内至少有一个颜色通道的强度接近0。但雾天图像因为大气光的叠加暗通道不再是0雾越浓的地方暗通道的值越高。利用这个反差可以估计每个像素的透射率本质上就是生成一张雾气浓度图。工程上我会把透射率估计的流程做成离线可复用的模块先对输入图像做暗通道提取再用一个引导滤波平滑最后按大气散射模型反推透射率。流程大概是这样import numpy as np import cv2 def calc_dark_channel(img, patch_size15): # 对BGR三通道分别取最小值再在局部窗口取最小值 b, g, r cv2.split(img) min_channel cv2.min(cv2.min(b, g), r) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (patch_size, patch_size)) return cv2.erode(min_channel, kernel) def calc_atmospheric_light(img, dark_channel): # 取暗通道中前0.1%亮度的像素对应的原图最大值作为大气光估计 h, w dark_channel.shape flat_dark dark_channel.reshape(-1) count max(1, int(h * w * 0.001)) indices np.argsort(flat_dark)[-count:] return np.max(img.reshape(-1, 3)[indices], axis0) def estimate_transmission(img, omega0.95, patch_size15): dark calc_dark_channel(img, patch_size) A calc_atmospheric_light(img, dark) norm_img img.astype(np.float64) / A norm_dark calc_dark_channel(norm_img, patch_size) t 1.0 - omega * norm_dark return np.clip(t, 0.1, 1.0), A这里有两个工程上的坑。一个是天空区域暗通道假设在天空处是不成立的因为天空本身就是亮的、没有某个通道接近0按这套公式算出来天空的透射率极低会被误判成浓雾。另一个是低光照区域暗通道先验在夜间场景下会失效因为暗通道低可能是本来就暗而不是被雾盖住。我给出的经验是透射率图只能当作一个弱先验不能当作精确测量值。后来我在框架里又叠加了一个光照置信度遮罩亮度极低的像素会被压低权重才算真正稳住。2.2 雨纹先验方向性、频率与可学习提取器雨跟雾不一样它的先验不是全局的浓度而是局部、方向性强的条纹叠加。单帧雨图可以近似看成背景加上一个雨层雨层在空域表现为高频、方向一致、在亮暗交界处尤其明显。手工滤波也能提一些雨纹痕迹比如方向性的Gabor滤波或者高频分量提取但鲁棒性很差一到夜间车灯炫光就会被误判成暴雨。我建议用一个小型可学习的雨纹提取器先在公开的去雨任务上预训练一个轻量编解码器让它学会把输入拆成背景图和雨层图训练完只保留编码器部分输出特征当作雨纹先验。这样得到的先验不是一张单通道的响应图而是一组特征信息量比手工滤波器丰富很多。要注意的是雨纹特征噪声极大。直接把它拼进检测器的特征图检测器可能学会有雨纹困难样本输出低置信度而不是真正利用雨纹去剥离干扰。我的做法是对雨纹特征做PCA降维只保留前几个主成分再按通道做一个响应图的熵筛选把熵值过高的通道丢掉。简单说就是留住雨的主方向去掉雨纹的细碎噪声。2.3 先验的归一化与可信度遮罩防止天气信息变成对抗噪声这是我在实验里踩过最深的一课。第一版框架里我直接把透射率图和雨纹响应图 concat 到输入通道训练特别不稳定域判别器收敛得飞快检测器却几乎不涨点。后来可视化发现域判别器根本不看目标特征它光凭透射率图的统计分布就能完美区分源域和目标域。换句话说先验图成了域判别器最简单的作弊信号梯度反转之后特征提取器被带着往错误方向走。解决办法是给先验加双重约束一是归一化让所有先验图都减去均值、除以标准差消除因为天气类型导致的整体统计差二是乘上一个可信度遮罩遮罩由透射率局域平滑度和雨纹响应的局部方差共同计算先验越不可靠的地方权重越低。做完之后域判别器才被迫去关注真正的语义特征检测器才开始涨点。这个细节看似简单但直接决定了先验是帮手还是干扰源。3. 域自适应怎么和先验握手三个对齐粒度的取舍与介入方式3.1 图像级对齐把先验当成天气迁移的条件图像级域自适应的典型做法是使用生成模型把源域图像转成目标域风格让检测器在伪目标域上训练。雾天雨天的场景下比较自然的方案是把透射率图、雨纹图作为条件信息喂进生成器让它生成的伪雨天图符合真实退化规律。这种做法的好处是能直接产出可视化结果方便调错缺点是图像级转化太间接生成器一次要管全局很容易把目标本身的细节也抹掉。我试过一些基于CycleGAN的方案生成的雾图确实像雾但放大看树木和车牌的纹理都被破坏了检测器就学会了把纹理缺失当成目标特征。所以后来图像级对齐我基本只用来做数据增强不再作为主对齐手段。3.2 特征级对齐先验引导注意力让对抗知道该对齐哪块特征级对齐是目前域自适应目标检测的主流玩法核心是一个域判别器加梯度反转层判别器试图区分特征来自源域还是目标域特征提取器则想方设法骗过它最终两者博弈找到一个域无关的特征空间。问题在于如果特征提取器摸鱼它可能靠我是雾天模型所以我的特征自带雾感来骗过判别器这种特征对目标检测没意义。加入先验后可以做一件很有效的事把特征空间分成天气纹理路径和语义目标路径对抗只作用于语义路径。具体实现是先验引导的空间注意力把透射率低的区域标记为强退化区让对抗损失在这些区域加大权重同时把高频雨纹区域标记为噪声区把对抗损失在这些区域减小权重。直观说就是告诉对抗网络要逼特征提取器在目标轮廓上对齐不要在雨丝细节上对齐。我试过用注意力可视化对比是否加先验区别非常明显不加先验时特征激活图总有一块跟着雾的轮廓走加先验之后激活图会主动绕开天空和雾区聚焦到行人、车辆的可判别区域上。3.3 实例级对齐候选区域与先验加权检测任务里更自然的基本单位是候选框而不是整张图或全局特征。一辆被雾遮挡的卡车和一个骑自行车的人它们受天气影响的方式、需要对齐的强度都不一样。所以最终的方案里域判别器放在ROI特征这一层对每个候选框单独判断域来源判别器尝试区分这个框来自源域还是目标域检测头在对抗压力的同时学习域无关的实例表示。每个框还会根据它内部的平均透射率算出一个天气影响权重透射率低的框说明退化严重需要更强的对齐压力透射率接近1的框说明基本没被天气干扰对齐压力弱一点。这个权重可以很轻地实现直接从透射率图上做ROI pooling就能拿到。实测下来对AP50和AP75两项指标都有帮助尤其是远处小目标因为它们的框内透射率天然偏低。4. 完整框架搭建过程从骨架选择到损失函数再到训练节奏4.1 整体协作先验提取器、检测骨架、域判别器各司其职我把完整框架搭成三个模块协作的形式。检测骨架用带特征金字塔的两阶段检测器骨架输出多层特征后先过一个融合了雾先验和雨先验的注意力模块再进入区域提议网络和ROI分类回归头。域判别器作用在ROI特征上输出源域/目标域的二分类结果。雾先验分支和雨先验分支不是平行的两个孤立网络它们在特征层面做融合由一个轻量的天气分型头对当前输入做一个雾雨倾向判断输出一个0到1之间的权重然后对雾先验特征和雨先验特征做加权求和。这个分型头很轻只增加几十个GFLOPs里的零头却让框架同时应对雾天和雨天时不会顾此失彼。4.2 损失函数的组合对抗、先验一致性与检测损失怎么配比整体损失可以写成三个部分相加检测损失包括RPN的分类回归损失和ROI头的分类回归损失、对抗损失域判别器的交叉熵梯度反转、先验一致性损失。先验一致性损失的作用是监督先验提取器不要发散在合成数据上我们可以拿到真值透射率和真值雨层对先验输出做L1约束让先验提取器既服务于检测又不至于偏离物理规律太远。我的损失权重经验值是这样把检测损失权重设为1对抗损失权重设在0.1到0.3之间先验一致性损失权重设在0.05到0.1之间。不要一开始就把对抗损失权重拉太高否则梯度反转会先摧毁先验提取器。训练循环的伪代码大致长这样for batch_src, batch_tgt in train_loader: # batch_src来自清晰源域batch_tgt来自雾/雨目标域 pri_src prior_extractor(batch_src) # 透射率图 雨纹特征 pri_tgt prior_extractor(batch_tgt) feat_src detector_backbone(batch_src, pri_src) feat_tgt detector_backbone(batch_tgt, pri_tgt) loss_det det_loss(feat_src, batch_src.annotations) # 先验一致性约束先验提取器不偏离物理真值 loss_prior l1_loss(pri_src, batch_src.transmission_map) \ l1_loss(pri_tgt, batch_tgt.rain_layer_map) # 对抗损失GRL会反转梯度传给backbone loss_adv domain_loss(feat_src, 0) domain_loss(feat_tgt, 1) total_loss loss_det 0.1 * loss_adv 0.05 * loss_prior total_loss.backward()4.3 训练节奏与伪标签一个不容易被注意到的稳定器很多域自适应检测框架会引入自训练就是先用当前模型在目标域上打出伪标签再挑一些高置信度的伪标签当作真值继续训练。这在常规域偏移下效果不错但在雾天雨天下非常危险因为天气干扰导致的置信度漂移太严重模型可能对一辆被雾挡住一半的卡车打出0.95的高置信度但这个框位置已经完全偏了。我的做法是用先验图给伪标签做一个置信度调制每个伪标签框内先算平均透射率透射率低于0.5的框伪标签权重直接减半透射率高于0.8的框才允许进入下一轮训练。这样做的逻辑很简单透射率低说明这个区域的信息本身就不完整检测器给出的框很可能并不可靠硬训进去只会把错误固化。这个修正让我在真实雨天视频上的最终评估多了3个点左右而且训练振荡明显减少。5. 验证与对比实验真正说明问题的三个数字和一个坑5.1 数据组合与评估协议严格来说这类工作要准备三部分数据源域清晰天气训练数据、目标域雾天/雨天的训练数据用来做无监督域自适应不需要检测框标注但最好要有干净的图像、以及带框标注的目标域测试集。实验里我用的是一套公开的驾驶场景语义数据做源域合成雾天版本和真实雨天视频片段做目标域。评估指标至少要同时看几个维度mAP、AP50、AP75反映框定位质量、小目标AP、推理时延。只看mAP会被AP50和AP75之间的结构性变化掩盖问题。5.2 提升里有多少是先验的作用必须做三组消融很多文章做对比实验只看最终mAP但不说明提升到底来自先验还是来自域自适应。我实际做下来完整方案比直接检测器在强雾场景下能回涨10个点以上比只做通用域自适应多回涨3到5个点但这个数字受骨架和天气强度影响很大绝对数值不值得参考大家要看趋势和消融。三组消融必须做第一组只去掉先验纯域自适应第二组只保留先验但不做域自适应相当于多通道输入第三组完整方案。如果完整方案相比第一组的提升远大于相比第二组的提升说明先验与域自适应是协同关系先验不是简单地多给了一个输入通道而是让对抗对齐找准了方向。我最后看到的结果正是这种协同尤其在透射率低于0.3的强退化区域先验的好处更凸显。5.3 公平性对比的三个陷阱第一先验提取器要么完全冻结要么在微调阶段以极低学习率更新否则对比不公平。第二不要在推理时用域标签做条件分支因为你部署时根本不知道当前画面算雾天还是雨天只能让模型自己判断。第三所有方法必须保持相同的锚框配置、NMS阈值、数据增强种子这在学术上基本是常识但工程里很容易被忽略。还有一个不常被提到但很重要的点统计推理时延的时候要把先验提取器的耗时也算进去。很多论文只报检测部分的时延把先验提取器给省略了导致实际部署时你会发现一个轻量的先验模块反而吃掉了一半的实时性预算。6. 落地时我绕开过的几个大坑6.1 天空区域和白色车身的透射率误判第一个大坑就是天空。暗通道先验在天空处失效算出来透射率极低模型会把大片天空当成浓雾区域注意力全被吸过去了结果地面目标的特征反而弱化。白色车辆、浅色建筑也有类似问题。我的解法是在透射率图上叠加一个亮度-平滑度遮罩如果某个区域亮度高、局部方差低就认定它是晴朗平面的特征而不是被雾覆盖。用这个遮罩修正透射率后白色车辆的召回率回升非常明显。6.2 雨刷区域和车窗反光先验网络挡不住的非自然雨真实行车视频里的雨跟合成数据里的雨差太远了。雨刷扫过的区域图像是清晰的但雨刷本身有运动模糊会被雨纹提取器当成大面积强雨车窗玻璃上的反光、路灯晕光也会被识别成雨线。一开始我的雨纹分支把这一切都当成雨结果检测器为了对抗雨把真实目标也抑制了。后来我在雨纹分支上额外加了一个空域一致性约束要求相邻帧的雨纹响应在光流对齐后保持平滑。雨滴和雨线在多帧里是独立扫过的、响应变化剧烈而反光和运动模糊是连续存在的这个约束能把两者分离开来。这也是我把框架逐渐往视频输入端推的原因单帧的雨先验信息量确实不够。6.3 别指望一个先验提取器同时吃遍所有天气雾和雨虽然都是恶劣天气但物理退化机制差异很大。最开始的框架里我让一个共享的先验提取器同时估计透射率和雨纹结果它在雾天偏向雨纹响应、在雨天偏向透射率响应两个任务互相干扰。后来的方案就是之前提到的天气分型头输入先过一个极轻量分类器判断当前帧是偏雾还是偏雨然后给雾分支和雨分支的输出做一个线性加权融合。这个分型头的训练数据可以从合成天气里来整体成本低、收益明显。最后再分享一个我自己的土办法任何改动如果只提升了指标但我用注意力可视化看不出原因就先别急着上线。我会把检测器的特征激活图、透射率图、雨纹响应图叠在一起看几遍通常很快就能定位到是先验帮助了对齐还是模型学会了某个不为人知的捷径。这套方案本身不算复杂但雾天雨天检测的难点恰恰在细节上——先验怎么提、在哪里介入、怎么稳定的对齐每一步都得亲自试过才会知道水有多深。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑