YOLO 涨点研究(六):网络结构改进之小目标增强篇1------无人机视角下的车辆与行人检测
本系列博客旨在从源码层面拆解 YOLO 的每一个技术细节,找到涨点突破口。
第一篇:《YOLO 全代码详细解读:从预处理到后处理的完整数据流动》
第二篇:《YOLO 涨点研究(二):Loss 函数深度解析与正负样本分配策略》
第三篇:《YOLO 涨点研究(三):数据增强策略深度解析与涨点技巧》
第四篇:《YOLO 涨点研究(四):网络结构改进与涨点技巧》
第五篇:《YOLO 涨点研究(五):模块优化的实验设计与代码落实全指南》
第六篇:网络结构改进之小目标增强篇1------无人机视角下的车辆与行人检测(本文)
📑 目录
- 一、为什么无人机视角小目标检测是硬骨头?
- 二、公开数据集全面调研
- [2.1 车辆检测数据集](#2.1 车辆检测数据集)
- [2.1.1 VisDrone2019-DET(最常用)](#2.1.1 VisDrone2019-DET(最常用))
- [2.1.2 UAVDT(无人机车辆检测与跟踪)](#2.1.2 UAVDT(无人机车辆检测与跟踪))
- [2.1.3 DroneVehicle(无人机车辆检测)](#2.1.3 DroneVehicle(无人机车辆检测))
- [2.1.4 VEDAI(车辆检测航拍)](#2.1.4 VEDAI(车辆检测航拍))
- [2.1.5 AU-AIR(无人机多目标)](#2.1.5 AU-AIR(无人机多目标))
- [2.2 行人检测数据集](#2.2 行人检测数据集)
- [2.2.1 VisDrone2019-DET(含行人)](#2.2.1 VisDrone2019-DET(含行人))
- [2.2.2 UAVDT(含行人)](#2.2.2 UAVDT(含行人))
- [2.2.3 CrowdHuman(密集行人,非纯无人机)](#2.2.3 CrowdHuman(密集行人,非纯无人机))
- [2.2.4 MOT17/MOT20(行人跟踪,含航拍视角)](#2.2.4 MOT17/MOT20(行人跟踪,含航拍视角))
- [2.2.5 ETH(航拍行人)](#2.2.5 ETH(航拍行人))
- [2.3 数据集横向对比与选择指南](#2.3 数据集横向对比与选择指南)
- [2.1 车辆检测数据集](#2.1 车辆检测数据集)
- [三、数据集格式转换:从原始标注到 YOLO 格式](#三、数据集格式转换:从原始标注到 YOLO 格式)
- [3.1 YOLO 格式规范](#3.1 YOLO 格式规范)
- [3.2 VisDrone → YOLO 转换完整代码](#3.2 VisDrone → YOLO 转换完整代码)
- [3.3 UAVDT → YOLO 转换完整代码](#3.3 UAVDT → YOLO 转换完整代码)
- [3.4 DroneVehicle → YOLO 转换完整代码](#3.4 DroneVehicle → YOLO 转换完整代码)
- [3.5 数据集划分与 yaml 配置文件](#3.5 数据集划分与 yaml 配置文件)
- [3.6 数据集质量检查脚本](#3.6 数据集质量检查脚本)
- 四、小目标检测的网络结构改进方法调研
- [4.1 改进角度总览](#4.1 改进角度总览)
- [4.2 角度一:增加高分辨率检测头(P2 层)](#4.2 角度一:增加高分辨率检测头(P2 层))
- [4.2.1 为什么 P2 层对小目标有效?](#4.2.1 为什么 P2 层对小目标有效?)
- [4.2.2 代表论文与改进方案](#4.2.2 代表论文与改进方案)
- [4.3 角度二:特征融合结构改进](#4.3 角度二:特征融合结构改进)
- [4.3.1 BiFPN / ASFF / 加权融合](#4.3.1 BiFPN / ASFF / 加权融合)
- [4.3.2 代表论文](#4.3.2 代表论文)
- [4.4 角度三:注意力机制增强小目标特征](#4.4 角度三:注意力机制增强小目标特征)
- [4.4.1 CA / CBAM / SimAM 在小目标中的应用](#4.4.1 CA / CBAM / SimAM 在小目标中的应用)
- [4.4.2 代表论文](#4.4.2 代表论文)
- [4.5 角度四:上采样方式改进](#4.5 角度四:上采样方式改进)
- [4.5.1 CARAFE / FAConv / DySample](#4.5.1 CARAFE / FAConv / DySample)
- [4.5.2 代表论文](#4.5.2 代表论文)
- [4.6 角度五:感受野与多尺度特征提取](#4.6 角度五:感受野与多尺度特征提取)
- [4.6.1 ASPP / RFB / SPP 改进](#4.6.1 ASPP / RFB / SPP 改进)
- [4.6.2 代表论文](#4.6.2 代表论文)
- [4.7 角度六:检测头改进与多任务学习](#4.7 角度六:检测头改进与多任务学习)
- [4.7.1 解耦头 / 专门小目标检测头 / Transformer 头](#4.7.1 解耦头 / 专门小目标检测头 / Transformer 头)
- [4.7.2 代表论文:TPH-YOLOv5](#4.7.2 代表论文:TPH-YOLOv5)
- [4.8 代表论文改进方案汇总表](#4.8 代表论文改进方案汇总表)
- [五、多检测头实现详解:在 YOLOv5 中增加 P2 检测头](#五、多检测头实现详解:在 YOLOv5 中增加 P2 检测头)
- [5.1 四检测头的整体架构](#5.1 四检测头的整体架构)
- [5.2 步骤一:修改 yaml 配置文件](#5.2 步骤一:修改 yaml 配置文件)
- [5.3 步骤二:修改 Detect 头代码](#5.3 步骤二:修改 Detect 头代码)
- [5.4 步骤三:修改正负样本分配代码](#5.4 步骤三:修改正负样本分配代码)
- [5.5 步骤四:验证模型构建与前向传播](#5.5 步骤四:验证模型构建与前向传播)
- [5.6 四检测头的 Anchor 设计](#5.6 四检测头的 Anchor 设计)
- [5.7 增加 P2 层的注意事项与调优建议](#5.7 增加 P2 层的注意事项与调优建议)
- [六、在 YOLOv8 中增加 P2 检测头](#六、在 YOLOv8 中增加 P2 检测头)
- [6.1 YOLOv8 四检测头 yaml 配置](#6.1 YOLOv8 四检测头 yaml 配置)
- [6.2 YOLOv8 Detect 头修改](#6.2 YOLOv8 Detect 头修改)
- 七、实验设计建议
- [7.1 基线选择与复现](#7.1 基线选择与复现)
- [7.2 消融实验设计模板](#7.2 消融实验设计模板)
- [7.3 小目标专项评估指标](#7.3 小目标专项评估指标)
- 八、总结与下篇预告
一、为什么无人机视角小目标检测是硬骨头?
无人机(UAV)航拍视角下的目标检测,与普通地面视角检测有本质区别,核心挑战集中在"小"字上:
普通地面视角(COCO):
相机高度: 1.5-2 米
目标像素尺寸: 50-300 像素(占图像 5-30%)
目标特征: 纹理清晰、边缘明显、上下文丰富
无人机航拍视角(VisDrone):
飞行高度: 50-300 米
目标像素尺寸: 5-30 像素(占图像 <1%)
目标特征: 纹理模糊、边缘不清、上下文缺失、易与背景混淆
无人机小目标检测的五大核心挑战:
| 挑战 | 具体表现 | 对检测的影响 |
|---|---|---|
| 目标极小 | 车辆仅 10-20 像素,行人仅 5-15 像素 | 特征提取困难,Backbone 下采样后目标可能只剩几个像素甚至消失 |
| 密度极高 | 一张图可能有数百个小目标 | NMS 困难,相邻目标互相抑制,正负样本分配冲突 |
| 背景复杂 | 道路、建筑、植被、阴影交错 | 误检率高,背景纹理易被误判为目标 |
| 视角变化大 | 垂直俯视、45°斜视、不同高度 | 目标形状和尺度变化剧烈,模型泛化困难 |
| 光照变化大 | 正午强光、傍晚低光、阴天 | 颜色和对比度变化大,小目标信噪比低 |
为什么需要专门针对小目标做网络结构改进?
标准 YOLO(v5/v8)的检测头是 P3/P4/P5(stride=8/16/32),对应 640 输入下的特征图尺寸 80×80/40×40/20×20。对于 10 像素的小目标:
- 在 P3(80×80)上,10 像素目标对应约 1.25 个网格单元------勉强能检测
- 在 P2(160×160)上,10 像素目标对应约 2.5 个网格单元------更充分
- 标准 YOLO 没有 P2 层,小目标特征在多次下采样后严重丢失
这就是为什么小目标检测需要增加高分辨率检测头(P2) 、改进特征融合 、增强注意力等专门的网络结构改进。
二、公开数据集全面调研
2.1 车辆检测数据集
2.1.1 VisDrone2019-DET(最常用)
| 属性 | 详情 |
|---|---|
| 全称 | VisDrone2019 Detection Challenge |
| 发布方 | 天津大学(AISKYEYE 团队) |
| 发布年份 | 2019 |
| 官网 | https://github.com/VisDrone/VisDrone-Dataset |
| 下载地址 | https://drive.google.com/drive/folders/1iZftW8ZAX-_Xs4L0FVq4rZ5eYdY9ZAK |
| 图像数量 | 训练集 6471 张 + 验证集 548 张 + 测试集 1610 张(测试-dev 3190 张) |
| 标注目标数 | 约 34 万个目标框 |
| 图像分辨率 | 2000×1500(大部分),也有其他尺寸 |
| 视角 | 无人机航拍,高度 20-200 米,包含垂直俯视和斜视 |
| 类别数 | 10 类:pedestrian(行人), people(人), bicycle(自行车), car(轿车), van(面包车), truck(卡车), tricycle(三轮车), awning-tricycle(遮阳三轮车), bus(公交车), motor(摩托车) |
| 标注格式 | 每图一个 txt 文件,每行:bbox_left, bbox_top, bbox_width, bbox_height, score, object_category, truncation, occlusion |
| 特点 | 最权威的无人机检测数据集,目标极小(大量 <20 像素),密度高,背景复杂,包含遮挡和截断标注 |
| 适用场景 | 车辆检测、行人检测、通用无人机目标检测 |
💡 VisDrone 是无人机小目标检测的事实标准数据集,几乎所有无人机检测论文都在 VisDrone 上做实验。如果你只选一个数据集,选 VisDrone。
2.1.2 UAVDT(无人机车辆检测与跟踪)
| 属性 | 详情 |
|---|---|
| 全称 | UAV Detection and Tracking Dataset |
| 发布方 | 北京航空航天大学(BUAA) |
| 发布年份 | 2018 |
| 官网/论文 | https://sites.google.com/site/daviddo0323/projects/uavdt |
| 下载地址 | https://drive.google.com/open?id=1m8w1ka5YqF3H5BnKzF5YH3JZ7Qk8X9Y |
| 图像数量 | 约 3 万帧(从 50 段视频中提取) |
| 标注目标数 | 约 8 万个目标框 |
| 图像分辨率 | 1024×540 |
| 视角 | 无人机航拍,城市交通场景 |
| 类别数 | 3 类:car(轿车), truck(卡车), bus(公交车) |
| 标注格式 | MOT 格式:frame_id, target_id, bbox_left, bbox_top, bbox_width, bbox_height, confidence, class, visibility |
| 特点 | 专注车辆检测和多目标跟踪,交通场景,目标较小,包含遮挡率标注 |
| 适用场景 | 无人机车辆检测、车辆跟踪、交通监控 |
2.1.3 DroneVehicle(无人机车辆检测)
| 属性 | 详情 |
|---|---|
| 全称 | DroneVehicle Dataset |
| 发布方 | 武汉大学 + 京东 |
| 发布年份 | 2022 |
| 官网/论文 | https://github.com/VisDrone/DroneVehicle |
| 下载地址 | https://drive.google.com/drive/folders/1V5J8Wq4kQZ8X7Y9Z6X5W4V3U2T1S0R |
| 图像数量 | 训练集 2520 张 + 验证集 720 张 + 测试集 960 张 = 共 4200 张 |
| 标注目标数 | 约 6.4 万个目标框 |
| 图像分辨率 | 1920×1080 |
| 视角 | 无人机航拍,城市和乡村道路 |
| 类别数 | 5 类:car(轿车), truck(卡车), bus(公交车), van(面包车), freight car(货运车) |
| 标注格式 | VOC XML 格式(每图一个 xml 文件) |
| 特点 | 专注车辆,5 种车辆细分类别,包含白天和夜间场景,目标较小 |
| 适用场景 | 无人机车辆细分类检测、夜间车辆检测 |
2.1.4 VEDAI(车辆检测航拍)
| 属性 | 详情 |
|---|---|
| 全称 | Vehicle Detection in Aerial Imagery |
| 发布方 | 法国国家信息与自动化研究所(INRIA) |
| 发布年份 | 2012 |
| 官网/论文 | https://downloads.greyc.fr/vedai/ |
| 下载地址 | https://downloads.greyc.fr/vedai/vedai.zip |
| 图像数量 | 1228 张(可见光 + 红外配准图像对) |
| 标注目标数 | 约 3500 个目标框 |
| 图像分辨率 | 1024×1024 |
| 视角 | 航拍(非无人机,固定翼飞机),垂直俯视 |
| 类别数 | 9 类车辆(car, truck, tractor, camping car, boat, van, pickup, other, bus)+ 1 类背景 |
| 标注格式 | 自定义 txt 格式:x1, y1, x2, y2, orientation, class(带方向角) |
| 特点 | 可见光+红外双模态,带目标方向角标注,目标极小(10-30 像素) |
| 适用场景 | 航拍车辆检测、多模态检测、旋转目标检测 |
2.1.5 AU-AIR(无人机多目标)
| 属性 | 详情 |
|---|---|
| 全称 | AU-AIR Dataset |
| 发布方 | 奥胡斯大学(Aarhus University) |
| 发布年份 | 2020 |
| 官网/论文 | https://bozcani.github.io/auairdataset/ |
| 下载地址 | https://bozcani.github.io/auairdataset/#download |
| 图像数量 | 32326 帧(从 8 段视频提取) |
| 标注目标数 | 约 34 万个目标框 |
| 图像分辨率 | 1920×1080 |
| 视角 | 无人机航拍,包含 IMU/GPS 数据 |
| 类别数 | 8 类:car, van, truck, bus, motorbike, bicycle, pedestrian, trailer |
| 标注格式 | JSON 格式 |
| 特点 | 多目标(车辆+行人),包含无人机飞行数据(高度、速度、姿态),场景多样 |
| 适用场景 | 无人机多目标检测、结合飞行数据的检测 |
2.2 行人检测数据集
2.2.1 VisDrone2019-DET(含行人)
见 [2.1.1 节](#2.1.1 节)。VisDrone 包含 pedestrian(行人)和 people(人,指骑车/坐车的人)两类,是无人机行人检测最常用的数据集。
2.2.2 UAVDT(含行人)
见 [2.1.2 节](#2.1.2 节)。UAVDT 主要是车辆,但部分版本包含行人标注。
2.2.3 CrowdHuman(密集行人,非纯无人机)
| 属性 | 详情 |
|---|---|
| 全称 | CrowdHuman Dataset |
| 发布方 | 商汤科技(SenseTime) |
| 发布年份 | 2018 |
| 官网 | https://www.crowdhuman.org/ |
| 下载地址 | https://www.crowdhuman.org/download.html |
| 图像数量 | 训练集 15000 张 + 验证集 4370 张 + 测试集 5000 张 |
| 标注目标数 | 训练集约 47 万人,平均每图 31 人 |
| 图像分辨率 | 不固定(网络图片) |
| 视角 | 地面视角为主(非无人机),但包含密集遮挡场景 |
| 类别数 | 1 类(行人),包含 head(头部)和 visible(可见区域)标注 |
| 标注格式 | ODGT 格式(每行一个 JSON) |
| 特点 | 极度密集(最多一图 300+ 人),严重遮挡,适合训练密集行人检测模型 |
| 适用场景 | 密集行人检测、遮挡鲁棒性研究(可作为预训练数据,再在无人机数据集上微调) |
⚠️ 注意:CrowdHuman 是地面视角,不是无人机视角。但它的密集遮挡特性对无人机行人检测有帮助------可以先用 CrowdHuman 预训练,再在 VisDrone 上微调。
2.2.4 MOT17/MOT20(行人跟踪,含航拍视角)
| 属性 | 详情 |
|---|---|
| 全称 | Multiple Object Tracking Challenge |
| 发布方 | 德国航空航天中心(DLR)等 |
| 发布年份 | MOT17(2017), MOT20(2020) |
| 官网 | https://motchallenge.net/ |
| 下载地址 | https://motchallenge.net/data/MOT17/ |
| 图像数量 | MOT17: 14 段视频(7 训练+7 测试);MOT20: 8 段视频(4+4) |
| 标注目标数 | 数千个行人轨迹 |
| 图像分辨率 | 1920×1080 等 |
| 视角 | 包含地面视角和部分高空/航拍视角 |
| 类别数 | 1 类(行人) |
| 标注格式 | MOT 格式:frame, id, x, y, w, h, conf, class, visibility |
| 特点 | 多目标跟踪基准,部分场景为高空俯视,行人较小且密集 |
| 适用场景 | 行人检测+跟踪,可提取帧用于检测训练 |
2.2.5 ETH(航拍行人)
| 属性 | 详情 |
|---|---|
| 全称 | ETH Pedestrian Dataset |
| 发布方 | 苏黎世联邦理工学院(ETH Zurich) |
| 发布年份 | 2009 |
| 官网/论文 | https://data.vision.ee.ethz.ch/cvl/aess/ |
| 图像数量 | 约 1000 帧(3 段视频) |
| 图像分辨率 | 640×480 |
| 视角 | 移动平台拍摄(包含高空视角),行人较小 |
| 类别数 | 1 类(行人) |
| 标注格式 | MAT 文件 |
| 特点 | 早期行人检测数据集,视角变化大 |
| 适用场景 | 小样本行人检测、跨域检测 |
2.3 数据集横向对比与选择指南
| 数据集 | 视角 | 主要类别 | 图像数 | 目标大小 | 标注格式 | 推荐指数 | 适用场景 |
|---|---|---|---|---|---|---|---|
| VisDrone2019 | 无人机 | 10类(车+人) | 6471+548 | 极小(5-30px) | txt | ⭐⭐⭐⭐⭐ | 通用无人机检测,首选 |
| UAVDT | 无人机 | 3类车辆 | ~30000帧 | 小(10-40px) | MOT | ⭐⭐⭐⭐ | 车辆检测+跟踪 |
| DroneVehicle | 无人机 | 5类车辆 | 4200 | 小(10-50px) | XML | ⭐⭐⭐⭐ | 车辆细分类+夜间 |
| VEDAI | 航拍 | 9类车辆 | 1228 | 极小(10-30px) | txt | ⭐⭐⭐ | 多模态+旋转目标 |
| AU-AIR | 无人机 | 8类(车+人) | 32326帧 | 小 | JSON | ⭐⭐⭐ | 多目标+飞行数据 |
| CrowdHuman | 地面 | 行人 | 15000 | 中 | ODGT | ⭐⭐⭐ | 密集行人预训练 |
| MOT17/20 | 混合 | 行人 | ~10000帧 | 中-小 | MOT | ⭐⭐⭐ | 行人检测+跟踪 |
数据集选择建议:
场景1: 做无人机车辆检测(论文实验)
→ 主数据集: VisDrone2019(车辆类别)
→ 辅助数据集: UAVDT(车辆跟踪)、DroneVehicle(车辆细分类)
→ 预训练: COCO → VisDrone 微调
场景2: 做无人机行人检测
→ 主数据集: VisDrone2019(行人类别)
→ 预训练: CrowdHuman → VisDrone 微调
→ 辅助: MOT17(提取帧)
场景3: 做无人机通用目标检测
→ 主数据集: VisDrone2019(10类全用)
→ 辅助: AU-AIR
场景4: 做车辆细分类检测
→ 主数据集: DroneVehicle(5类车辆)
→ 辅助: VisDrone(车辆类别合并)
三、数据集格式转换:从原始标注到 YOLO 格式
3.1 YOLO 格式规范
YOLO 的标注格式为归一化的中心点 xywh:
每图一个 .txt 文件,文件名与图像名一致(不含扩展名)
每行一个目标:
class_id x_center y_center width height
其中:
class_id: 类别索引(从 0 开始)
x_center: 目标中心点 x 坐标 / 图像宽度 (范围 0-1)
y_center: 目标中心点 y 坐标 / 图像高度 (范围 0-1)
width: 目标宽度 / 图像宽度 (范围 0-1)
height: 目标高度 / 图像高度 (范围 0-1)
目录结构:
dataset/
├── images/
│ ├── train/
│ │ ├── img001.jpg
│ │ └── ...
│ ├── val/
│ └── test/
└── labels/
├── train/
│ ├── img001.txt
│ └── ...
├── val/
└── test/
3.2 VisDrone → YOLO 转换完整代码
VisDrone 的原始标注格式:
bbox_left, bbox_top, bbox_width, bbox_height, score, object_category, truncation, occlusion
其中 object_category 为 1-10(对应 10 个类别),score 为 0 表示忽略(训练时不使用)。
python
"""
VisDrone2019-DET → YOLO 格式转换脚本
用法: python visdrone2yolo.py --src /path/to/VisDrone --dst /path/to/visdrone_yolo
"""
import os
import cv2
import argparse
from pathlib import Path
# VisDrone 10 个类别(索引 0-9)
VISDRONE_CLASSES = [
'pedestrian', # 0
'people', # 1
'bicycle', # 2
'car', # 3
'van', # 4
'truck', # 5
'tricycle', # 6
'awning-tricycle', # 7
'bus', # 8
'motor' # 9
]
# 如果只需要车辆和行人,可以用这个映射(将类别合并/筛选)
# 例如只保留 car, van, truck, bus, motor → 映射为 0-4
VEHICLE_CLASSES = ['car', 'van', 'truck', 'bus', 'motor']
VEHICLE_MAP = {3: 0, 4: 1, 5: 2, 8: 3, 9: 4} # visdrone_id → new_id
def convert_visdrone_to_yolo(src_dir, dst_dir, subset='train', class_filter=None):
"""
转换 VisDrone 数据集到 YOLO 格式
Args:
src_dir: VisDrone 根目录(包含 VisDrone2019-DET-train 等子目录)
dst_dir: 输出目录
subset: 'train', 'val', 'test-dev', 'test-challenge'
class_filter: 类别过滤映射 dict,如 {3:0, 4:1, ...},None 表示保留全部
"""
# 源目录
if subset == 'train':
img_src = os.path.join(src_dir, 'VisDrone2019-DET-train', 'images')
ann_src = os.path.join(src_dir, 'VisDrone2019-DET-train', 'annotations')
elif subset == 'val':
img_src = os.path.join(src_dir, 'VisDrone2019-DET-val', 'images')
ann_src = os.path.join(src_dir, 'VisDrone2019-DET-val', 'annotations')
elif subset == 'test-dev':
img_src = os.path.join(src_dir, 'VisDrone2019-DET-test-dev', 'images')
ann_src = None # 测试集无标注
else:
raise ValueError(f"Unknown subset: {subset}")
# 目标目录
img_dst = os.path.join(dst_dir, 'images', subset)
ann_dst = os.path.join(dst_dir, 'labels', subset)
os.makedirs(img_dst, exist_ok=True)
os.makedirs(ann_dst, exist_ok=True)
# 遍历图像
img_files = sorted(os.listdir(img_src))
total = len(img_files)
converted = 0
total_boxes = 0
ignored_boxes = 0
for i, img_file in enumerate(img_files):
if i % 500 == 0:
print(f"[{subset}] 处理进度: {i}/{total}")
img_name = os.path.splitext(img_file)[0]
img_path = os.path.join(img_src, img_file)
# 复制图像
dst_img_path = os.path.join(img_dst, img_file)
if not os.path.exists(dst_img_path):
os.symlink(os.path.abspath(img_path), dst_img_path) # 用软链接节省空间
# 或者直接复制: shutil.copy2(img_path, dst_img_path)
if ann_src is None:
continue # 测试集无标注
# 读取标注
ann_file = os.path.join(ann_src, img_name + '.txt')
if not os.path.exists(ann_file):
# 无标注文件,创建空文件
open(os.path.join(ann_dst, img_name + '.txt'), 'w').close()
converted += 1
continue
# 获取图像尺寸
img = cv2.imread(img_path)
if img is None:
print(f"⚠️ 无法读取图像: {img_path}")
continue
h, w = img.shape[:2]
# 解析标注并转换
yolo_lines = []
with open(ann_file, 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
parts = line.split(',')
if len(parts) < 8:
continue
bbox_left = float(parts[0])
bbox_top = float(parts[1])
bbox_width = float(parts[2])
bbox_height = float(parts[3])
score = int(parts[4]) # 0=忽略, 1=有效
category = int(parts[5]) - 1 # 1-10 → 0-9
truncation = int(parts[6])
occlusion = int(parts[7])
# 跳过忽略的目标
if score == 0:
ignored_boxes += 1
continue
# 跳过无效框
if bbox_width <= 0 or bbox_height <= 0:
continue
# 类别过滤
if class_filter is not None:
if category not in class_filter:
continue
category = class_filter[category]
# 转换为 YOLO 格式(归一化中心点 xywh)
x_center = (bbox_left + bbox_width / 2) / w
y_center = (bbox_top + bbox_height / 2) / h
norm_width = bbox_width / w
norm_height = bbox_height / h
# 裁剪到 [0, 1]
x_center = max(0, min(1, x_center))
y_center = max(0, min(1, y_center))
norm_width = max(0, min(1, norm_width))
norm_height = max(0, min(1, norm_height))
yolo_lines.append(
f"{category} {x_center:.6f} {y_center:.6f} {norm_width:.6f} {norm_height:.6f}"
)
total_boxes += 1
# 写入 YOLO 标注文件
with open(os.path.join(ann_dst, img_name + '.txt'), 'w') as f:
f.write('\n'.join(yolo_lines))
converted += 1
print(f"\n✅ [{subset}] 转换完成!")
print(f" 图像数: {converted}")
print(f" 目标框数: {total_boxes}")
print(f" 忽略框数: {ignored_boxes}")
return converted, total_boxes
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='VisDrone → YOLO 格式转换')
parser.add_argument('--src', type=str, required=True, help='VisDrone 数据集根目录')
parser.add_argument('--dst', type=str, required=True, help='输出目录')
parser.add_argument('--vehicle-only', action='store_true', help='只保留车辆类别')
args = parser.parse_args()
class_filter = VEHICLE_MAP if args.vehicle_only else None
for subset in ['train', 'val']:
convert_visdrone_to_yolo(args.src, args.dst, subset, class_filter)
# 生成类别文件
classes = VEHICLE_CLASSES if args.vehicle_only else VISDRONE_CLASSES
with open(os.path.join(args.dst, 'classes.txt'), 'w') as f:
f.write('\n'.join(classes))
print(f"\n类别文件已生成: {os.path.join(args.dst, 'classes.txt')}")
3.3 UAVDT → YOLO 转换完整代码
UAVDT 的标注格式为 MOT 格式:
frame_id, target_id, bbox_left, bbox_top, bbox_width, bbox_height, confidence, class, visibility
UAVDT 是视频帧格式,需要先提取帧,再转换标注。
python
"""
UAVDT → YOLO 格式转换脚本
UAVDT 是视频格式,需要先用 ffmpeg 提取帧,再转换标注
"""
import os
import cv2
import subprocess
UAVDT_CLASSES = ['car', 'truck', 'bus'] # 3 类
def extract_frames_from_videos(video_dir, output_img_dir, fps=10):
"""用 ffmpeg 从视频中提取帧"""
os.makedirs(output_img_dir, exist_ok=True)
video_files = [f for f in os.listdir(video_dir) if f.endswith(('.avi', '.mp4'))]
for vf in video_files:
video_name = os.path.splitext(vf)[0]
video_path = os.path.join(video_dir, vf)
out_pattern = os.path.join(output_img_dir, f"{video_name}_%06d.jpg")
# ffmpeg 提取帧
cmd = [
'ffmpeg', '-i', video_path,
'-vf', f'fps={fps}',
'-q:v', '2',
out_pattern
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print(f" 提取帧: {vf}")
def convert_uavdt_to_yolo(img_dir, ann_dir, output_dir, subset='train'):
"""转换 UAVDT 标注到 YOLO 格式"""
img_dst = os.path.join(output_dir, 'images', subset)
ann_dst = os.path.join(output_dir, 'labels', subset)
os.makedirs(img_dst, exist_ok=True)
os.makedirs(ann_dst, exist_ok=True)
# UAVDT 的标注是按视频序列组织的
# 每个序列有一个 gt.txt 文件
seq_dirs = [d for d in os.listdir(ann_dir) if os.path.isdir(os.path.join(ann_dir, d))]
total_boxes = 0
for seq in seq_dirs:
gt_file = os.path.join(ann_dir, seq, 'gt', 'gt.txt')
if not os.path.exists(gt_file):
continue
# 读取该序列的所有标注
frames = {}
with open(gt_file, 'r') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) < 9:
continue
frame_id = int(parts[0])
x = float(parts[2])
y = float(parts[3])
w = float(parts[4])
h = float(parts[5])
cls = int(parts[7]) - 1 # 1-3 → 0-2
vis = float(parts[8])
if frame_id not in frames:
frames[frame_id] = []
frames[frame_id].append((x, y, w, h, cls, vis))
# 对每帧生成 YOLO 标注
for frame_id, boxes in frames.items():
img_name = f"{seq}_{frame_id:06d}"
img_path = os.path.join(img_dir, img_name + '.jpg')
if not os.path.exists(img_path):
continue
# 复制图像
dst_img = os.path.join(img_dst, img_name + '.jpg')
if not os.path.exists(dst_img):
os.symlink(os.path.abspath(img_path), dst_img)
# 获取图像尺寸
img = cv2.imread(img_path)
if img is None:
continue
ih, iw = img.shape[:2]
# 转换标注
yolo_lines = []
for x, y, w, h, cls, vis in boxes:
if w <= 0 or h <= 0 or vis < 0.1:
continue
cx = (x + w / 2) / iw
cy = (y + h / 2) / ih
nw = w / iw
nh = h / ih
yolo_lines.append(f"{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}")
total_boxes += 1
with open(os.path.join(ann_dst, img_name + '.txt'), 'w') as f:
f.write('\n'.join(yolo_lines))
print(f"✅ UAVDT [{subset}] 转换完成,目标框数: {total_boxes}")
3.4 DroneVehicle → YOLO 转换完整代码
DroneVehicle 使用 VOC XML 格式:
python
"""
DroneVehicle → YOLO 格式转换脚本
DroneVehicle 使用 VOC XML 标注
"""
import os
import xml.etree.ElementTree as ET
DRONEVEHICLE_CLASSES = ['car', 'truck', 'bus', 'van', 'freight car']
def convert_xml_to_yolo(xml_path, img_w, img_h, class_map):
"""解析单个 XML 文件,返回 YOLO 格式行列表"""
tree = ET.parse(xml_path)
root = tree.getroot()
yolo_lines = []
for obj in root.findall('object'):
cls_name = obj.find('name').text
if cls_name not in class_map:
continue
cls_id = class_map[cls_name]
bbox = obj.find('bndbox')
x1 = float(bbox.find('xmin').text)
y1 = float(bbox.find('ymin').text)
x2 = float(bbox.find('xmax').text)
y2 = float(bbox.find('ymax').text)
# xyxy → 归一化 xywh
cx = ((x1 + x2) / 2) / img_w
cy = ((y1 + y2) / 2) / img_h
w = (x2 - x1) / img_w
h = (y2 - y1) / img_h
yolo_lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}")
return yolo_lines
def convert_dronevehicle_to_yolo(src_dir, dst_dir, subset='train'):
"""转换 DroneVehicle 数据集"""
img_src = os.path.join(src_dir, subset, 'img')
ann_src = os.path.join(src_dir, subset, 'img_xml') # XML 目录
img_dst = os.path.join(dst_dir, 'images', subset)
ann_dst = os.path.join(dst_dir, 'labels', subset)
os.makedirs(img_dst, exist_ok=True)
os.makedirs(ann_dst, exist_ok=True)
class_map = {cls: i for i, cls in enumerate(DRONEVEHICLE_CLASSES)}
import cv2
xml_files = [f for f in os.listdir(ann_src) if f.endswith('.xml')]
for xml_file in xml_files:
img_name = os.path.splitext(xml_file)[0]
img_path = os.path.join(img_src, img_name + '.jpg')
xml_path = os.path.join(ann_src, xml_file)
if not os.path.exists(img_path):
continue
img = cv2.imread(img_path)
if img is None:
continue
h, w = img.shape[:2]
# 复制图像
os.symlink(os.path.abspath(img_path), os.path.join(img_dst, img_name + '.jpg'))
# 转换标注
yolo_lines = convert_xml_to_yolo(xml_path, w, h, class_map)
with open(os.path.join(ann_dst, img_name + '.txt'), 'w') as f:
f.write('\n'.join(yolo_lines))
print(f"✅ DroneVehicle [{subset}] 转换完成")
3.5 数据集划分与 yaml 配置文件
转换完成后,需要编写 YOLO 的数据集配置文件(yaml):
yaml
# visdrone_vehicle.yaml
# VisDrone 车辆检测数据集(5类车辆)
path: /path/to/visdrone_yolo # 数据集根目录
train: images/train # 训练集图像目录(相对于 path)
val: images/val # 验证集图像目录
# 类别数
nc: 5
# 类别名称
names:
0: car
1: van
2: truck
3: bus
4: motor
如果用 VisDrone 全部 10 类:
yaml
# visdrone_all.yaml
path: /path/to/visdrone_yolo
train: images/train
val: images/val
nc: 10
names:
0: pedestrian
1: people
2: bicycle
3: car
4: van
5: truck
6: tricycle
7: awning-tricycle
8: bus
9: motor
3.6 数据集质量检查脚本
转换完成后,必须检查数据集质量:
python
"""
数据集质量检查脚本
检查: 图像尺寸分布、目标尺寸分布、类别分布、空标注图比例、小目标比例
"""
import os
import cv2
import numpy as np
from collections import Counter
def check_dataset(dataset_yaml, img_dir, label_dir):
"""检查数据集质量"""
img_sizes = []
box_sizes = []
class_counts = Counter()
empty_images = 0
total_images = 0
small_boxes = 0 # <32 像素
tiny_boxes = 0 # <16 像素
label_files = [f for f in os.listdir(label_dir) if f.endswith('.txt')]
for lf in label_files:
img_name = os.path.splitext(lf)[0]
img_path = os.path.join(img_dir, img_name + '.jpg')
if not os.path.exists(img_path):
# 尝试 png
img_path = os.path.join(img_dir, img_name + '.png')
if not os.path.exists(img_path):
continue
img = cv2.imread(img_path)
if img is None:
continue
h, w = img.shape[:2]
img_sizes.append((w, h))
total_images += 1
# 读取标注
label_path = os.path.join(label_dir, lf)
with open(label_path, 'r') as f:
lines = f.readlines()
if not lines or all(not l.strip() for l in lines):
empty_images += 1
continue
for line in lines:
parts = line.strip().split()
if len(parts) < 5:
continue
cls = int(parts[0])
cx, cy, bw, bh = map(float, parts[1:])
# 转为像素尺寸
px_w = bw * w
px_h = bh * h
box_area = px_w * px_h
box_sizes.append((px_w, px_h, box_area))
class_counts[cls] += 1
if px_w < 32 or px_h < 32:
small_boxes += 1
if px_w < 16 or px_h < 16:
tiny_boxes += 1
# 统计结果
print("=" * 60)
print("数据集质量检查报告")
print("=" * 60)
print(f"总图像数: {total_images}")
print(f"空标注图像数: {empty_images} ({empty_images/total_images*100:.1f}%)")
print(f"总目标框数: {len(box_sizes)}")
print(f"平均每图目标数: {len(box_sizes)/total_images:.1f}")
if box_sizes:
widths = [b[0] for b in box_sizes]
heights = [b[1] for b in box_sizes]
areas = [b[2] for b in box_sizes]
print(f"\n目标宽度: min={min(widths):.1f}, max={max(widths):.1f}, mean={np.mean(widths):.1f}, median={np.median(widths):.1f}")
print(f"目标高度: min={min(heights):.1f}, max={max(heights):.1f}, mean={np.mean(heights):.1f}, median={np.median(heights):.1f}")
print(f"小目标(<32px): {small_boxes} ({small_boxes/len(box_sizes)*100:.1f}%)")
print(f"极小目标(<16px): {tiny_boxes} ({tiny_boxes/len(box_sizes)*100:.1f}%)")
print(f"\n类别分布:")
for cls, count in sorted(class_counts.items()):
print(f" 类别 {cls}: {count} ({count/len(box_sizes)*100:.1f}%)")
img_ws = [s[0] for s in img_sizes]
img_hs = [s[1] for s in img_sizes]
print(f"\n图像尺寸: {min(img_ws)}×{min(img_hs)} ~ {max(img_ws)}×{max(img_hs)}")
return {
'total_images': total_images,
'empty_images': empty_images,
'total_boxes': len(box_sizes),
'small_ratio': small_boxes / len(box_sizes) if box_sizes else 0,
}
四、小目标检测的网络结构改进方法调研
4.1 改进角度总览
针对无人机小目标检测,相关论文从以下六个角度改进网络结构:
小目标检测网络结构改进的六大角度:
│
├─ 角度1: 增加高分辨率检测头(P2层)
│ 核心: 让 160×160 特征图直接参与检测,保留小目标细节
│ 代表: YOLOv5-P2, YOLOv8-P2, TPH-YOLOv5
│
├─ 角度2: 特征融合结构改进
│ 核心: 让高层语义更有效地传递到高分辨率特征图
│ 代表: BiFPN, ASFF, PANet改进, 跳跃连接增强
│
├─ 角度3: 注意力机制增强
│ 核心: 让模型关注小目标区域,抑制背景噪声
│ 代表: CA, CBAM, SimAM, 小目标专用注意力
│
├─ 角度4: 上采样方式改进
│ 核心: 更精细地恢复高分辨率特征,减少信息损失
│ 代表: CARAFE, FAConv, DySample, 反卷积
│
├─ 角度5: 感受野与多尺度特征提取
│ 核心: 增强 Backbone 对多尺度目标的特征提取能力
│ 代表: ASPP, RFB, SPP改进, 空洞卷积
│
└─ 角度6: 检测头改进与多任务学习
核心: 专门为小目标设计检测头,或增加辅助任务
代表: 解耦头, Transformer头, 专门小目标检测头, 边缘检测辅助
4.2 角度一:增加高分辨率检测头(P2 层)
4.2.1 为什么 P2 层对小目标有效?
标准 YOLO 的三个检测头:
| 检测头 | stride | 640输入下特征图 | 适合目标尺寸 | 10像素目标对应网格数 |
|---|---|---|---|---|
| P3 | 8 | 80×80 | 中目标 (>32px) | 1.25 个 |
| P4 | 16 | 40×40 | 大目标 (>64px) | 0.625 个 |
| P5 | 32 | 20×20 | 超大目标 (>128px) | 0.3125 个 |
增加 P2 层后:
| 检测头 | stride | 640输入下特征图 | 适合目标尺寸 | 10像素目标对应网格数 |
|---|---|---|---|---|
| P2 | 4 | 160×160 | 小目标 (8-32px) | 2.5 个 |
| P3 | 8 | 80×80 | 中目标 | 1.25 个 |
| P4 | 16 | 40×40 | 大目标 | 0.625 个 |
| P5 | 32 | 20×20 | 超大目标 | 0.3125 个 |
P2 层的有效性分析:
- 更高的分辨率------160×160 特征图上,10 像素目标对应 2.5 个网格单元,特征更充分。
- 更浅的网络层------P2 特征经过更少的下采样,保留了更多细节信息(边缘、纹理)。
- 专门的小目标 Anchor------P2 层可以配置更小的 Anchor(如 5,10 / 8,15 / 12,20),更匹配小目标尺寸。
代价:
- 参数量增加约 10-15%(P2 层的卷积和检测头)。
- GFLOPs 增加约 20-30%(160×160 特征图的计算量大)。
- 推理速度下降约 15-25%。
4.2.2 代表论文与改进方案
| 论文/方法 | 年份 | 改进方案 | 数据集 | 提升 |
|---|---|---|---|---|
| YOLOv5-P2 | 2021 | 增加 P2 检测头,4 检测头 | VisDrone | +2-3 mAP |
| TPH-YOLOv5 | 2021 | P2 头 + Transformer Prediction Head + CBAM | VisDrone | +5-8 mAP |
| YOLOv7-tiny-P2 | 2022 | 增加 P2 头 + 改进 Neck | UAVDT | +2-4 mAP |
| FS-YOLO | 2023 | P2 头 + 特征筛选模块 | VisDrone | +3-5 mAP |
| AF-YOLO | 2023 | P2 头 + 注意力融合 | VisDrone | +3-6 mAP |
4.3 角度二:特征融合结构改进
4.3.1 BiFPN / ASFF / 加权融合
核心思想:标准 FPN+PAN 用简单的 Concat 融合不同层的特征,没有区分不同层特征的重要性。加权融合让网络自己学习每层特征的权重。
BiFPN(EfficientDet):
- 加权特征融合:
O = Σ(w_i × I_i) / (Σw_i + ε),w_i 可学习。 - 跨层跳跃连接:除了相邻层,增加原始层到输出层的直接连接。
- 重复堆叠:BiFPN 模块可以堆叠多次。
ASFF(Adaptively Spatial Feature Fusion):
- 对每个空间位置,学习三个尺度特征的融合权重。
- 比 BiFPN 更细粒度(空间级权重 vs 层级权重)。
在小目标检测中的效果:
- 小目标主要依赖 P2/P3 层高分辨率特征,加权融合可以增强 P2/P3 的权重。
- 跨层连接让 P2 层能直接获取 P5 的高层语义,提升小目标的分类准确性。
4.3.2 代表论文
| 论文/方法 | 年份 | 改进方案 | 数据集 | 提升 |
|---|---|---|---|---|
| BiFPN-YOLO | 2020 | FPN+PAN → BiFPN | COCO/VisDrone | +1-2 mAP |
| ASFF-YOLO | 2021 | Concat → ASFF 自适应融合 | VisDrone | +1-3 mAP |
| PANet改进 | 2022 | 增加跳跃连接 + 深度可分离融合 | UAVDT | +1-2 mAP |
| GFPN-YOLO | 2023 | 门控特征金字塔网络 | VisDrone | +2-4 mAP |
4.4 角度三:注意力机制增强小目标特征
4.4.1 CA / CBAM / SimAM 在小目标中的应用
CA(Coordinate Attention)------小目标首选:
- 将空间位置信息编码到通道注意力中,特别适合小目标(小目标的位置信息很重要)。
- 分别在水平和垂直方向做池化,保留位置信息。
- 在 VisDrone 等小目标数据集上,CA 比 SE/CBAM 效果更好。
CBAM(Channel + Spatial Attention):
- 通道注意力 + 空间注意力串联。
- 空间注意力能增强目标区域、抑制背景,对复杂背景下的小目标有效。
SimAM(无参数注意力):
- 基于能量函数的注意力,不需要额外参数。
- 适合参数量敏感的场景,对小目标也有一定提升。
插入位置建议(小目标场景):
- Backbone 的 P2/P3 层后------增强高分辨率特征中的小目标特征。
- Neck 的 P2 融合后------增强融合后的小目标特征。
- SPPF 后------增强全局特征中的小目标响应。
4.4.2 代表论文
| 论文/方法 | 年份 | 注意力模块 | 插入位置 | 数据集 | 提升 |
|---|---|---|---|---|---|
| TPH-YOLOv5 | 2021 | CBAM | Backbone+Neck | VisDrone | +2-3 mAP |
| CA-YOLO | 2022 | CA | P2/P3层后 | VisDrone | +1-3 mAP |
| SimAM-YOLO | 2022 | SimAM | 全局 | UAVDT | +1-2 mAP |
| GAM-YOLO | 2023 | GAM | Neck | VisDrone | +1-2 mAP |
| 小目标专用注意力 | 2023 | 自定义 | P2层 | VisDrone | +2-4 mAP |
4.5 角度四:上采样方式改进
4.5.1 CARAFE / FAConv / DySample
核心问题:标准最近邻上采样只是简单复制像素,没有利用特征内容,导致上采样后的特征模糊,小目标细节丢失。
CARAFE(Content-Aware ReAssembly of FEatures):
- 根据特征内容动态生成上采样核,每个位置的上采样核不同。
- 上采样核由一个轻量子网络预测,计算量适中。
- 能恢复更清晰的小目标特征。
FAConv(Feature Aggregation Conv):
- 上采样前先聚合局部特征,再用可变形卷积上采样。
- 比 CARAFE 更关注局部特征聚合。
DySample(Dynamic Sampling):
- 动态预测采样点偏移,比 CARAFE 更轻量。
- 参数量和计算量更小,适合实时检测。
在小目标检测中的效果:
- 上采样主要在 Neck 的 FPN 部分使用(P5→P4→P3→P2)。
- 更好的上采样能让高层语义更准确地传递到高分辨率特征图。
- 对 P2 层的小目标特征恢复尤其重要。
4.5.2 代表论文
| 论文/方法 | 年份 | 上采样方式 | 数据集 | 提升 |
|---|---|---|---|---|
| CARAFE-YOLO | 2021 | 最近邻 → CARAFE | COCO/VisDrone | +0.5-1.5 mAP |
| FA-YOLO | 2022 | 最近邻 → FAConv | VisDrone | +0.5-1 mAP |
| DySample-YOLO | 2023 | 最近邻 → DySample | VisDrone | +0.3-0.8 mAP |
4.6 角度五:感受野与多尺度特征提取
4.6.1 ASPP / RFB / SPP 改进
核心问题:标准 Backbone 的感受野范围有限,小目标和大目标的尺度差异大(无人机视角下,车辆可能 10 像素,也可能 100 像素),需要更强的多尺度特征提取能力。
ASPP(Atrous Spatial Pyramid Pooling):
- 用不同膨胀率的空洞卷积并行提取多尺度特征。
- 膨胀率 1, 6, 12, 18 对应不同的感受野。
- 比 SPP 的感受野范围更大、更灵活。
RFB(Receptive Field Block):
- 模拟人类视觉系统的感受野,用不同大小的卷积核 + 空洞卷积组合。
- 多分支结构,每个分支有不同的感受野。
- 比 ASPP 更轻量,适合实时检测。
SPP 改进(SPPCSPC、SPPF-ASPP):
- SPPF + ASPP 组合,同时利用 SPPF 的高效和 ASPP 的多尺度。
- 在 Backbone 末尾使用,增强全局多尺度特征。
在小目标检测中的效果:
- 多尺度特征提取能让网络同时关注小目标(小感受野分支)和大目标(大感受野分支)。
- 空洞卷积在不增加计算量的情况下扩大感受野,适合小目标场景。
4.6.2 代表论文
| 论文/方法 | 年份 | 改进模块 | 数据集 | 提升 |
|---|---|---|---|---|
| ASPP-YOLO | 2021 | SPPF → ASPP | VisDrone | +0.5-1.5 mAP |
| RFB-YOLO | 2022 | 增加 RFB 模块 | UAVDT | +0.5-1 mAP |
| SPPCSPC-YOLO | 2022 | SPPF → SPPCSPC | VisDrone | +0.3-0.8 mAP |
| 多尺度Backbone | 2023 | 空洞卷积 + 多分支 | VisDrone | +1-2 mAP |
4.7 角度六:检测头改进与多任务学习
4.7.1 解耦头 / 专门小目标检测头 / Transformer 头
解耦头(Decoupled Head):
- 分类和回归各有独立的卷积分支,互不干扰。
- 对小目标特别有效:小目标的分类和定位需求差异更大(分类需要语义,定位需要精确位置),解耦能让两个任务各自最优。
专门小目标检测头:
- 为 P2 层设计专门的检测头,与 P3/P4/P5 的检测头结构不同。
- 小目标检测头可以用更多的卷积层、更小的 Anchor、专门的损失权重。
- 有些论文设计了"小目标专家头",专门负责小目标检测,与通用检测头并行。
Transformer 检测头:
- 用 Transformer 的自注意力机制建模全局依赖,能捕捉小目标和上下文的关系。
- TPH-YOLOv5 用 Transformer Prediction Head 替换部分检测头,在 VisDrone 上提升显著。
- 但 Transformer 头计算量大,推理慢,适合精度优先的场景。
多任务辅助学习:
- 增加辅助任务(如边缘检测、语义分割),辅助主任务(检测)。
- 边缘检测辅助能增强小目标的边缘特征,提升定位精度。
- 语义分割辅助能提供像素级语义,提升小目标的分类准确性。
4.7.2 代表论文:TPH-YOLOv5
TPH-YOLOv5(2021,ICCV Workshops)是无人机小目标检测的经典工作:
| 改进点 | 具体方案 |
|---|---|
| P2 检测头 | 增加 stride=4 的高分辨率检测头 |
| Transformer Prediction Head | 用 Transformer 替换部分检测头的卷积,建模全局依赖 |
| CBAM 注意力 | 在 Backbone 和 Neck 中插入 CBAM,增强小目标特征 |
| 多尺度训练 | 输入尺寸从 640 增大到 960/1280 |
在 VisDrone2019 上的结果:
- 基线 YOLOv5s: mAP@0.5 ≈ 34.5
- TPH-YOLOv5: mAP@0.5 ≈ 41.2(提升约 6.7)
💡 TPH-YOLOv5 的启示:小目标检测的提升往往来自多个改进的组合(P2头 + Transformer + 注意力 + 大输入尺寸),单一改进的效果有限。但组合改进时要注意推理速度的代价。
4.8 代表论文改进方案汇总表
| 论文 | 年份 | 数据集 | P2头 | 特征融合 | 注意力 | 上采样 | 检测头 | 多尺度 | 提升(mAP@.5) |
|---|---|---|---|---|---|---|---|---|---|
| TPH-YOLOv5 | 2021 | VisDrone | ✓ | PAN | CBAM | 最近邻 | Transformer | ✓(960) | +6.7 |
| YOLOv5-P2 | 2021 | VisDrone | ✓ | PAN | - | 最近邻 | 耦合 | - | +2.5 |
| BiFPN-YOLO | 2020 | VisDrone | - | BiFPN | - | 最近邻 | 耦合 | - | +1.8 |
| CA-YOLO | 2022 | VisDrone | ✓ | PAN | CA | 最近邻 | 解耦 | - | +3.2 |
| CARAFE-YOLO | 2021 | VisDrone | - | PAN | - | CARAFE | 耦合 | - | +1.2 |
| FS-YOLO | 2023 | VisDrone | ✓ | 改进PAN | - | 最近邻 | 解耦 | - | +4.1 |
| AF-YOLO | 2023 | VisDrone | ✓ | 注意力融合 | 自定义 | 最近邻 | 解耦 | - | +4.5 |
| ASPP-YOLO | 2021 | UAVDT | - | PAN | - | 最近邻 | 耦合 | - | +1.3 |
| RFB-YOLO | 2022 | UAVDT | ✓ | PAN | - | 最近邻 | 耦合 | - | +2.0 |
五、多检测头实现详解:在 YOLOv5 中增加 P2 检测头
5.1 四检测头的整体架构
增加 P2 检测头后的整体架构:
输入: (B, 3, 640, 640)
│
▼ Backbone
P2: (B, 64, 160, 160) stride=4 ← 新增(从 Backbone 第2层引出)
P3: (B, 128, 80, 80) stride=8
P4: (B, 256, 40, 40) stride=16
P5: (B, 512, 20, 20) stride=32
│
▼ Neck (FPN + PAN,增加 P2 融合)
P2_out: (B, 64, 160, 160) ← 新增检测头输入
P3_out: (B, 128, 80, 80)
P4_out: (B, 256, 40, 40)
P5_out: (B, 512, 20, 20)
│
▼ Head (4 个检测头)
检测头0 (P2): (B, 3, 160, 160, 85) → (B, 76800, 85)
检测头1 (P3): (B, 3, 80, 80, 85) → (B, 19200, 85)
检测头2 (P4): (B, 3, 40, 40, 85) → (B, 4800, 85)
检测头3 (P5): (B, 3, 20, 20, 85) → (B, 1200, 85)
│ cat
▼
输出: (B, 102000, 85) ← 76800+19200+4800+1200 = 102000
🔑 注意:增加 P2 头后,总 anchor 数从 25200 增加到 102000(增加了约 4 倍),后处理的 NMS 计算量也会增加。需要注意推理速度。
5.2 步骤一:修改 yaml 配置文件
复制 yolov5s.yaml,命名为 yolov5s_p2.yaml,修改 Backbone 和 Neck:
yaml
# YOLOv5s with P2 detection head (4 heads)
nc: 80
depth_multiple: 0.33
width_multiple: 0.50
anchors:
# P2 层的 anchor(小目标专用)
- [3,6, 5,10, 8,15] # P2 (stride=4) ← 新增
- [10,13, 16,30, 33,23] # P3 (stride=8)
- [30,61, 62,45, 59,119] # P4 (stride=16)
- [116,90, 156,198, 373,326] # P5 (stride=32)
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4 ← P2 从这里引出
[-1, 3, C3, [128]], # 2
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]], # 4
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]], # 6
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3, [1024]], # 8
[-1, 1, SPPF, [1024, 5]], # 9
]
head:
[[-1, 1, Conv, [512, 1, 1]], # 10
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 11
[[-1, 6], 1, Concat, [1]], # 12 cat P4
[-1, 3, C3, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]], # 14
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 15
[[-1, 4], 1, Concat, [1]], # 16 cat P3
[-1, 3, C3, [256, False]], # 17
# === 新增: P2 上采样融合 ===
[-1, 1, Conv, [128, 1, 1]], # 18
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 19
[[-1, 2], 1, Concat, [1]], # 20 cat P2 (backbone 第2层)
[-1, 3, C3, [128, False]], # 21 P2_out
# === PAN 自底向上 ===
[-1, 1, Conv, [128, 3, 2]], # 22 下采样
[[-1, 17], 1, Concat, [1]], # 23 cat P3_out
[-1, 3, C3, [256, False]], # 24 P3_out
[-1, 1, Conv, [256, 3, 2]], # 25 下采样
[[-1, 13], 1, Concat, [1]], # 26 cat P4_out
[-1, 3, C3, [512, False]], # 27 P4_out
[-1, 1, Conv, [512, 3, 2]], # 28 下采样
[[-1, 9], 1, Concat, [1]], # 29 cat P5
[-1, 3, C3, [1024, False]], # 30 P5_out
# === 4 个检测头 ===
[[21, 24, 27, 30], 1, Detect, [nc, anchors]], # 31 Detect(P2, P3, P4, P5)
]
⚠️ 关键修改点:
anchors从 3 组增加到 4 组(增加 P2 的小 anchor)。- Backbone 不变,但 P2 特征从第 2 层(
C3 [128]输出)引出。- Neck 增加 P2 的上采样融合路径(第 18-21 层)。
- PAN 部分增加 P2→P3 的下采样融合(第 22-24 层)。
- Detect 层的输入从
[17, 20, 23](3个)改为[21, 24, 27, 30](4个)。
5.3 步骤二:修改 Detect 头代码
YOLOv5 的 Detect 类在 models/yolo.py 中。需要修改以下部分:
python
class Detect(nn.Module):
stride = None
export = False
def __init__(self, nc=80, anchors=(), ch=()):
super().__init__()
self.nc = nc
self.no = nc + 5
self.nl = len(anchors) # 自动检测检测头数量(3 或 4)
self.na = len(anchors[0]) // 2
self.grid = [torch.zeros(1)] * self.nl
self.anchor_grid = [torch.zeros(1)] * self.nl
self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))
# 每个检测头一个 1×1 卷积,ch 是每个检测头的输入通道数(4个)
self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)
def forward(self, x):
z = []
for i in range(self.nl): # 自动遍历 3 或 4 个检测头
x[i] = self.m[i](x[i])
bs, _, ny, nx = x[i].shape
x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
if not self.training:
if self.grid[i].shape[2:4] != x[i].shape[2:4]:
self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)
y = x[i].sigmoid()
y[..., 0:2] = (y[..., 0:2] * 2 - 0.5 + self.grid[i]) * self.stride[i]
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]
z.append(y.view(bs, -1, self.no))
return x if self.training else (torch.cat(z, 1), x)
💡 好消息 :YOLOv5 的
Detect类本身就支持任意数量的检测头(self.nl = len(anchors)自动检测),所以不需要修改 Detect 类的代码!只要 yaml 配置正确,4 个检测头就能自动工作。需要确认的是
_make_grid和stride的计算是否支持 4 个检测头。YOLOv5 的stride是在模型构建时自动计算的(model.stride = torch.tensor([...])),4 个检测头会自动计算出[4, 8, 16, 32]。
5.4 步骤三:修改正负样本分配代码
YOLOv5 的 build_targets 函数在 utils/loss.py 中。同样,它也支持任意数量的检测头:
python
def build_targets(p, targets, model):
det = model.model[-1]
na, nt = det.na, targets.shape[0]
tcls, tbox, indices, anch = [], [], [], []
gain = torch.ones(6, device=targets.device)
ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt)
targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2)
for i in range(det.nl): # 自动遍历 3 或 4 个检测头
anchors = det.anchors[i]
gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]]
t = targets * gain
# ... 其余逻辑不变
💡 好消息 :
build_targets也自动支持 4 个检测头,不需要修改代码。但需要注意 :P2 层的 anchor 必须与小目标尺寸匹配。如果 anchor 太大,小目标无法匹配到 P2 层,P2 层就没有正样本,等于白加。建议用
k-means对数据集的目标尺寸重新聚类 anchor。
5.5 步骤四:验证模型构建与前向传播
python
"""验证 4 检测头模型"""
import sys
sys.path.insert(0, 'yolov5')
import torch
from models.yolo import Model
# 构建模型
model = Model('yolov5/models/yolov5s_p2.yaml', ch=3, nc=80)
model = model.cuda()
# 打印模型结构
print(model)
# 测试前向传播
x = torch.randn(2, 3, 640, 640).cuda()
model.train()
pred_train = model(x)
print(f"\n训练模式输出: {len(pred_train)} 个检测头")
for i, p in enumerate(pred_train):
print(f" 检测头 {i}: {p.shape}")
model.eval()
with torch.no_grad():
pred_infer = model(x)
print(f"\n推理模式输出: {pred_infer[0].shape}")
# 检查 stride
print(f"\n检测头 stride: {model.stride.tolist()}")
print(f"检测头数量: {model.model[-1].nl}")
print(f"每个检测头 anchor 数: {model.model[-1].na}")
# 计算参数量和 GFLOPs
from thop import profile
flops, params = profile(model, inputs=(x,), verbose=False)
print(f"\n参数量: {params/1e6:.2f}M")
print(f"GFLOPs: {flops/1e9:.2f}G")
# 对比基线
model_base = Model('yolov5/models/yolov5s.yaml', ch=3, nc=80).cuda()
flops_base, params_base = profile(model_base, inputs=(x,), verbose=False)
print(f"\n基线参数量: {params_base/1e6:.2f}M, 增加: {(params-params_base)/params_base*100:.1f}%")
print(f"基线 GFLOPs: {flops_base/1e9:.2f}G, 增加: {(flops-flops_base)/flops_base*100:.1f}%")
预期输出:
训练模式输出: 4 个检测头
检测头 0: torch.Size([2, 3, 160, 160, 85])
检测头 1: torch.Size([2, 3, 80, 80, 85])
检测头 2: torch.Size([2, 3, 40, 40, 85])
检测头 3: torch.Size([2, 3, 20, 20, 85])
推理模式输出: torch.Size([2, 102000, 85])
检测头 stride: [4.0, 8.0, 16.0, 32.0]
检测头数量: 4
每个检测头 anchor 数: 3
参数量: 8.15M
GFLOPs: 22.35G
基线参数量: 7.23M, 增加: 12.7%
基线 GFLOPs: 16.53G, 增加: 35.2%
5.6 四检测头的 Anchor 设计
P2 层的 Anchor 必须针对小目标设计。建议用 k-means 聚类:
python
"""
对数据集的目标尺寸进行 k-means 聚类,生成适合的 Anchor
"""
import numpy as np
from sklearn.cluster import KMeans
def cluster_anchors(label_dir, num_anchors=12, img_size=640):
"""
聚类 Anchor(4 个检测头 × 3 个 anchor = 12 个)
Args:
label_dir: YOLO 标注目录
num_anchors: 总 anchor 数
img_size: 训练输入尺寸
Returns:
anchors: (num_anchors, 2) 按面积排序的 anchor 宽高
"""
# 收集所有目标的宽高(像素值)
boxes = []
for label_file in os.listdir(label_dir):
if not label_file.endswith('.txt'):
continue
with open(os.path.join(label_dir, label_file), 'r') as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 5:
w = float(parts[3]) * img_size
h = float(parts[4]) * img_size
if w > 0 and h > 0:
boxes.append([w, h])
boxes = np.array(boxes)
print(f"收集到 {len(boxes)} 个目标框")
# k-means 聚类(用 IoU 距离)
def iou_distance(boxes, clusters):
"""计算每个框到每个聚类中心的 IoU 距离"""
# ... 简化为欧氏距离(实际应用 IoU 距离更好)
return np.linalg.norm(boxes[:, None] - clusters[None], axis=2)
kmeans = KMeans(n_clusters=num_anchors, random_state=42, n_init=10)
kmeans.fit(boxes)
anchors = kmeans.cluster_centers_
# 按面积排序(从小到大,对应 P2→P5)
areas = anchors[:, 0] * anchors[:, 1]
anchors = anchors[np.argsort(areas)]
print(f"\n聚类得到的 12 个 Anchor(按面积排序):")
for i, (w, h) in enumerate(anchors):
head = i // 3
print(f" 检测头{head} anchor{i%3}: {w:.1f} × {h:.1f}")
# 格式化为 YOLO yaml 格式
print(f"\nYAML 格式(4组×3个):")
for head in range(4):
group = anchors[head*3:(head+1)*3]
anchor_str = ', '.join([f"{int(w)},{int(h)}" for w, h in group])
print(f" - [{anchor_str}]")
return anchors
VisDrone 数据集聚类得到的参考 Anchor(640 输入):
yaml
anchors:
- [4,7, 6,11, 9,16] # P2 (stride=4) 小目标
- [12,20, 19,32, 28,48] # P3 (stride=8)
- [38,65, 55,95, 80,140] # P4 (stride=16)
- [110,190, 160,260, 240,380] # P5 (stride=32)
5.7 增加 P2 层的注意事项与调优建议
| 注意事项 | 说明 | 建议 |
|---|---|---|
| Anchor 匹配 | P2 层的 anchor 必须足够小,否则小目标匹配不到 | 用 k-means 重新聚类 anchor |
| 学习率 | 增加 P2 层后参数量增加,可能需要调整学习率 | 降低初始学习率 10-20%(0.01→0.008) |
| 训练轮数 | P2 层需要更多轮数收敛 | 增加训练轮数 20-30%(300→400) |
| 输入尺寸 | P2 层对输入尺寸敏感,大输入尺寸效果更好 | 尝试 960 或 1280 输入(显存允许的话) |
| NMS 阈值 | P2 层增加了大量预测框,NMS 计算量增加 | 适当提高 conf_thres(0.25→0.3)减少计算量 |
| 显存占用 | 160×160 特征图的显存占用大 | 降低 batch size,或用梯度累积 |
| 推理速度 | P2 层显著降低推理速度 | 如果速度敏感,可以只在训练时用 P2,推理时剪枝 |
| 过拟合 | P2 层可能过拟合小目标的噪声 | 增加数据增强强度(mosaic、mixup) |
💡 调优建议:增加 P2 层后,建议先跑一个 baseline(相同配置但无 P2),然后逐步调整学习率、训练轮数、输入尺寸,找到最优组合。不要一次性改太多参数。
六、在 YOLOv8 中增加 P2 检测头
6.1 YOLOv8 四检测头 yaml 配置
YOLOv8 是 Anchor-Free 的,配置更简洁:
yaml
# YOLOv8s with P2 detection head (4 heads)
nc: 80
scales:
n: [0.33, 0.25, 1024]
s: [0.33, 0.50, 1024]
m: [0.67, 0.75, 768]
l: [1.00, 1.00, 512]
x: [1.00, 1.25, 512]
backbone:
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f, [128, True]] # 2 ← P2 输出
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f, [256, True]] # 4
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f, [512, True]] # 6
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [1024, True]] # 8
- [-1, 1, SPPF, [1024, 5]] # 9
head:
- [-1, 1, nn.Upsample, [None, 2, nearest]] # 10
- [[-1, 6], 1, Concat, [1]] # 11
- [-1, 3, C2f, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, nearest]] # 13
- [[-1, 4], 1, Concat, [1]] # 14
- [-1, 3, C2f, [256]] # 15
# 新增 P2 融合
- [-1, 1, nn.Upsample, [None, 2, nearest]] # 16
- [[-1, 2], 1, Concat, [1]] # 17
- [-1, 3, C2f, [128]] # 18 P2_out
# PAN 自底向上
- [-1, 1, Conv, [128, 3, 2]] # 19
- [[-1, 15], 1, Concat, [1]] # 20
- [-1, 3, C2f, [256]] # 21 P3_out
- [-1, 1, Conv, [256, 3, 2]] # 22
- [[-1, 12], 1, Concat, [1]] # 23
- [-1, 3, C2f, [512]] # 24 P4_out
- [-1, 1, Conv, [512, 3, 2]] # 25
- [[-1, 9], 1, Concat, [1]] # 26
- [-1, 3, C2f, [1024]] # 27 P5_out
# 4 个检测头
- [[18, 21, 24, 27], 1, Detect, [nc]] # 28
6.2 YOLOv8 Detect 头修改
YOLOv8 的 Detect 头在 ultralytics/nn/modules/head.py 中。同样支持任意数量的检测头,不需要修改代码。但需要确认 stride 计算正确。
YOLOv8 是 Anchor-Free 的,不需要设计 Anchor,比 YOLOv5 更简单。
七、实验设计建议
7.1 基线选择与复现
小目标检测实验的基线选择:
| 基线模型 | 理由 | VisDrone mAP@0.5(参考) |
|---|---|---|
| YOLOv5s | 最常用的基线,社区广泛认可 | ~34.5 |
| YOLOv8s | 最新的 YOLO 基线,Anchor-Free | ~36.8 |
| YOLOv7-tiny | 轻量级基线 | ~33.2 |
| YOLOX-s | Anchor-Free 基线 | ~35.1 |
基线复现标准:
- 自己跑的基线 mAP 与论文报告值差距 < 1.0(小目标数据集方差大,放宽到 1.0)。
- 如果差距大,检查:输入尺寸(VisDrone 建议 960+)、训练轮数(建议 300+)、数据增强、预训练权重。
7.2 消融实验设计模板
以"YOLOv5s + P2头 + CA注意力 + CARAFE上采样"为例:
模块消融(主表):
┌──────┬─────┬─────┬────────┬─────────┬──────────┬────────┬────────┐
│ Exp │ P2 │ CA │ CARAFE │ mAP@.5 │mAP@.5:.95│ AP_s │ Params │
├──────┼─────┼─────┼────────┼─────────┼──────────┼────────┼────────┤
│ 1 │ │ │ │ 34.5 │ 18.2 │ 15.2 │ 7.2M │
│ 2 │ ✓ │ │ │ 37.2 │ 20.1 │ 19.8 │ 8.2M │
│ 3 │ │ ✓ │ │ 35.6 │ 18.9 │ 16.5 │ 7.5M │
│ 4 │ │ │ ✓ │ 35.2 │ 18.7 │ 16.1 │ 7.6M │
│ 5 │ ✓ │ ✓ │ │ 38.5 │ 21.2 │ 21.5 │ 8.5M │
│ 6 │ ✓ │ │ ✓ │ 38.1 │ 20.8 │ 21.0 │ 8.6M │
│ 7 │ ✓ │ ✓ │ ✓ │ 39.3 │ 21.8 │ 22.6 │ 8.9M │
└──────┴─────┴─────┴────────┴─────────┴──────────┴────────┴────────┘
位置消融(CA 注意力加在哪里):
┌──────┬──────────────┬─────────┬────────┐
│ Exp │ 位置 │ mAP@.5 │ AP_s │
├──────┼──────────────┼─────────┼────────┤
│ 1 │ 无 CA │ 37.2 │ 19.8 │
│ 2 │ P2层后 │ 38.1 │ 21.2 │
│ 3 │ P3层后 │ 37.6 │ 20.1 │
│ 4 │ SPPF后 │ 37.5 │ 19.9 │
│ 5 │ P2+P3层后 │ 38.5 │ 21.5 │
└──────┴──────────────┴─────────┴────────┘
输入尺寸消融:
┌──────┬────────┬─────────┬────────┬───────┐
│ Exp │ 输入尺寸│ mAP@.5 │ AP_s │ FPS │
├──────┼────────┼─────────┼────────┼───────┤
│ 1 │ 640 │ 37.2 │ 19.8 │ 85 │
│ 2 │ 800 │ 38.5 │ 21.5 │ 55 │
│ 3 │ 960 │ 39.3 │ 22.8 │ 38 │
│ 4 │ 1280 │ 39.8 │ 23.5 │ 22 │
└──────┴────────┴─────────┴────────┴───────┘
7.3 小目标专项评估指标
除了标准的 mAP,小目标检测还应该报告以下专项指标:
| 指标 | 说明 | 计算方式 |
|---|---|---|
| AP_s | 小目标 AP | area < 32² |
| AP_m | 中目标 AP | 32² < area < 96² |
| AP_l | 大目标 AP | area > 96² |
| AR_s | 小目标召回率 | 小目标的最大召回率 |
| MR^-2 | 小目标漏检率 | 小目标的漏检率(越低越好) |
| 小目标 mAP | 自定义小目标阈值 | 如 area < 20² 或 < 16² |
| 每类 AP | 每个类别的 AP | 分析哪些类别提升大 |
VisDrone 专用评估:
- VisDrone 官方评估工具:https://github.com/VisDrone/VisDrone2019-DET-toolkit
- 支持按目标大小、遮挡程度、截断程度分别评估。
八、总结与下篇预告
8.1 本文核心要点回顾
-
无人机小目标检测的核心挑战:目标极小(5-30像素)、密度极高、背景复杂、视角变化大。标准 YOLO 的 P3/P4/P5 检测头对小目标特征保留不足,需要专门改进。
-
公开数据集:VisDrone2019-DET 是事实标准(10类,6471+548张,目标极小),UAVDT 专注车辆,DroneVehicle 专注车辆细分类,CrowdHuman 可用于行人预训练。每个数据集的标注格式不同,需要转换为 YOLO 归一化 xywh 格式。
-
数据集格式转换:本文给出了 VisDrone、UAVDT、DroneVehicle 三个数据集的完整转换代码,以及数据集质量检查脚本(目标尺寸分布、类别分布、小目标比例)。
-
网络结构改进的六大角度:①增加 P2 高分辨率检测头(最有效,+2-3 mAP);②特征融合改进(BiFPN/ASFF);③注意力机制(CA 首选);④上采样改进(CARAFE);⑤感受野改进(ASPP/RFB);⑥检测头改进(解耦头/Transformer头/多任务辅助)。
-
多检测头实现:本文给出了 YOLOv5 增加 P2 检测头的完整步骤------修改 yaml(增加 P2 融合路径和第4个检测头)、Anchor 设计(k-means 聚类小 anchor)、验证模型构建。YOLOv5 的 Detect 类和 build_targets 自动支持任意数量检测头,不需要修改代码。
-
实验设计:基线选择(YOLOv5s/YOLOv8s)、消融实验模板(模块消融/位置消融/输入尺寸消融)、小目标专项评估指标(AP_s/AR_s/MR^-2)。
8.2 下篇预告
本系列第七篇将聚焦于小目标增强篇2------从损失函数与训练策略角度:
- 小目标专用损失函数:Focal Loss、Varifocal Loss、Quality Focal Loss
- 对抗生成方法:GAN 增强小目标特征、超分辨率辅助检测
- 强化学习激励:基于强化学习的样本分配、难例挖掘
- 小目标专用数据增强:Copy-Paste、Small Object Augmentation、上下文增强
- 训练策略:多尺度训练、课程学习、小目标过采样
- 相关论文的损失函数改进方案汇总
如果本文对你有帮助,欢迎点赞、收藏、关注! 有任何问题欢迎在评论区交流。
系列回顾:
- 第一篇:《YOLO 全代码详细解读:从预处理到后处理的完整数据流动》
- 第二篇:《YOLO 涨点研究(二):Loss 函数深度解析与正负样本分配策略》
- 第三篇:《YOLO 涨点研究(三):数据增强策略深度解析与涨点技巧》
- 第四篇:《YOLO 涨点研究(四):网络结构改进与涨点技巧》
- 第五篇:《YOLO 涨点研究(五):模块优化的实验设计与代码落实全指南》
- 第六篇:《YOLO 涨点研究(六):网络结构改进之小目标增强篇1》(本文)
下一篇:《YOLO 涨点研究(七):网络结构改进之小目标增强篇2------损失函数与训练策略》