034、特定场景优化(一):小目标检测的改进策略合集
一、从产线瑕疵检测说起上周在客户现场调试产线摄像头拍到的PCB板图像里那些微小的焊点缺陷和刻痕YOLOv11一个都没抓出来。不是模型精度不够而是这些目标在640×640的输入尺度下往往只有4×4甚至更小的像素区域——在经历了多次下采样之后特征图上可能就剩个寂寞了。小目标检测一直是工业落地中的硬骨头。今天我们就拆解几套实战中验证过的改进策略不搞理论堆砌直接上能写进代码的干货。二、数据层面别急着改模型遇到小目标漏检第一反应不应该是改网络结构。先看看数据怎么喂的。保持原始分辨率YOLO默认的640输入对于小目标就是灾难。我一般先尝试跳到1024甚至1280# 训练时直接改输入尺寸model.train(datapcb.yaml,imgsz1280,batch8)# batch得调小显存警告# 推理时也要保持一致resultsmodel(defect.jpg,imgsz1280)# 这里踩过坑训练推理尺寸不一致效果直接崩但注意大尺寸训练会显著拖慢速度且容易过拟合小目标——其他目标可能反而变差。切图大法好更稳的一招是大图切块训练滑动窗口推理。把高分辨率原图切成重叠的小块每块里的小目标就变成了“中目标”# 简易切图示例defslide_crop(img,crop_size640,overlap0.2):strideint(crop_size*(1-overlap))patches[]# 别这样写死循环大图会炸建议用yield省内存foryinrange(0,img.shape[0],stride):forxinrange(0,img.shape[1],stride):patchimg[y:ycrop_size,x:xcrop_size]ifpatch.shape[0]crop_sizeandpatch.shape[1]crop_size:patches.append(patch)returnpatches训练时对每个切块单独标注推理时切块检测再拼回去。代价是计算量翻倍但召回率提升明显。人工放大标注区域对于特别关键的小缺陷可以在标注时稍微把框画大一圈给模型一点学习余地。这招有点“作弊”但产线急上线时很管用。三、网络结构三个必改点1. 减少下采样次数YOLO默认下采样32倍小目标到最后一层特征图早就没了。把最后一个stride2的卷积或池化去掉改成16倍下采样# 修改model.yaml的backbone部分# 把某个P5层的stride从2改成1记得调整后续通道数匹配# 这块改完一定要算清楚特征图尺寸不然head会报shape不对2. 增加高分辨率检测头YOLOv11默认三个检测头P3/P4/P5。对于小目标我在P2更大特征图上加了一个检测头# 在head部分新增一个P2输出-from:[backbone.某个浅层输出]number:1args:[[128,256,NeckBlock,...]]# 这里参数根据实际通道数调整# 注意这个头只检测小目标anchor要重新聚类然后针对P2头只用小尺寸的anchor比如8×8、16×16避免大anchor干扰。3. 注意力机制不是银弹很多人喜欢加SE、CBAM等注意力模块。对于小目标空间注意力比通道注意力更重要。我习惯在浅层特征后加一个简单的空间注意力classSimpleSpatialAttention(nn.Module):def__init__(self):super().__init__()self.convnn.Conv2d(2,1,kernel_size7,padding3,biasFalse)self.sigmoidnn.Sigmoid()defforward(self,x):# 沿着通道维度做均值池化和最大池化avg_outtorch.mean(x,dim1,keepdimTrue)max_out,_torch.max(x,dim1,keepdimTrue)concattorch.cat([avg_out,max_out],dim1)attentionself.sigmoid(self.conv(concat))returnx*attention# 增强重要区域但注意注意力模块会拖慢推理速度部署前要评估性价比。四、损失函数让模型更“关注”小目标1. 修改anchor匹配策略YOLO默认用IoU匹配anchor小目标因为位置敏感可以改用NWDNormalized Wasserstein Distance作为匹配度量。NWD对微小框的位置偏差更敏感# NWD计算函数defwasserstein_loss(pred,target):# 把框转为高斯分布算Wasserstein距离# 具体实现略篇幅有限可搜NWD for YOLOreturndistance2. 损失权重动态调整在loss计算时给小目标更高的权重# 在compute_loss函数里fori,piinenumerate(pred):# pi是第i个检测头的输出# 根据target的尺寸动态赋权box_loss_scale2.0-target[:,3]*target[:,4]# 框越小权重越高# 乘到bbox_loss上3. 分类损失用QFLQuality Focal Loss不仅解决正负样本不平衡还对难例小目标有更好的梯度响应。把普通的Focal Loss换成QFL小目标召回能提2-3个点。五、后处理与部署陷阱1. NMS的改进小目标经常密集出现传统NMS容易误杀。试试Soft-NMS或DIoU-NMS# 直接用torchvision里的soft_nmsfromtorchvision.opsimportnms,soft_nms# soft_nms返回的是新分数需要阈值过滤更简单的办法是调高NMS的iou_thres比如从0.45调到0.6让重叠的小目标更容易保留。2. 部署时的分辨率对齐训练时用了大尺寸或切图部署时也要保持一致。嵌入式端显存不够怎么办动态分辨率或者ROI区域检测// 嵌入式C伪代码// 第一遍用低分辨率检测大目标// 裁出疑似区域第二遍高分辨率细查// 这样整体耗时可控3. 量化带来的精度损失小目标对量化更敏感8bit量化后可能消失。建议对小目标检测头单独用更高精度如16bit用感知量化训练QAT而不是后训练量化PTQ量化前先做通道蒸馏让网络对小目标更鲁棒六、个人经验包小目标检测没有通用解法先分析你的目标到底多小、多密、多模糊。拿张典型图用画图工具数像素再定策略。数据永远比模型重要。花两天时间精细化标注比调一个月模型提升更大。特别是边界模糊的小目标标注一致性是关键。不要盲目堆模块。先试输入分辨率→再试数据增强如mosaic小目标复制→最后改网络。我见过有人先改了一堆结构最后发现是训练时忘了开mosaic。部署时留余量。训练时小目标召回95%部署后可能只剩80%。预留一些阈值调整空间比如检测分数阈值做成可配置参数。终极方案上高像素相机。算法工程师的尊严是能用算法解决但实际项目里换个好相机可能立竿见影——硬件升级有时候是最经济的方案。