摘要
本文提出一种基于空间依赖感知模块(SDP)的YOLO11-MM多模态目标检测框架改进方法。SDP模块通过在特征金字塔相邻层间构建像素级cross-attention机制,有效解决了传统FPN上采样导致的小目标特征错位问题。该模块采用局部块注意力设计,在保持计算效率的同时显著提升了红外与可见光模态的特征对齐精度。实验表明,SDP在FLIR、M3FD等数据集上能有效改善小目标检测性能,同时保持模型的计算效率。文章详细介绍了SDP模块的实现原理、网络集成方法和实验结果,为多模态目标检测任务提供了一种兼顾精度与效率的改进方案。
目录
二、空间依赖感知模块(Spatial Dependency Perception, SDP)
一、核心思想(SDP × YOLO11-MM × 像素级跨层建模)
二、突出贡献(SDP 在 YOLO11-MM 多模态检测中的价值)
三、优势特点(FLIR / M3FD / LLVIP 多数据集实战表现)
三、逐步手把手添加空间依赖感知模块(Spatial Dependency Perception, SDP)
4.1 YOLO11-MM中期(early)加入空间依赖感知模块(Spatial Dependency Perception, SDP)
一、引言
本文围绕 YOLO11-MM 多模态目标检测框架 的结构改进与性能优化展开研究,重点探讨在红外(Infrared)与可见光(Visible)多模态场景下,如何通过更加精细的特征建模与跨模态交互机制,提升模型在复杂环境中的目标检测性能。
针对传统多模态特征融合方法在小目标、弱纹理及复杂背景场景下表达能力不足的问题,本文引入 空间依赖感知模块(Spatial Dependency Perception, SDP),在相邻特征金字塔层之间构建像素级 cross-attention 机制,使上采样后的高层语义特征能够与底层细节特征实现细粒度对齐。该模块有效强化了小目标的纹理与边缘信息表达,从而提升红外与可见光特征融合的精度与判别性。
在具体实现层面,本文系统分析了 SDP 模块在网络不同阶段的插入策略,围绕多模态特征融合流程,设计并对比了 中期融合(Middle Fusion) 实验方案,深入探讨 SDP 介入时机对特征表达能力及最终检测性能的影响,为多模态目标检测网络的结构设计提供了具有参考价值的工程实践经验。
需要特别说明的是,本文实验所采用的数据集为 FLIR 数据集的子集,而非完整 FLIR 数据集。在进行实验复现或进一步扩展研究时,读者需注意数据划分与配置设置上的差异,以避免由数据规模或分布不一致带来的结果偏差。希望本文的研究思路与实践经验,能够为多模态目标检测领域的研究者与工程实践者提供具有参考价值的技术借鉴与实现范式。
二、空间依赖感知模块(Spatial Dependency Perception, SDP)
论文链接:https://arxiv.org/pdf/2412.10116
代码链接:https://github.com/ShiZican/HS-FPN
一、核心思想(SDP × YOLO11-MM × 像素级跨层建模)
空间依赖感知模块 SDP(Spatial Dependency Perception) 的核心思想是:在特征金字塔相邻层之间,通过像素级 cross-attention 显式建模空间依赖关系,解决 FPN 中因多次上采样带来的特征错位问题,从而提升小目标与细粒度结构的表达能力。
在 YOLO11-MM 多模态框架中,SDP 以低层特征 Ci与已上采样的高层特征 Pi+1作为输入,通过 1×1卷积分别生成 Query 与 Key:
随后将特征划分为固定大小的 patch(如 p×p),仅在局部特征块内部计算像素级注意力:
![]()
“块内像素交互、跨层语义对齐”,使 SDP 能够在不引入 ViT 式全局注意力高开销的前提下,有效捕获高层语义与底层细节之间的空间映射关系,特别适合红外–可见光小目标检测场景。

二、突出贡献(SDP 在 YOLO11-MM 多模态检测中的价值)
SDP 在 YOLO11-MM 框架中的突出贡献,在于首次在 FPN 跨层融合阶段引入可控的像素级空间依赖建模机制,从结构层面补足了传统 FPN 仅做逐像素相加、缺乏空间感知能力的缺陷。
结合论文与代码实现,SDP 的关键贡献体现在三点:
1)跨层像素级对齐:通过 cross-attention 显式学习 Ci与 Pi+1之间的空间映射关系,缓解上采样造成的目标位置偏移;
2)局部块注意力设计:将特征划分为 patch 后再计算注意力,计算复杂度显著低于 ViT 的全局自注意力;
3)工程友好的残差融合:最终输出以残差形式与低层特征相加,保证训练稳定性和梯度传播顺畅。

