摘要

本文提出了一种基于多尺度稀疏交叉注意力(MSC)的YOLO11-MM多模态目标检测框架改进方法。通过在P4和P5层特征之间引入MSC模块,实现了红外与可见光特征的高效融合。MSC结合多尺度上下文建模和Top-K稀疏策略,有效抑制噪声干扰并提升目标检测精度。实验表明,该方法在FLIR、M3FD和LLVIP等多模态数据集上表现出色,具有工程友好性和场景适应性。文章详细介绍了MSC模块的实现原理、代码结构和在YOLO11-MM框架中的集成步骤,为多模态目标检测研究提供了实用参考。

目录

一、引言

二、多尺度稀疏交叉注意力 (MSC)

2.1、核心思想(MSC × YOLO11-MM × 多数据集多模态融合)

2.2、突出贡献(MSC 在 YOLO11-MM 多模态检测中的价值)

2.3、优势特点(FLIR / M3FD / LLVIP 多场景实战表现)

2.4 代码说明:

三、逐步手把手添加MSC

3.1 第一步

3.2 第二步

3.3 第三步

3.4 第四步 

四 完整yaml

4.1 YOLO11-MM加入MSC

 五 训练代码和结果

5.1 模型训练代码

5.2 模型训练结果

六 总结

一、引言

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

在具体实现层面,本文系统分析了 MSC 模块在网络不同阶段的插入策略,旨在探索更加合理且高效的红外–可见光特征融合方式。基于多组对比实验,本文采用 中期融合(Middle Fusion) 作为主要实现方案,重点研究多尺度稀疏交叉注意力介入时机对特征表达能力及最终检测性能的影响。通过在P4和P5层特征之间引入 MSC,模型能够在不同语义层级上实现更充分的信息交互。

从机制上看,MSC 一方面能够挖掘隐藏在复杂场景中的 多尺度结构与语义信息,有效建模不同模态之间的互补关系;另一方面,通过引入 稀疏注意力策略,显著减少无关区域和冗余特征对融合过程的干扰,从而在保证表达能力的同时降低噪声影响,使融合特征更加聚焦于目标区域。

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

二、多尺度稀疏交叉注意力 (MSC)

论文链接:https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=10820553

代码链接:https://github.com/TangXu-Group/Remote-Sensing-Images-Classification/tree/main/MSCN

2.1、核心思想(MSC × YOLO11-MM × 多数据集多模态融合)

在 YOLO11-MM 多模态目标检测框架中,多尺度稀疏交叉注意力(MSC, Multi-Scale Sparse Cross-Attention)的核心思想是:通过多尺度上下文建模 + 稀疏交叉注意力机制,在红外与可见光特征之间建立高效、可控的跨模态信息交互通道,从而避免传统全量注意力与简单拼接带来的冗余干扰问题

具体而言,MSC 以两路等尺度特征作为输入(通常对应红外与可见光,或不同尺度/阶段的融合特征),首先对其中一路特征引入多尺度平均池化(3×3 / 5×5 / 7×7),在空间维度上显式编码不同感受野下的上下文信息;随后以 Query–Key–Value 形式构建跨模态注意力,并通过 Top-K 稀疏策略 在注意力矩阵中仅保留最相关的响应位置。既捕获跨模态的关键互补信息,又避免背景噪声、热噪声或光照干扰被无差别放大。

2.2、突出贡献(MSC 在 YOLO11-MM 多模态检测中的价值)

MSC 在 YOLO11-MM 框架中的突出贡献,在于以较低的工程复杂度,引入了一种“可解释、可控、可扩展”的跨模态融合机制,有效弥补了传统 Concat / Add 融合方式在多尺度与跨模态建模上的结构性缺陷。

从论文与代码实现角度看,MSC 的创新点主要体现在三方面:
1)多尺度上下文先验:通过多尺度池化聚合上下文信息,使注意力计算不局限于单一空间尺度,尤其适用于 M3FD 遥感场景中尺度变化剧烈的目标分布;
2)Top-K 稀疏交叉注意力:通过两组不同稀疏比例(k1 / k2)并引入可学习权重 α、β,使模型能够在“强对齐”和“弱对齐”两种注意力模式之间自适应权衡;
3)工程友好的模块化设计:MSC 作为独立模块,可直接插入 YOLO11-MM 的中期融合阶段,在 FLIR 与 LLVIP 场景下显著提升红外与可见光的互补利用效率。

2.3、优势特点(FLIR / M3FD / LLVIP 多场景实战表现)

从多数据集实验与工程实践角度来看,MSC 在 YOLO11-MM 框架中展现出明显的优势特点,并且在不同类型的多模态数据集上具有良好的适应性。

FLIR 数据集(红外–可见光夜间检测)中,MSC 能够有效抑制红外模态中的热噪声干扰,同时利用可见光中更清晰的结构信息,实现对行人、车辆等目标的精准对齐;
M3FD 数据集(遥感多模态场景)中,多尺度上下文池化使 MSC 对大范围场景与尺度变化具备更强的建模能力,避免小目标在高层语义中被淹没;
LLVIP 数据集(低照度与夜间行人检测)中,稀疏交叉注意力机制显著降低了无关区域对融合结果的影响,使模型更加聚焦于行人轮廓与关键区域。

2.4 代码说明:

import torch
import torch.nn as nn


