最近在做PCB板缺陷检测的项目,拿YOLO26-s训了一版,大目标的焊盘、插件检测AP都能到95%以上,偏偏针脚虚焊、微孔划痕这类20×20以下的小目标,AP卡在27%死活上不去。
调过anchor比例,加过CBAM注意力,把FPN层砍了又加,折腾了两周,最多涨了0.8个点,一部署到Jetson Orin上还掉速严重。
后来沉下心扒了YOLO26的训练源码和检测头逻辑才发现:很多人改小目标的思路从一开始就错了——总想着在backbone和FPN上堆模块,却忽略了YOLO26架构里三个针对小目标的天生短板。
这三个问题不解决,加再多注意力都是杯水车薪。今天就把我们团队踩了一个月坑总结出的优化方案分享出来,从标签分配、特征融合到回归头全链路调整,在我们的PCB缺陷数据集上,小目标AP从27.1%涨到30.8%,VisDrone小目标子集涨了3.7个点,推理速度只掉了4%,完全不影响端侧部署。
死穴一:标签分配的马太效应,小目标抢不到正样本
问题根源
YOLO26主推的STAL标签分配,对外号称是“小目标感知”,但本质逻辑依然是按匹配度排序分配。
对每个gt框,计算所有anchor的分类得分+定位得分,按分数从高到低排,取前k个当正样本。这套逻辑对大目标非常友好:大目标特征强,分类得分稳,和anchor的IOU天然高,永远排在队伍前面,能分到足够的正样本。
但小目标就惨了:本身像素少、特征弱,分类置信度低,边界稍微模糊一点,IOU就上不去,得分天然比大目标低一截。更坑的是,当大小目标在同一个grid附近重叠时,有限的正样本配额全被大目标抢走,小目标直接变成负样本。
久而久之就形成了马太效应:大目标正样本越来越多,越训越准;小目标连正样本都抢不到,梯度信号不足,越训越漏。
精准打击方案
我们没有推翻STAL的整体框架,而是加了三个小改动,专门给小目标“开小灶”:
小目标预分配优先权
在标签分配的最前面,先遍历所有gt框,把宽高都小于32像素的目标挑出来,优先给它们分配正样本,每个小目标保底分配2个正样本。剩下的配额再按原有的STAL逻辑分配给中大型目标。核心逻辑就是:先保小目标有汤喝,再让大目标吃肉。邻域正样本扩充
对小目标,除了中心所在的grid,额外把上下左右四个相邻grid也设为正样本,同时适当降低IOU阈值(从0.5降到0.3)。小目标本身的梯度信号就弱,多几个正样本,训练收敛会稳很多,也不容易出现漏检。动态面积加权损失
在分类损失和回归损失前,乘一个和目标面积负相关的权重系数,目标越小,权重越高,最高放大3倍。强制模型把梯度重心往小目标偏移,避免被大目标的损失主导。
area=(gt_w*gt_h).clamp(min=1.0)weight=(target_area/area).sqrt().clamp(max=3.0)loss_cls*=weight loss_reg*=weight这三点改完,不用动任何网络架构,小目标的召回率直接涨了2个点,是整个优化流程里投入产出比最高的一步。
死穴二:FPN特征融合的信息稀释,小目标细节被淹没
问题根源
很多人以为FPN是用来增强小目标的,但其实YOLO26的FPN对小目标非常不友好。
YOLO26为了提升速度,FPN只做了三次下采样和两次上采样融合,小目标对应的P2层特征,要经过两次上采样,和P3、P4的深层特征融合后,才送到检测头。
深层特征的通道数多、语义强,但分辨率低,没有小目标的细节;浅层特征细节足,但通道少,语义弱。两者直接相加融合的时候,深层特征的数值幅度更大,会直接把浅层的细节信息“盖过去”。
说白了就是:融合了一圈,小目标的细节信息被稀释得差不多了,到检测头的时候,已经分不清哪是小目标哪是背景噪声。
精准打击方案
我们没有加额外的FPN层数,而是做了两个轻量化的优化,专门保留小目标的细节:
浅层特征旁路分支
从backbone的P2层直接引出一条旁路,经过两个3×3卷积做特征增强,不经过FPN的上采样融合,直接送到对应的小目标检测头。这条旁路只负责检测32×32以下的目标,通道数设为原来的一半,参数量增加很少,但能完整保留浅层的细节信息,小目标的边缘清晰度提升非常明显。动态加权融合
把原来FPN里的直接相加,改成通道级的动态加权融合:对每个融合位置,通过一层1×1卷积+Sigmoid计算浅层特征和深层特征的权重,自动调整融合比例。在小目标密集的区域,浅层特征的权重会自动升高,保留细节;在大目标区域,深层特征权重升高,保证语义。整个模块的计算量可以忽略不计,不会影响推理速度。
改完之后可视化特征图能明显看到,小目标的特征响应变强了,之前很多模糊不清的微小缺陷,现在都能被模型捕捉到。
死穴三:无DFL回归的精度断层,小目标定位失准
问题根源
YOLO26最大的架构改动之一,就是彻底拿掉了DFL(分布焦点损失),改用直接坐标回归。这个改动对部署非常友好,少了很多复杂算子,TensorRT加速后速度涨了不少,但对小目标来说,直接就是精度灾难。
为什么?大目标几十上百个像素,预测框偏移1-2个像素,IOU变化不大;但小目标本身就10-20个像素,边界偏移1个像素,IOU可能直接从0.8掉到0.5以下,直接从正样本变成负样本。
DFL的优势就是通过分布建模,让边界回归更精细,哪怕偏移零点几个像素也能拟合到。拿掉DFL之后,小目标的定位误差被放大,很多时候不是模型没检测到,是框不准,导致IOU不够被算成了误检。
精准打击方案
我们没有简单地把DFL加回去——那样YOLO26的部署优势就没了。而是设计了一个轻量级分支分布回归头(LDR-Head),只给小目标用分布回归,大目标保持原生的直接回归,兼顾精度和速度。
具体做法:
双分支回归头
检测头的回归部分分成两个分支:大目标分支和原生YOLO26一致,直接回归四个坐标值,速度快;小目标分支用简化版的DFL,把原来的16个分布bin缩减到4个,只对小范围的偏移做分布建模,计算量只有原版DFL的1/4。动态路由切换
推理的时候,根据预测框的大小自动选择分支:小于32×32的框用小目标分支的结果,大于的用大目标分支。整个切换逻辑只有几个判断操作,完全不影响推理速度。中心度约束
在小目标回归分支加入中心度损失,惩罚预测框中心偏离目标中心的情况,进一步降低小目标的定位漂移,提升高IOU阈值下的AP。
这套方案下来,小目标的定位精度提升非常明显,IOU=0.5的AP涨幅最大,同时整体计算量只增加了不到5%,导出ONNX、TensorRT完全没有兼容性问题,端侧部署不受影响。
实测效果:工业数据集涨点3.7%,速度损失可忽略
我们在两个典型的小目标场景数据集上做了完整验证:
- PCB缺陷数据集:12000张图片,80%以上是小于32×32的缺陷目标;
- VisDrone2024验证集:聚焦其中小于32×32的小目标子集。
测试基底为YOLO26-s,输入尺寸640×640,测试环境RTX 3090 + TensorRT 8.6。
| 模型版本 | PCB小目标AP | VisDrone小目标AP | 参数量 | FPS |
|---|---|---|---|---|
| 原生YOLO26-s | 27.1% | 28.5% | 9.4M | 112 |
| 改进版YOLO26-s | 30.8% | 32.2% | 9.7M | 107 |
从数据能很直观地看到:
- 小目标AP分别涨了3.7和3.7个点,提升幅度非常显著;
- 参数量只增加了0.3M,涨幅不到3.2%;
- 推理速度从112降到107,下降不到4.5%,完全在工业部署的可接受范围内。
最重要的是,所有改进都用的是标准卷积和常规算子,没有任何自定义的复杂算子,导出ONNX、转TensorRT、部署到Jetson边缘设备上都一帆风顺,不用做额外的算子适配和改写。
最后说几句
做了这么多YOLO的小目标优化,最深的感受就是:不要上来就堆模块。
很多人一遇到小目标效果差,第一反应就是加注意力、换更大的backbone、加深FPN,结果涨点有限,速度掉得厉害,还没法部署。
其实对于YOLO26这种已经高度优化的架构,小目标的核心问题根本不在backbone的特征提取能力,而是在标签分配够不够、特征融合保不保留细节、回归头精不精准这三个环节。
把这三个死穴打通,用最少的改动换最大的涨点,同时保住YOLO26部署友好的核心优势,这才是工业落地最需要的优化思路。
如果你的项目也被小目标检测卡着,不妨先从这三个方向改起,成本低,见效快。