上个月刚交付一个河道巡检的无人机项目,最大的挑战就是检测那些在几百米高空下只有几个像素点大的漂浮物。用老版本YOLO跑,召回率(Recall)惨不忍睹,老板的脸都快绿了。好在赶上了Ultralytics官方发布的YOLOv11,配合一套针对性的训练策略,总算是把问题给解决了。今天就把这套实战方案分享出来,全是干货。
一、前期准备:环境与数据
1. 环境确认
YOLOv11是Ultralytics在2024年底推出的最新官方版本,安装方式和YOLOv8一样简单:
pip install ultralytics==8.3.0 # 确保版本号正确
我的训练环境是Ubuntu 20.04 + RTX 4090 + CUDA 12.1。
2. 数据集痛点分析
我们的航拍数据集有两大特点:
- 极端小目标:大量目标尺寸小于16×16像素。
- 尺度变化大:同一张图里,近处的船和远处的瓶子大小能差几十倍。
拿到手的原始数据是DJI无人机拍摄的4K视频,需要先抽帧,再用CVAT进行精细标注。
二、分步实操:三大核心策略
1. 第一招:启用YOLOv11原生多尺度训练
YOLOv11对多尺度训练的支持比v8更完善。在配置文件里,我们只需开启multi_scale选项,并合理设置尺度范围。
# train.yaml 配置片段
imgsz: 1280 # 基础输入尺寸设大些
multi_scale: True
scale_factor: 0.5 # 尺度在 [1280*0.5, 1280*1.5] 间随机缩放
augment:
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5 # 这里的scale和multi_scale不冲突
mosaic: 1.0
mixup: 0.1
这个设置能让模型在训练时“见多识广”,有效应对不同距离的目标。
2. 第二招:自定义损失函数,聚焦小目标
YOLOv11的检测头对小目标依然不够敏感。我修改了损失函数,在计算定位损失(box loss)时,给小目标更高的权重。
# 在ultralytics/models/yolo/detect.py中修改
def bbox_loss(self, pred_bboxes, gt_bboxes, ...):
# 计算每个GT框的面积
areas = (gt_bboxes[..., 2] – gt_bboxes[..., 0]) * (gt_bboxes[..., 3] – gt_bboxes[..., 1])
# 面积越小,权重越大
small_obj_weight = torch.where(areas < 0.01, 2.0, 1.0)
# 将权重应用到loss计算中
loss = self.iou_loss(pred_bboxes, gt_bboxes) * small_obj_weight
return loss.mean()
别小看这2行代码,它能让模型在反向传播时,更“在意”那些微小的错误。
3. 第三招:推理时采用TTA(Test-Time Augmentation)
为了在部署时榨干最后一点性能,我们在推理阶段启用了TTA。虽然会牺牲一点速度,但对关键任务来说,这点代价完全值得。
from ultralytics import YOLO
model = YOLO('yolov11s.pt')
# 启用TTA进行预测
results = model.predict(source='drone_image.jpg', imgsz=1280, augment=True)
augment=True 会让模型对输入图像进行多种变换(如翻转、缩放),然后融合所有结果,显著提升小目标的检出率。
三、问题排查:避坑指南
- 显存爆炸? 多尺度训练很吃显存。解决方法:减小batch_size,或者使用梯度累积(accumulate参数)。
- mAP上去了但Recall没变? 检查你的验证集阈值。小目标检测通常需要将NMS的IoU阈值调低(比如从0.7降到0.5),避免抑制掉密集的小目标。
- 模型在Jetson上跑太慢? YOLOv11官方提供了TensorRT导出支持,记得用yolo export format=engine命令直接生成优化后的引擎文件。
四、总结
无人机航拍小目标检测,核心在于让模型既能“看得远”,又能“看得清”。通过这次项目,我验证了以下几点:
最终,我们的方案将小目标(<32×32像素)的召回率从42%提升到了82%,顺利通过了客户验收。希望这篇复盘能帮你少走弯路!