class MSC(nn.Module):
    """Multiscale Sparse Cross-Attention(双输入→单输出)。

    Args:
        dim (int | None): 通道数(自动注入)
        num_heads (int): 注意力头数
        kernel (list[int]): 多尺度 avgpool kernel 列表
        s (list[int]): 多尺度 stride 列表
        pad (list[int]): 多尺度 padding 列表
        k1 (int): Top-k 比例分母(N1/k1)
        k2 (int): Top-k 比例分母(N1/k2)
    """

    def __init__(
        self,
        dim: int | None = None,
        num_heads: int = 8,
        kernel: list[int] = [3, 5, 7],
        s: list[int] = [1, 1, 1],
        pad: list[int] = [1, 2, 3],
        k1: int = 2,
        k2: int = 3,
    ) -> None:
        super().__init__()
        self.dim = dim
        self.num_heads = int(num_heads)
        self.k1 = int(k1)
        self.k2 = int(k2)
        self.q = nn.Linear(dim if dim is not None else 1, dim if dim is not None else 1, bias=True)
        self.kv = nn.Linear(dim if dim is not None else 1, (dim if dim is not None else 1) * 2, bias=True)
        self.attn_drop = nn.Dropout(0.0)
        self.proj = nn.Linear(dim if dim is not None else 1, dim if dim is not None else 1)
        self.proj_drop = nn.Dropout(0.0)
        self.attn1 = nn.Parameter(torch.tensor([0.5]), requires_grad=True)
        self.attn2 = nn.Parameter(torch.tensor([0.5]), requires_grad=True)
        self.avgpool1 = nn.AvgPool2d(kernel_size=kernel[0], stride=s[0], padding=pad[0])
        self.avgpool2 = nn.AvgPool2d(kernel_size=kernel[1], stride=s[1], padding=pad[1])
        self.avgpool3 = nn.AvgPool2d(kernel_size=kernel[2], stride=s[2], padding=pad[2])
        self.layer_norm = nn.LayerNorm(dim if dim is not None else 1)

    def forward(self, x, y=None):
        if y is None and isinstance(x, (list, tuple)):
            x, y = x
        if not isinstance(x, torch.Tensor) or not isinstance(y, torch.Tensor):
            raise TypeError("MSC 需要两路输入张量")
        if x.shape != y.shape:
            raise ValueError(f"MSC 要求两路输入形状一致,got {x.shape} vs {y.shape}")
        B, C, H, W = x.shape
        # 多尺度池化融合上下文 y
        y1 = self.avgpool1(y)
        y2 = self.avgpool2(y)
        y3 = self.avgpool3(y)
        y = y1 + y2 + y3
        y = y.flatten(-2, -1).transpose(1, 2)
        y = self.layer_norm(y)
        N1 = y.shape[1]
        kv = self.kv(y).reshape(B, N1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        k, v = kv[0], kv[1]
        q = self.q(x.flatten(2).transpose(1, 2))
        N = q.shape[1]
        q = q.reshape(B, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
        attn = (q @ k.transpose(-2, -1)) * (C // self.num_heads) ** -0.5
        # Top-k 两种稀疏度
        mask1 = torch.zeros(B, self.num_heads, N, N1, device=x.device, dtype=torch.bool)
        idx1 = torch.topk(attn, k=max(N1 // self.k1, 1), dim=-1, largest=True)[1]
        mask1.scatter_(-1, idx1, True)
        attn1 = torch.where(mask1, attn, torch.full_like(attn, float('-inf'))).softmax(dim=-1)
        attn1 = self.attn_drop(attn1)
        out1 = attn1 @ v
        mask2 = torch.zeros(B, self.num_heads, N, N1, device=x.device, dtype=torch.bool)
        idx2 = torch.topk(attn, k=max(N1 // self.k2, 1), dim=-1, largest=True)[1]
        mask2.scatter_(-1, idx2, True)
        attn2 = torch.where(mask2, attn, torch.full_like(attn, float('-inf'))).softmax(dim=-1)
        attn2 = self.attn_drop(attn2)
        out2 = attn2 @ v
        out = out1 * self.attn1 + out2 * self.attn2
        x = out.transpose(1, 2).reshape(B, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)
        hw = int(N ** 0.5)
        x = x.transpose(1, 2).reshape(B, C, hw, hw)
        return x

三、逐步手把手添加MSC

3.1 第一步

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

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

3.2 第二步

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

3.3 第三步

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

3.4 第四步 

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

        elif m in frozenset({CAM,MSC,HighFrequencyPerception}):
            # 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加入MSC

训练信息:summary: 320 layers, 4,881,530 parameters, 4,881,514 gradients, 10.5 GFLOPs

# Ultralytics YOLOMM 🚀 Mid-Fusion (MSC Test)

# 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]

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 融合(MSC) ==========
  - [[6, 17], 1, MSC, [null, 8, [3,5,7], [1,1,1], [1,2,3], 2, 3]]  # 22: P4 MSC(dim 自动)
  - [[10, 21], 1, MSC, [null, 8, [3,5,7], [1,1,1], [1,2,3], 2, 3]] # 23: P5 MSC
  - [-1, 2, C3k2, [1024, True]]                                    # 24
  - [-1, 1, C2PSA, [1024]]                                         # 25 (Fused_P5)

head:
  # 自顶向下路径 (FPN)
  - [25, 1, nn.Upsample, [None, 2, "nearest"]]  # 26 Fused_P5 上采样
  - [[-1, 22], 1, Concat, [1]]                  # 27 + Fused_P4(来自 MSC)
  - [-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)

 五 训练代码和结果

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/FFT/yolo11n-mm-mid-msc.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 群~
你的支持,就是我持续输出的最大动力!✨

更多推荐