☰
无人机俯视视角车辆行人检测:YOLOv5数据集训练与调优实战
2026/10/5 10:47:48 网站建设 项目流程

简介:这份资源是面向无人机俯视视角目标检测任务的YOLO格式数据集,适合从事车辆与行人检测的算法工程师、研究生及竞赛选手使用,可直接用于训练与验证模型。数据集包含1000余张标注图像,压缩包内共2000个文件,其中1648个txt标注文件、351张jpg图像及1个yaml配置文件,整体约850.13MB。目录已按train、valid、test划分完毕,data.yaml中定义names为car与person两类,yolov5、yolov7、yolov8等主流框架均可直接读取训练,省去格式转换与划分工作。目前已有1681人学习下载,配套博文提供了检测结果参考。读者可获得一套开箱即用的无人机视角车辆行人检测数据,便于快速复现实验、对比算法性能或作为课程与项目的数据基础。

1. 无人机俯视视角下的车辆和行人检测:这个数据集到底解决什么问题

拿到 vis-drone-yolov5-dataset-1.zip 这个包名的时候,我第一反应不是「又一个 YOLO 数据集」,而是「俯视视角」这四个字。地面监控摄像头拍车拍人,角度是斜的、距离是固定的、光照是可控的;无人机挂载相机飞到几十米高空往下看,画面里的人和车变成几十个像素的斑点,透视关系完全变了,背景从街道变成屋顶、树冠、停车场纹理。把在 COCO 或者 BDD100 上训出来的模型直接拿去跑无人机俯拍画面,召回率掉得让人怀疑人生——这不是模型不行,是域差太大。

这个数据集要解决的就是这个域差问题:它提供无人机俯视视角下标注好的车辆和行人框,让你能在 YOLOv5 框架里做微调,把通用检测器拉回到航拍场景可用。适合谁?做无人机视觉感知的、搞航拍巡检的、想用 YOLOv5 训练自己数据集但手头没有俯视标注样本的,以及需要验证「移动小目标检测」在低空场景下到底能做到什么程度的工程师。下面我按实际落地顺序,从数据检查、格式转换、训练配置到踩坑排查,把这条路走一遍。

2. 先搞清楚 vis-drone 数据集的标注结构和 YOLOv5 的胃口

2.1 俯视视角下车辆和行人的标注难点

无人机俯拍和地面拍摄最大的区别在于目标外观。地面视角看车,你能看到车头、车窗、侧面轮廓;俯视看车,基本就是一个矩形色块加挡风玻璃的反光。行人更极端,俯视下只剩头顶和肩膀,手臂摆动几乎不可见。这意味着标注时边界框的松紧程度会直接影响模型学到什么——框太松,模型把路面纹理也当特征;框太紧,小目标本身就没几个像素,裁掉边缘后特征更少。

vis-drone 这类数据集通常采用水平边界框(HBB)标注,类别就两类:vehicle 和 pedestrian。有些版本会细分 car、truck、bus,但从包名看,vis-drone-yolov5-dataset-1 大概率是粗粒度两类,方便直接映射到 YOLOv5 的 nc=2。标注格式可能是 VOC XML、COCO JSON 或已经转好的 YOLO txt,解压后第一件事就是确认这一点。

2.2 解压后先跑一遍数据体检脚本

别急着写训练命令。我一般会先写个脚本统计类别分布、框的宽高分布、每张图的实例数,看看有没有空标注、有没有宽高为 0 的脏框。下面这段代码假设标注是 YOLO txt 格式(每行class x_center y_center width height,归一化到 0-1):

import os import glob from collections import Counter label_dir = "vis-drone-yolov5-dataset-1/labels/train" img_dir = "vis-drone-yolov5-dataset-1/images/train" cls_counter = Counter() w_list, h_list = [], [] empty_files = [] for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt) as f: lines = [l.strip() for l in f if l.strip()] if not lines: empty_files.append(txt) continue for line in lines: parts = line.split() c = int(parts[0]) w = float(parts[3]) h = float(parts[4]) cls_counter[c] += 1 w_list.append(w) h_list.append(h) print("类别分布:", cls_counter) print("空标注文件数:", len(empty_files)) print("宽 min/mean/max:", min(w_list), sum(w_list)/len(w_list), max(w_list)) print("高 min/mean/max:", min(h_list), sum(h_list)/len(h_list), max(h_list))

逻辑说明:遍历所有 label 文件,统计每个类别的框数量,同时收集归一化宽高。参数说明:label_dir和img_dir按实际解压路径改;如果标注是 XML 或 JSON,需要先解析再转成同样的统计逻辑。重点看两个数——空标注文件数和宽高最小值。空标注文件如果占比超过 5%,说明数据清洗没做完,直接训会让模型学到「这张图没有目标」的假阴性;宽高最小值如果低于 0.01(即原图 640 像素下不到 7 像素),这些框在训练时经过下采样基本就消失了,需要考虑是否过滤或放大输入分辨率。

2.3 从 VOC/COCO 转到 YOLO txt 的转换脚本

