☰
无人机俯视车辆行人检测:VisDrone数据集YOLOv5训练与避坑指南
2026/10/10 18:52:35 网站建设 项目流程

简介:无人机俯视视角下的车辆与行人目标检测数据集,面向目标检测算法研发人员与相关专业学习者,专为YOLO系列模型快速迭代而整理。资源包含一千多张无人机实拍俯视图像,所有样本均标注车辆与行人两类目标,可应用于智慧交通、低空安防、作业巡检等场景的算法验证。压缩包内共两千个文件,以JPG图像、TXT标注文件和数据配置文件为主;JPG为原始图片,TXT为归一化标注坐标,配置文件内写明了类别名称与图片路径。数据集已经预先拆分为训练集、验证集、测试集三个部分,并附带可直接读取的配置文件,使用主流YOLO系列算法时无需再做数据整理或格式转换,解压后即可启动训练。资源包整体约九百六十兆,目前已有超过一千三百人学习下载,配套博文还给出了检测效果示例,特别适合需要现成无人机视角高精度标注数据的开发者直接用于实验研究。

1. 无人机俯视视角下的车辆行人检测:为什么说这份VisDrone数据包值得先跑通

做无人机目标检测的工程师,大多在自建数据集上栽过跟头:飞一下午拍几百张图,逐帧标注到凌晨,训练出来的模型照样漏检。问题不在模型,在数据分布——无人机俯视视角下车辆行人目标小、密度高、遮挡多,和地面平视数据集完全是两回事。VisDrone就是为这个场景采集的公开基准,这个vis-drone-yolov5-dataset-2.zip就是把它整理成YOLOv5格式的成品数据包。

全文围绕一条主线:把这份数据包跑通并真正用起来。先拆解VisDrone标注体系,再写转换脚本转成YOLOv5归一化标注,接着讲小目标场景下yolov5超参数怎么调,最后落到验证与边缘部署的必查项。适合做无人机巡检、安防监控、智慧交通的工程师,照着走一遍能省两周标注时间。

2. VisDrone数据集拆解:标注格式、类别体系与YOLOv5的适配关系

2.1 VisDrone的TXT标注字段逐行解读

VisDrone原始标注是每张图片对应一个同名txt文件,每行一条目标记录,字段以逗号分隔,顺序固定:左坐标、上坐标、宽、高、置信度、类别、截断程度、遮挡程度。前四个是像素坐标,第五个是标注置信度,第六个是类别编号,后两个表示目标被截断和遮挡的状态。拿到这个zip后,第一件事不是急着训练,而是先抽查几个标注文件把字段读明白。

比如某一行长这样:

102,238,48,86,1,4,0,1

含义是:框左上角在图像坐标(102, 238),框宽48像素、高86像素,置信度1.0,类别4即car,截断程度0表示完整,遮挡程度1表示部分遮挡。注意这里的坐标是像素值,不是归一化值,后面转YOLOv5格式时必须除以图像宽高。

两个容易踩坑的字段是第五位的score和第六位的category。VisDrone标注规范里,score为0的框代表"此目标未确认",不作为正样本;category为0的框代表"忽略区域",这些区域里的目标既不该参与训练也不该被当作负样本。很多初学者拿到转换好的数据集就直接开训,结果发现loss不降或者mAP虚高,多半就是这两个字段处理错了。

我拿到数据包后的习惯是先用脚本统计每个类别的样本数和框数,确认类别分布没跑偏再动手转换。这一步能提前发现类别映射错误、空标注文件等问题,属于花两分钟省两小时的操作。

2.2 类别映射表:10类如何收敛成车辆与行人2类

VisDrone总共定义了10个类别,编号从0到10:0是ignore区域,1是pedestrian(行人),2是people(站姿行人),3是bicycle(自行车),4是car(轿车),5是van(面包车),6是truck(卡车),7是tricycle(三轮车),8是awning-tricycle(带篷三轮车),9是bus(公交车),10是motor(摩托车)。

这份数据包既然定位为"车辆和行人目标检测",常见做法是把10类收敛成两类:person和vehicle。映射关系如下:

VisDrone编号原始类别映射到YOLOv5
0ignored丢弃
1pedestrianperson(0)
2peopleperson(0)
3bicyclevehicle(1)
4carvehicle(1)
5vanvehicle(1)
6truckvehicle(1)
7tricyclevehicle(1)
8awning-tricyclevehicle(1)
9busvehicle(1)
10motorvehicle(1)

