拓冰建站拓冰建站
首页 / 资讯中心 / 正文

YOLOv5无人机俯视目标检测:VisDrone数据集格式转换、训练与推理优化

简介面向无人机俯视视角下的车辆与行人检测任务YOLOv5格式数据集dataset2包含1000多张经标注的现场图像已将目标类别统一为car和person并提前划分好训练集、验证集与测试集。压缩包内共2000个文件其中1887个txt为YOLO标签文件112张jpg为原始图像1个yaml为模型训练所需的data.yaml配置压缩包大小约960.43MB目录结构按train/valid/test组织可直接供YOLOv5、YOLOv7、YOLOv8等主流框架加载训练。目前已有1321人学习或下载适合计算机视觉学习者、算法工程师及智慧交通/无人机巡检项目开发者。利用这份资源可省去数据采集、标注与目录整理的繁琐步骤快速验证模型性能配合原博文中的检测结果展示能帮助使用者更直观地评估数据集质量与训练效果是无人机目标检测落地的实用基础数据。1. 从 vis-drone-yolov5-dataset-2 开始这不是下载完就能训的数据集无人机俯视视角下的目标检测和行车记录仪视角完全是两码事。YOLOv5 在 COCO 上表现再好直接拿去推理 VisDrone 的俯视画面小目标漏检率会高到让你怀疑模型没训练过。原因很简单俯视视角下车辆、行人动辄只有几十个像素而 YOLOv5 的默认锚框和输入尺寸是按自然图像设计的。vis-drone-yolov5-dataset-2 这个数据集压缩包本质上是把 VisDrone 原始标注转换成了 YOLOv5 可直接消费的格式但它只是原料不是答案。你需要理解标注格式、类别映射、目录组织方式才能在训练时不被数据坑第二次。这篇文章从压缩包内部结构讲起逐步拆到训练配置和评估方法最终落到一个视频推理的实用技巧。适合正在用 YOLOv5 做无人机视觉感知、或者拿 VisDrone 做毕业设计但被标签格式卡住的人。2. 解开 vis-drone-yolov5-dataset-2 的目录与标注格式很多人拿到压缩包第一件事就是解压、扔进 YOLOv5 的 datasets 目录、然后跑 train.py结果报错 File not found 或者标签全为空。问题几乎都出在没搞清这个数据集的目录层级和 VisDrone 原生标注的字段含义。这一章把目录结构、标注格式、类别映射讲清楚最后给一个转换前的一致性检查脚本避免带病训练。2.1 压缩包内的目录层级和 YOLOv5 期望的目录树VisDrone 官方数据集的组织方式是VisDrone2019-DET-train/images存放图片VisDrone2019-DET-train/annotations存放同名 txt 标注。而 vis-drone-yolov5-dataset-2 这类二次打包的数据集通常已经帮你调整成 YOLOv5 的规范目录vis-drone-yolov5-dataset-2/ ├── dataset.yaml ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/常见做法是把原始 VisDrone 中的uav0000009_00000_s这类序列文件夹拍平所有图片直接堆进 train/val/test 三个目录图片和标签通过文件名前缀对应。你必须先确认一件事images/train下的图片数量是否等于labels/train下的 txt 数量。如果不等优先检查是否有空标注文件被生成工具跳过。YOLOv5 的 dataloader 会读取图片路径后把/images/替换成/labels/把.jpg替换成.txt来找标签文件。如果你发现训练时所有图片的标签都是 0 个目标基本就是这个替换规则没对上比如目录名写成了image而不是images。2.2 VisDrone 标注字段与 YOLOv5 标签的差异VisDrone 原始标注的每个 txt 文件每一行是一个目标共 10 个字段逗号分隔bbox_left,bbox_top,bbox_width,bbox_height,score,object_category,truncation,occlusion以实际的一行标注为例168,192,102,71,0,2,0,1含义分别是边界框左上角 x 坐标 168左上角 y 坐标 192框宽 102框高 71置信度 0训练集中无意义类别 2car截断程度 0遮挡程度 1。YOLOv5 的标签则是归一化的中心点坐标加宽高共 5 个字段class_id, x_center_norm, y_center_norm, width_norm, height_norm。转换公式如下x_center_norm (bbox_left bbox_width / 2) / image_width y_center_norm (bbox_top bbox_height / 2) / image_height width_norm bbox_width / image_width height_norm bbox_height / image_height很多人只转坐标忘了做归一化导致训练时损失直接变成 NaN。每次写转换脚本都应该打印 5 个随机样本的原始标注和转换结果对照肉眼确认数字在 0 到 1 之间。2.3 类别重映射把 11 类压缩成两类或多类VisDrone 数据集中共有 11 个类别其中 0 代表 ignore 区域类别 11 是 others。直接全部保留会让 YOLOv5 的类别预测头变大而且在无人机俯视视角下pedestrian 和 people 在视觉上高度相似强行区分只会引入标注噪声。原始类别 ID原始名称建议处理0ignored删除1pedestrian映射为 02car映射为 1或并入 vehicle3van映射为 14truck映射为 15tricycle视需求保留或删除6awning-tricycle视需求保留或删除7bus映射为 18motor视需求保留或删除9bicycle视需求保留或删除10people映射为 011others删除我一般会保留三个类别pedestrian、vehicle、two-wheeler。其中 two-wheeler 把 motor、bicycle、tricycle 合并因为俯视视角下这些类别之间的边界本来就模糊。如果你拿这个数据集做车辆和行人的二分类检测就把类别映射成 0 和 1 即可。映射逻辑应该写在转换脚本里不要手动改 txt否则几百个文件改到崩溃。2.4 转换前的一致性检查脚本在正式转换之前先跑一段检查脚本确保图片能读取、标注坐标没有超出边界、类别 ID 没有越界。这能避免训练到一半发现数据有问题。# check_visdrone.py import os from PIL import Image images_dir images/val labels_dir labels/val for label_name in os.listdir(labels_dir): base os.path.splitext(label_name)[0] img_path os.path.join(images_dir, base .jpg) if not os.path.exists(img_path): print(f[ERROR] 图片缺失: {img_path}) continue img Image.open(img_path) img_w, img_h img.size with open(os.path.join(labels_dir, label_name), r) as f: for line in f: parts line.strip().split(,) if len(parts) ! 10: print(f[ERROR] 字段数不对: {label_name}) continue cat_id int(parts[5]) if cat_id 0 or cat_id 11: print(f[ERROR] 类别越界: {label_name}, cat_id{cat_id}) bbox_w int(parts[2]) bbox_h int(parts[3]) if bbox_w 0 or bbox_h 0: print(f[ERROR] 框宽高无效: {label_name})脚本逻辑分三层先检查图片存在性再检查标注字段完整性最后检查坐标合理性。其中bbox_w 0是最容易被忽视的VisDrone 原始数据里确实存在宽度或高度为 0 的坏框直接保留会导致 YOLOv5 计算损失时除以零。3. YOLOv5 训练自己的数据集data.yaml、超参与 anchors 调整格式转换完成后训练本身会有三个新的坑data.yaml 配置不对、超参数不适应小目标、anchors 不匹配俯视场景。这一章给出最小配置、必调参数和训练命令。3.1 data.yaml 与目录结构的最小配置YOLOv5 使用 data.yaml 描述数据集路径和类别。data.yaml 中的路径既可以是绝对路径也可以是相对 YOLOv5 项目根目录的路径但最常见的错误是路径写错层级。# visdrone.yaml train: datasets/vis-drone-yolov5-dataset-2/images/train val: datasets/vis-drone-yolov5-dataset-2/images/val test: datasets/vis-drone-yolov5-dataset-2/images/test nc: 2 names: [pedestrian, vehicle]注意 train 和 val 路径指向的是 images 目录不是 images/train 本身的上层目录。train.py 会识别 images 目录并自动找同级 labels 目录。如果你在 Windows 上训练路径分隔符要写反斜杠或者用正斜杠让 YOLOv5 统一处理。还有一个常见坑data.yaml 里的路径末尾不要带斜杠否则拼接文件名时会出现双斜杠。训练命令本身很直接python train.py --data visdrone.yaml --weights yolov5s.pt --img 1280 --batch-size 16 --epochs 100 --workers 8--img 1280是一个关键参数。VisDrone 图片原始分辨率是 2000×1500直接缩放到 640 会丢失大量小目标信息。1280 能保住更多细节但显存占用会暴涨8GB 显存基本只能跑 batch-size 4 到 8。如果显卡不够退而求其次用 960并用多尺度训练--multi-scale来弥补。3.2 针对无人机俯视场景的 YOLOv5 超参数调整YOLOv5 的超参数在data/hyps/hyp.scratch-low.yaml中定义默认是针对 COCO 自然图像调的。俯视场景下小目标占比极高需要针对性调整。# hyp.visdrone.yaml lr0: 0.01 lrf: 0.2 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 box: 0.05 cls: 0.5 cls_pw: 1.0 obj: 1.0 obj_pw: 1.0 anchor_t: 4.0 fl_gamma: 0.2 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4两个参数值得单独解释。anchor_t是 anchor 与 ground truth 的长宽比阈值默认 4.0表示 gt 与 anchor 的比值超过 4 就认为不匹配。俯视视角下车辆长宽比相对固定但行人姿态各异调低到 3.0 能提高小目标的召回。fl_gamma是 focal loss 的 gamma默认是 0即不使用 focal loss。VisDrone 的类别不平衡严重车辆数量远多于行人设置成 0.5 让模型更关注困难样本。从 YOLOv5 的 v6.0 版本开始hyp.scratch-low.yaml中的cls分类损失权重默认是 0.5。如果你感觉模型能框住目标但类别总判错比如把行人判成车辆把cls提高到 0.7 到 1.0 会有效果。反过来如果检测框位置不准box权重可以从 0.05 提到 0.1。3.3 anchors 自动计算与训练命令YOLOv5 默认使用 COCO 数据集上聚类出来的 anchors这些锚框对俯视小目标的适配性很差。每个 epoch 训练到 100 轮时模型会自动重新聚类 anchors但前提是你必须在训练命令中开启这个功能。YOLOv5 训练时如果没指定 anchors默认使用model里自带的预设值。要针对 VisDrone 重新计算 anchors 有两种方式一是直接调utils/autoanchor.py脚本二是在训练命令中加--noautoanchor的相反选项。v6.0 之后默认启用了自动 anchors 重算但你仍然可以显式跑一次聚类来提前检查 anchors 质量python utils/autoanchor.py --cfg yolov5s.yaml --data visdrone.yaml运行后脚本会输出当前 anchors 与 k-means 聚类 anchors 的平均召回率对比。如果最优召回率大于当前值超过 10%说明你需要接受新的 anchors。自动计算出的 anchors 会写在你指定的 yaml 文件的anchors:字段中之后训练直接使用这些值。完整训练命令和关键参数说明如下python train.py \ --data visdrone.yaml \ --weights yolov5s.pt \ --img 1280 \ --batch-size 16 \ --epochs 120 \ --hyp hyp.visdrone.yaml \ --workers 8 \ --device 0 \ --project runs/train \ --name visdrone_exp--project和--name指定输出目录训练日志、权重文件会分别保存到runs/train/visdrone_exp/weights/best.pt和last.pt。训练中注意观察终端输出的 P精确率、R召回率、mAP50 三个指标如果 mAP50 在前 30 个 epoch 内基本不动大概率是数据有问题不要死等 120 轮结束再排查。4. 小目标检测的评估方法mAP、混淆矩阵与可视化验证训练结束后模型表现如何不能只看 loss 曲线。VisDrone 这类以中远距离拍摄为主的俯视数据集小目标占比极高用 YOLOv5 自带的 val.py 评估时必须知道 mAP 的两种口径、怎么解读混淆矩阵以及如何用可视化结果发现标注噪声。4.1 mAP0.5 与 mAP0.5:0.95 对俯视场景的不同参考价值YOLOv5 的 val.py 默认输出两组指标mAP0.5 和 mAP0.5:0.95。mAP0.5 表示 IoU 阈值 0.5 下所有类别的平均精确率mAP0.5:0.95 则是在从 0.5 到 0.95 以 0.05 步长取 10 个 IoU 阈值下 mAP 的平均值。指标名称计算口径对俯视场景的参考价值mAP0.5IoU 0.5 即视为检测正确高反映基本检出能力mAP0.5:0.95横跨多个 IoU 阈值取均值低过度惩罚小目标的框偏移对一个在 VisDrone 上训练好的 YOLOv5s 模型mAP0.5 能达到 0.4 到 0.5 是及格线而 mAP0.5:0.95 通常只有 0.2 左右。这不代表模型差而是因为小目标的框即使预测正确中心点偏移 3 到 5 个像素也会让 IoU 大幅下降。所以评估无人机俯视检测模型以 mAP0.5 为主mAP0.5:0.95 只作参考。4.2 用 val.py 跑通验证并输出混淆矩阵YOLOv5 的 val.py 可以直接使用训练好的权重文件python val.py \ --data visdrone.yaml \ --weights runs/train/visdrone_exp/weights/best.pt \ --img 1280 \ --batch-size 8 \ --conf-thres 0.001 \ --iou-thres 0.6注意--conf-thres 0.001必须设得很低。默认值是 0.001如果你手动调高了比如设成 0.25那么评估结果中召回率会严重偏低因为很多置信度在 0.1 到 0.2 之间的真目标会被直接过滤掉。验证集的置信度阈值应该尽量低把所有可能的框都纳进来再用 mAP 公式去权衡误检和漏检。运行结束后runs/val/exp/目录下会生成混淆矩阵图confusion_matrix.png和test_batch0_pred.jpg等可视化文件。混淆矩阵能让类别间互相误判的关系一眼可见下面是一个典型的 VisDrone 二分类混淆矩阵片段预测为行人 预测为车辆 实际为行人 1325 214 实际为车辆 189 5910从这个矩阵可以直观看出两个类别的主要误判模式以及类别不平衡的实际程度。4.3 可视化验证阶段发现标注问题在确认指标正常之后务必人工查看预测结果图。将test_batch0_pred.jpg放大查看如果发现模型把车辆检测框框在路灯杆上而数据集原始标注确实也把路灯杆标注成了车辆那问题不在模型而在标签质量这需要回到数据清洗环节不是换网络结构能解决的。对比模型预测框和高层标注框判断时重点检查三类目标极小目标的漏检区域、密集停车区域的框重叠、行人与人形立牌的混淆。如果小目标漏检集中在图片边缘可以考虑在训练时增加马赛克数据增强的概率YOLOv5 的 Mosaic 增强默认开启也可以通过超参数mosaic调整设置mosaic: 0.5代表 50% 概率使用 Mosaic 增强。5. 一个实用技巧用帧间目标平滑抑制无人机视频推理抖动YOLOv5 目标检测往往是逐帧推理的但无人机视频中每一帧画面都在抖动检测框会跟着目标来回跳尤其是小目标前一秒框在车头下一秒框到车尾。要让检测结果直接用于统计或跟踪必须在推理流程中加一层时序平滑而不是单帧独立输出这是我处理俯视视频时不会跳过的一步。5.1 基于指数移动平均的检测框平滑实现核心思路是对同一目标的检测框做指数移动平均让框位置缓慢跟随新的检测结果而不是瞬移。实现分为两步用 IoU 匹配相邻帧的检测框然后对匹配上的框做平滑更新。# smooth_detections.py import numpy as np class DetectionSmoother: def __init__(self, alpha0.6, iou_threshold0.3): self.alpha alpha self.iou_threshold iou_threshold self.tracks {} def _iou(self, box1, box2): x1 max(box1[0], box2[0]) y1 max(box1[1], box2[1]) x2 min(box1[2], box2[2]) y2 min(box1[3], box2[3]) inter_area max(0, x2 - x1) * max(0, y2 - y1) box1_area (box1[2] - box1[0]) * (box1[3] - box1[1]) box2_area (box2[2] - box2[0]) * (box2[3] - box2[1]) return inter_area / (box1_area box2_area - inter_area 1e-6) def update(self, detections, frame_id): smoothed [] unmatched_tracks set(self.tracks.keys()) for det in detections: best_track_id None best_iou 0 for track_id, track_box in self.tracks.items(): iou self._iou(det[:4], track_box[:4]) if iou best_iou: best_iou iou best_track_id track_id if best_track_id is not None and best_iou self.iou_threshold: track_box self.tracks[best_track_id] for i in range(4): track_box[i] self.alpha * track_box[i] (1 - self.alpha) * det[i] self.tracks[best_track_id] track_box smoothed.append(track_box[:4]) unmatched_tracks.discard(best_track_id) else: new_track_id len(self.tracks) self.tracks[new_track_id] det[:4].copy() smoothed.append(det[:4]) for track_id in unmatched_tracks: del self.tracks[track_id] return np.array(smoothed)这段代码里alpha是历史框权重设 0.6 意味着新框位置受到上一帧位置 60% 的影响数值越大越平滑但延迟越高。iou_threshold设为 0.3 是因为俯视小目标本身框小相邻帧目标位移稍大 IoU 就会低于 0.5过高的阈值会导致同一目标被频繁新建轨迹平滑失效。另外只平滑位置不平滑分类置信度因为置信度波动本身是模型不确定性的一种表达强行平滑会导致漏报无法及时体现。把这段逻辑封装成类可以很自然地嵌入到 YOLOv5 的 detect.py 推理循环中。调用方式是在拿到一帧检测结果后直接替换原始框输出python detect.py --weights runs/train/visdrone_exp/weights/best.pt --source video.mp4 --img 1280在detect.py里实例化DetectionSmoother把det[:, :4]传入update方法用返回的平滑框替代原来的框绘制其余流程完全不动。无人机俯视视频中小目标框的抖动能在视觉效果上降低一半以上后续接入目标计数的准确性也会同步提高代价仅仅是每帧多几毫秒的纯 Python 计算对实时性影响可以忽略如果你追求更低的延迟可以把alpha调到 0.4平滑速度和跟随性会找到更好的平衡点。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门