news 2026/8/1 13:58:16

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

作者头像

张小明

前端开发工程师

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

一、为什么需要 SACR?

无人机航拍场景下的目标检测一直有个老大难问题——小目标看不见

原因很简单:

  • 目标在画面中占比极小,可能只有几十个像素

  • 飞行高度变化导致目标尺度剧烈变化

  • 背景极其复杂(屋顶、路面、树木、车辆混在一起)

现有的 Deformable DETR 架构虽然绕过了传统 FPN,但它有一个致命短板:最高分辨率的特征图感受野太有限了。也就是说,模型"看"到小目标的时候,周围的上下文信息不够,很容易被背景噪声干扰,把屋顶当车、把树影当人。

为了解决这个问题,论文作者提出了SACR(Scale‑Adaptive Contextual Refinement,尺度自适应上下文精炼)​ 模块,放在 Encoder 和 Decoder 之间。

它的设计目标很明确:

  • 不降低空间分辨率(小目标不能糊掉)

  • 捕获多尺度上下文信息

  • 抑制背景噪声

  • 结构足够轻量,可以即插即用到各种网络中


二、SACR 做了什么?

SACR 本质上就两步,非常简单:

第一步:多尺度空洞卷积

在最高分辨率的特征图上,同时用多个不同"扩张率"的 3×3 卷积去提取特征。论文用的是 6、12、18 这三个扩张率,再加上一个 1×1 卷积,一共四条支路,最后把所有结果拼在一起。

这样做的好处是:不用降低图像分辨率,就能看到不同大小的"周围区域",相当于给小目标补上了上下文信息。

第二步:自适应通道重校准

拿到多尺度特征之后,对每一个通道做一个"重要性打分"。具体做法是:

  1. 对每个通道做全局平均池化,得到通道级别的统计值

  2. 通过一个一维卷积学习通道之间的关系(卷积核大小会根据通道数自动调整)

  3. 用 Sigmoid 激活得到每个通道的权重

  4. 把权重乘回到原来的特征上

这一步的作用是:让跟小目标相关的通道信号更强,把背景噪声对应的通道压下去。

两步串在一起,就是一个完整的 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,p=None,d=1): if d>1: k=d*(k-1)+1 if isinstance(k,int) else [d*(x-1)+1 for x in k] if p is None: p=k//2 if isinstance(k,int) else [x//2 for x in k] return p class Conv(nn.Module): default_act=nn.SiLU() def __init__(self,c1,c2,k=1,s=1,p=None,g=1,d=1,act=True): super().__init__() self.conv=nn.Conv2d(c1,c2,k,s,autopad(k,p,d),groups=g,dilation=d,bias=False) self.bn=nn.BatchNorm2d(c2) self.act=self.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__() k=int(abs((math.log2(c)+1)/2)); k=k if k%2 else k+1 self.pool=nn.AdaptiveAvgPool2d(1) self.conv=nn.Conv1d(1,1,k,padding=(k-1)//2,bias=False) self.sig=nn.Sigmoid() def forward(self,x): y=self.pool(x).squeeze(-1).transpose(-1,-2) y=self.conv(y).transpose(-1,-2).unsqueeze(-1) return x*self.sig(y) class SACR(nn.Module): def __init__(self,c): super().__init__() m=max(c//4,1) self.b0=Conv(c,m,1) self.b1=Conv(c,m,3,d=6) self.b2=Conv(c,m,3,d=12) self.b3=Conv(c,m,3,d=18) self.fuse=Conv(m*4,c,1) self.eca=ECA(c) def forward(self,x): y=torch.cat([self.b0(x),self.b1(x),self.b2(x),self.b3(x)],1) y=self.eca(self.fuse(y)) return x+y class Bottleneck_SACR(nn.Module): def __init__(self,c1,c2,shortcut=True,g=1,k=(3,3),e=0.5): super().__init__() c_=int(c2*e) self.cv1=Conv(c1,c_,k[0]); self.cv2=Conv(c_,c2,k[1],g=g) self.add=shortcut and c1==c2 self.att=SACR(c2) def forward(self,x): y=self.att(self.cv2(self.cv1(x))) return x+y if self.add else y class C3k_SACR(C3): def __init__(self,c1,c2,n=1,shortcut=True,g=1,e=0.5,k=3): super().__init__(c1,c2,n,shortcut,g,e) c_=int(c2*e) self.m=nn.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,shortcut=True): super().__init__() self.att=SACR(c) self.ffn=nn.Sequential(Conv(c,c*2,1),Conv(c*2,c,1,act=False)) self.add=shortcut def forward(self,x): x=x+self.att(x) if self.add else self.att(x) x=x+self.ffn(x) if self.add else self.ffn(x) return x class C2PSA_SACR(nn.Module): def __init__(self,c1,c2,n=1,e=0.5): super().__init__(); assert c1==c2 self.c=int(c1*e) self.cv1=Conv(c1,2*self.c,1); self.cv2=Conv(2*self.c,c1,1) self.m=nn.Sequential(*(PSABlock_SACR(self.c) for _ in range(n))) def forward(self,x): a,b=self.cv1(x).split((self.c,self.c),1); b=self.m(b) return self.cv2(torch.cat((a,b),1)) class C3k2_SACR(C2f): def __init__(self,c1,c2,n=1,c3k=False,e=0.5,attn=False,g=1,shortcut=True): super().__init__(c1,c2,n,shortcut,g,e) self.m=nn.ModuleList((nn.Sequential(Bottleneck_SACR(self.c,self.c,shortcut,g),PSABlock(self.c,attn_ratio=0.5,num_heads=max(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__": x=torch.randn(1,64,80,80) m=SACR(64) y=m(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. 'model=YOLO26n.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. 'model=yolo26n.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)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 13:58:13

AI语音播客制作全栈拆解(含Whisper+ElevenLabs+Descript深度调参手册)

更多请点击: https://kaifayun.com 第一章:AI语音播客制作的演进逻辑与技术全景 AI语音播客制作已从早期TTS合成人工剪辑的线性流程,跃迁为数据驱动、多模态协同、端到端优化的智能生产范式。其演进逻辑根植于三大底层动力:语音合…

作者头像 李华
网站建设 2026/8/1 13:57:51

音乐格式转换工具:如何在浏览器中解决加密音乐播放问题

音乐格式转换工具:如何在浏览器中解决加密音乐播放问题 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地址: http…

作者头像 李华
网站建设 2026/8/1 13:51:28

Kindle漫画转换器终极指南:如何将漫画完美适配电子阅读器

Kindle漫画转换器终极指南:如何将漫画完美适配电子阅读器 【免费下载链接】kcc KCC (a.k.a. Kindle Comic Converter) is a comic and manga converter for ebook readers. 项目地址: https://gitcode.com/gh_mirrors/kc/kcc 你是否曾经想过在Kindle上阅读漫…

作者头像 李华
网站建设 2026/8/1 13:46:05

Sunshine游戏串流完全指南:5步搭建你的私人云游戏平台

Sunshine游戏串流完全指南:5步搭建你的私人云游戏平台 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 想在手机、平板或电视上畅玩PC游戏?Sunshine游戏串流…

作者头像 李华