摘要

本文提出了一种改进的YOLO11-MM多模态目标检测框架,通过引入轻量级跨模态注意力融合(CAM)模块,实现了红外与可见光特征的高效融合。该模块采用通道级注意力机制,仅需一个可学习参数即可完成跨模态特征对齐,显著提升了FLIR数据集中复杂场景下的检测性能。研究对比了中期和后期两种融合策略,实验结果表明该方法在保持模型轻量化的同时有效提高了检测精度。文章详细介绍了CAM模块的实现原理、代码结构以及在YOLO11-MM框架中的集成方式,为多模态目标检测提供了可复用的技术方案。

目录

一、引言

二、跨模态注意力融合 (CMA)

三、逐步手把手添加跨模态注意力融合 (CMA)

3.1 第一步

3.2 第二步

3.3 第三步

3.4 第四步 

四 完整yaml

4.1 YOLO11-MM中期(early)加入跨模态注意力融合 (CMA)

4.2 YOLO11-MM后期(late)加入跨模态注意力融合 (CMA)

五 训练代码和结果

5.1 模型训练代码

5.2 模型训练结果

六 总结

一、引言

本文围绕 YOLO11-MM 多模态目标检测框架 的结构改进与性能优化展开研究,重点探讨了如何引入 跨模态交叉注意力机制(Cross-Modal Attention, CAM),实现红外(Infrared)与可见光(Visible)特征的高效融合,从而提升复杂场景下的目标检测鲁棒性与准确性。

在具体实现层面,本文系统分析了 CAM 模块在网络不同阶段的插入策略,分别设计并对比了 中期融合(Middle Fusion)后期融合(Late Fusion) 两种实验方案,深入探讨注意力机制介入时机对特征表达能力及最终检测性能的影响。CAM 模块在设计上借鉴了 通道注意力(Channel Attention) 的核心思想,有效缓解了异构模态之间的特征分布与维度不匹配问题。值得注意的是,该模块在保证性能提升的同时实现了极致轻量化,仅引入 一个可学习参数,即可完成跨模态特征的自适应引导与对齐,具备良好的工程部署潜力。

需要特别说明的是,本文实验所采用的数据集为 FLIR 数据集的子集,而非完整 FLIR 数据集。在进行实验复现或进一步扩展研究时,读者需注意数据划分及配置上的差异,以避免结果偏差。希望本文的研究与实践经验,能够为多模态目标检测领域的研究者和工程实践者提供具有参考价值的技术思路与实现范式。

二、跨模态注意力融合 (CMA)

论文链接:https://arxiv.org/abs/2502.16214

代码链接:https://github.com/zhao-chunyu/SaliencyMamba

跨模态注意力融合(CMA / CAM)模块 是 YOLO11-MM 多模态目标检测框架在 FLIR 红外–可见光数据集上的核心改进之一,其设计思想源于通道注意力机制,并针对红外与可见光在语义表达、成像机理及特征分布上的天然差异进行了针对性优化。该模块通过构建 通道级别的跨模态注意力(C×C Attention),显式建模红外特征与可见光特征之间的通道相关性,引导网络在融合过程中“关注有效互补信息、抑制模态不匹配噪声”,从而缓解多模态直接拼接或加权带来的语义冲突问题。

在 YOLO11-MM 框架中,CAM 模块以 双输入、单输出 的形式无缝嵌入主干或颈部网络,支持中期融合与后期融合等多种策略。具体而言,两路来自红外与可见光分支的特征首先通过 1×1 卷积自适配到统一通道维度,随后以一支模态作为 Query,另一支模态作为 Key–Value,计算通道间的跨模态注意力权重,并将其作用于特征重构过程。最终通过一个可学习的缩放参数 γ 与残差连接完成融合,在保证稳定训练的同时逐步释放跨模态信息增益。值得强调的是,该 CAM 模块 仅引入一个可学习参数 γ,几乎不增加计算量与参数规模,却能显著提升 FLIR 数据集中夜间、低对比度及复杂背景场景下的检测性能。

它通过一种类通道注意力的机制来计算语义通道对图像通道的影响力权重,解决了跨模态特征维度不匹配的问题。

最终,将计算出的注意力权重应用于图像特征,并通过一个可学习参数进行缩放,与原始图像特征相加,得到融合后的特征。该设计在几乎不增加参数的情况下,实现了有效的语义引导。