提示:如果只做车辆检测,把3、7、8、10也丢掉没毛病;但如果做的是无人机巡检场景的通用目标检测,建议把行人、自行车、摩托车都保留,宁可在训练时多学几类,也不要在部署后再回头补数据。

收敛成两类还有个好处:类别间样本量差距比10类时小得多。VisDrone原始数据里car和pedestrian样本量最大,tricycle和awning-tricycle少一个量级,直接训10类很容易出现长尾效应,少样本类别几乎学不到东西。合并成person和vehicle后,两个类别的样本量基本同数量级,训练稳定很多。

2.3 目录结构与文件名规则:训练集验证集怎么对应

标准的YOLOv5数据集目录是images和labels两个镜像目录,各自下面再按train、val、test分目录。解压后建议先确认目标目录结构长这样再动手:

vis-drone-yolov5-dataset-2/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── README.txt

镜像的意思很关键:images/train里有一张0000001_00001_d_0000011.jpg,labels/train里就必须有同名的0000001_00001_d_0000011.txt。YOLOv5的训练脚本按图片名找标注文件,名字对不上就直接跳过那张图,而且不报错——这是特别容易翻车的地方。

VisDrone原始数据集本身按train/val/test划分,文件名带序列信息。如果数据包已经是转换好的YOLOv5结构,目录可能直接就是上面这样的。如果拿到的是原始VisDrone标注,就用下一章的脚本自己转。

data.yaml是YOLOv5训练的入口配置,至少要写清楚路径、类别数、类别名三个字段。这里有一个和VisDrone直接相关的问题:nc应该写2还是写10?前面做了类别收敛,答案当然是2。但很多人拿着VisDrone原始10类标签、套着一个nc=2的data.yaml就开始训,跑到一半才发现类别编号越界。下一节的转换脚本会先把这个问题解决掉。

3. 把VisDrone转成YOLOv5训练格式:转换脚本与三个边界坑

3.1 Python转换脚本:VisDrone Txt到YOLO TXT的坐标换算

如果数据包里已经是YOLOv5格式,这一步可以跳过;但绝大多数场景下你拿到的VisDrone原始标注都需要自己转。转换的本质是把像素坐标框归一化成YOLOv5要求的x_center, y_center, w, h格式,同时做类别映射。下面是我常用的转换脚本:

import os import glob from PIL import Image # VisDrone 10类 -> YOLOv5 2类 (person / vehicle) # 0:ignored 1:pedestrian 2:people 3:bicycle 4:car 5:van 6:truck # 7:tricycle 8:awning-tricycle 9:bus 10:motor CATEGORY_MAP = { 1: 0, # pedestrian -> person 2: 0, # people -> person 3: 1, # bicycle -> vehicle 4: 1, # car -> vehicle 5: 1, # van -> vehicle 6: 1, # truck -> vehicle 7: 1, # tricycle -> vehicle 8: 1, # awning-tricycle -> vehicle 9: 1, # bus -> vehicle 10: 1, # motor -> vehicle } def convert_visdrone_to_yolo(txt_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) txt_files = sorted(glob.glob(os.path.join(txt_dir, "*.txt"))) for txt_path in txt_files: base = os.path.splitext(os.path.basename(txt_path))[0] img_path = os.path.join(img_dir, base + ".jpg") if not os.path.exists(img_path): print(f"[warn] 找不到图片: {img_path}") continue with Image.open(img_path) as im: img_w, img_h = im.size out_path = os.path.join(out_dir, base + ".txt") with open(txt_path, "r") as f_in, open(out_path, "w") as f_out: for line in f_in: parts = line.strip().split(",") if len(parts) < 8: continue left = float(parts[0]) top = float(parts[1]) box_w = float(parts[2]) box_h = float(parts[3]) score = float(parts[4]) cat = int(parts[5]) if cat == 0 or score == 0: continue # 丢弃忽略区域和未确认目标 if cat not in CATEGORY_MAP: continue x_center = (left + box_w / 2) / img_w y_center = (top + box_h / 2) / img_h norm_w = box_w / img_w norm_h = box_h / img_h # 越界保护:标签坐标必须落在[0,1]区间 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) norm_w = min(max(norm_w, 0.0), 1.0) norm_h = min(max(norm_h, 0.0), 1.0) f_out.write( f"{CATEGORY_MAP[cat]} {x_center:.6f} {y_center:.6f} " f"{norm_w:.6f} {norm_h:.6f}\n" ) print(f"[ok] {base}.txt") if __name__ == "__main__": convert_visdrone_to_yolo( txt_dir="VisDrone2019-DET-val/annotations", img_dir="VisDrone2019-DET-val/images", out_dir="labels/val", )