如果解压出来是 VOC XML,用下面这个脚本转。核心是把xmin, ymin, xmax, ymax转成归一化的中心点加宽高:

import xml.etree.ElementTree as ET import os classes = ["vehicle", "pedestrian"] # 按你的实际类别顺序改 xml_dir = "vis-drone-yolov5-dataset-1/annotations" out_dir = "vis-drone-yolov5-dataset-1/labels/train" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in classes: continue cls_id = classes.index(name) bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 裁剪到图像边界,防止标注越界 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) xc = (xmin + xmax) / 2.0 / img_w yc = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") out_path = os.path.join(out_dir, xml_file.replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines))

逻辑说明:解析每个 XML,读取图像宽高,把绝对坐标转成归一化中心点格式。参数说明:classes列表的顺序必须和后续 YOLOv5 的data.yaml里names顺序一致,否则类别会错位;边界裁剪那两行是防止标注框超出图像范围导致归一化后出现负值或大于 1 的值,YOLOv5 遇到这种框会直接报错或静默丢弃。转换完再跑一遍 2.2 的体检脚本确认。

3. 用 YOLOv5 在 vis-drone 数据集上跑通训练的最小闭环

3.1 环境配置和目录结构

YOLOv5 对环境不算挑剔,但版本要对齐。我一般用 Python 3.8+、PyTorch 1.10+,CUDA 版本跟显卡驱动匹配就行。克隆仓库后先装依赖:

git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt

目录结构按 YOLOv5 的约定来,data.yaml里用相对路径或绝对路径都行,但相对路径是相对于yolov5根目录:

yolov5/ data/ vis_drone.yaml datasets/ vis_drone/ images/train/ images/val/ labels/train/ labels/val/

vis_drone.yaml内容:

path: ../datasets/vis_drone train: images/train val: images/val nc: 2 names: ["vehicle", "pedestrian"]

注意path是相对于yolov5根目录的,train和val再相对于path。这个层级关系搞错是新手最常见的翻车点,报错通常是FileNotFoundError或者训练时No labels found。

3.2 训练命令和关键超参数

最小训练命令:

python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/vis_drone.yaml \ --weights yolov5s.pt \ --project runs/train \ --name vis_drone_s

参数说明:--img 640是输入分辨率,俯视小目标多的话可以提到 1024 或 1280,但显存占用会平方级增长;--batch 16在 8G 显存下跑 640 分辨率基本安全,显存不够就降到 8 并配合--accumulate做梯度累积;--weights yolov5s.pt用预训练权重,从零训在几千张图上基本没戏;--epochs 100是起步值,看验证集 mAP 曲线,如果 50 轮后还在涨就继续加。

YOLOv5 的超参数分两套:data/hyps/hyp.scratch-low.yaml适合小数据集从预训练微调,hyp.scratch-high.yaml适合大数据集从头训。vis-drone 这种规模,我一般用 low 版本,重点调三个:lr0(初始学习率,默认 0.01,小数据集可以降到 0.001)、lrf(最终学习率因子,默认 0.01)、mosaic(马赛克增强概率,默认 1.0,俯视场景下建议降到 0.5,因为马赛克拼接会破坏俯视视角的空间一致性)。

3.3 训练过程看什么指标

启动后终端会打印每轮的box_loss、obj_loss、cls_loss和验证集的P、R、mAP@0.5、mAP@0.5:0.95。俯视小目标检测最该盯的是mAP@0.5:0.95和mAP@0.5的差距——如果前者远低于后者,说明框的定位精度不够,可能是小目标回归难,也可能是标注框松紧不一致。另一个信号是obj_loss不降,通常意味着正负样本分配有问题,检查一下 anchor 尺寸和数据集里目标的实际宽高分布是否匹配。

训练完在runs/train/vis_drone_s/下会有weights/best.pt和last.pt,还有results.png和confusion_matrix.png。混淆矩阵能直接看出 vehicle 和 pedestrian 有没有互相误判,俯视下车辆和行人的纹理差异其实比地面视角更大,误判通常来自极端小目标。

4. 俯视小目标检测的避坑与排查清单

4.1 现象:训练 loss 正常下降但验证 mAP 极低

原因:训练集和验证集分布不一致。常见情况是同一个视频序列的相邻帧被随机分到了 train 和 val,导致验证集里的目标在训练集里出现过几乎一样的副本,模型过拟合到具体场景而不是学到类别特征。另一个原因是验证集里小目标占比远高于训练集。

解决:按视频序列或拍摄架次划分 train/val,不要按帧随机分。如果数据集没有序列信息,至少按图像文件名前缀或拍摄日期做分组划分。验证集的小目标比例要和训练集接近,差太多就重新采样。

4.2 现象:模型把路面纹理、屋顶边缘检测成 vehicle

原因:俯视场景下背景纹理复杂,车辆在低分辨率下和某些矩形地物外观接近。加上如果标注框偏松,模型会把周围背景也纳入特征。