总体来看,CAM 模块在 YOLO11-MM + FLIR 多模态检测任务中的突出贡献体现在三点:(1)通道级跨模态对齐,避免尺度与语义错配;(2)极致轻量化设计,兼顾精度与实时性;(3)工程友好,易于在 YOLO 系列网络中直接集成。这一设计为红外–可见光多模态目标检测提供了一种高效、可复用且具有实际落地价值的融合范式,尤其适合对算力敏感的真实场景应用。

代码说明

import torch
import torch.nn as nn

"""
Cross-Modal Attention Mechanism (CAM)

论文:SalM2: An Extremely Lightweight Saliency Mamba Model for Real-Time Cognitive Awareness of Driver Attention(AAAI 2025)
链接:https://ojs.aaai.org/index.php/AAAI/article/view/32157/34312
导出模块:CAM(跨模态注意力,双输入→单输出)

说明:
- 接口:forward(x1, x2=None) 支持 YAML 双输入 [left, right]
- 通道自适配:两路特征映射到公共维度后做 C×C 注意力,输出对齐左分支通道
- 形状约束:两路输入空间尺寸 (H, W) 必须一致
"""


class CAM(nn.Module):
    """Cross-Modal Attention Mechanism (CAM).

    Args:
        dim (int | None): 公共通道维度。若为 None,则在首次前向时采用左分支通道数。
                          该参数与 tasks.yaml 解析逻辑兼容:会自动注入左分支通道。
    """

    def __init__(self, dim: int | None = None) -> None:
        super().__init__()
        self.dim = dim
        self.gamma = nn.Parameter(torch.zeros(1))
        self.softmax = nn.Softmax(dim=-1)

        # 惰性构建的通道投影(依据实际输入通道构建)
        self._built = False
        self._c_left = None
        self._c_right = None
        self._c_common = None
        self.proj_img_in: nn.Module | None = None
        self.proj_txt_in: nn.Module | None = None
        self.proj_out: nn.Module | None = None

    def _build_if_needed(self, c_left: int, c_right: int) -> None:
        if self._built and self._c_left == c_left and self._c_right == c_right:
            return

        c_common = self.dim if self.dim is not None else c_left
        # 输入侧投影,使两路特征具有相同的通道维度
        self.proj_img_in = nn.Identity() if c_left == c_common else nn.Conv2d(c_left, c_common, 1, bias=False)
        self.proj_txt_in = nn.Identity() if c_right == c_common else nn.Conv2d(c_right, c_common, 1, bias=False)
        # 输出侧投影,保证输出通道与左分支一致
        self.proj_out = nn.Identity() if c_common == c_left else nn.Conv2d(c_common, c_left, 1, bias=False)

        self._built = True
        self._c_left = c_left
        self._c_right = c_right
        self._c_common = c_common

    def forward(self, x1, x2=None):
        # 兼容 parse_model 输入为 [x1, x2] 的形式
        if x2 is None and isinstance(x1, (list, tuple)):
            x1, x2 = x1

        if not isinstance(x1, torch.Tensor) or not isinstance(x2, torch.Tensor):
            raise TypeError("CAM 需要两路输入张量")

        b1, c1, h1, w1 = x1.shape
        b2, c2, h2, w2 = x2.shape
        if (h1 != h2) or (w1 != w2):
            raise ValueError(f"CAM 要求两路输入空间大小一致,got {(h1, w1)} vs {(h2, w2)}")
        if b1 != b2:
            raise ValueError(f"CAM 要求两路输入 batch 一致,got {b1} vs {b2}")

        # 构建投影层(仅首次或形状变化时)
        self._build_if_needed(c1, c2)

        # 投影至公共维度
        img = self.proj_img_in(x1)
        txt = self.proj_txt_in(x2)

        B, C, H, W = img.shape
        # 通道×通道的跨模态注意力
        q = img.view(B, C, -1)              # [B, C, HW]
        k = txt.view(B, C, -1).permute(0, 2, 1)  # [B, HW, C]
        attn = torch.bmm(q, k)              # [B, C, C]
        attn = self.softmax(attn)

        v = txt.view(B, C, -1)              # [B, C, HW]
        attn_info = torch.bmm(attn, v)      # [B, C, HW]
        attn_info = attn_info.view(B, C, H, W)

        out = self.gamma * attn_info + img    # 残差融合
        out = self.proj_out(out)              # 输出对齐到左分支通道
        return out

