【论文复现】AAAI 2026 AerialMind 中的 SACR 模块:航拍小目标增强,即插即用!附赠YOLO26改进

【论文复现】AAAI 2026 AerialMind 中的 SACR 模块:航拍小目标增强,即插即用!附赠YOLO26改进 一、为什么需要 SACR无人机航拍场景下的目标检测一直有个老大难问题——小目标看不见。原因很简单目标在画面中占比极小可能只有几十个像素飞行高度变化导致目标尺度剧烈变化背景极其复杂屋顶、路面、树木、车辆混在一起现有的 Deformable DETR 架构虽然绕过了传统 FPN但它有一个致命短板最高分辨率的特征图感受野太有限了。也就是说模型看到小目标的时候周围的上下文信息不够很容易被背景噪声干扰把屋顶当车、把树影当人。为了解决这个问题论文作者提出了SACRScale‑Adaptive Contextual Refinement尺度自适应上下文精炼​ 模块放在 Encoder 和 Decoder 之间。它的设计目标很明确不降低空间分辨率小目标不能糊掉捕获多尺度上下文信息抑制背景噪声结构足够轻量可以即插即用到各种网络中二、SACR 做了什么SACR 本质上就两步非常简单第一步多尺度空洞卷积在最高分辨率的特征图上同时用多个不同扩张率的 3×3 卷积去提取特征。论文用的是 6、12、18 这三个扩张率再加上一个 1×1 卷积一共四条支路最后把所有结果拼在一起。这样做的好处是不用降低图像分辨率就能看到不同大小的周围区域相当于给小目标补上了上下文信息。第二步自适应通道重校准拿到多尺度特征之后对每一个通道做一个重要性打分。具体做法是对每个通道做全局平均池化得到通道级别的统计值通过一个一维卷积学习通道之间的关系卷积核大小会根据通道数自动调整用 Sigmoid 激活得到每个通道的权重把权重乘回到原来的特征上这一步的作用是让跟小目标相关的通道信号更强把背景噪声对应的通道压下去。两步串在一起就是一个完整的 SACR 模块。三、模块结构图四、完整代码以及适配yolo26的代码import math import torch import torch.nn as nn from ultralytics.nn.modules import C2f, C3 from ultralytics.nn.modules.block import PSABlock #论文 https://arxiv.org/abs/2511.21053v2 __all__[SACR,C2PSA_SACR,C3k2_SACR] def autopad(k,pNone,d1): if d1: kd*(k-1)1 if isinstance(k,int) else [d*(x-1)1 for x in k] if p is None: pk//2 if isinstance(k,int) else [x//2 for x in k] return p class Conv(nn.Module): default_actnn.SiLU() def __init__(self,c1,c2,k1,s1,pNone,g1,d1,actTrue): super().__init__() self.convnn.Conv2d(c1,c2,k,s,autopad(k,p,d),groupsg,dilationd,biasFalse) self.bnnn.BatchNorm2d(c2) self.actself.default_act if act is True else act if isinstance(act,nn.Module) else nn.Identity() def forward(self,x): return self.act(self.bn(self.conv(x))) class ECA(nn.Module): def __init__(self,c): super().__init__() kint(abs((math.log2(c)1)/2)); kk if k%2 else k1 self.poolnn.AdaptiveAvgPool2d(1) self.convnn.Conv1d(1,1,k,padding(k-1)//2,biasFalse) self.signn.Sigmoid() def forward(self,x): yself.pool(x).squeeze(-1).transpose(-1,-2) yself.conv(y).transpose(-1,-2).unsqueeze(-1) return x*self.sig(y) class SACR(nn.Module): def __init__(self,c): super().__init__() mmax(c//4,1) self.b0Conv(c,m,1) self.b1Conv(c,m,3,d6) self.b2Conv(c,m,3,d12) self.b3Conv(c,m,3,d18) self.fuseConv(m*4,c,1) self.ecaECA(c) def forward(self,x): ytorch.cat([self.b0(x),self.b1(x),self.b2(x),self.b3(x)],1) yself.eca(self.fuse(y)) return xy class Bottleneck_SACR(nn.Module): def __init__(self,c1,c2,shortcutTrue,g1,k(3,3),e0.5): super().__init__() c_int(c2*e) self.cv1Conv(c1,c_,k[0]); self.cv2Conv(c_,c2,k[1],gg) self.addshortcut and c1c2 self.attSACR(c2) def forward(self,x): yself.att(self.cv2(self.cv1(x))) return xy if self.add else y class C3k_SACR(C3): def __init__(self,c1,c2,n1,shortcutTrue,g1,e0.5,k3): super().__init__(c1,c2,n,shortcut,g,e) c_int(c2*e) self.mnn.Sequential(*(Bottleneck_SACR(c_,c_,shortcut,g,((3,3),(3,3)),1.0) for _ in range(n))) class PSABlock_SACR(nn.Module): def __init__(self,c,shortcutTrue): super().__init__() self.attSACR(c) self.ffnnn.Sequential(Conv(c,c*2,1),Conv(c*2,c,1,actFalse)) self.addshortcut def forward(self,x): xxself.att(x) if self.add else self.att(x) xxself.ffn(x) if self.add else self.ffn(x) return x class C2PSA_SACR(nn.Module): def __init__(self,c1,c2,n1,e0.5): super().__init__(); assert c1c2 self.cint(c1*e) self.cv1Conv(c1,2*self.c,1); self.cv2Conv(2*self.c,c1,1) self.mnn.Sequential(*(PSABlock_SACR(self.c) for _ in range(n))) def forward(self,x): a,bself.cv1(x).split((self.c,self.c),1); bself.m(b) return self.cv2(torch.cat((a,b),1)) class C3k2_SACR(C2f): def __init__(self,c1,c2,n1,c3kFalse,e0.5,attnFalse,g1,shortcutTrue): super().__init__(c1,c2,n,shortcut,g,e) self.mnn.ModuleList((nn.Sequential(Bottleneck_SACR(self.c,self.c,shortcut,g),PSABlock(self.c,attn_ratio0.5,num_headsmax(self.c//64,1))) if attn else (C3k_SACR(self.c,self.c,2,shortcut,g) if c3k else Bottleneck_SACR(self.c,self.c,shortcut,g))) for _ in range(n)) if __name____main__: xtorch.randn(1,64,80,80) mSACR(64) ym(x) print(x.shape,y.shape)五、yolo26yaml文件1.yolo26_C2PSA_SACR# Ultralytics AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. modelYOLO26n.yaml will call YOLO26.yaml with scale n # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs #来自:YOLO目标检测 改进 # YOLO26n backbone backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2, [1024, True]] - [-1, 1, SPPF, [1024, 5, 3, True]] # 9 - [-1, 2, C2PSA_SACR, [1024]] # 10 # YOLO26n head head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 2, C3k2, [512, True]] # 13 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 13], 1, Concat, [1]] # cat head P4 - [-1, 2, C3k2, [512, True]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 10], 1, Concat, [1]] # cat head P5 - [-1, 1, C3k2, [1024, True, 0.5, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)2.YOLO26_C3K2_yaml# Ultralytics AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. modelyolo26n.yaml will call yolo26.yaml with scale n # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs #来自:YOLO目标检测 改进 # YOLO26n backbone backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_SACR, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_SACR, [1024, True]] - [-1, 1, SPPF, [1024, 5, 3, True]] # 9 - [-1, 2, C2PSA, [1024]] # 10 # YOLO26n head head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 2, C3k2_SACR, [512, True]] # 13 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 13], 1, Concat, [1]] # cat head P4 - [-1, 2, C3k2_SACR, [512, True]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 10], 1, Concat, [1]] # cat head P5 - [-1, 1, C3k2_SACR, [1024, True, 0.5, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)