解决:先检查标注框是否过松,用 2.2 的脚本看宽高分布,如果均值明显大于目标实际尺寸,需要重新标注或收紧。训练时开启--rect矩形推理减少 padding 引入的噪声,同时把mosaic降到 0.3 以下,避免拼接出虚假的车辆排列模式。

4.3 现象:小目标行人完全检不到,mAP 为 0

原因:行人俯视下只有十几个像素,经过 YOLOv5 的 32 倍下采样后,在 P3 特征图上只剩不到一个像素,特征消失。默认 anchor 也是按 COCO 目标尺寸设计的,没有覆盖这么小的框。

解决:把输入分辨率提到 1280,让行人在 P3 上至少有 2-3 个像素;用python utils/autoanchor.py重新聚类 anchor,或者手动在models/yolov5s.yaml里把 P3 层的 anchor 改小;如果还是不行,考虑切到 YOLOv5 的 P2 变体(修改 yaml 增加一个更浅层的检测头),代价是计算量翻倍。

4.4 现象:训练到一半突然报 CUDA out of memory

原因:YOLOv5 默认开启 mosaic 和 mixup,某些 batch 里拼接后的图像尺寸波动大,显存峰值不可预测。另外验证阶段如果--img和训练不一致,也会额外占显存。

解决:固定--img训练和验证一致;把--batch降到 8 并加--accumulate 2保持等效 batch;在train.py里把--workers降到 4 以下减少数据加载的显存碎片。如果还不行,用--device 0指定单卡,避免多卡通信的额外开销。

4.5 现象:推理时框的位置整体偏移

原因:训练时用了--rect矩形推理,但推理时没有开,或者反过来。YOLOv5 的 letterbox 填充方式在训练和推理不一致时会导致坐标映射错位。

解决:训练和推理保持相同的--rect设置。用detect.py测试时显式加--rect或都不加。另外检查data.yaml里的path是否在训练后被改动过,路径变化会导致验证集加载错位。

5. 把 vis-drone 模型推到实际无人机画面上的验证技巧

训练完拿到best.pt只是第一步,真正要验证的是它在实际无人机视频流上的表现。我一般会做三件事:切片推理、时序平滑、以及和地面视角模型的对比测试。

切片推理是针对高分辨率航拍图的。无人机画面动辄 4K,直接缩到 640 会丢掉所有小目标。做法是把原图切成有重叠的子图,每张子图单独推理,再把框映射回原图坐标做 NMS。YOLOv5 官方没有内置这个,但可以用saic或者自己写:

import cv2 import numpy as np from yolov5.models.common import DetectMultiBackend from yolov5.utils.general import non_max_suppression, scale_boxes model = DetectMultiBackend("runs/train/vis_drone_s/weights/best.pt", device="cuda") img = cv2.imread("drone_frame.jpg") h, w = img.shape[:2] tile_size, overlap = 640, 128 step = tile_size - overlap all_boxes = [] for y in range(0, h, step): for x in range(0, w, step): tile = img[y:y+tile_size, x:x+tile_size] if tile.shape[0] < tile_size or tile.shape[1] < tile_size: tile = cv2.copyMakeBorder(tile, 0, tile_size-tile.shape[0], 0, tile_size-tile.shape[1], cv2.BORDER_CONSTANT, value=(114,114,114)) # 推理并记录偏移量,此处省略预处理细节 # pred = model(tile_tensor) # 把框坐标加上 (x, y) 偏移后收集到 all_boxes pass # 对所有框做一次全局 NMS # keep = non_max_suppression(torch.cat(all_boxes), conf_thres=0.25, iou_thres=0.45)

逻辑说明:按固定步长滑动窗口切图,每张子图推理后把框坐标加回原图偏移,最后统一 NMS。参数说明:tile_size和训练分辨率一致,overlap至少覆盖最大目标尺寸,否则跨切片的物体会被截断。这个方案计算量是整图推理的几倍,但小目标召回率提升明显。

时序平滑是针对视频流的。单帧检测会有闪烁,同一辆车在连续帧里时有时无。简单做法是用卡尔曼滤波或者 IoU 跟踪做帧间关联,只保留连续 3 帧以上出现的检测框。这个技巧在无人机巡检场景里特别实用,因为误检通常只出现在单帧,真实目标会持续存在。

最后一个验证习惯:拿同一个场景的地面摄像头模型和俯视模型做交叉测试。如果俯视模型在地面画面上表现很差,说明它确实学到了俯视特有的特征,而不是靠颜色或纹理走捷径。这个反向验证能帮你判断模型是不是真的可迁移。

我自己踩过最深的坑是拿一个在 vis-drone 上 mAP 0.85 的模型直接去跑另一个城市拍的无人机画面,结果掉到 0.4。后来发现两个数据集的飞行高度差了 30 米,目标像素尺寸差了一倍。所以每次换场景,先量一下目标在画面里的像素中位数,和训练集对比,差太多就重新微调,别硬跑。希望帮到你。

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

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

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

立即咨询