三、逐步手把手添加跨模态注意力融合 (CMA)

3.1 第一步

在 ultralytics/nn 目录下面,新建一个叫 fusion的文件夹,然后在里面分别新建一个.py 文件,把注意力模块的“核心代码”粘进去。

注意🔸 如果你使用我完整的项目代码,这个 fusion文件夹已经有了、里面的模块也是有的,直接使用进行训练和测试,如果没有你只需要在里面新建一个 py 文件或直接修改已有的即可,如下图所示。

3.2 第二步

第二步:在该目录下新建一个名为 __init__.py 的 Python 文件(如果使用的是我项目提供的工程,该文件一般已经存在,无需重复创建),然后在该文件中导入我们自定义的注意力EMA,具体写法如下图所示。

3.3 第三步

第三步:找到 ultralytics/nn/tasks.py 文件,在其中完成我们模块CAM的导入和注册(如果使用的是我提供的项目工程,该文件已自带,无需新建)。具体书写方式如下图所示

3.4 第四步 

第四步:找到 ultralytics/nn/tasks.py 文件,在 parse_model 方法中加入对应配置即可,具体书写方式如下图所示。

        elif m in frozenset({CAM}):
            # Expect exactly two inputs; output channels follow the left branch
            if isinstance(f, int) or len(f) != 2:
                raise ValueError(f"{m.__name__} expects 2 inputs, got {f} at layer {i}")
            c_left, c_right = ch[f[0]], ch[f[1]]
            # Auto infer dim if not provided (None or missing)
            if len(args) == 0:
                args.insert(0, c_left)
            elif args[0] is None:
                args[0] = c_left
            c2 = c_left

四 完整yaml

4.1 YOLO11-MM中期(early)加入跨模态注意力融合 (CMA)

训练信息:summary: 310 layers, 4,551,544 parameters, 4,551,528 gradients, 10.2 GFLOPs


# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024]
  s: [0.50, 0.50, 1024]
  m: [0.50, 1.00, 512]
  l: [1.00, 1.00, 512]
  x: [1.00, 1.50, 512]

# P4/P5 融合:P4 使用 CAM(单路输出),P5 使用 CAM(单路输出)+ 常规变换
backbone:
  # ========== RGB路径 (层0-10) ==========
  - [-1, 1, Conv, [64, 3, 2], 'RGB']      # 0-P1/2 RGB路径起始
  - [-1, 1, Conv, [128, 3, 2]]            # 1-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]     # 2
  - [-1, 1, Conv, [256, 3, 2]]            # 3-P3/8 (RGB_P3)
  - [-1, 2, C3k2, [512, False, 0.25]]     # 4
  - [-1, 1, Conv, [512, 3, 2]]            # 5-P4/16
  - [-1, 2, C3k2, [512, True]]            # 6 (RGB_P4)
  - [-1, 1, Conv, [1024, 3, 2]]           # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]           # 8
  - [-1, 1, SPPF, [1024, 5]]              # 9
  - [-1, 2, C2PSA, [1024]]                # 10 (RGB_P5)

  # ========== X路径 (层11-21) ==========
  - [-1, 1, Conv, [64, 3, 2], 'X']        # 11-P1/2 X路径起始
  - [-1, 1, Conv, [128, 3, 2]]            # 12-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]     # 13
  - [-1, 1, Conv, [256, 3, 2]]            # 14-P3/8 (X_P3)
  - [-1, 2, C3k2, [512, False, 0.25]]     # 15
  - [-1, 1, Conv, [512, 3, 2]]            # 16-P4/16
  - [-1, 2, C3k2, [512, True]]            # 17 (X_P4)
  - [-1, 1, Conv, [1024, 3, 2]]           # 18-P5/32
  - [-1, 2, C3k2, [1024, True]]           # 19
  - [-1, 1, SPPF, [1024, 5]]              # 20
  - [-1, 2, C2PSA, [1024]]                # 21 (X_P5)

  # ========== P4/P5 融合 ==========
  - [[6, 17], 1, CAM, []]                  # 22: P4 融合(单路输出,dim 自动注入)
  - [[10, 21], 1, CAM, []]                 # 23: P5 CAM 融合(单路输出)
  - [-1, 2, C3k2, [1024, True]]            # 24
  - [-1, 1, C2PSA, [1024]]                 # 25 (Fused_P5)

