做目标检测改进的兄弟应该都遇到过这种场景同样的算法单模态输入调来调去也就涨个零点几个点但把红外和可见光两块信息合起来用上限一下子就不一样了。白天可见光纹理清楚晚上或者恶劣天气全靠红外热辐射撑场面两种模态各有各的“主场”也各有各的短板。怎么让网络自己学会该看哪路信号别被另一路的噪声带偏这就是多模态融合里最核心、也最折磨人的问题。这个项目做的就是这件事在YOLOv12的基础上引入一个叫FDSM的频率域动态选择模块专门用来处理红外和可见光特征的融合问题从频率域角度做动态特征筛选保留有用信息、压掉冗余和噪声然后平稳接进检测框架里涨点。标题里“CVPR 2025”不是说我这个工作发到了顶会而是我在设计模块时参考了近两年CVPR以及相关顶会上关于频率域表征学习、动态选择机制的研究思路把这些思想落到了YOLOv12这个工程性更强的框架里。这个项目适合两类人看一类是做检测算法改进、需要跑实验刷精度的同学另一类是落地多模态感知系统、被红外可见光特征融合困扰的工程师。篇幅会比较长我把设计动机、模块原理、接入步骤、实验配置到坑位排查整个链路都过一遍。1. 项目整体设计与思路拆解1.1 红外与可见光融合到底卡在哪里先聊一个我反复跟人解释的问题红外和可见光融合的难点不在“怎么把两路特征拼起来”而在“拼完之后怎么处理信息冲突”。早期做法很简单通道维度直接concat或者按像素相加。这种粗暴操作很快会遇到麻烦红外图像里热源区域高亮、背景几乎没纹理可见光图像里白天纹理丰富、夜晚却漆黑一片。两路特征在数值分布、语义粒度上差异很大直接相加会把弱模态的信息淹没direct concat则会让网络花大量参数去学“哪路特征该忽略”学得慢还容易过拟合。更麻烦的是噪声特性的差异。可见光在低光照下噪声颗粒感重红外在高温差场景下容易出现光晕和过曝区域。这些噪声在空间域看起来和真实目标的边缘混淆度很高但从频率域看它们和高频目标细节、低频背景纹理的分布规律是有差异的。所以我一开始就确定了一个方向多模态特征筛选这件事放到频率域去做比在空间域硬磨卷积要高效得多。1.2 为什么基座选择了YOLOv12选YOLOv12不是因为它是最新版本就无脑上。我之前在YOLOv8和YOLOv11上都试过类似的多模态融合结构对比下来YOLOv12有几个特性特别适合做这种改进实验。第一YOLOv12的Backbone对跨尺度特征的重组能力比前代更强高层语义特征和底层细节特征的通道结构更清晰这给后续的频域筛选提供了一个“特征质量还说得过去”的前置条件。第二它的Neck部分计算冗余相对小插入额外模块后对整体推理速度的影响可控。第三也是最重要的YOLOv12的生态成熟不管是Ultralytics风格的结构还是各种改版可参考的工程实现多踩坑之后比较容易定位问题。当然选它也有实际考虑项目和团队一直在YOLO系列上做开发对内部结构的熟悉程度决定了改进效率这属于“基于已验证的工程基础做加法”的思路不是盲目追新。1.3 FDSM模块的定位做“特征筛选器”而不是“特征生成器”在设计FDSM之前我给自己定了一条规矩这个模块只做筛选和重标定不自己去生成大尺度的新特征。原因很简单检测框架的Neck部分已经负责了多尺度特征生成如果我再塞一个大型融合网络进去训练难度和推理开销都会爆炸。FDSM的定位更像一个“智能路由”输入两路特征输出一路增强后的融合特征。它不改变特征的空间尺寸也不大幅增加通道数而是在频率域里动态判断哪些频段的信息可靠哪些频段是干扰然后据此调整两路特征的贡献权重。这种设计思路说白了就是把“融合策略”做成网络可以自己学习的部分而不是靠人去拍脑袋定权重。2. FDSM模块细节解析频率域动态选择机制2.1 为什么要从空间域切换到频率域这是整个项目里最需要被理解的一步。空间域上的卷积操作感受野内的信息是混在一起的一个3x3的卷积核同时看到了边缘、纹理和平坦区域很难精准地只对某一类信号做响应。而频率域天然将信息按“变化快慢”做了分解整体轮廓和光照变化在低频段边缘和细节纹理在高频段随机噪声则往往分布在极高频段。这种分解对多模态融合来说是天然的筛选规则。红外特征中的目标热辐射轮廓通常集中在低频和中频可见光特征中的精细纹理分布在更高的频段而两路各自的噪声则往往在极端高频段扎堆。在频率域做动态选择本质上是告诉网络低频该信谁高频该信谁哪些频段干脆直接把权重压到最小。2.2 频率域变换的具体实现方式实际工程里我不会直接对整张特征图做傅里叶变换那样计算量太大而且全局变换会丢失空间位置信息。我采用的是分块频率域变换的思路把输入特征图沿空间维度切成固定大小的patch在每个patch内部做二维离散余弦变换。选离散余弦变换而不是傅里叶变换有三个原因。第一DCT变换后能量更集中特征图这种自然图像信号的低频系数占比很高更容易做阈值筛选。第二DCT系数是实数不用处理复数域的计算和梯度传播实现起来简洁稳定。第三JPEG等图像压缩算法底层的频率分解方式和DCT一致工程上对DCT系数的处理手段非常成熟参考实现多、好调试。具体做法是输入的红外特征和可见光特征分别经过一个1x1卷积统一通道数然后各自切patch做DCT得到频域系数张量。这个系数张量的每个位置对应一个频段我们把它看作“该位置的频域能量分布图”。2.3 动态选择机制的内部结构这里说的“动态”不是指用固定阈值去截断频率分量而是让网络根据两路特征的当前状态自行决定每个频段该保留多少、该抑制多少。具体实现分为三步。第一步把红外特征和可见光特征的频域系数按频段分组形成一组频带描述子每个描述子统计该频段内的能量水平。第二步把这组描述子concat起来经过一个轻量级的门控网络输出两路特征各自在每一个频带上的权重系数。门控网络我用的是两层全连接加Sigmoid激活参数量很小但足以建模频带间的相关性。第三步用学习到的权重去缩放各自频域系数然后做逆DCT变换回空间域经过一次通道注意力重标定最后与原始空间域特征做残差连接。加残差连接是这里的关键细节。我见过很多改进模块的效果不稳定原因就是模块直接重写了特征分布把原来预训练权重学到的空间语义冲掉了。FDSM加了一条从输入到输出的残差通路让模块在最差情况下退化为“原始特征微小修正”这能显著降低训练初期的震荡概率。2.4 与现有主流融合方案的对比思路做实验的时候我习惯先拉几个对照方案。主流的多模态融合方法大致有三类基于空间注意力机制的门控融合、基于通道重标定的特征加权、基于Transformer跨模态注意力交互的方法。前两类的问题在于空间注意力和通道注意力都是在原始像素或通道粒度上做权重分配但噪声和干扰信号在空间上往往和真实目标位置重叠单纯从空间域很难区分。第三类效果通常最好但Transformer结构在检测框架里对算力的要求偏高部署时容易成为瓶颈。FDSM的差异点在于它在频域粒度上做权重分配相当于把“哪些信息可信”的问题拆成了“哪些频段可信”而这个问题的答案往往比空间域更清晰。它也不是完全不和空间域交互逆变换回来之后接的通道注意力就是在空间-通道维度上做二次校正两个维度互相配合。3. 基于YOLOv12的实战接入方法3.1 模块插入位置与整体网络结构这个模块最自然的位置是Neck部分具体是在Backbone输出高层特征之后、进入PANet结构之前的特征融合节点。以YOLOv12的经典结构来看Backbone会输出三个尺度的特征我选择对最大尺度和中间尺度两路特征做多模态融合最小尺度负责小目标频域筛选优先级可以低一点。每个尺度上的具体流程是这样的红外特征F_i和可见光特征F_v同时送入FDSM模块内部完成频域动态选择后输出融合特征F_fused。这个F_fused将替代原来单模态特征进入后续的Neck结构。原网络中的单模态分支保留两路输入都会经过模态特定的预处理卷积层用于对齐通道数和特征分布。关于监督信号我是直接沿用检测头的损失函数没有额外给FDSM加辅助损失。这一点可能有争议但我倾向于让频域选择机制完全由检测任务驱动学习不加约束反而能学到更贴合任务的特征筛选策略加了辅助损失之后模块容易为了满足辅助约束而偏离检测目标。3.2 FDSM模块的核心代码逻辑代码层面我会把FDSM封装成一个独立的PyTorch模块。注意这里展示的是结构逻辑的核心部分实际项目中还需要处理patch边界、频带划分等细节。import torch import torch.nn as nn import torch.nn.functional as F class FDSM(nn.Module): def __init__(self, in_ch, num_band8, patch_size8): super().__init__() self.patch_size patch_size self.num_band num_band # 统一通道数的两个预处理卷积 self.conv_i nn.Conv2d(in_ch, in_ch, 1) self.conv_v nn.Conv2d(in_ch, in_ch, 1) # 频带权重生成器输入两路频带描述子拼接输出两路权重 self.gate nn.Sequential( nn.Linear(num_band * 2, num_band * 4), nn.ReLU(inplaceTrue), nn.Linear(num_band * 4, num_band * 2), nn.Sigmoid() ) # 融合后的通道注意力 self.ch_attn nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_ch, in_ch // 4, 1), nn.ReLU(inplaceTrue), nn.Conv2d(in_ch // 4, in_ch, 1), nn.Sigmoid() ) def _forward_dct(self, x): # 将特征图切成 patch每个 patch 在内部做二维 DCT b, c, h, w x.shape p self.patch_size x_patch x.view(b, c, h // p, p, w // p, p) x_patch x_patch.permute(0, 1, 2, 4, 3, 5).contiguous() x_patch x_patch.view(b, c, -1, p, p) # 简化实现用可学习的频率基做线性变换 # 实际项目中使用的是 DCT 系数矩阵乘法 coeff self._dct_transform(x_patch) # [b, c, num_patch, p*p] return coeff def _band_energy(self, coeff): # 按频段划分低频到高频分为 num_band 组 # 每组统计能量均值得到频带描述子 b, c, n, _ coeff.shape coeff coeff.view(b, c, n, self.num_band, -1) energy coeff.abs().mean(dim-1) # [b, c, n, num_band] energy energy.mean(dim2) # 在 patch 维度上平均 return energy # [b, c, num_band] def forward(self, feat_i, feat_v): # 1. 统一通道数 fi self.conv_i(feat_i) fv self.conv_v(feat_v) # 2. 频率域变换 ci self._forward_dct(fi) cv self._forward_dct(fv) # 3. 频带能量描述子 ei self._band_energy(ci) ev self._band_energy(cv) # 4. 动态权重生成 gate_in torch.cat([ei.mean(dim1), ev.mean(dim1)], dim-1) weights self.gate(gate_in) # [b, num_band * 2] w_i, w_v torch.split(weights, self.num_band, dim-1) # 5. 按频带缩放频域系数 ci self._scale_band(ci, w_i) cv self._scale_band(cv, w_v) # 6. 逆变换回空间域并加权融合 ri self._inverse_dct(ci) rv self._inverse_dct(cv) fused ri rv # 7. 通道注意力与残差连接 attn self.ch_attn(fused) fused fused * attn return feat_i feat_v fused简单解释几个容易被忽视的逻辑点。_band_energy得到的频带描述子在通道和patch维度上做了平均是为了让门控网络接收低噪声的统计量避免被单个patch的异常频点带偏。权重生成后我们在频域系数上按频带做广播缩放再分别逆变换回空间域相加这相当于“两路特征各自滤波后再融合”比先融合再滤波更容易保留各自的有效信息。3.3 数据准备与训练配置多模态训练数据有两个来源公开的多模态检测数据集以及自建的可见光-红外配对数据。使用公开数据集时要注意一个坑很多数据集的红外图像和可见光图像分辨率不一致或者存在像素级偏移。我通常会先做离线配准用特征点匹配加单应性变换把两路图像对齐再同步裁剪生成训练样本。训练配置上我用的输入分辨率是640x640batch size根据显存设置在8到16之间。优化器选择SGD初始学习率0.01配合warmup和cosine退火。这里有个经验加了频域模块之后优化器尽量别换成AdamW拉满跑我在实验中发现SGD的收敛曲线更稳定尤其在训练后期SGD对频域权重的微调更精细AdamW偶尔会把门控网络的权重带偏。当然这只代表我自己的实验结果大家可以在自己的数据上测试对比。数据增强方面多模态场景和单模态有明显区别。常规的随机翻转、缩放、色彩抖动照做但“随机擦除”这类增强要慎用。随机擦除在单模态里是很好的正则化手段在多模态里会把某一模态的图像区域抹成纯色导致该区域的频域能量直接被清零FDSM的权重学习会被误导。4. 实验效果分析与调参要点4.1 对比实验怎么设计才有说服力做“涨点改进”类工作对比实验的说服力取决于控制变量是否严格。我最常用的四组对照是这样的第一组是纯可见光基线即单模态输入、原版YOLOv12结构用于确认单模态能力上限。第二组是红外和可见光直接通道concat后输入YOLOv12代表最简单的多模态方案。第三组是在concat基础上加空间注意力门控融合模块代表当前主流改进方式。第四组是加FDSM模块后的完整方案。在公开多模态检测数据集上四组对比下来我的典型结果是concat方案相比单模态提升约1到2个点空间注意力方案再提升0.5到1个点FDSM方案相对空间注意力方案又能多出0.5到1.5个点的收益。最关键的是FDSM在夜间和弱光子集上的提升幅度明显高于白天子集这点让我确定它确确实实在“抑制噪声”这件事上起了作用。4.2 两个关键超参的经验值FDSM里两个超参最值得花时间调patch_size和num_band。patch_size决定了频率变换的局部性。我试过4、8、16三种patch越小频域分析越局部越能捕捉细节区域的变化但计算量和显存开销也越大。patch为8时在效率和效果之间最平衡。patch为4时小目标检测精度有微弱提升但训练速度掉了将近四分之一性价比不高patch为16时大目标的融合效果还行小目标区域的频域特征被过度平滑了漏检率明显上升。num_band控制频带划分粒度。8个频带是我验证下来最稳的少于6个频带时门控网络分不清噪声频段和纹理频段抑制噪声的能力打了折扣高于12个频带时每个频带包含的频率分量太少权重估计的方差变大训练过程中容易震荡。如果你的红外图像噪声特性特别强可以把这个值往12靠一点但需要同步降低学习率。5. 常见问题排查与踩坑实录5.1 加了FDSM之后精度不升反降这是最常被问到的问题。我排查的思路按顺序来先确认两路输入特征是否对齐包括空间对齐和通道对齐再检查频带描述子的统计量是否出现异常值可以打印训练时的频带能量曲线看看是不是某个频带的能量数值跨度太大压过了门控网络的判别能力最后检查残差连接是不是被弱化了如果输出被模块的修正项主导原始语义特征被冲淡精度大概率要跌。一个很隐蔽的问题出在预处理卷积上。刚开始我用的是3x3卷积做通道对齐参数量倒不大但3x3卷积会改变特征的局部空间关系。后来统一改成1x1卷积精度立刻恢复。道理也简单通道对齐只需要做线性组合不需要引入额外的空间卷积偏置。5.2 训练震荡、损失曲线反复跳我遇到这种情况时第一反应是门控网络的输出饱和了。Sigmoid的输出如果经常贴近0或1权重的梯度会消失模块的筛选行为就会在两个极端之间反复横跳。解决办法是在Sigmoid之前给门控网络的输出加一个小幅值的噪声或者改用带温度系数的Softmax替代让权重分布更平滑。如果再不行就检查是否某一模态的输入占比过高。红外图像如果整体偏暗、对比度低它的频带能量会整体偏小门控网络很容易直接把红外路径的权重压到接近0退化成纯可见光模式。这时需要在预处理卷积后对红外特征做一次实例归一化把频带能量拉回正常区间。5.3 推理速度优化与显存控制FDSM毕竟多了一条频率域变换的通路推理耗时不可能完全无感。我实际测下来在同等配置下FDSM比原版YOLOv12大概增加12%到18%的推理时间主要开销在patch切分和DCT变换。显存方面两路特征同时送入模块中间变量比单模态多了不少训练时如果不控制batch size很容易OOM。两个工程上的优化手段可以缓解一是把DCT变换部分合并到预处理卷积的权重里在通道维度上预先做频域混合这里涉及一些数学等价变换但能省掉一半以上的显式变换耗时二是推理阶段将FDSM的频带权重固化不再动态计算描述子直接使用训练好的权重表做缩放精度几乎无损推理速度能再提一截。当然固化权重只适合部署场景训练时还是保持完整的动态机制。6. 最后的几点提醒训练结束后我复盘时最大的感受是这种频率域动态选择模块收益不是来自“加入了频域信息”这个动作本身而是来自“让网络学会了在不同频段上分别判断两路模态的可信度”。所以如果你打算在自己的项目里复现别急着改结构先花时间去理解你手里的红外和可见光数据在频域上的差异特征。动手之前写个脚本把两组图像的DCT系数分布可视化出来看看高频区域到底是目标边缘多还是噪声多低频区域哪些频带两路特征高度相似、哪些高度不互补——这些观察会直接指导你后续调参的方向。我也留个私心提醒任何“涨点改进”都要基于你自己的任务和数据进行验证。我这个项目里的参数和经验值是基于常见公开多模态数据和自建场景数据调出来的不代表在任意数据集上都能直接复制同样幅度的提升。多模态数据本身的质量差异很大有的数据两路图像配准精度高有的则存在好几个像素的偏移。配准问题不解决换再先进的融合模块也救不回来。如果你在复现的过程中发现精度提升不明显可以先检查数据配准再检查频带能量分布最后考虑调节patch_size和num_band这两个超参。多模态融合这条方向值得深入研究的点还有很多频率域只是其中一个挺有潜力的切入口。后续我打算在这个模块的基础上继续尝试把频域选择机制扩展到时序维度的多帧融合上让车辆、行人这类目标在运动状态下的跨模态一致性得到更好的建模那时再来跟大家分享新一轮的踩坑经验。