这段脚本做了四件事:读取VisDrone每行8个字段、按映射表转换类别、把像素框转成归一化中心点坐标、过滤掉忽略区域和未确认目标。if cat == 0 or score == 0: continue这行是核心过滤逻辑。需要说明的是,脚本没有用truncation和occlusion字段,如果你的场景里遮挡目标特别多,可以考虑把occlusion=2(严重遮挡)的目标单独处理,而不是简单保留。

注意img_w和img_h必须来自真实图片尺寸。VisDrone视频流里有不同分辨率帧,不能假设所有图都是同样尺寸,否则坐标归一化就失真。我的做法是转换时用PIL读取每张图片的真实宽高,虽然慢一点但靠谱。

3.2 边界坑一:忽略区域、低置信度目标不能简单删掉

VisDrone的ignore区域标注了那些"不该被评估"的目标,比如严重遮挡的行人、小到无法确认的车辆。很多人图省事,转换时直接跳过这些框,这本身没错,但要注意:跳过意味着这些目标会被当作背景,如果忽略区域里恰好有一辆很明显的车,模型就会误学"这个位置的车不是车",推理时同样位置就会漏检。

更稳妥的做法是保留ignore框作为"不参与损失计算"的先验信息。YOLOv5不支持显式的ignore区域标注,但可以在转换时把ignore框单独存一份,训练后分析漏检时对照使用。实际操作中我的经验是:如果你只是用VisDrone做预训练再Fine-tune自己的数据,直接跳过ignore框问题不大;如果你是拿这份数据做最终模型,就必须分析ignore区域的分布,否则验证集上的指标会虚高。

3.3 边界坑二:目录镜像结构与文件命名必须严格对应

YOLOv5的Dataset类在加载标签时,是把图片路径里的/images/替换成/labels/、把.jpg替换成.txt来定位标注文件的。这意味着图片目录和标签目录不镜像、或者图片后缀不统一,都会导致标签静默丢失。

我遇到过最隐蔽的问题:一部分图片是.jpg,另一部分是.jpeg,标签目录里全是.txt,结果.jpg对应的标签正常加载,.jpeg对应的全部找不到。YOLOv5不会报错,只是训练日志里少了很多标签,mAP看起来还行但实际上模型只学到了一半数据。

解决方法是转换前先统一图片格式。我一般会在预处理阶段批量转成.jpg:

find . -name "*.jpeg" -exec rename 's/\.jpeg$/.jpg/' {} \; find . -name "*.png" -exec rename 's/\.png$/.jpg/' {} \;

然后用下面的命令核对图片和标签数量是否一致:

for split in train val; do echo "$split imgs: $(ls images/$split/*.jpg | wc -l)" echo "$split labels: $(ls labels/$split/*.txt | wc -l)" done

数量一致不一定没问题,但数量不一致一定有问题。这个习惯帮我避免了至少三次无效训练。

3.4 边界坑三:归一化坐标防越界与data.yaml类别数一致性

VisDrone有些标注框会略微超出图像边界,比如left是负值,或者left+w超过图像宽度。转换时不处理,训练时YOLOv5会计算异常的锚框面积,轻则loss异常,重则直接崩。前面脚本里已经加了min(max(...))的越界保护,但要注意:保护之后的框如果宽或高变成0,这一行应该直接丢弃而不是保留。

data.yaml的nc字段是另一个高频翻车点。如果把10类收敛成了2类,data.yaml里就必须是nc=2,并写上对应的names。如果nc写10而标签里只有0和1两个编号,训练能跑但类别数对不上,最终结果完全不可信。下面是一份可直接用的data.yaml模板:

path: /workspace/vis-drone-yolov5-dataset-2 train: images/train val: images/val test: images/test nc: 2 names: 0: person 1: vehicle

path字段建议写绝对路径。写相对路径时YOLOv5会相对当前工作目录解析,换个目录训练就要重新配,这个坑我踩过不止一次。