三、优势特点(FLIR / M3FD / LLVIP 多数据集实战表现)
在 FLIR 红外–可见光数据集 上,SDP 能够显著改善小目标在特征金字塔中的空间一致性,使红外高响应区域与可见光结构信息精准对齐;
在 M3FD 遥感多模态数据集 中,SDP 的局部块注意力机制对大场景、多尺度目标尤为有效,避免全局注意力带来的无关区域干扰;
在 LLVIP 数据集 中,SDP 在低照度条件下有效弥补可见光模态的不稳定性,使跨模态融合更加鲁棒。
此外,相比 ViT 式注意力机制,SDP 仅在 patch 内部计算注意力,显著降低显存占用与 FLOPs,更适合与 YOLO11-MM 这类强调实时性与工程可落地性的检测框架结合。整体来看,SDP 为多模态目标检测提供了一种兼顾精细空间建模能力与工程效率的跨层融合方案。
四、代码说明
# 论文: HS-FPN: High Frequency and Spatial Perception FPN for Tiny Object Detection (AAAI 2025)
# 链接: https://arxiv.org/abs/2412.10116
# 模块作用: 双路 patch 级依赖建模,通过块内 QK 相关将高相关空间依赖从一侧引导到另一侧,提升跨模态对齐后单路表达。
import torch
import torch.nn as nn
class SpatialDependencyPerception(nn.Module):
def __init__(self, dim: int | None = None, patch: int = 8, inter_dim: int | None = None) -> None:
super().__init__()
self.dim = dim
self.patch = int(patch)
self.inter_dim = inter_dim
self._built = False
self._c = None
self._ci = None
self.conv_q: nn.Module | None = None
self.conv_k: nn.Module | None = None
self.softmax = nn.Softmax(dim=-1)
self.conv1x1: nn.Module | None = None
def _build_if_needed(self, c: int) -> None:
if self._built and self._c == c:
return
ci = c if self.inter_dim is None else int(self.inter_dim)
self.conv_q = nn.Sequential(nn.Conv2d(c, ci, 1, bias=False), nn.GroupNorm(min(32, ci), ci))
self.conv_k = nn.Sequential(nn.Conv2d(c, ci, 1, bias=False), nn.GroupNorm(min(32, ci), ci))
self.conv1x1 = nn.Conv2d(c, ci, 1) if ci != c else nn.Identity()
self._built = True
self._c = c
self._ci = ci
def forward(self, x_low, x_high=None):
if x_high is None and isinstance(x_low, (list, tuple)):
x_low, x_high = x_low
if not isinstance(x_low, torch.Tensor) or not isinstance(x_high, torch.Tensor):
raise TypeError("SpatialDependencyPerception 需要两路输入张量")
if x_low.shape != x_high.shape:
raise ValueError(f"SDFM 要求两路输入形状一致,got {x_low.shape} vs {x_high.shape}")
B, C, H, W = x_low.shape
if H % self.patch != 0 or W % self.patch != 0:
raise ValueError(f"SDFM 要求 H/W 能被 patch={self.patch} 整除,got {(H, W)}")
self._build_if_needed(C)
ci = self._ci
q = self.conv_q(x_low)
k = self.conv_k(x_high)
p = self.patch
q_unf = torch.nn.functional.unfold(q, kernel_size=p, stride=p)
k_unf = torch.nn.functional.unfold(k, kernel_size=p, stride=p)
L = q_unf.shape[-1]
pa = p * p
q_blk = q_unf.transpose(1, 2).contiguous().view(B * L, ci, pa).transpose(1, 2)
k_blk = k_unf.transpose(1, 2).contiguous().view(B * L, ci, pa)
attn = torch.bmm(q_blk, k_blk)
attn = attn / (ci ** 0.5)
attn = self.softmax(attn)
v = k_blk.transpose(1, 2)
out_blk = torch.bmm(attn, v)
out_unf = out_blk.transpose(1, 2).contiguous().view(B, L, ci, pa).transpose(1, 2).contiguous().view(B, ci * pa, L)
out = torch.nn.functional.fold(out_unf, output_size=(H, W), kernel_size=p, stride=p)
if C != ci:
x_low = self.conv1x1(x_low)
return out + x_low
三、逐步手把手添加空间依赖感知模块(Spatial Dependency Perception, SDP)
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({SpatialDependencyPerception}):
# 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)加入空间依赖感知模块(Spatial Dependency Perception, SDP)
训练信息: summary: 314 layers, 4,716,918 parameters, 4,716,902 gradients
# Ultralytics YOLOMM 🚀 Mid-Fusion (SDFM Test)
# 在 P4/P5 融合处使用 SpatialDependencyPerception(SDFM,双输入、patch 注意力)。
# 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 融合(SDFM) ==========
- [[6, 17], 1, SpatialDependencyPerception, [null, 8, null]] # 22: P4 SDFM(patch=8)
- [[10, 21], 1, SpatialDependencyPerception, [null, 8, null]] # 23: P5 SDFM
- [-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(来自 SDFM)
- [-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/fusion/yolo11n-mm-mid-sdfm.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 群~
你的支持,就是我持续输出的最大动力!✨

被折叠的 条评论
为什么被折叠?



