
简介面向无人机俯视视角下的车辆与行人检测任务这份数据集已完成YOLO格式标注与目录划分可直接衔接yolov5、yolov7、yolov8等主流检测框架的训练流程适合航拍目标识别、智能交通等场景的算法学习与模型调优。压缩包共2000个文件体量约960MB以1887个txt标签文件为主同时包含112张jpg航拍图像和1个data.yaml配置data.yaml中已定义car、person两个类别并指定train、valid、test三个子目录的图片路径省去数据整理与格式转换的环节。已有1325人浏览学习配套作者博客还提供了检测效果参考便于使用者对比不同模型在无人机视角下的表现。就绪的目录结构与标签配置可让学习者直接进入训练环节适合作为目标检测实战项目的数据支撑也方便在此基础上进行数据增强、模型微调等进阶实验。1. VisDrone 俯视视角数据集到底是什么为什么做 YOLOv5 检测绕不开它做无人机视角下的车辆和行人检测第一件事往往不是调模型而是先找一套能真实反映“俯视、小目标、密集遮挡”的数据集。VisDrone 是目前公认的航拍目标检测基准数据之一而这个 vis-drone-yolov5-dataset-2.zip 的作用就是把 VisDrone 的原始图片和框标注整理成 YOLOv5 可以直接开始训练的目录形态。适合的人群很直接需要做无人机车流统计、安防巡检、遥感图像目标检测的工程师和学生想快速走通 YOLOv5 训练自己的数据集流程又没精力人工标注几万张图的都可以从这套数据起步。它解决的核心问题是让模型在俯视视角下把车辆、行人从密集的小目标中框出来而不是只在常规视角的公开数据上表现好看。2. 解包后的目录结构与 YOLOv5 能用的标注格式转换转换脚本与三个边界坑2.1 压缩包里有什么目录划分与文件命名规则拿到 zip 解压后常见做法是看到一个主目录里面按 images 和 labels 分开放train、val 各自再建子目录结构大致像下面这样visdrone-yolo/ ├── images/ │ ├── train/ │ │ ├── 0000156.jpg │ │ └── ... │ └── val/ ├── labels/ │ ├── train/ │ │ ├── 0000156.txt │ │ └── ... │ └── val/ └── visdrone.yaml图片名和标注 txt 文件名一一对应这个命名规则是 YOLOv5 的基本要求。如果 zip 里只有一个合并目录你需要自己按 9:1 拆成 train 和 val。注意 VisDrone 的官方标注文件是 CSV 风格的 txt里面每行一个目标前 6 个字段分别是bbox_left, bbox_top, bbox_width, bbox_height, score, category后续还有截断和遮挡标记。这类原始标注不能直接喂给 YOLOv5因为 YOLOv5 要求每行是class x_center y_center width height后 4 个值全部归一化到 0~1。第一个边界坑就在这里格式不转换训练会直接报错或者把框画到九霄云外。第二个边界坑是图片分辨率。VisDrone 不同序列的图尺寸并不统一有 2000x1500也有 1360x765甚至更小。很多转换脚本为了省事把宽高写死结果一部分图片的标注全部偏移。正确做法是逐张读取图片尺寸再归一化下面这个脚本就是按这个思路写的。2.2 把 VisDrone 的框标注转成 YOLOv5 的 txt逐张读取尺寸再归一化import cv2 from pathlib import Path src_dir Path(raw_labels/train) # 原始 VisDrone 标注目录 img_dir Path(images/train) # 对应图片目录 dst_dir Path(yolo_labels/train) # 输出 YOLOv5 标注目录 dst_dir.mkdir(parentsTrue, exist_okTrue) # 只保留俯视场景里高频出现的 7 类others 直接丢弃 category_map { pedestrian: 0, car: 1, van: 2, truck: 3, bus: 4, motor: 5, bicycle: 6, } for label_path in src_dir.glob(*.txt): image_path img_dir / (label_path.stem .jpg) img cv2.imread(str(image_path)) if img is None: print(fmissing image: {image_path}) continue h, w img.shape[:2] # 逐张取宽高禁止写死 lines label_path.read_text().strip().splitlines() out_lines [] for line in lines: parts line.split(,) if len(parts) 7: continue x, y, bw, bh, score, cat parts[:6] if float(score) 0: # 忽略标注为 ignore 的实例 continue if cat not in category_map: continue cx (float(x) float(bw) / 2) / w cy (float(y) float(bh) / 2) / h nw float(bw) / w nh float(bh) / h # 防止四舍五入后越界 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) out_lines.append(f{category_map[cat]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if out_lines: (dst_dir / (label_path.stem .txt)).write_text(\n.join(out_lines))这段脚本解决的两个问题一是逐张读图取宽高不写死分辨率从根本上规避了多分辨率数据集的偏移二是过滤score 0的框。VisDrone 原始标注里有不少低置信度或需要忽略的区域训练数据里混入这些噪声框轻则让 loss 波动重则让模型学到错误的边界。参数说明上category_map的写入顺序就是 YOLOv5 类别索引后面 data.yaml 的names列表必须和这个顺序保持一致if cat not in category_map把 others 这类语义模糊的类别直接丢掉因为俯视场景里它们对车辆和行人检测没有帮助反而会增加后处理阶段的假阳性。转换完成后建议抽查 5~10 张图把 txt 和原图叠在一起可视化。可以用任何目标检测常用标注工具打开也可以用 OpenCV 画框这一步能发现坐标偏移、类别错位和归一化方向错误。别跳过因为这种黑匣子问题在训练跑到一半时才暴露浪费时间。2.3 类别映射与数据集划分按视频序列拆而不是按帧拆VisDrone 的数据来自无人机连续拍摄的视频帧同一辆车在相邻帧里会反复出现。如果你用随机 shuffled 方式做 9:1 划分很容易出现同一辆车在 train 和 val 里各出现一次mAP 虚高部署时立刻缩水。正确做法是先按视频序列分组再以序列为单位划分。VisDrone 原始文件名通常带序列信息比如0000156这种编号你可以解析文件名前缀来做序列 ID。import random from pathlib import Path label_files sorted(Path(yolo_labels/train).glob(*.txt)) seq_groups {} for f in label_files: seq_id f.stem.rsplit(_, 1)[0] # 按文件名中的序列前缀分组 seq_groups.setdefault(seq_id, []).append(f) seq_ids list(seq_groups.keys()) random.Random(42).shuffle(seq_ids) train_seqs seq_ids[: int(len(seq_ids) * 0.9)] val_seqs seq_ids[int(len(seq_ids) * 0.9):] train_files [] for s in train_seqs: train_files.extend(seq_groups[s]) # 把 train_files 移动到 images/train 和 labels/train这里random.Random(42)固定随机种子让数据集划分可复现后续调整超参数时不会因为数据变了而误判效果。划分后还要检查一下两个子集的类别数量分布如果 val 里恰好没有 bus那么 bus 类在验证指标上会显示为 0不代表模型没学好。2.4 data.yaml 怎么写路径、类别名和 nc 三处最容易错YOLOv5 的 data.yaml 是训练入口写错路径或类别名通常不会报错只在结果里表现出诡异现象。下面是一个经过压缩包整理后的典型配置# visdrone.yaml path: ./datasets/visdrone-yolo train: images/train val: images/val test: images/test # 可选 nc: 7 names: 0: pedestrian 1: car 2: van 3: truck 4: bus 5: motor 6: bicyclepath最好用相对路径并且整个项目目录不要带中文或空格否则在 Windows 和服务器之间迁移时容易出问题。names的索引必须和转换脚本里的category_map完全对应如果脚本里car是 1data.yaml 里1的位置也必须写car。这个错位很隐蔽训练时 loss 正常但画框或解析类别时会张冠李戴。nc的值也必须和 names 数量一致多写或少写都会在模型输出层留下隐患。3. 用 YOLOv5 训练 VisDrone 数据集训练命令与必调参数3.1 从 conda 环境到训练命令一套能跑通的最小配置YOLOv5 的环境配置本身不算复杂Python 3.8 到 3.10 都行PyTorch 按显卡版本装对应 CUDA 版。装完后用下面这条命令就能开始训练conda activate yolo cd yolov5 pip install -r requirements.txt python train.py --data visdrone.yaml --weights yolov5s.pt \ --batch-size 16 --epochs 100 --img 640 \ --device 0 --name visdrone_s--weights yolov5s.pt加载的是 COCO 预训练权重迁移到 VisDrone 航拍域时能明显加快收敛速度不要用随机初始化的--weights 从头训除非你想在绝大多数任务上浪费大量时间。--img 640是最小的训练输入跑通流程用它可以但航拍小目标在这种尺寸下几乎会被压没后面会专门讲为什么建议提到 1280。参数说明--name visdrone_s是训练结果保存目录名所有输出会放在runs/train/visdrone_s下--batch-size和--img相互制约显存不够时先降 batch不要降 img因为对 VisDrone 来说输入尺寸比 batch 更影响最终精度。如果你的 GPU 只有 8G 显存想跑 1280 输入可以试试开启梯度累积--batch-size 4 --accumulate 4等效 batch 为 16。3.2 YOLOv5 超参数调整航拍场景优先动这四个YOLOv5 的超参数文件在data/hyps/hyp.scratch-low.yaml很多人直接默认配置跑结果在 VisDrone 上效果一般。针对俯视视角的密集小目标我一般会改四个地方lr0: 0.01 # 初始学习率 lrf: 0.1 # 最终学习率训练到后期衰减到 lr0 * lrf mosaic: 1.0 # 保留 Mosaic 数据增强 scale: 0.5 # 随机缩放比例默认 0.9航拍场景下调小 hsv_h: 0.015 # 色调扰动保留作为光照变化模拟scale是我改动最多的项。默认值 0.9 会让目标在训练中被随机缩放到 0.1~1.9 倍对 COCO 这种目标尺度丰富的数据集是好事但 VisDrone 里同一张图既有 300 像素的卡车又有 30 像素的行人缩放幅度过大会破坏小目标的空间比例让模型更关注大目标。调到0.5后小目标的召回率普遍能提升几个点。mosaic: 1.0要保持开启它把四张图拼成一张相当于强行增加了训练样本密度对密集目标场景帮助很大。如果训练后期 loss 波动剧烈可以把mosaic在最后 20 个 epoch 关闭方法是训练结束后用关闭 mosaic 的配置继续微调。另外还有一个容易被忽略的类别损失权重问题。在hyp.scratch-low.yaml里没有直接按类别设权重的参数如果想提高行人这一类别的权重需要用--classification之外的定制手段或者直接保证类别映射里行人样本数足够。VisDrone 的原始数据里行人标注其实不少但很多是小目标如果你切了图再训练行人的小框会更容易被模型注意到。3.3 处理小目标的关键增强切图、多尺度与推理参数联动无人机俯视视角下行人可能只有 15x15 像素。YOLOv5 默认检测头里小目标依赖的浅层特征容易被丢弃。常见的可靠手段是切图训练把 2000x1500 的图切成 4 张 1000x750 的小块切图后的标注跟着算一遍偏移量。每张小图的目标尺度变大模型更容易学到特征。切图后可以用--img 640训练显存占用反而更小效果通常优于直接喂大图。第二种手段是多尺度训练命令里加一个--multi-scalepython train.py --data visdrone.yaml --weights yolov5s.pt \ --batch-size 8 --epochs 100 --img 1280 \ --multi-scale --device 0 --name visdrone_ms--multi-scale会让每轮迭代随机采样输入尺寸从 0.5 倍到 1.5 倍之间波动模型被迫适应不同尺度的目标对 VisDrone 这种尺度跨度大的数据集很合适。缺点是训练时间变长约增加 30% 到 50%。推理时也要配合调整。很多人在训练时用 1280推理时却为了省时间用 640效果大幅下降。YOLOv5 对输入尺寸很敏感尤其是小目标场景。部署时如果算力允许推理图尺寸尽量保持和训练一致至少不要低于训练的 0.8 倍。先大图后小图的策略也可以用 1280 训练前 30 个 epoch然后切回到 640 再训练 30 个 epoch模型既学到了大图上的细节又能在实际部署时保持可接受的推理速度。这个技巧在 yolo 系列项目里称为多尺度迁移是航拍检测里性价比很高的做法。4. 无人机俯视视角 YOLOv5 的常见问题排查5 个经典翻车现场4.1 训练 loss 一直在掉val mAP 却像心电图一样乱跳现象训练日志里 box loss 和 cls loss 持续下降看起来一切正常但 val 集的 mAP 忽高忽低最终模型效果也很差。原因最常见的是训练集和验证集之间存在“数据泄漏”。VisDrone 的同一视频序列里同一辆车和同一个人会连续出现在几十帧中。如果划分方式用了随机打散而不是按序列分组train 里出现的车在 val 里又出现模型记住的是这辆车而不是车的类别特征val mAP 自然忽上忽下。解决回到第 2.3 节按序列前缀分组后重新划分。如果你已经在跑训练那就重新做数据集划分清空runs/train下的旧结果再训不要带着虚高的 mAP 去做任何参数判断。这一步是航拍数据集和老老实实的单帧数据集最不一样的地方。4.2 转换脚本看着没问题画出来的框全是歪的现象把 VisDrone 标注转成 YOLOv5 格式后用可视化脚本叠框部分图的框偏移严重有的框甚至落到画面外。原因写死了图片宽高。我见过很多人把img_w, img_h 2000, 1500写在脚本开头结果遇到 1360x765 的图就出错。VisDrone 的图来自不同飞行平台和相机分辨率不一致不是所有图都是 2000x1500。解决用cv2.imread逐张取img.shape[:2]再算归一化坐标。另外注意 VisDrone 标注里可能存在越界坐标比如框的左边界超出图像宽度直接除法归一化后可能得到大于 1 的值训练时会被 YOLOv5 内部裁剪但也会带来隐性损失。解决方法是把归一化后的中心点和宽高都min(max(...))夹到 0~1这条我写进了第 2.2 节脚本里。4.3 模型训练完检测结果里疯狂输出“others”假阳性现象val 的时候总体 mAP 还行但可视化结果里经常冒出一些框在树丛、屋顶、阴影上这些位置在 VisDrone 原始标注里被标成 others 或 ignore。原因原始标注里score0的 ignore 框和 others 类别都会向模型传递大量低质量监督信号。ignore 框如果不过滤模型会尝试去拟合标注者自己也说不清的目标others 类别语义太杂包含各种背景杂物类别内部差异比车辆和行人之间的差异还要大。解决在转换脚本里按score 0过滤同时把 others 从类别表里移除。如果你已经训练完了重新生成训练标签把类别数从 11 压缩到 7再从头训一次。类别少了每一类的正样本数量相对变多小目标的 AP 曲线也会更平滑。4.4 输入尺寸调到 1280显存直接爆掉现象--img 1280 --batch-size 16启动后直接 OOM报CUDA out of memory。原因分辨率增加一倍特征图面积增加四倍显存开销不是线性涨的。很多人在 640 下用 batch 16 跑得动就认为 1280 也可以实际上 1280 在 batch 16 下需要的显存可能是 640 的 4 倍以上。解决降 batch。--img 1280 --batch-size 4在大多数 16G 显存的显卡上可以跑。如果还是爆用--batch-size 2 --accumulate 8做梯度累积。另一种办法是先切图切成 1000x750 后--img 640也能获得较好效果。不要硬扛 1280YOLOv5 的切图逻辑对航拍数据很友好用切图替代大分辨率是更省算力的路线。4.5 推理画框时大量目标互相重叠一个行人被截断成两个框现象检测结果里同一辆车被输出多个框行人区域出现断裂框与框之间交错得一塌糊涂。原因YOLOv5 的 NMS 参数设置偏严格。默认--iou-thres 0.45在常规目标上够用但 VisDrone 俯视图片中目标密集且彼此靠近当两个真实目标的 IoU 超过 0.45 时NMS 会把其中一个当成重复框直接干掉。框被干掉后剩余框的置信度分布也跟着乱。解决推理时把--iou-thres提到 0.6 到 0.7减少被误杀的框。同时把--conf-thres从默认 0.25 适当调到 0.3 到 0.4抑制掉一批低置信度的假阳性。在人群和车流密集的视频流里这两个阈值基本是必调项。训练时不需要改后处理阶段单独验证就行。5. 航拍检测的落地验证mAP 之外还要看这四项5.1 用 val.py 回归验证小目标指标怎么看训练结束后val.py 输出的 mAP50 容易让人高兴但对无人机俯视场景我更看重四个数小目标的 mAP50-95、中目标的 mAP50-95、每张图的平均漏检数、NMS 之后的假阳性数。可以这样跑python val.py --data visdrone.yaml --weights runs/train/visdrone_s/weights/best.pt \ --img 1280 --conf-thres 0.25 --iou-thres 0.6 --save-json--img 1280要与训练输入保持一致或更高否则精度数据没有可比性--iou-thres 0.6对应密集场景的 NMS 阈值用来模拟真实部署时的表现。看结果时别只盯总 mAP重点看输出的 per-class 表格。如果 pedestrian 的 recall 低于 0.5说明俯视行人的漏检仍然严重部署到视频流里会看到框断断续续。此时优先考虑切图训练而不是盲目加大模型规模。5.2 用少量自采数据微调少样本条件下的定位每次拿到新的航拍场景我不会直接重新训练而是在 VisDrone 模型基础上用自己拍到的 30 到 50 张标注图做微调。命令如下python train.py --data my_drone.yaml --weights runs/train/visdrone_s/weights/best.pt \ --batch-size 8 --epochs 60 --img 1280 --freeze 10 --name visdrone_finetune--freeze 10让前 10 层骨干权重冻结只更新后面的特征层和检测头能避免少量样本把预训练特征冲掉。新场景的 data.yaml 也要按照第 2.4 节的格式重写尤其要检查新场景里类别是否覆盖 VisDrone 的全部类别如果只做车辆识别可以把nc减小。微调后我习惯用一段没有标注的飞行视频做目测回归看框的位置是否稳定、是否把树影和车顶搞混。如果发现某些特定角度漏检就把那几帧挑出来手动加标继续迭代。这个流程比反复调整超参数更管用也是我做航拍检测项目时的一个固定习惯。希望帮到你。本文还有配套的精品资源点击获取