4. 训练参数与预训练权重:小目标场景下yolov5超参数怎么调

4.1 锚框与输入尺寸:俯视小目标为什么默认参数表现差

VisDrone俯视视角的目标尺寸分布和COCO差异巨大。COCO里常见的中大型目标在无人机俯视图里变成几十像素的小框,很多行人甚至不到20×40像素。yolov5默认锚框是在COCO上聚类出的,长宽比和尺寸尺度都偏向地面视角。直接用它训练VisDrone,小目标分支P3的召回率通常很难看。

有人把这类任务归到遥感目标检测范畴,但和卫星遥感图不同,VisDrone拍摄高度低、视角更接近垂直俯拍,目标尺度跨越很大。YOLOv5内置了自动锚框机制,训练时会在指定epoch重新聚类。我建议确认--noautoanchor没有被误加,用默认结构时训练日志里会出现类似AutoAnchor: 6.09 anchors/target, 0.997 Best Possible Recall的输出,BPR越接近1说明锚框越适配当前数据集。

输入尺寸方面,640是速度和精度的折中点。VisDrone小目标多,640下很多行人只有十几个像素,基本不可能被检出。我的做法是先用640跑通流程,确认数据管线没问题后再用1280训练一版对比。1280的显存占用提升约4倍,但小目标AP通常能提升5到8个点,这个提升在俯视场景里非常可观。

4.2 关键超参数:batch、epochs与学习率的取舍

先说batch。VisDrone的类别分布相对均衡,batch=16在单卡上比较稳妥。batch再大对收敛有帮助,但显存不够时不要硬凑。yolov5超参数里的learning rate一般保持默认的0.01,配合warmup即可,不要在VisDrone上盲目调高——小目标场景对学习率更敏感,lr过大容易振荡。

epochs的设置分两种情况。如果你要在VisDrone上做完整训练,300个epoch是合理起点,配合cosine学习率调度。如果你只想要一个能用的预训练模型去Fine-tune自己的数据,100个epoch就足够,重点是让loss稳定下降趋势清晰即可。

一个我用过多次的实用做法:先训50个epoch,看results.png里的验证集mAP曲线趋势。如果精确率和召回率都在涨,说明数据没问题,继续跑就行;如果精确率涨召回率不涨,去查类别分布和忽略区域处理;如果两个都不涨,大概率是转换脚本出了问题,别再烧GPU了。

4.3 数据增强与预训练权重:从COCO迁移到VisDrone

YOLOv5默认开启的mosaic增强对小目标非常友好,它把四张图拼成一张,相当于变相提高了小目标密度。俯视视角下目标本来就密集,mosaic增强的效果会被进一步放大。不过mosaic在训练后期建议关闭或降低概率,因为合成图像和真实俯视图有差异,一直用会让模型对真实场景的适应性变差。

预训练权重方面,官方yolov5s.pt在COCO上训练过,COCO里有person和car这些类别,迁移到VisDrone有天然优势。我建议不要用随机权重从头训练,直接用yolov5s.pt或yolov5m.pt作为起点。yolov5m比s的精度高一些,显存占用约2倍。VisDrone小目标多,yolov5m的收益比在COCO上更明显,因为m的P3特征图通道更多,保留的小目标细节也更多。

注意:官方权重是COCO 80类,迁移时前几层权重直接复用,最后一层检测头会随机初始化。打印出的模型结构里head层参数会重新训练,这是正常的,不用怀疑权重坏了。

5. VisDrone训练避坑:4个高频问题与排查记录

5.1 现象1:loss震荡不降,mAP在早期停滞

原因:最常见的是学习率过大或batch太小。VisDrone的标注噪声比COCO大,尤其遮挡严重的框,loss曲线本身就比地面数据集波动大。训练日志里如果出现个别step的box_loss突然冲高,多半是遇到了极端标注的目标。

解决:先把batch调到16以上,学习率保持0.01。如果还震荡,把图片尺寸降到640并确认数据增强没有叠加过度。另一个容易忽略的原因是类别映射错误导致标签编号越界,比如把忽略区域映射成了类别2而data.yaml里nc=2,yolov5不会报错,但loss计算会异常。检查标签文件里类别编号的最大值:

awk '{print $1}' labels/train/*.txt | sort -n | uniq -c

如果输出里出现2或更大的数字且data.yaml的nc=2,那就是映射表写错了,跑多少轮都白跑。

5.2 现象2:验证集mAP高但推理时漏检密集小目标

原因:这是VisDrone上最典型的假象。验证时数据增强和训练时不一致,mAP高不代表模型真的学好了。更常见的原因是NMS阈值设置不当——小目标密集场景下,默认的conf_thres=0.25会滤掉大量低置信度但真实的小目标。无人机俯视图里一个20像素的行人,模型给出的置信度可能只有0.15到0.3。

解决:推理时显式降低conf_thres到0.1或0.05,同时把iou_thres保持在0.45附近,然后看检测结果里小目标的召回率是否明显提升。如果提升了,说明模型本身学到了,只是阈值卡太严:

python detect.py --weights runs/train/exp/weights/best.pt \ --source val_imgs/ --conf-thres 0.1 --iou-thres 0.45

5.3 现象3:训练中途OOM,batch调小后准确率下降

原因:显存不够时把batch从16调到4,看起来解决了OOM,但BN层的统计量会剧烈波动,对模型收敛影响很大,准确率下降是必然的。我给这类问题起过名字:用batch换显存,是拿模型质量换的。

解决:优先降低输入尺寸而不是batch。比如batch=16、img=640时OOM,改成batch=16、img=512,显存占用约下降36%,模型精度损失远小于把batch降到4的损失。如果img=512也不行,再降batch到8,并用--batch -1的auto-batch模式让yolov5自动找最大可用batch。

5.4 现象4:类别数量与配置文件不匹配,不报错但结果离谱

原因:nc写错是yolov5里最容易忽略的问题之一。还有个变种是标签文件里有类别编号为负数的行——这种情况通常是转换脚本里某个类别没映射成功,直接输出了一个非法标签。

解决:训练前跑一个完整的标签合法性检查,确认每个标签文件的行格式都是"整数 空格 浮点数 浮点数 浮点数 浮点数",且第一列在0到nc-1之间。不要只抽查几个文件,用脚本全量统计一次,几十秒的事,能省几小时排错时间。

for f in labels/train/*.txt; do awk '{if ($1<0 || $1>1) print FILENAME": "$0}' $f done

这条命令把类别编号不在0和1之间的行全部打印出来,配合前面的uniq统计,基本能把标签问题一次查干净。

6. 验证与部署:用训练好的权重跑通无人机俯视检测

6.1 用detect.py跑通单张俯视图推理

训练完成后第一件事,不是急着上板子,而是拿验证集里最有代表性的几张图做推理。我会挑三类图:密集行人的十字路口、大范围俯拍的停车场、以及有遮挡的树荫路段。跑通一条命令:

python detect.py --weights runs/train/exp/weights/best.pt \ --source val_imgs/selection/ --conf-thres 0.1 --img 1280

观察输出图里的检测框是否贴合真实目标,特别关注有没有把车顶误检成人、密集行人有没有被合并成一个框。这类定性判断几分钟就能完成,比任何指标都直观。

6.2 从验证集读数判断模型是否值得部署

部署前只看mAP不够,我会额外看两个数:小目标AP和类别别的召回率。yolov5的val.py输出里自带按尺寸分层的AP指标,注意看small那一档。VisDrone场景里small AP低于0.3基本不具备实用价值,至少到0.5左右才敢往边缘设备上放。这是血泪经验换来的标准——很多项目在演示时看着不错,一到实际巡检就漏检,就是栽在只看了总mAP没看small AP上。

6.3 边缘设备部署的量化与剪枝取舍

如果目标设备是树莓派4B或RK3568这类边缘板子,FP32模型通常跑不动。推荐路径是先导出torchscript再做INT8量化,或者用export.py导出ONNX再接推理引擎。量化后精度掉多少是玄学,但有一个经验规律:如果模型在640输入下small AP已经不高,量化后小目标大概率直接消失;反过来,如果640下指标很稳,INT8量化通常能保住大部分精度。先让模型在PC上跑到精度下限,再上板子量化,这一步不能省。

我的习惯是训练结束后跑一次完整的val.py拿到confusion matrix,再用detect.py做一组推理就走完收工。无人机视觉感知项目里最大的风险不是模型没训好,而是没验证就往真机上搬。花半天把验证和部署链路走通,后面迭代就快。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询