# 标准YOLOv11检测头 (Neck + Head)
head:
  # 自顶向下路径 (FPN)
  - [25, 1, nn.Upsample, [None, 2, "nearest"]]  # 26 Fused_P5 上采样
  - [[-1, 22], 1, Concat, [1]]                  # 27 + Fused_P4(来自 CAM)
  - [-1, 2, C3k2, [512, False]]                 # 28

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]  # 29
  - [[-1, 4], 1, Concat, [1]]                   # 30 + RGB_P3(4)
  - [-1, 2, C3k2, [256, False]]                 # 31 (P3/8-small)

  # 自底向上路径 (PAN)
  - [-1, 1, Conv, [256, 3, 2]]                  # 32
  - [[-1, 28], 1, Concat, [1]]                  # 33
  - [-1, 2, C3k2, [512, False]]                 # 34 (P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]                  # 35
  - [[-1, 25], 1, Concat, [1]]                  # 36
  - [-1, 2, C3k2, [1024, True]]                 # 37 (P5/32-large)

  - [[31, 34, 37], 1, Detect, [nc]]             # 38 Detect(P3, P4, P5)

4.2 YOLO11-MM后期(late)加入跨模态注意力融合 (CMA)

训练信息:summary: 330 layers, 4,749,401 parameters, 4,749,385 gradients, 11.4 GFLOPs

# Ultralytics YOLO11 🚀 MultiModal Late Fusion Reference
# 说明:该“late-ref”版本遵循 YOLOMM 设计范式与分支书写规范,使用第5字段进行模态路由标注。

# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.50, 0.25, 1024] # YOLO11n summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
  s: [0.50, 0.50, 1024] # YOLO11s summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
  m: [0.50, 1.00, 512]  # YOLO11m summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
  l: [1.00, 1.00, 512]  # YOLO11l summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
  x: [1.00, 1.50, 512]  # YOLO11x summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.4 GFLOPs


backbone:
  # RGB路径 (层0-10) - 完整的RGB特征提取
  - [-1, 1, Conv, [64, 3, 2], 'RGB']      # 0-P1/2 RGB路径起始
  - [-1, 1, Conv, [128, 3, 2]]            # 1-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]     # 2
  - [-1, 1, Conv, [256, 3, 2]]            # 3-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]     # 4
  - [-1, 1, Conv, [512, 3, 2]]            # 5-P4/16
  - [-1, 2, C3k2, [512, True]]            # 6
  - [-1, 1, Conv, [1024, 3, 2]]           # 7-P5/32
  - [-1, 2, C3k2, [1024, True]]           # 8
  - [-1, 1, SPPF, [1024, 5]]              # 9
  - [-1, 2, C2PSA, [1024]]                # 10 RGB路径结束

  # X路径 (层11-21) - 完整的X模态特征提取
  - [-1, 1, Conv, [64, 3, 2], 'X']        # 11-P1/2 X路径起始(from=-1但实际从X模态输入)
  - [-1, 1, Conv, [128, 3, 2]]            # 12-P2/4
  - [-1, 2, C3k2, [256, False, 0.25]]     # 13
  - [-1, 1, Conv, [256, 3, 2]]            # 14-P3/8
  - [-1, 2, C3k2, [512, False, 0.25]]     # 15
  - [-1, 1, Conv, [512, 3, 2]]            # 16-P4/16
  - [-1, 2, C3k2, [512, True]]            # 17
  - [-1, 1, Conv, [1024, 3, 2]]           # 18-P5/32
  - [-1, 2, C3k2, [1024, True]]           # 19
  - [-1, 1, SPPF, [1024, 5]]              # 20
  - [-1, 2, C2PSA, [1024]]                # 21 X路径结束

