YOLOv8小目标检测优化:工业质检实战
1. 项目背景与核心挑战
去年夏天参与了一个工业质检项目,需要从高空拍摄画面中检测微小缺陷。当我把现成的YOLOv5模型直接套用上去时,recall值直接跌到30%以下——那些在2000万像素图像中只有20×20像素左右的焊点缺陷,就像大海捞针一样难以捕捉。这促使我系统研究了YOLOv8在小目标检测上的优化方案,特别是在Visidron这类无人机航拍数据集上的实践。
小目标检测的难点主要来自三个方面:首先,经过常规下采样后,小目标的特征信息几乎消失殆尽;其次,航拍图像中存在大量相似背景干扰(如建筑物纹理、植被等);最后,Visidron数据集特有的低对比度成像特点,使得目标与背景区分度进一步降低。传统方案通常直接缩小输入尺寸来提升小目标检测效果,但这会导致大目标检测性能断崖式下跌,我们需要更精细的改进策略。
2. 数据准备与增强策略
2.1 数据集特性分析
Visidron数据集包含约15,000张4K分辨率航拍图像,目标物体平均仅占图像面积的0.02%-0.5%。通过统计分析发现两个关键特征:一是目标多集中在图像中心区域(无人机云台稳定拍摄导致);二是70%的目标呈现灰黑色调,与水泥地面、沥青屋顶等背景颜色相近。
基于这些发现,我们放弃了通用的RandomCrop增强,改为:
transform = [ A.CenterCrop(height=2048, width=2048, p=0.8), # 聚焦中心区域 A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=30, val_shift_limit=20, p=0.7), # 增强色彩对比 A.RandomGamma(gamma_limit=(60,140), p=0.5) # 对抗低对比度 ]2.2 标签自适应优化
原始标注存在两个问题:一是部分目标被标注为"difficult"但未做特殊处理;二是密集小目标存在漏标。我们的解决方案是:
- 对difficult样本进行2×过采样
- 使用SAHI工具进行滑动窗口辅助标注:
python scripts/sliced_inference.py \ --image_dir ./raw_images \ --model_path yolov8x.pt \ --slice_size 512 \ --overlap_ratio 0.2通过这种方式新增了1,200个漏标目标,使mAP@0.5提升约4.3%。
3. 模型架构改进方案
3.1 特征金字塔重构
YOLOv8默认的PANet结构在深层特征融合时丢失了大量小目标信息。我们进行了三项关键修改:
- 在Backbone的stage3后增加一个P2输出层(160×160分辨率)
- 将原始的add操作改为concat+1×1卷积融合
- 为浅层特征添加Coordinate Attention模块
class CA_PAN(nn.Module): def __init__(self, in_channels): super().__init__() self.ca = CoordAtt(in_channels, reduction=8) self.conv = Conv(in_channels*2, in_channels, k=1) def forward(self, x1, x2): x1 = self.ca(x1) x = torch.cat([x1, F.interpolate(x2, scale_factor=2)], dim=1) return self.conv(x)3.2 检测头优化
针对小目标特点,我们:
- 将obj分支的BCE损失改为FocalLoss(alpha=0.8, gamma=2)
- 在分类头前加入SimAM注意力模块
- 调整anchor设置:使用k-means重新聚类得到更适合小目标的anchor尺寸
修改后的检测头在Visidron测试集上召回率提升12.6%,特别是对20-50像素目标的检测效果显著改善。
4. 训练技巧与超参调优
4.1 渐进式图像缩放
采用分阶段训练策略:
- 前50epoch:输入尺寸640×640,lr=0.01
- 中间30epoch:尺寸增至1024×1024,lr=0.001
- 最后20epoch:尺寸1280×1280,lr=0.0001
配合使用EMA(decay=0.9999)和AMP混合精度训练,在保持训练稳定的同时,使小目标AP@0.5提升约8.2%。
4.2 损失函数调参
通过消融实验确定最优权重组合:
loss: box: 7.0 # 原始值为5.0 cls: 1.5 # 原始值为0.5 dfl: 1.2 # 新增DFL损失 obj: 2.0 # 原始值为1.0这种设置特别有利于解决小目标分类模糊的问题。
5. 后处理优化方案
5.1 动态置信度阈值
传统固定阈值(如0.25)会过滤掉大量真实小目标。我们实现动态调整:
def dynamic_thresh(pred, min_thresh=0.1, scale=0.3): # pred: [..., xywh, conf, cls] area = pred[..., 2] * pred[..., 3] # w*h adj = min_thresh + (1 - area) * scale return pred[pred[..., 4] > adj]该方法在保证精度的同时,召回率提升5.7%。
5.2 多尺度测试集成
最终预测采用三尺度融合:
- 原图1280×1280
- 1.5倍放大
- 滑动窗口512×512(步长384)
使用WBF加权框融合,关键参数设置:
weights = [0.6, 0.3, 0.1] # 对应三个尺度 iou_thr = 0.35 skip_box_thr = 0.00016. 实战效果与问题排查
6.1 性能指标对比
在Visidron测试集上的对比结果:
| 方法 | AP@0.5 | AP@0.5:0.95 | 小目标召回率 |
|---|---|---|---|
| YOLOv8官方模型 | 0.423 | 0.261 | 0.381 |
| 本方案 | 0.587 | 0.402 | 0.692 |
| 改进幅度 | +38.7% | +54.0% | +81.6% |
6.2 典型问题解决方案
问题1:训练初期loss震荡剧烈
- 现象:前10个epoch的box_loss波动超过30%
- 排查:发现部分标注框宽高比异常(最大达1:28)
- 解决:添加数据清洗步骤,过滤宽高比>1:15的样本
问题2:验证集指标突然下降
- 现象:第45epoch时mAP下降约5%
- 分析:学习率调度器在尺寸切换时未同步调整
- 修复:添加尺寸变化时的lr warmup:
if epoch == 50: # 首次放大尺寸 scheduler.last_epoch = 0 # 重置学习率调度问题3:GPU显存溢出
- 场景:使用1280尺寸训练时出现OOM
- 优化:采用梯度累积(每4个step更新一次)
train: batch: 8 accumulate: 47. 工程部署建议
在实际部署中发现两个关键点:
- TensorRT加速时,需要保持动态尺度输入的一致性:
config->setOptimizationProfile(0) ->setDimensions(INPUT_NAME, OptProfileSelector::kMIN, Dims4(1,3,640,640)) ->setDimensions(INPUT_NAME, OptProfileSelector::kOPT, Dims4(1,3,1280,1280))- 对持续视频流检测,建议采用背景差分法预筛选ROI区域,可使推理速度提升3-5倍。
