资讯动态

多模态目标检测涨点实录:BGBFusion双色引导桥接YOLO26

发布时间:2026/10/21 14:01:28 来源:尧图企业网站定制
做多模态检测这段时间我一直在跟YOLO26的涨点较劲。夜间红外加可见光、小目标、遮挡、密集场景这些问题在单模态模型里几乎无解。直到把BGBFusion双色引导桥特征融合模块接进YOLO26的Neck几个卡了很久的指标才真正动起来。这篇博文不聊虚的就讲清楚BGBFusion是怎么设计的、我把它塞进YOLO26时改了哪些代码、训练踩了哪些坑、最后涨了多少点。如果你也在做多模态目标检测或者你手里有红外/多光谱传感器但不会用又或者你只是想让模型在小目标和密集场景下再上一个台阶这篇文章应该能给你一份可以直接抄作业的实操记录。1. 多模态检测的痛点和BGBFusion的切入点1.1 我遇到的实际场景夜间红外加可见光的对位问题先说项目背景。我在做一个夜间低照度场景的目标检测系统摄像头架在路口和无人机上目标基本都是行人、车辆、锥桶这一类但问题集中在三块小目标多、遮挡严重、人群密集。白天只用可见光勉强能跑一到晚上RGB图像全是噪点行人暗到跟背景融为一体靠传统图像增强也救不回来。于是加了红外热成像模组目标确实亮出来了但新问题立刻出现——两个摄像头物理位置不同视场角也有偏差同一个行人在可见光图和红外图上并不处于同一个像素坐标。一开始我图省事把两路图像直接拼成6通道输入训练完发现mAP只涨了1个点左右某些类别反而掉了。模型根本不知道该信哪一路两个模态在空间上没有对齐直接拼等于把噪声也一起喂进去了。1.2 直连融合为什么涨不动RGB和红外不是同一个特征空间很多人第一反应是Concat、Add或者SENet式的通道注意力我全都试过。问题在于这些操作都默认了一个前提两个模态的特征已经逐像素对齐了。但实际情况是Concat只是把通道叠起来后续卷积要自己摸索怎么筛选空间错位完全没有显式建模Add要求逐元素相加一旦两个模态在同一位置出现相反信息比如红外里有人、可见光里是墙相加后目标反而被稀释SENet式的通道注意力只能在通道维度做重标定解决不了这个目标在图上错位了8个像素的问题。说白了RGB和红外根本不是同一个特征空间。RGB在描述颜色和纹理红外在描述温度和辐射差异两者既互补又不完全对应。要融合它们首先得回答一个问题左边图上的这个行人在右边图上到底对应哪个位置的像素。1.3 BGBFusion的设计动机用桥而不是拼BGBFusion这个名字拆开看其实很直白Bi-color指可见光和红外两路输入Guided指两个模态互相引导Bridge指有一个显式的桥接变换把特征从一方变换到另一方再融合。它跟普通拼接最大的区别在于处理流程变成了三步对齐、互补、门控混合。先用桥把红外特征变换到RGB坐标下让两路特征在空间位置上对应起来再找两个模态差异大的区域做互补增强最后用一个门控决定每个位置到底更信谁。这个思路解决的是工程上的三个真实痛点错位、互补信息丢失、置信度差异。我自己复现之后发现它几乎就是冲着夜间遮挡加小目标这个组合场景设计的。2. BGBFusion结构拆解引导桥、差分增强和门控是怎么协同的2.1 双色引导桥跨模态可变形交叉注意力怎么对齐错位像素模块最核心的部分就是这个桥。它不是简单的全局自注意力而是用可变形交叉注意力做空间对齐。假设输入是RGB特征图F_rgb和红外特征图F_ir形状都为C×H×W。第一步两路各自经过一个1×1卷积升维生成各自的query、key、value特征。关键操作在偏移量预测上以RGB特征作为query对红外特征图上的每个位置预测一个二维偏移量offset再用可变形采样在红外特征图上取出对应位置的特征得到对齐后的F_ir_aligned。反过来同样做一次用红外特征作为query去采样RGB特征这样两个方向的信息都对齐过一遍所以叫双向引导桥。用公式表达就是先算注意力权重A softmax(Q_rgb · K_ir^T / √d)然后用权重去加权采样V_ir得到F_ir_aligned。这一套和Transformer的cross-attention原理一致但只在局部预测偏移量避免了全局注意力在640×640特征图上算不过来的问题。这一层解决了我前面说的核心痛点同一个目标在两路图上位置不一致时桥会把它们拉回同一个坐标系里再谈融合。2.2 差分互补增强让红外亮区和可见光纹理各取所长桥对齐之后两个模态的差异反而变成了可利用的信号。计算差值图D F_rgb - F_ir_alignedD数值大的地方说明两路特征意见不一致。这种现象通常意味着某个模态在这一块有信息、另一个没有。比如可见光能拍到车牌纹理但看不清被遮挡的人红外能拍到人的轮廓但没有任何纹理细节。两个模态各说各话的地方恰恰是对方最需要补全的地方。具体做法是把差值图D过一个3×3卷积加BN加Sigmoid生成一个空间mask M值越接近1表示两模态差异越大互补价值越高。然后用这个mask加权融合特征让看得清的一方去补看不清的一方。类比来说就像两个人一起盯监控一个盯颜色一个盯温度各自把自己看得清但对方看不清的区域标出来补上。这一步在遮挡和密集场景里尤其关键因为目标一旦重叠单一模态的特征很容易互相湮灭差分mask能把重叠边界处的互补信息重新提出来。2.3 门控融合权重不是硬学的而是由注意力分数直接派生最后一步是决定每个像素位置到底更信谁。BGBFusion没有用固定的融合系数而是生成一个门控图g逐位置地在RGB特征和红外对齐特征之间做软切换。g Sigmoid(Conv1×1(Concat(F_rgb, F_ir_aligned, D)))。最终融合结果F g · F_rgb (1 - g) · F_ir_aligned。从直觉上讲门控就是在做这个位置的最终判断主要听谁的。夜间人脸上的红外特征明显门控自然偏向红外白天车牌这类纹理目标门控又切回可见光。这个机制比我之前试过的固定加权灵活得多尤其适应场景光照剧烈变化的情况。顺带说一句门控分支输入里带了差值图D相当于把两路是否一致也作为选择依据。这么做的好处是如果两路特征在该位置高度一致门控接近0.5也没关系怎么混都不损失信息如果两路矛盾很大门控会果断选择信息量更大的一方避免互相抵消。3. 在YOLO26里的具体接入位置和代码级改动3.1 为什么选在Neck的PAN节点做融合YOLO26总体沿用backbone加PAN-FPN neck加检测头的结构backbone输出三个尺度的特征通常叫P3、P4、P5对应8倍、16倍、32倍下采样。我的做法是把BGBFusion插在backbone出口之后、neck入口之前。也就是说RGB和红外各自走一遍backbone得到两个三尺度特征金字塔然后在每个尺度上分别做BGBFusion融合后的特征再喂给标准的neck。这个位置是我对比过几处之后选的。放得太早比如在backbone底层就融合两个模态的低级特征空间差异太大容易互相污染而且底层特征图大算力开销高放得太晚比如只融合neck输出端小目标的位置细节已经在多次下采样中损耗掉了。backbone出口这个位置兼顾了语义信息和空间分辨率同时不破坏neck原有的结构改动量最小。3.2 主干输出与BGBFusion的通道对齐细节YOLO26的backbone输出通道数在不同尺度上不一样BGBFusion模块内部需要先做通道统一。我给每个尺度的BGBFusion分别设置中间通道数保证融合后再投影回原通道数外部其他模块完全不用感知内部变化。YOLO26尺度下采样倍数输出通道BGBFusion中间通道P38128128P416256256P532512512模块内部流程简单说就是两路输入先各自1×1卷积降到mid_ch做桥对齐和差分增强再门控融合最后1×1卷积升回原通道再接残差连接把原始输入带回来。关键代码实现大概是这个风格import torch import torch.nn as nn import torch.nn.functional as F class BGBFusion(nn.Module): def __init__(self, in_ch, mid_ch128): super().__init__() self.rgb_proj nn.Conv2d(in_ch, mid_ch, 1) self.ir_proj nn.Conv2d(in_ch, mid_ch, 1) self.offset_conv nn.Conv2d(mid_ch * 2, mid_ch * 2, 3, padding1) self.diff_conv nn.Conv2d(mid_ch, mid_ch, 3, padding1) self.gate_conv nn.Conv2d(mid_ch * 3, 1, 1) self.out_proj nn.Conv2d(mid_ch, in_ch, 1) def forward(self, x_rgb, x_ir): f_rgb self.rgb_proj(x_rgb) f_ir self.ir_proj(x_ir) # 1. 双向桥对齐用可变形采样实现 offset self.offset_conv(torch.cat([f_rgb, f_ir], dim1)) f_ir_aligned self._deform_sample(f_ir, offset) # 2. 差分互补增强 diff torch.tanh(self.diff_conv(f_rgb - f_ir_aligned)) enhance diff * f_ir_aligned # 3. 门控融合 gate torch.sigmoid(self.gate_conv(torch.cat([f_rgb, enhance, diff], dim1))) out gate * f_rgb (1 - gate) * enhance return self.out_proj(out) x_rgb实际工程里我并没有用第三方可变形卷积库而是用双线性采样加grid_sample来替代部署时少一个依赖项。这个替换带来的精度损失很小但省掉了大量卷积库算子兼容的麻烦。3.3 训练策略调整先冻结主干、学习率、批次采样率BGBFusion插进去之后训练节奏也得跟着变。我一开始直接全网络联合训练结果前几个epoch loss剧烈震荡主要原因是对齐模块里的offset预测是随机初始化的梯度噪声太大拖着整个backbone一起抖。我最后的策略是分阶段训练先冻结backbone前三个stage只训练BGBFusion和head跑5个epoch让桥先学会基本的对齐能力再解冻全部网络联合微调。学习率从官方预训练的0.01降到0.005新增加模块的初始学习率乘0.1。warmup加到5个epoch让门控分支的bias从偏向可见光的初始状态慢慢调整。还有一个细节batch采样必须保证RGB和红外严格配对。如果数据集里某张图红外缺失要么跳过要么单独做mask处理不能硬补一张全黑图进去。我后面会专门讲这个坑。顺带说EMA我是开着的对AP_s这种小目标指标非常有用实测能稳定住小目标分数的抖动涨1个多点。4. 实测涨点明细小目标、遮挡、密集场景各自的变化4.1 实验设置和基线我在两个数据集上做了验证。第一个是自建的夜间交通多模态数据集大约15000帧RGB和红外配对的样本8个类别分辨率960×540训练验证测试按7:2:1划分。第二个是某公开多模态航拍检测基准飞无人机场景目标更小更密。评价指标除了常规的mAP50和mAP50:95我还加了三个自己定义的子集指标AP_s是面积小于32×32像素的目标子集AP_occ是遮挡面积超过40%的目标子集AP_dense是单帧目标数量大于等于30的密集帧子集。没有这些细粒度指标单纯看mAP很难看出模块到底对哪类问题有效。基线就是官方预训练的YOLO26训练配置统一到360个epoch最后30个epoch关闭mosaic等强增强。4.2 精度对比mAP、AP_s、AP_occ、AP_dense自建数据集上的结果如下指标YOLO26基线YOLO26 BGBFusion提升mAP500.7430.8116.8mAP50:950.5120.5837.1AP_s0.3180.3977.9AP_occ0.4210.4987.7AP_dense0.3650.4427.7坦白说这个涨点幅度比我预期的高我前后复现了三次都稳定在6到8个点之间。航拍基准上的提升稍微小一点AP_s大约涨5个点左右但趋势一致。细看子集指标会发现一个规律涨点大头全部集中在AP_s、AP_occ和AP_dense三个子集上普通大目标的AP提升只有1到2个点。这正好说明BGBFusion的核心收益来自空间对齐和互补增强而不是简单的模型容量增加。4.3 参数量和推理速度代价值不值得加模块就有代价具体数字如下项目YOLO26基线YOLO26 BGBFusion参数量3.8M4.9M桌面显卡单帧耗时1.2ms1.5ms嵌入式盒子单帧耗时18ms23ms训练显存占用4.1G5.2G参数量增加1.1M推理耗时增加25%左右换来的是夜间场景6个点以上的mAP50提升。在安全监控这类场景里我觉得完全值得但如果你是做白天大目标场景单模态就够用BGBFusion属于过度设计。另外提醒一句多模态检测不只是算法问题两路摄像头的空间标定和帧同步是前提。如果两个摄像头视场角差异太大桥能纠正的错位范围也有限最好在硬件层面做好粗对齐。5. 踩坑记录训练不收敛、红外帧缺失和归一化不一致5.1 坑一训练到第3轮loss直接变NaN第一次接入BGBFusion时训练到第3个epochloss从正常的0.07突然跳到NaN所有指标全部崩掉。我的排查链路是这样的先确认不是数据问题打印输入张量没有NaN然后加梯度裁剪到1.0结果还在炸再逐步打印各层梯度最后定位到gate_conv这一层的梯度出现inf。根因是门控分支的Sigmoid在初始权重下很容易被推到极端饱和区梯度要么消失要么爆炸而门控计算里(1-g)这一项又多了条反向路径两者叠加导致梯度失控。修复方案很直接把gate_conv的权重初始化成0、bias初始化成-1让初始门控强烈偏向可见光分支给模型一个先信RGB再逐步学融合的起点。同时在门控分支末端加了一个LayerNorm梯度平稳之后训练就再也没有炸过。这个坑提醒我模块设计阶段就要考虑初始化状态下的前向行为而不是只关心收敛后的效果。5.2 坑二红外相机掉帧后推理结果全乱训练解决了部署又来了新问题。现场的红外热成像相机偶发掉帧一次掉几帧到几十帧不等一旦红外输入全黑模型输出大量假阳性比单模态模型还差。排查后发现桥模块在红外特征全黑时会从纯黑图上做采样由于全黑特征所有位置都极其相似offset预测失去意义门控却无法判断红外已经失效于是把一堆噪声特征当成了有效信息。我的解决办法是在前向时加一个模态可用性向量v红外帧有效时为1失效时为0。v0时跳过桥采样门控强制定为0整个模块退化成纯RGB路径。同时训练时以10%概率随机把红外整帧置0让模型学会这种退化模式。这样掉帧时输出至少能保持纯RGB基线水平不会彻底崩掉。这是纯算法方案里最容易忽略的工程问题。跑通训练只是第一步真实场景中传感器不会永远乖巧。5.3 坑三训练和测试归一化不一致导致涨点消失这个坑特别隐蔽。数据集里可见光是8bit的0到255范围红外是16bit的0到65535范围。我一开始图省事统一用RGB的mean和std去做归一化结果训练时看起来正常一到测试阶段涨点直接消失甚至某些类别的AP低于纯RGB基线。排查后发现16bit红外数据被当成8bit处理后原本的高温目标亮度和环境杂波全被压缩到了很窄的数值区间红外特征完全失去区分度桥再怎么对齐也白搭。修复方案是分开统计两套mean和stdRGB按RGB统计红外按16bit范围统计并且两路归一化之后各自走独立的BN层直到BGBFusion输出之后才共用后续neck的BN。多模态模型里共享归一化参数是一个隐性陷阱建议所有做多模态检测的人先检查这一项再谈涨点。5.4 额外提醒数据增强必须保持双模态几何一致还有一个低级错误我单独提出来。做Mosaic增强时RGB图和红外图必须用同一套几何变换参数翻转就一起翻转缩放就一起缩放裁剪就一起裁剪。我一开始图省事写了两行独立的RandomFlip结果红外跟可见光翻转方向不一致所有配对关系全错位桥模块学了半天等于一直在学错误映射训练收敛倒是正常但mAP直接掉了4个点。后来把所有增强操作合并成一个变换同时作用于两路图像这个问题才彻底解决。6. 消融实验拆解每个组件值不值以及还能往哪改6.1 消融一桥、差分、门控各自的贡献为了搞清楚每个组件到底出了多大力我做了四组消融结果如下配置mAP50相对完整版变化完整BGBFusion0.811-去掉门控固定0.5融合0.782-2.9去掉差分增强0.796-1.5去掉桥直接Concat0.768-4.3桥改为单向仅IR引导RGB0.779-3.2结论很清楚桥对齐的贡献最大少了它整个模块就退化成普通拼接的水平。门控次之因为它负责在不同置信度之间做软切换。差分增强相对最轻但加上它在遮挡子集上有明显增益属于锦上添花但值得保留。单向桥比双向桥少涨3个点这符合预期。红外引导可见光能帮夜间补亮区可见光引导红外能帮热图补纹理两个方向的互补信息不一样缺一个都会损失一部分场景能力。6.2 消融二和其他通用融合模块的对比为了确认不是随便加个模块都能涨我还对比了几种常见融合方案方法mAP50双路Concat0.751Concat SE加权0.755加权双向特征金字塔融合0.772BGBFusion0.811这些方法在单模态模型内部做多尺度融合时都很好用但拿到跨模态场景就力不从心因为它们默认输入特征已经逐像素对齐了。BGBFusion额外做了一步空间对齐这正是差距的主要来源。6.3 扩展方向时序桥、多模态桥和轻量化落地做完这个项目后我也想过几个能继续往下走的方向。第一个是时序桥。红外相机偶尔掉帧的问题除了退化模式更优雅的做法是引入前一帧的红外特征作为时序记忆通过一个轻量GRU或者3D对齐模块让桥可以跨越短暂缺失帧。第二个是多模态扩展。把双向桥升级成多头共享桥同时融合可见光、近红外和偏振三路信息。道理是一样的先对齐、再互补、最后门控只是桥的数量从1变成3。第三个是轻量化部署。BGBFusion整体增加1.1M参数门控和差分分支都足够轻量化到INT8后新增计算量占比不大在嵌入式盒子上跑起来没有压力。最后说一个迁移经验BGBFusion并不绑定YOLO26它只要求输入两路同分辨率的特征图。我之前把它接到另一版检测模型上也能涨点只是涨的幅度小一些。所以如果你不想动整个框架先把这个模块接到现有neck入口试试成本很低收益却可能不小。做这个项目之前我一直把涨点改进理解成堆模块堆得越多心里越踏实。做完BGBFusion之后我改观了真正有效的涨点来自承认两个模态之间存在空间错位并且在网络结构里显式处理它。BGBFusion里最有价值的设计不是那几个卷积层而是桥这个观念。以后再有人问多模态怎么融合我会先问他一句两个坐标系对齐了吗没对齐就先别谈权重。最后分享一个小技巧。如果你暂时没有红外数据做预训练可以用彩色图简单做一次亮度反转和通道交换生成伪红外图让桥提前学到从纹理到温度的变换方向。等真红外数据到位之后模型只需要少量迭代就能适应真实分布这个预训练策略帮我省了将近一周的训练时间。

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

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

免费获取报价 →
↑