# 独立检测头 - RGB和X模态各自独立检测,最后融合决策
head:
  # RGB检测分支 (层22-33)
  - [10, 1, nn.Upsample, [None, 2, "nearest"]]   # 22 从RGB backbone
  - [[-1, 6], 1, Concat, [1]]                    # 23 cat RGB backbone P4
  - [-1, 2, C3k2, [512, False]]                  # 24

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]   # 25
  - [[-1, 4], 1, Concat, [1]]                    # 26 cat RGB backbone P3
  - [-1, 2, C3k2, [256, False]]                  # 27 (RGB P3/8-small)

  - [-1, 1, Conv, [256, 3, 2]]                   # 28
  - [[-1, 24], 1, Concat, [1]]                   # 29 cat RGB head P4
  - [-1, 2, C3k2, [512, False]]                  # 30 (RGB P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]                   # 31
  - [[-1, 10], 1, Concat, [1]]                   # 32 cat RGB backbone P5
  - [-1, 2, C3k2, [1024, True]]                  # 33 (RGB P5/32-large)

  # X模态检测分支 (层34-45)
  - [21, 1, nn.Upsample, [None, 2, "nearest"]]   # 34 从X backbone
  - [[-1, 17], 1, Concat, [1]]                   # 35 cat X backbone P4
  - [-1, 2, C3k2, [512, False]]                  # 36

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]   # 37
  - [[-1, 15], 1, Concat, [1]]                   # 38 cat X backbone P3
  - [-1, 2, C3k2, [256, False]]                  # 39 (X P3/8-small)

  - [-1, 1, Conv, [256, 3, 2]]                   # 40
  - [[-1, 36], 1, Concat, [1]]                   # 41 cat X head P4
  - [-1, 2, C3k2, [512, False]]                  # 42 (X P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]                   # 43
  - [[-1, 21], 1, Concat, [1]]                   # 44 cat X backbone P5
  - [-1, 2, C3k2, [1024, True]]                  # 45 (X P5/32-large)

  - [[27, 39], 1, CAM, []]                   # 46 融合P3
  - [[30, 42], 1, CAM, []]                   # 47 融合P4
  - [[33, 45], 1, CAM, []]                   # 48 融合P5



  - [[46, 47, 48], 1, Detect, [nc]]              # 49 Detect(P3, P4, P5)

五 训练代码和结果

5.1 模型训练代码

import warnings
from ultralytics import YOLOMM

# 1. 可选:屏蔽 timm 的未来弃用警告(不影响训练,仅减少控制台噪音)
warnings.filterwarnings(
    "ignore",
    category=FutureWarning,
    message="Importing from timm.models.layers is deprecated, please import via timm.layers"
)

if __name__ == "__main__":
    # 2. 加载多模态模型配置(RGB + IR)
    #    这里使用官方提供的 yolo11n-mm-mid 配置,你也可以换成自己的 yaml
    model = YOLOMM("ultralytics/cfg/models/fusion/yolo11n-mm-late-cam.yaml")

    # 3. 启动训练
    model.train(
        data="FLIR3C/data.yaml",   # 多模态数据集配置(上一节已经编写)
        epochs=10,                 # 训练轮数,实际实验中建议 100+ 起步
        batch=4,                   # batch size,可根据显存大小调整
        imgsz=640,                 # 输入分辨率(默认 640),可与数据集分辨率统一
        device=0,                  # 指定 GPU id,CPU 训练可写 "cpu"
        workers=4,                 # dataloader 线程数(Windows 一般 0~4 比较稳)
        project="runs/mm_exp",     # 训练结果保存根目录
        name="rtdetrmm_flir3c",    # 当前实验名,对应子目录名
        # resume=True,             # 如需从中断的训练继续,可打开此项
        # patience=30,             # 早停策略,连降若干轮 mAP 不提升则停止
        # modality="X",            # 模态消融参数(默认由 data.yaml 中的 modality_used 决定)
        # cache=True,              # 启用图片缓存,加快 IO(内存足够时可打开)
    )

5.2 模型训练结果

六 总结

到这里,本文的正式内容就告一段落啦。

最后也想郑重向大家推荐我的专栏 「YOLO11-MM 多模态目标检测」。目前专栏整体以实战为主,每一篇都是我亲自上手验证后的经验沉淀。后续我也会持续跟进最新顶会的前沿工作进行论文复现,并对一些经典方法及改进机制做系统梳理和补充。

✨如果这篇文章对你哪怕只有一丝帮助,欢迎订阅本专栏、关注我,并私信联系,我会拉你进入 「YOLO11-MM 多模态目标检测」技术交流群 QQ 群~
你的支持,就是我持续输出的最大动力!✨

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