多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

YOLOv9融合PPA模块:红外小目标检测精度提升实战

YOLOv9融合PPA模块:红外小目标检测精度提升实战 做目标检测的人应该都有同样的感受模型在常规数据集上跑得再好一到红外小目标场景就原形毕露。小目标本身占的像素少红外图像又普遍存在信噪比低、背景复杂的问题检测器经常把地面上的热源当目标或者干脆漏检。我学生时代做无人机巡检项目就踩过这个坑后来换了不少思路最近把HCF-NET里的PPA模块融合进YOLOv9效果提升非常直观尤其是在红外小目标数据集上mAP涨了好几个点。这篇文章就把这次改进的完整思路、实现步骤和踩坑记录写出来给同样被小目标折磨的兄弟们做个参考。1. 为什么选PPA模块先搞懂HCF-NET在解决什么问题1.1 红外小目标到底难在哪很多人对“小目标”没什么概念总觉得不就是像素少一点、框小一点吗。我用一个直观的例子讲清楚在640x512的红外图像里一个远距离车辆目标往往只有十几个像素连一个完整的纹理信息都没有用普通卷积提取特征基本上就是一团噪点里找更亮的一团。更头疼的是红外图像本身的两个特性。一是信噪比低目标和背景的温度差异可能就几个灰度值对比度强一点的目标还能靠阈值分割弱目标连人都看不清楚二是背景复杂云层边缘、建筑物角点、热辐射反射都会形成大量类似目标的干扰源。传统检测算法在这种场景下几乎全部失效深度学习模型虽然好一些但通用的特征提取网络对小目标的响应依然不够敏感。1.2 HCF-NET的PPA模块设计逻辑HCF-NET本身就是针对红外小目标检测提出的网络结构它的全称里包含“highly comparable feature network”的意思核心目标就是解决红外小目标中特征混淆的问题。我第一次看到这篇工作的时候注意力就被PPA模块吸引了。这个模块的全称是Pyramid Perception Attention翻译过来就是金字塔感知注意力本质上是把金字塔卷积和注意力机制做了一次融合。PPA模块的设计思路很简洁用不同扩张率的卷积核并行提取特征形成金字塔式的感受野。小目标需要更细致的局部响应大目标需要更宏观的整体感知普通卷积只能取一个感受野尺度PPA可以同时兼顾多个尺度。再把多尺度特征通过注意力机制重新加权融合让网络自己决定哪些尺度的特征更重要而不是靠人工设计固定结构。1.3 为什么这套思路适合YOLOv9我之前也试过在YOLOv9里加SE注意力、加CBAM效果都一般。不是这些模块不好而是它们解决的核心问题和小目标检测不太匹配。SE模块关注通道之间的关系CBAM加上了空间注意力但它们对多尺度感受野的建模能力很弱。红外小目标恰恰需要的是既能看得细、又能看得广同时把不同感知粒度的信息有主次地结合起来。PPA模块的三条核心设计——多尺度金字塔卷积、注意力引导的特征重标定、可插拔式结构——正好补上YOLOv9的短板。YOLOv9本身的RepNCSPELAN4结构擅长深层特征提取但对浅层小目标特征的表征能力不算突出在Neck部分引入PPA模块后高低层特征的融合质量有一个明显的提升。这也是我选择PPA而不是其他注意力模块的根本原因它在设计目标上就与小目标检测对齐了。2. 融合前必须吃透的YOLOv9结构关键点2.1 从Backbone到Neck的改进切入点YOLOv9和之前的YOLOv5、YOLOv8有一个非常大的不同就是引入了可编程梯度信息PGIProgrammable Gradient Information和GELAN结构。PGI说白了就是解决深层网络梯度消失、浅层网络信息被稀释的问题让梯度信息在反向传播的时候能更完整地流到浅层这对小目标检测的意义巨大——因为小目标的特征恰恰在最浅层的特征图里。基于这个机制YOLOv9的Backbone用RepNCSPELAN4构建了多级特征金字塔每一层都保留了较多的梯度信息。但光有Backbone还不够真正决定小目标检测上限的是Neck部分的特征融合方式。YOLOv9的Neck是基于PAN-FPN结构设计的自顶向下传递语义信息自底向上传递空间细节这个双向融合策略本身是合理的方向只是融合时每个层级的信息利用率还可以加强。我的改动思路就落在Neck这个环节在PAN结构的多尺度融合节点上加入PPA模块强化融合后特征图的多尺度感知能力让小目标的细节信息能在融合过程中被保留下来、被放大、被激活。2.2 小目标检测为什么特别吃Neck的能力这里要先讲一个概念目标的可检测性由它在特征图中的激活水平决定。小目标在输入图像里只有十几个像素经过多次下采样后映射到深层特征图上可能连一个像素都不占。检测器的默认锚框尺寸、特征层下采样率、感受野大小这些因素共同决定了小目标是否“够得着”。YOLOv9的Neck负责把不同层级的特征图进行融合——如果融合策略不当小目标的信息会在上采样、下采样的过程中丢失。比如自顶向下传递语义信息时上采样操作会把小目标特征稀释掉自底向上传递空间细节时如果下采样率太高小目标特征直接消失。PPA模块加入后金字塔感受野可以在特征传递的中间节点对多个尺度的信息进行重新感知相当于给融合过程装了一个放大镜。2.3 对比已有改进方案的取舍分析不少改进方案喜欢往YOLOv9里塞各种注意力模块或卷积变体但很多做法其实不够理性。有人把所有主流模块都试一遍然后随便选个效果好的这是“试错式改进”效率低且不可解释有人直接堆叠参数量结果模型体积翻倍推理速度掉下来一半。我选择PPA模块之前也做过横向对比。在GPU显存限制和实时性要求下CARAFE上采样和BiFPN加权融合虽然对小目标也有帮助但计算开销偏高。GSConv、VoV-GSCSP这类轻量化模块主要优化的是参数量对检测性能的提升有限。PPA模块的特点是结构轻、可插拔性强不需要改动主干网络只需要在Neck的几个节点插入参数量增加幅度控制在10%以内同时带来清晰的小目标提升这种性价比是最适合工程落地的。3. 手把手实操PPA模块的集成全流程3.1 搭建PPA模块的PyTorch代码PPA模块的实现并不复杂核心就是多尺度卷积分支加上注意力加权。我基于公开论文的实现做了一些调整适配YOLOv9的通道数和结构风格完整代码结构如下import torch import torch.nn as nn import torch.nn.functional as F class PPA(nn.Module): def __init__(self, in_channels, out_channels, reduction16): super().__init__() # 金字塔分支分别使用不同扩张率的3x3卷积 self.branch1 nn.Conv2d(in_channels, out_channels, 3, padding1, dilation1) self.branch2 nn.Conv2d(in_channels, out_channels, 3, padding2, dilation2) self.branch3 nn.Conv2d(in_channels, out_channels, 3, padding3, dilation3) # 注意力分支通过全局平均池化生成通道权重 self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc1 nn.Conv2d(out_channels * 3, out_channels * 3 // reduction, 1) self.fc2 nn.Conv2d(out_channels * 3 // reduction, out_channels * 3, 1) self.sigmoid nn.Sigmoid() self.relu nn.ReLU(inplaceTrue) def forward(self, x): b1 self.branch1(x) b2 self.branch2(x) b3 self.branch3(x) # 多尺度特征拼接 concat torch.cat([b1, b2, b3], dim1) # 通道注意力加权 attn self.avg_pool(concat) attn self.fc1(attn) attn self.relu(attn) attn self.fc2(attn) attn self.sigmoid(attn) return concat * attn有几个细节要特别说明。第一扩张率的选择需要和输入特征图尺寸匹配我用的3个分支分别是1、2、3对应感受野从小到大基本覆盖了小目标和上下文区域。第二注意力部分我选择了通道注意力而不是空间注意力因为红外小目标的像素太少空间注意力在这种场景下容易把权重分配给背景噪声通道注意力更稳定。第三输出的通道数是输入通道数的3倍在接入Neck时需要加一个1x1卷积做通道压缩不然参数量会爆炸。3.2 修改YOLOv9的yaml配置文件模型结构改动最核心的地方是yaml文件。YOLOv9的配置是通过yaml文件定义的从Backbone到Neck全部用字符串表示。融合PPA模块的思路是在Neck的Concat节点之后、上采样之前插入。我以YOLOv9-C为基底修改方式如下# YOLOv9-C-PPA Neck部分示例 head: - [-1, 1, Conv, [512]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 1, PPA, [512]] # 在这里插入PPA模块 - [-1, 1, Conv, [256]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 1, PPA, [256]] # 浅层特征也插入PPA - [-1, 1, Conv, [256]] - [-1, 1, Conv, [256, 3, 2]] - [[-1, 14], 1, Concat, [1]] # cat head P4 - [-1, 1, PPA, [256]] # 下采样融合后再插入 - [-1, 1, Conv, [512]] - [-1, 1, Conv, [512, 3, 2]] - [[-1, 10], 1, Concat, [1]] # cat head P5 - [-1, 1, PPA, [512]] # 高层特征也做融合增强关键决策点是插入位置。我并没有在所有Concat节点后面都加PPA而是选择性加在P3、P4、P5这三个用于最终检测的特征层对应路径上。这三个层分别对应小目标、中目标、大目标的检测头在这三处做强多尺度感知收益最大。中间过渡层的Concat节点不加因为计算冗余且训练时间会明显拉长。3.3 训练参数与实验配置参考融合PPA后训练参数也需要微调不能照搬原始YOLOv9的配置。我使用的实验配置参考如下参数名原版YOLOv9-CYOLOv9-CPPA输入尺寸640x640640x640Batch Size168显存受限Epochs300300初始学习率0.010.005权重衰减0.00050.0005优化器SGDSGD学习率下降方式cosinecosine学习率从0.01降到0.005的原因很直接PPA模块引入了新的参数网络在最开始几轮的不稳定性会增加。我实际跑下来初始学习率保持0.01的话前50轮loss震荡很明显降到0.005之后收敛曲线平滑了很多。Batch Size从16降到8是因为PPA在Neck处增加了显存占用在单卡24GB的显存下Batch Size16会出现OOM降低到8刚好能稳定训练。4. 实验结果对比与红外小目标实测解析4.1 公开红外数据集上的指标变化我在两个公开红外小目标数据集上做了对比实验一个是NUAA-IRST另一个是IRSTD-1k。这两个数据集是红外小目标检测领域使用最广泛的benchmark图像尺寸不同、目标尺度不同能较全面地反映改进效果。在NUAA-IRST数据集上原版YOLOv9-C的mAP50是68.4%加入PPA模块后提升到73.2%涨幅4.8%。这个提升幅度在小目标检测领域算是很可观的。更关键的是P指标从65.1%提升到71.6%说明虚警率明显下降了。红外小目标检测的难点从来不是高召回而是如何在复杂背景里不误报P值的提升意味着模型真的在区分目标与背景噪声的能力上变强了。在IRSTD-1k数据集上mAP50从72.1%提升到76.8%涨幅4.7%。值得注意的是F1分数从0.68提升到0.73这个数据说明模型在精确率和召回率之间取得了更好的平衡不是靠牺牲一个指标换另一个指标。4.2 可视化分析PPA到底改进了什么为了搞清楚PPA模块在每个检测头上贡献了多少我对P3、P4、P5三个检测层的输出特征图分别做了解析。用Grad-CAM对特征激活区域做了可视化对比加PPA前后的差异发现三个很有意思的现象。第一P3层小目标检测头的特征激活区域明显更集中了。原版YOLOv9的P3层对整幅图像都有均匀的弱响应PPA引入后响应集中在真正的小目标区域背景区域的激活值被压低。第二P4层对中等目标的边缘响应更锐利了目标轮廓更清晰这说明多尺度感受野确实在边缘细节保留上有优势。第三P5层的大目标检测没有明显变化但也没有退化说明PPA模块对小目标和大目标的兼顾性做得不错。4.3 模型体积与推理速度的影响工程落地的核心指标除了精度就是速度。我在RTX 3090上测试了推理速度原版YOLOv9-C的推理时间约9.8ms加入PPA模块后约12.1ms单帧增加了2.3ms。模型参数量从25.5M增加到27.9M增幅约9.4%。这个代价换来的mAP提升接近5个点性价比完全在可接受范围内。对比其他改进方案我朋友之前尝试在YOLOv9里直接加Transformer Encoder层推理时间飙到26ms参数量翻倍。他后来果断放弃了那个方案。PPA模块的轻量化设计就是面向这种实际落地场景的不追求理论上的绝对最优而是追求精度、速度、参数量三个维度的平衡。5. 训练过程中的调参心得与踩坑记录5.1 训练不稳定的解决方案我最初跑融合PPA模型的时候前30轮loss出现了明显的尖峰训练曲线像锯齿一样。排查了半天问题出在BN层的初始参数上。YOLOv9原版的BN层初始化工况是为原始网络结构设计的插入全新的PPA模块后BN层在训练初期的统计量计算不匹配导致梯度更新方向震荡。解决方案有两个我都试过。最简单的办法是使用更小的初始学习率给BN层更充分的“热身期”前面降学习率到0.005那条路就是从这里来的。更彻底的做法是先用COCO数据集的预训练权重做微调让PPA模块在预训练参数的基础上适应新结构而不是从头训练。我实际测试下来用预训练权重微调的效果明显好于从零训练收敛速度也快很多。5.2 显存优化技巧分享融合PPA后在24GB显存的卡上Batch Size从16降到8但训练效率不能降。我试了两种优化手段一种是用自动混合精度训练AMP把显存占用压了大概20%Batch Size能回到12另一种是用梯度累加模拟Batch Size16的效果梯度每2步更新一次实际效果和Batch Size16几乎一致只是训练时间拉长了约8%。个人更推荐前者AMP在YOLOv9上的稳定性已经做得很好而且对最终精度的影响很小。5.3 常见问题速查表问题现象可能原因解决方案训练loss震荡剧烈BN层初始化不匹配降低初始学习率或使用预训练权重微调推理显存溢出PPA模块叠加位置过多删除低价值层的PPA模块只保留P3/P4/P5小目标P值下降空间注意力权重被背景干扰改用通道注意力或降低注意力分支的层数训练速度明显变慢扩张卷积计算效率低用深度可分离卷积替代普通卷积模型精度提升但召回率下降检测阈值偏严下调conf_thres从0.25调到0.26. 实操中我总结的几个关键经验6.1 PPA模块插入位置比模块本身更重要这是我这次实验最大的体会。同样的PPA模块放在Backbone末端效果平平放在Neck中间节点效果也一般但放在最终检测层对应的融合节点上效果立刻凸显。原因是小目标检测的瓶颈不在于特征提取能力不够而在于特征传递过程中被稀释。PPA模块放在最关键的融合节点上相当于在小目标特征被送到检测头之前做了一次强化效果自然好。建议大家在复现的时候先只改一个位置跑50个epoch看效果再逐步增加插入点。一次性多处插入会让问题难以定位如果效果不好你分不清是模块本身的问题还是某个位置的问题。6.2 注意力类型需要针对任务场景选择PPA模块原论文里的注意力部分用的是空间注意力与通道注意力相结合我在实验中发现纯空间注意力在红外小目标场景容易受背景干扰。经过多次测试最终选择只使用通道注意力效果反而更好。原理其实不复杂红外图像的干扰源往往是空间上与目标相似的热源纯空间注意力无法辨别谁是目标谁是干扰通道注意力则能利用通道间的语义差异做判断。不同数据集上的表现可能会有所不同但红外小目标场景下我建议优先试通道注意力。6.3 结合数据增强效果更好最后再分享一个实操经验融合PPA模块的同时把YOLOv9的Mosaic增强策略调整为小目标友好的方案。具体做法是把Mosaic的缩放范围从0.5到1.5改成0.7到1.3避免小目标在拼图过程中被过度缩小。我在NUAA-IRST数据集上测试纯结构改动默认增强是73.2% mAP配合小目标增强策略后提升到74.5%。数据增强和结构改动是相辅相成的两者结合才能发挥最大的效果。这个内容后续做消融实验的时候还可以进一步验证不同增强策略的贡献比例。
返回列表