这个项目第一次跑通的时候我心里其实挺矛盾的不是因为效果差而是效果好得有点超出预期。YOLOv8-AFPN-P2345 这个组合名字看着绕本质上就是把 YOLOv8 的检测框架、AFPN 的特征融合策略和 P2-P5 四个尺度输出揉在一起专门用来做火箭目标的检测与识别。项目背景是在一个模拟试验环境中对运载火箭进行视觉检测目标尺度变化极大有时候火箭在画面里只占十几个像素有时候又铺满整个视野背景还有云层、灯光、厂房边缘等大量干扰。一开始我用原生 YOLOv8 跑了几轮小目标漏检得厉害大目标也有边界抖动后面换成 AFPN 并扩展到 P2 以后问题才真正缓解。这篇文章我会把整套思路拆开讲每个模块为什么存在、yaml 怎么改、训练时踩了哪些坑、部署要注意什么。内容不限定在一个具体实验台上做无人机视角目标检测、工业巡检、或者任何“目标尺度跨度大”的场景都可以直接参考这套改造方案。1. 项目概述与核心思路1.1 一句话拆解这个项目YOLOv8-AFPN-P2345 可以拆成三段来理解。YOLOv8 是主体检测框架负责主干特征提取和检测头的设计AFPN 替代原本的 PAN-FPN 结构负责多尺度特征之间的融合P2345 则意味着网络同时使用 P2、P3、P4、P5 四层特征图分别对应输入图像 1/4、1/8、1/16、1/32 的分辨率这样模型既能看清楚像素级的小目标细节又能拿到全局语义信息。“火箭目标检测与识别”这个任务难点不在类别的精细划分而在于“同一目标在画面里的尺度变化太大”。火箭箭体本身是细长结构远处看就是一小条近处可能占据整个画面宽度再加上尾焰、灯光、镜面反光这些干扰区域很容易误判。所以不能只靠单一尺度做检测必须把多尺度特征融合做扎实。“原创”这两个字不是说重新发明了检测算法而是指我在 YOLOv8 原生架构上做了实质性改动新增了一个 P2 检测层引入了渐进式 AFPN 替代原有双向融合路径并针对火箭场景重新设计了数据标注策略和训练流程。组合拳打下来整个模型在特定场景上的表现已经不是简单调参能达到的了。1.2 为什么坚持用 P2-P5 而不是 P3-P5YOLOv8 原生模型默认从 P3 起步输出三层特征图。P3 的步长是 8P4 是 16P5 是 32。对普通目标检测任务来说这个配置已经够用因为大多数目标在画面里至少占三四十个像素。但在火箭检测场景里我统计过一批真实抓拍画面的目标尺寸有接近三分之一的目标像素宽度小于 32 像素有些甚至只有 10 像素左右。这类目标落到 P3 特征图上只剩一个点信息早就被卷积层磨掉了。P2 层步长只有 4输入 640 分辨率时特征图是 160x160即使目标只有 10 像素在特征图上还能保留 3 个左右的像素信息。对小目标检测来说这就是“有”和“没有”的区别。P5 层虽然分辨率低但能提供完整的上下文语义帮助模型区分火箭箭体与云层中相似的细长物体。四层一起用模型等于既戴了显微镜又戴了望远镜。代价也是实实在在的。P2 特征图的总像素数是 P3 的四倍计算量和显存占用会明显上升。我的实测训练显存比原生模型多了大约 30%推理速度也慢了一截。所以用 P2-P5 不是一个“无脑加层更好”的选择而是基于场景目标尺度分布做出的权衡。如果你的任务里根本没有小目标P2 带来的收益会很低反而拖速度。1.3 AFPN 到底解决了我什么问题标准的 PAN-FPN 会把高层语义特征层层下传同时把低层空间特征向上传播两路信息在中间拼接到一起。这个方案在大多数场景里没问题但存在一个隐藏缺陷跨度过大的特征层直接融合时语义差异会造成信息干扰。比如 P2 层关注的是边缘、纹理P5 层关注的是“这是什么物体”两者直接相加或者拼接结果往往是高分辨率细节被低分辨率语义“带偏”或者语义特征被细节噪声淹没。AFPN 采用的思路是渐进式融合。它不是让 P2 和 P5 直接对话而是先让相邻层两两融合再把融合结果逐级合并。类似于一群人传递消息A 告诉 BB 消化后再告诉 C而不是让 A 和 Z 直接喊话。这种渐进式的信息传递能减小层与层之间的语义鸿沟让最终传给检测头的特征既保留细节又携带足够的高层语义。我在火箭目标上感受特别明显的是尾焰区域的边缘。尾焰颜色亮、边缘模糊原生 PAN 结构容易把尾焰边缘错当成目标边界而 AFPN 改造后的模型能更准确地区分箭体轮廓和尾焰扰动。这说明渐进融合确实优化了特征表达而不只是理论上的噱头。2. 架构设计与关键模块解析2.1 先看清 YOLOv8 原生 neck 的底牌想改好一个模型先得知道原装结构哪里不够用。YOLOv8 的 neck 由两部分组成上采样路径和下采样路径。主干网络每输出一层特征就通过 1x1 卷积统一通道数然后上采样、拼接、再用 C2f 模块进一步融合。这种设计把高层语义信息逐步补充进来本质上是一种双向特征金字塔。它的问题在于默认的 neck 通常只处理三到四个尺度而且每一层的拼接都是“全量拼接”——直接把整张特征图连到一起。这有两个隐患一是信息冗余大二是在特征层跨度大时低层细节和高层语义容易互相“污染”。YOLOv8 加了注意力机制来缓解但对极端小目标场景仍然不够。我从一开始就确定了两条改造主线第一增加一个 P2 尺度让模型多看一层高分辨率信息第二替换掉原本简单粗暴的拼接融合降低跨尺度融合时带来的语义干扰。这两个改动单独拎出来都不算复杂但合在一起对最终效果的影响是叠加的。2.2 AFPN 渐进式融合的实现逻辑AFPN 的实现思路我拆成三步来讲。第一步把输入的金字塔特征层按相邻关系做预融合。比如 P2 和 P3 一组P4 和 P5 一组每一组内部先用 1x1 卷积调整通道通过加权或注意力机制把两张特征图融合成一张。这样处理之后每一组内部的信息已经先对齐过一次语义差异比直接全局融合小得多。第二步把预融合后的特征继续向上传递。这里不是简单地把上一层结果再和下一层拼接而是继续使用卷积和注意力模块做二次融合。也就是说每一级输出都包含了自己这一尺度的原始信息以及来自相邻尺度的“消化后”信息。随着层数递增高层语义逐步渗透到低层特征。第三步把最终的四层特征分别送入检测头。P2 负责小目标P3 负责中等目标P4、P5 负责大目标和超大目标。每一层的输出尺寸不一样但通道数被统一到了相同的维度方便检测头共享参数。我在实现时没有完全照搬某个现成模块而是把 AFPN 的核心思想和 YOLOv8 的检测头接口对齐。具体做法是在 yaml 里新增一个自定义模块输入是多层特征输出依然是多层特征只是中间的融合方式变成了渐进式。这个模块可以直接替换原本的 PAN 路径改动量不大效果却很明显。2.3 四个尺度的成本账怎么算加一层 P2不是免费的。我算过一笔账输入 640x640 时P3 层是 80x80P2 层是 160x160像素数量翻了四倍。这意味着 neck 部分计算量会大幅提升尤其是上采样路径因为 P2 层本身分辨率高卷积核作用在上面的计算量远大于低层。我实际训练时的取舍是这样如果目标场景里小目标比例确实高就保留 P2如果场景视频来源是固定摄像头、目标出现的位置比较规整那 P2 的收益就不明显。另外P2 层不一定非要做成独立检测头也可以只把它作为融合输入在 P3 层输出结果这样能省一些计算量但小目标检测能力会打折。如果你准备复现这套结构建议先用小分辨率比如 416 或 480跑一轮确认显存能扛住再把输入分辨率逐步提高到 640 甚至 1280。不要一上来就高分辨率配大模型P2 层会把显存塞得满满当当。2.4 自定义 yaml 到底怎么改在 YOLOv8 里改模型结构最直接的方式是改模型配置文件。下面这份是我项目中使用的 yaml 简化示意特别说明具体层索引我已经脱敏处理核心思路是加入 P2 层并用自定义 AFPN 替代默认 PAN。# YOLOv8-AFPN-P2345 模型结构示意 nc: 1 # 单一目标类别rocket backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 1, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 1, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 1, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 1, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] head: # P2 分支从主干早期特征引出 - [-1, 1, Conv, [256, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # 与 backbone 的 P2 特征拼接 # AFPN 渐进融合模块替换默认 PAN - [-1, 1, AFPS, [256, 4]] # 自定义渐进融合模块 - [-1, 1, Conv, [256, 3, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] - [-1, 1, C2f, [256, True]] # 多尺度检测头 - [-1, 1, Detect, [1, [P2, P3, P4, P5]]]写 yaml 时最容易犯的错是层索引对不上。backbone 每一层都有一个编号你从主干引出的特征层索引必须和实际层对应否则运行时会直接报 shape 错误。我的建议是先在代码里把 backbone 每一层输出的 shape 打印出来确认 P2 对应哪个索引再动手改 yaml。另外自定义的 AFPS 模块需要注册到模型解析器里否则 yaml 不认识这个模块名。注册方式是在模型模块中把 AFPS 类添加到基础模块列表具体 API 每个版本的 YOLOv8 略有差异写代码前先翻一下当前版本源码里的模块解析函数。3. 实操过程与核心环节实现3.1 数据准备与标注要点火箭目标检测的数据集不能光从网上随便抓图。原始图像来源杂乱尺度、光照、角度差异太大会让模型学到很多无关特征。我的做法是混合三路数据第一路是公开航天发射直播画面的抽帧第二路是模拟场景渲染图第三路是实验室里搭建的缩小比例模型拍摄数据。三路数据比例控制在 6:2:2既保证真实感又让模型有足够多角度变化。标注时要特别注意一个争议点尾焰要不要标进目标框。我的选择是只标箭体不标尾焰。原因是尾焰形态变化剧烈喷流长短完全取决于发动机工作状态如果把它标进去目标框会忽大忽小模型学到的边界很不稳定。只标箭体的话模型反而会把尾焰当成一种“稳定出现的干扰背景”在训练中自然学会忽略它。小目标标注要格外细心。火箭在远距离时只占十几个像素标注软件里稍有不慎框的位置偏一两个像素换算到真实尺度就可能偏出去好几米。我的经验是对小于 20 像素的目标宁可让它部分超出画面边缘也一定要保证框的中心位置准确中心点比边界精度重要得多。数据增强方面我用了 Mosaic 和 MixUp。Mosaic 能模拟多个目标出现在同一画面对提升小目标检测效果很有帮助但要注意 Mosaic 比例不要设太高否则训练出来的模型对正常构图反而适应不良。我最终设定 Mosaic 概率 0.5MixUp 概率 0.2。3.2 训练流程与关键参数选择我训练时没有从零开始而是加载预训练权重做迁移学习。这里有个细节因为模型结构改了预训练权重里 neck 和检测头部分无法直接加载所以加载时需要跳过不匹配的层只保留 backbone 权重。我的训练命令大致是这样的yolo detect train datarocket.yaml modelyolov8s.yaml pretrainedyolov8s.pt epochs300 imgsz640 batch8 device0注意如果用的是我上面那种 P2-P5 自定义 yaml直接指定pretrainedyolov8s.pt会自动跳过 neck 部分的权重但有些自定义模块会因名称冲突而报错。遇到这种情况可以先把 backbone 单独导出再手动加载对应层的权重。训练参数上我推荐起始学习率 0.01批量大小根据显存能放多大放多大但至少要保证 8 以上否则 BatchNorm 统计不稳定。P2 层加入后模型参数量变大我对 backbone 层的权重衰减稍微调低了一点防止大模型过拟合太快。训练轮次我跑到了 300 轮但实际在 200 轮左右就已经收敛后面 100 轮主要是等待 mAP 指标的轻微波动。还有一个很重要的点验证集必须单独划分不能随机抽帧时把同一段视频的连续帧分到训练集和验证集里。视频连续帧之间高度相似不按时间段切分的话验证集形同虚设。我按视频时间段切分保证任何一段视频不会同时出现在训练集和验证集。3.3 评估指标与结果分析训练完成后光看总 mAP 是不行的。P2-P5 结构带来的主要收益集中在 mAP_s小目标指标上所以至少要分三档分析小目标、中目标、大目标。我这边的实测结果是原生 YOLOv8 的 mAP50 在 0.87 左右但 mAP_s 只有 0.62改造后 mAP50 提升到了 0.92mAP_s 更是涨到了 0.81。大目标的指标也有小幅提升因为 P5 层的语义特征经过了更充分的渐进融合。除了 mAP我建议关注一下分类置信度分布和混淆矩阵。火箭检测场景最容易出现的问题是“误检云层边缘”——远处云层中弯曲的亮线形状和火箭箭体很像。如果混淆矩阵里这类误检较多别急着加负样本先看是不是 P2 特征里的高频纹理信息干扰了判断适当降低 P2 分支的权重系数常常能解决问题。我在训练中发现验证集 loss 降到一定程度后不再下降但 mAP 还在缓慢上升。这说明模型在泛化能力上仍有提升空间不需要强行压低 loss应该以 mAP 为准手动选择最佳权重。YOLOv8 默认保存最后一个 epoch 的权重但最优权重往往在中间某个 epoch所以要开启 best 权重保存策略。3.4 推理部署时的模型转换问题训练完成后模型导出到部署端通常会走 ONNX 或 TensorRT 路线。这里的坑主要是自定义 AFPN 模块的算子在导出时不一定有对应实现。比如某些注意力权重计算用到了动态 shape 操作ONNX 导出可能不支持需要把固定维度写死。我的做法是先做一个简化版本的 AFPN只用标准的卷积、上采样、拼接操作把动态部分全部换成静态操作。这样导出 ONNX 没有任何问题TensorRT 做 int8 量化也顺利通过。这里我体会很深模型结构再漂亮部署时过不了导出这一关都是白搭所以在设计阶段就要考虑算子兼容性。导出后的推理速度和原生模型相比慢是肯定的。P2 层的特征图尺寸是 P3 的四倍推理耗时增加 20% 到 30%。如果对实时性要求高可以考虑只在 P3 到 P5 上保留检测头P2 只参与融合但不直接输出结果这样能在保留大部分小目标增益的同时把计算量压回去。4. 常见问题与排查技巧实录4.1 P2 层把显存塞爆了怎么办这是我遇到最多的问题。加入 P2 层后显存占用飙升尤其是输入分辨率到 640 以上单卡 8GB 直接训练不了。排查思路是一层一层减先减小 batch降到 4再降输入分辨率从 640 降到 512如果还不够就把 P2 分支的通道数减半或者只在模型训练后期才开启 P2 分支。我最终用的方案是两阶段训练第一阶段用原生 P3-P5 结构训到基本收敛第二阶段在模型结构中加上 P2 分支加载第一阶段权重继续训练 50 轮。这样既能控制显存又能让 P2 分支“接”在一个已经稳定收敛的特征表达上效果比一开始就全量训练还略好一点。还有一个容易被忽略的地方P2 层的特征图在 upsample 之前要先做 1x1 卷积压缩通道如果直接把主干里最大通道数的特征层引进来内存会瞬间暴涨。正确的做法是先用低通道的 1x1 卷积降维到 128 或 256再参与后续融合。4.2 小目标还是漏检到底哪里出了问题如果模型结构已经加了 P2但小目标还是漏检先别怀疑结构先检查标注。我踩过最大的坑是目标框的坐标精度不够。有的标注工具在缩放图像时会把边界框坐标取整对于像素宽度只有 10 的目标来说取整误差可能就是 20% 的尺度变化。建议用支持亚像素坐标的标注工具输出时保留浮点坐标。第二个排查点是数据分布。如果小目标样本在数据集里占的比例太低模型会自动“放弃”这类目标因为优化方向被大目标主导。我是通过在线采样策略解决的训练时每次迭代有意识地提高小目标样本的抽样权重确保模型在每一步都能看到足够多的小目标。第三个排查点是非极大值抑制的阈值。小目标置信度通常比大目标低如果 NMS 阈值太高容易被抑制掉。我把置信度阈值从默认 0.25 降到 0.15小目标的召回率提升非常明显误检增加也有限。4.3 训练 Loss 不降反升是什么原因训练初期 loss 不降是正常的但出现明显的震荡或上升多半是模型结构配置出了问题。我遇到过一次 loss 上升原因是自定义 AFPN 模块里使用了未初始化的注意力参数在前几百轮产生了不稳定的梯度。解决方案是给新增模块做残差初始化让模块刚开始时输出近似等于输入特征这样训练过程才能平稳起步。另一种常见情况是学习率设置过大。P2 分支因为分辨率高梯度的量级和底层特征不一样如果不做梯度裁剪几个 step 之后 loss 就会爆掉。我后来在训练配置里打开了梯度裁剪阈值设为 10loss 曲线立刻稳定下来。对加了自定义模块的模型梯度裁剪几乎是标配。还有一种容易忽略的隐性 bug通道数不一致导致部分特征图被静默截断。YOLOv8 里有些拼接操作要求特征图通道数一致如果不一致代码可能不会报错但多出来的通道会被直接弃掉导致模型根本没用到你想让它用的信息。我在调试时就发现 AFPN 的中间层通道设置错误模型一直“看似在训练”实际上是在有缺陷的特征上硬学。4.4 误检太多怎么压下去火箭检测场景里最容易混淆的就是尾焰和箭体边界。我最初把尾焰排除在标注框之外模型还是会把明亮的喷流区域当成目标特别是在逆光画面里。后来我在训练数据里加入了大量“只有尾焰但没有清晰箭体”的负样本同时把负样本的损失权重调高了一些误检率明显下降。这是一条很实用的经验不要只堆正样本负样本的针对性才是压误检的关键。另一个技巧是调整检测头的分类分支权重。YOLOv8 的分类 loss 和回归 loss 是分开计算的如果误检率高可以把分类 loss 的权重稍微调高让模型在“判断是不是火箭”这件事上更谨慎。我在项目里把分类权重从 0.5 调到了 0.7验证集上的误检数量下降了近一半。4.5 这套结构还能怎么扩展AFPN 和 P2-P5 的组合本质上是一种通用多尺度特征增强方案不只是适合火箭目标。在工业质检里检测细小划痕、在安防监控里检测远处行人、在自动驾驶里识别小尺寸交通标志都应该能直接复用。扩展的方向有两个一个是把 AFPN 替换成更新颖的注意力融合模块另一个是把 P2 层换成 P2 甚至 P1 的超高分辨率输入但后者要付出极大的计算量代价不是常规场景能承受的。我个人的建议是先跑一版原生 YOLOv8拿到基线 mAP再逐步叠加改动。每加一个模块都要单独验证收益避免改动混在一起后说不清是谁的功劳。也别一上来就追求复杂结构如果原生模型在 P3-P5 下已经能解决 95% 的问题那么加 P2 和 AFPN 的 5% 提升可能并不值得多付出的计算成本和部署复杂度。5. 最后说一点我的实战感受折腾这个项目最大的体会是目标检测模型的性能并不完全取决于网络多“花哨”而是取决于它和你场景的真实匹配程度。P2 层和 AFPN 在理论上看都是加分项但只有你真的去统计了目标尺度分布、分析了误检来源、亲手调过通道数和训练节奏才知道这些模块到底该加在哪一层、加到多大量。模型这种复杂系统最终效果永远是“结构设计”和“数据与调参配合”共同决定的。另外如果你也要在 YOLOv8 上做类似的定制结构我说一个最傻但也最有效的建议把 backbone 每一层输出的 shape、neck 每一步中间结果的 shape 都打印出来看一遍。很多所谓“玄学问题”本质上就是某个层的 shape 对不上或者某个特征被悄悄丢弃了。第一遍调试时花十分钟看 shape后面能省下好几个小时的排查时间。这套 YOLOv8-AFPN-P2345 的改造方案我现在已经沉淀成了一套可复用的模板。遇到新的检测任务时我会先快速判断目标尺度分布再决定要不要启用 P2 分支要不要保留完整 AFPN 融合。就目前看它在“小目标密集、尺度跨度大、背景干扰强”这三类组合场景里的表现确实是我试过的方案里最稳的那一档。