ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无人机俯视视角车辆行人检测:YOLOv5数据集训练与调优实战

无人机俯视视角车辆行人检测:YOLOv5数据集训练与调优实战 简介这份资源是面向无人机俯视视角目标检测任务的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 的 nc2。标注格式可能是 VOC XML、COCO JSON 或已经转好的 YOLO txt解压后第一件事就是确认这一点。2.2 解压后先跑一遍数据体检脚本别急着写训练命令。我一般会先写个脚本统计类别分布、框的宽高分布、每张图的实例数看看有没有空标注、有没有宽高为 0 的脏框。下面这段代码假设标注是 YOLO txt 格式每行class x_center y_center width height归一化到 0-1import 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_okTrue) 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.10CUDA 版本跟显卡驱动匹配就行。克隆仓库后先装依赖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、mAP0.5、mAP0.5:0.95。俯视小目标检测最该盯的是mAP0.5:0.95和mAP0.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, devicecuda) 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:ytile_size, x:xtile_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_thres0.25, iou_thres0.45)逻辑说明按固定步长滑动窗口切图每张子图推理后把框坐标加回原图偏移最后统一 NMS。参数说明tile_size和训练分辨率一致overlap至少覆盖最大目标尺寸否则跨切片的物体会被截断。这个方案计算量是整图推理的几倍但小目标召回率提升明显。时序平滑是针对视频流的。单帧检测会有闪烁同一辆车在连续帧里时有时无。简单做法是用卡尔曼滤波或者 IoU 跟踪做帧间关联只保留连续 3 帧以上出现的检测框。这个技巧在无人机巡检场景里特别实用因为误检通常只出现在单帧真实目标会持续存在。最后一个验证习惯拿同一个场景的地面摄像头模型和俯视模型做交叉测试。如果俯视模型在地面画面上表现很差说明它确实学到了俯视特有的特征而不是靠颜色或纹理走捷径。这个反向验证能帮你判断模型是不是真的可迁移。我自己踩过最深的坑是拿一个在 vis-drone 上 mAP 0.85 的模型直接去跑另一个城市拍的无人机画面结果掉到 0.4。后来发现两个数据集的飞行高度差了 30 米目标像素尺寸差了一倍。所以每次换场景先量一下目标在画面里的像素中位数和训练集对比差太多就重新微调别硬跑。希望帮到你。本文还有配套的精品资源点击获取
返回列表