简介这份行人车辆检测数据集面向计算机视觉学习者与目标检测开发者用于训练和评估行人与车辆检测算法尤其适合搭配YOLOv5等实时检测模型进行实践。压缩包共16821个文件约499.51MB其中5607张jpg图像、5607个xml标注文件与5607个txt标签文件一一对应xml记录边界框坐标txt适配YOLO训练格式可直接投入模型训练与验证流程。数据集包含4500张训练图像与500张验证图像覆盖不同光照、视角与遮挡条件下的行人和车辆实例有助于模型学习鲁棒特征。已有2889人学习下载说明其在同类资源中具备一定参考价值。借助完整标注与清晰目录结构读者可快速复现训练流程、评估mAP等指标并以此为基础开展自动驾驶、交通监控等场景的算法实验与对比研究。1. 4500 张训练图 500 张验证图行人车辆检测数据集到底该怎么用手上拿到一个行人车辆检测数据集一共 4500 张验证集 500 张很多人第一反应是「先跑起来再说」。但真正跑过几个项目就知道这个量级的数据集坑不在模型结构而在数据本身——标注格式对不对、类别是否平衡、验证集有没有混进训练集、小目标占比多少。行人车辆检测是典型的双类目标检测任务行人属于高宽比极端、遮挡严重的类别车辆则尺度跨度大、密集排列常见。4500 张训练图如果分布合理用 YOLOv8n 或 YOLOv8s 从预训练权重微调mAP0.5 做到 0.75 以上是现实的但如果标注质量差、验证集和训练集同源同场景指标虚高到 0.9 也不代表能落地。这篇文章面向的是拿到类似规模数据集、准备做行人车辆检测的工程师从数据检查、格式转换、训练配置到验证集评估把每一步的参数和踩坑点讲清楚让你少走弯路。2. 先搞清楚这 4500500 张图能不能直接用2.1 行人车辆检测数据集的四个必查项拿到数据集别急着写 dataloader先做四件事。第一看标注格式是 VOC XML、COCO JSON 还是 YOLO txt不同格式的坐标定义完全不同VOC 是左上角加右下角的绝对像素YOLO 是归一化的中心点加宽高COCO 是左上角加宽高的绝对像素。第二看类别定义有些数据集把「人」和「行人」分成两类有些把「轿车」「卡车」「公交车」都归到「车辆」你得确认自己的任务需要几类。第三看图片和标注是否一一对应常见问题是图片有 4500 张但标注只有 4300 个或者标注文件名和图片文件名大小写不一致。第四看验证集 500 张的来源如果验证集是从训练集里随机切出来的那指标只能反映拟合程度不能反映泛化能力。import os from pathlib import Path img_dir Path(dataset/images/train) label_dir Path(dataset/labels/train) imgs {p.stem for p in img_dir.glob(*.jpg)} labels {p.stem for p in label_dir.glob(*.txt)} print(f图片数: {len(imgs)}, 标注数: {len(labels)}) print(f有图无标注: {imgs - labels}) print(f有标注无图: {labels - imgs})这段脚本做的是最基础的完整性校验。imgs - labels输出有图片但没有对应标注文件的样本这些图在训练时会被当成负样本或者直接报错labels - imgs输出有标注但找不到图片的孤儿标注通常是文件名后缀不一致导致的。参数上只需要改img_dir和label_dir两个路径注意 YOLO 格式的标注是.txtVOC 是.xmlCOCO 是单个.json按实际情况调整 glob 的后缀。2.2 用统计脚本看穿类别分布和小目标占比行人车辆检测最怕两件事一是某一类样本极少比如 4500 张里只有 200 张有行人二是小目标太多行人远距离成像可能只有 20×40 像素YOLOv8 默认的 P3 特征图 stride 是 8太小的目标在深层特征图上几乎消失。跑一个统计脚本把每个类别的框数量、宽高分布、面积分布都算出来。import numpy as np from pathlib import Path from collections import Counter label_dir Path(dataset/labels/train) cls_counter Counter() wh_list [] for txt in label_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() cls_id int(parts[0]) w, h float(parts[3]), float(parts[4]) cls_counter[cls_id] 1 wh_list.append((w, h)) wh_arr np.array(wh_list) areas wh_arr[:, 0] * wh_arr[:, 1] print(类别框数:, dict(cls_counter)) print(宽高比中位数:, np.median(wh_arr[:, 0] / wh_arr[:, 1])) print(面积小于 0.01 的框占比:, (areas 0.01).mean())cls_counter告诉你每个类别的实例数如果行人类别占比低于 10%训练时需要考虑类别加权或者过采样。areas 0.01是归一化面积0.01 大约对应 640 输入下 64×64 像素的区域低于这个值的框属于小目标如果占比超过 30%建议把输入尺寸从 640 提到 960 或者 1280同时开启 YOLOv8 的close_mosaic参数在最后几轮关闭马赛克增强让小目标获得更稳定的梯度。2.3 验证集 500 张的划分陷阱500 张验证集听起来不少但如果这 500 张和 4500 张训练集来自同一段视频的连续帧那验证集里的场景、光照、目标外观和训练集高度相似评估出来的 mAP 会明显偏高。判断方法很简单看图片文件名是否连续编号或者用感知哈希做相似度聚类。import imagehash from PIL import Image from pathlib import Path def compute_hashes(img_dir, limit200): hashes [] for p in list(Path(img_dir).glob(*.jpg))[:limit]: h imagehash.phash(Image.open(p)) hashes.append((p.name, h)) return hashes train_h compute_hashes(dataset/images/train) val_h compute_hashes(dataset/images/val) dups 0 for vname, vh in val_h: for tname, th in train_h: if vh - th 5: dups 1 break print(f验证集中与训练集高度相似的图片数: {dups}/{len(val_h)})imagehash.phash计算感知哈希汉明距离小于等于 5 通常意味着两张图视觉上几乎一样。如果验证集里超过 20% 的图和训练集高度相似建议重新划分按视频来源或者时间戳切分确保验证集的场景在训练时没见过。这一步不做后面调参全是玄学。3. 把标注转成 YOLOv8 能吃的格式3.1 VOC 转 YOLO坐标归一化的四个边界坑很多公开的行人车辆数据集是 VOC 格式转 YOLO 时最容易翻车的地方有四个。第一VOC 的xmin, ymin, xmax, ymax是绝对像素YOLO 需要(x_center/w, y_center/h, bw/w, bh/h)除的是图片实际宽高而不是 640。第二有些 VOC 标注里xmax等于图片宽度归一化后中心点加半宽会超过 1.0YOLOv8 虽然会截断但最好自己 clamp。第三图片尺寸要从 XML 里的size字段读不能假设所有图都是 640×480。第四类别名到类别 ID 的映射要固定训练和推理必须一致。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image classes [person, vehicle] cls2id {c: i for i, c in enumerate(classes)} def voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in cls2id: continue 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) xc (xmin xmax) / 2 / img_w yc (ymin ymax) / 2 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h xc, yc min(max(xc, 0), 1), min(max(yc, 0), 1) bw, bh min(bw, 1), min(bh, 1) lines.append(f{cls2id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_path Path(out_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines)) for xml in Path(dataset/annotations).glob(*.xml): voc_to_yolo(xml, dataset/labels/train)cls2id决定了类别顺序YOLOv8 的data.yaml里names列表必须和这个顺序完全一致否则训练出来的模型会把行人和车辆搞反。min(max(xc, 0), 1)是保险措施防止个别标注越界导致训练时 loss 异常。输出保留 6 位小数足够YOLOv8 内部会再做一次解析。3.2 COCO 转 YOLO处理 iscrowd 和空标注COCO 格式的数据集转 YOLO 时iscrowd1的标注要特别处理。这类标注表示「这是一群人/车不单独标注每个实例」YOLOv8 训练时如果把它当普通框模型会学到一个巨大的、内部包含多个目标的框推理时 NMS 很难处理。常见做法是直接跳过iscrowd1的标注或者把它们转成多个小框。另外 COCO 的bbox是[x, y, w, h]左上角加宽高转 YOLO 时中心点是x w/2。import json from pathlib import Path coco json.load(open(dataset/annotations/instances_train.json)) img_id2info {img[id]: img for img in coco[images]} cat_id2name {c[id]: c[name] for c in coco[categories]} cls2id {person: 0, vehicle: 1} out_dir Path(dataset/labels/train) out_dir.mkdir(parentsTrue, exist_okTrue) for ann in coco[annotations]: if ann.get(iscrowd, 0) 1: continue img img_id2info[ann[image_id]] w, h img[width], img[height] x, y, bw, bh ann[bbox] xc, yc (x bw / 2) / w, (y bh / 2) / h bw, bh bw / w, bh / h name cat_id2name[ann[category_id]] if name not in cls2id: continue line f{cls2id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}\n with open(out_dir / f{img[file_name].rsplit(.,1)[0]}.txt, a) as f: f.write(line)用追加模式写文件是因为一张图可能有多个标注每次追加一行。iscrowd跳过是保守做法如果数据集中 crowd 标注很多可以考虑用pycocotools的mask转成多个独立框但实现复杂度高一般行人车辆检测任务里 crowd 占比不大跳过影响有限。3.3 data.yaml 的路径和类别配置YOLOv8 训练依赖data.yaml里面三个关键字段path是数据集根目录train和val是相对路径names是类别名列表。路径写错是新手最常见的翻车点YOLOv8 不会报「文件不存在」而是直接报「No labels found」让人误以为是标注问题。path: /home/user/dataset train: images/train val: images/val names: 0: person 1: vehiclepath用绝对路径最稳train和val相对于path。YOLOv8 会自动把images替换成labels去找标注所以目录结构必须是images/train和labels/train平行。如果标注目录名不是labels需要在data.yaml里显式指定train_labels和val_labels。4. 用 YOLOv8 在 4500 张图上跑通训练4.1 从预训练权重微调的最小命令4500 张图不算大从 COCO 预训练权重微调是最稳的选择。YOLOv8n 参数量 3.2MYOLOv8s 是 11.2M行人车辆检测这种双类任务YOLOv8s 通常比 n 高 3 到 5 个点 mAP但推理速度慢一倍。如果目标是边缘部署选 n如果追求精度选 s 或 m。yolo detect train \ modelyolov8s.pt \ data/home/user/dataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ warmup_epochs3 \ close_mosaic10 \ patience20 \ device0 \ projectruns/person_vehicle \ nameexp1lr00.01是初始学习率微调任务比从头训练要小0.01 是 YOLOv8 官方推荐的微调值。lrf0.01是最终学习率因子100 轮后学习率降到 0.0001。warmup_epochs3让学习率在前 3 轮从 0 线性升到 0.01避免一开始就大梯度破坏预训练权重。close_mosaic10在最后 10 轮关闭马赛克增强让模型在真实分布上收敛。patience20是早停20 轮验证指标不升就停省时间。4.2 行人车辆检测的四个关键超参除了上面的基础参数行人车辆检测有几个针对性调整。第一imgsz如果小目标多提到 960 或 1280但 batch 要相应降到 8 或 4否则显存爆。第二cls损失权重如果行人类别样本少可以在data.yaml里加cls_pw或者用focal_lossYOLOv8 默认的BCEWithLogitsLoss对类别不平衡有一定鲁棒性但极端情况需要手动加权。第三box损失用默认的 CIoU 就行行人高宽比极端CIoU 对宽高比敏感比 GIoU 收敛快。第四augment参数行人车辆场景里hsv_h0.015, hsv_s0.7, hsv_v0.4是默认值如果数据光照变化大可以适当提高hsv_v。from ultralytics import YOLO model YOLO(yolov8s.pt) results model.train( data/home/user/dataset/data.yaml, epochs100, imgsz960, batch8, lr00.01, close_mosaic10, mixup0.1, copy_paste0.1, degrees5.0, translate0.1, scale0.5, )mixup0.1和copy_paste0.1是额外增强mixup 把两张图按比例混合copy_paste 把目标复制粘贴到其他图上对行人车辆这种实例密集的场景有帮助但比例不能太高0.1 到 0.2 之间比较安全。degrees5.0限制旋转角度行人车辆通常是水平拍摄大角度旋转会引入不真实的样本。scale0.5允许 0.5 到 1.5 倍缩放增加尺度多样性。4.3 训练日志里该盯哪几个数YOLOv8 训练时输出一堆指标重点看四个box_loss、cls_loss、dfl_loss、mAP50。box_loss下降说明定位在改善cls_loss下降说明分类在改善dfl_loss是分布焦点损失YOLOv8 特有下降说明边界框回归的分布估计在收敛。mAP50是 IoU 0.5 时的平均精度行人车辆检测里这个数到 0.8 以上算不错。如果box_loss降但mAP50不升通常是过拟合看验证集的val/box_loss是不是在升。如果cls_loss震荡检查学习率是不是太大或者类别标注有没有错。tensorboard --logdir runs/person_vehicle/exp1TensorBoard 能看到 loss 曲线和 mAP 曲线比看终端输出直观。重点看val/box_loss和train/box_loss的差距差距超过 0.5 说明过拟合明显需要加数据增强或者减模型容量。5. 验证集 500 张怎么用才不白费5.1 验证集评估的三个层次500 张验证集不能只跑一个model.val()看 mAP 就完事。第一层是整体指标mAP50、mAP50-95、precision、recall。第二层是分类别指标行人的 AP 和车辆的 AP 分开看如果行人 AP 明显低说明行人样本少或者小目标多。第三层是分尺度指标YOLOv8 的val()会输出metrics/mAP50但不直接分尺度需要用pycocotools按面积区间统计。from ultralytics import YOLO import json model YOLO(runs/person_vehicle/exp1/weights/best.pt) metrics model.val(data/home/user/dataset/data.yaml, splitval) print(fmAP50: {metrics.box.map50:.4f}) print(fmAP50-95: {metrics.box.map:.4f}) print(f每类 AP50: {metrics.box.ap50}) print(f每类 precision: {metrics.box.p}) print(f每类 recall: {metrics.box.r})metrics.box.ap50是一个数组按类别顺序输出对应data.yaml里的names。如果行人的 AP50 比车辆低 0.15 以上优先补行人样本或者对行人做专门增强。metrics.box.p和metrics.box.r看精确率和召回率的平衡如果精确率高召回低说明模型保守可以降置信度阈值反过来则提高阈值。5.2 用混淆矩阵定位类别混淆YOLOv8 的val()会生成混淆矩阵存在runs/person_vehicle/exp1/confusion_matrix.png。行人车辆检测里最常见的混淆是「远处行人被当成车辆」或者「车辆里的乘客被当成行人」。混淆矩阵能直接看到哪两类在互相误判。如果行人被大量误判为背景说明行人的特征学习不足需要增加行人样本或者提高行人的损失权重。import matplotlib.pyplot as plt from ultralytics.utils.plotting import plot_confusion_matrix # 假设已经通过 model.val() 得到了 confusion_matrix cm metrics.confusion_matrix.matrix plot_confusion_matrix(cm, names[person, vehicle, background], save_dirruns/cm)confusion_matrix.matrix是归一化后的矩阵行是真实类别列是预测类别。最后一行是背景如果背景列的值高说明漏检多。这个图比数字直观调参时对着看。5.3 验证集上的失败案例怎么归因从验证集里挑出 mAP 最低的 20 张图逐张看。常见失败模式有四种一是小目标漏检行人在远处只有十几个像素二是遮挡车辆挡住行人或者行人互相遮挡三是运动模糊快速移动的车辆拖影四是极端光照夜间或者逆光。每种模式的解法不同小目标提输入尺寸遮挡加copy_paste增强运动模糊加运动模糊增强光照问题加 HSV 增强。归因做对了调参才有方向否则就是瞎试。import cv2 from pathlib import Path from ultralytics import YOLO model YOLO(runs/person_vehicle/exp1/weights/best.pt) val_imgs list(Path(dataset/images/val).glob(*.jpg)) # 找出检测框数量最少的图通常是漏检严重的 results model(val_imgs, conf0.25, iou0.5, verboseFalse) counts [(r.path, len(r.boxes)) for r in results] counts.sort(keylambda x: x[1]) for path, n in counts[:10]: print(f{path}: {n} 个检测框)conf0.25是推理置信度阈值iou0.5是 NMS 的 IoU 阈值。检测框数量少的图不一定是失败案例但如果人工看确实有目标却没检测到那就是漏检。把这些图单独拿出来用model.predict可视化看模型到底输出了什么。6. 避坑与排查行人车辆检测数据集训练的五个血泪教训6.1 验证集 mAP 0.95 但实际部署全漏检现象训练完在 500 张验证集上 mAP50 跑到 0.95信心满满部署到实际场景结果行人车辆几乎全漏。原因验证集和训练集同源场景、光照、拍摄角度高度相似模型只是记住了这个分布没有泛化能力。解决重新按场景或时间划分验证集确保验证集包含训练时没见过的场景如果数据本身来源单一考虑引入外部数据集做交叉验证比如用 BDD100 的验证集测一下泛化。6.2 训练 loss 正常但 mAP 一直是 0现象训练日志里 box_loss 和 cls_loss 都在降但 mAP50 始终是 0。原因data.yaml里的names顺序和标注文件里的类别 ID 不一致或者标注文件里的类别 ID 从 1 开始而不是从 0 开始。YOLO 格式要求类别 ID 从 0 开始连续编号如果标注里是 1 和 2模型会把 1 当成第 1 类索引 1但names里只有 0 和 1导致类别错位。解决检查标注文件第一列确保是 0 和 1检查data.yaml的names键值对确保 0 对应 person1 对应 vehicle。6.3 小目标行人检测不出来现象近处行人检测正常远处小目标行人全部漏检。原因YOLOv8 默认输入 640P3 特征图 stride 8对应 80×80 的特征图一个 16×32 像素的行人在 P3 上只有 2×4 个格子特征太弱。解决把imgsz提到 960 或 1280P3 特征图变成 120×120 或 160×160小目标有更多像素同时开启close_mosaic马赛克增强会把四张图拼成一张小目标变得更小最后几轮关掉让模型专注真实尺度。6.4 训练到一半显存爆了现象前几十轮正常突然报 CUDA out of memory。原因YOLOv8 的close_mosaic在最后几轮关闭马赛克增强关闭后每张图的实际像素量增加显存占用上升或者batch设得太大前期靠马赛克拼图降低了单图分辨率后期恢复后爆显存。解决把batch降到 8 或 4或者把imgsz从 960 降到 640如果必须用大尺寸开启amp混合精度训练YOLOv8 默认开启但某些显卡需要手动确认。6.5 验证集评估结果每次跑都不一样现象同一个best.pt每次model.val()出来的 mAP 差 1 到 2 个点。原因验证时的数据增强没关YOLOv8 的val()默认不做增强但如果data.yaml里配置了val的增强参数或者用了augmentTrue每次评估的输入不同。解决model.val()时显式设置augmentFalse确保评估用的是原始图片另外检查val的batch是否一致batch 大小会影响 BN 层的统计量虽然推理时用 running mean但某些实现会有细微差异。7. 用 500 张验证集做阈值搜索和模型集成训练完拿到best.pt只是开始真正落地前还有两件事值得做。第一件是置信度和 NMS 阈值的联合搜索。YOLOv8 默认conf0.25, iou0.7但行人车辆检测里行人的最优置信度通常比车辆低因为行人特征变化大。可以在验证集上跑一个网格搜索找出使 F1 最大的阈值组合。import numpy as np from ultralytics import YOLO model YOLO(runs/person_vehicle/exp1/weights/best.pt) best_f1, best_conf, best_iou 0, 0.25, 0.7 for conf in np.arange(0.1, 0.6, 0.05): for iou in np.arange(0.4, 0.8, 0.05): m model.val(data/home/user/dataset/data.yaml, confconf, iouiou, verboseFalse) f1 2 * m.box.p * m.box.r / (m.box.p m.box.r 1e-6) if f1.mean() best_f1: best_f1 f1.mean() best_conf, best_iou conf, iou print(f最优 conf{best_conf:.2f}, iou{best_iou:.2f}, F1{best_f1:.4f})这段搜索在 500 张验证集上跑每个组合大约几秒到十几秒总共几十个组合几分钟能跑完。f1.mean()是所有类别 F1 的平均如果更关注行人可以只取行人类别的 F1。找到最优阈值后部署时用这组参数比默认值通常能提升 2 到 5 个点的 F1。第二件事是模型集成。4500 张图训一个模型可能不够稳可以训三个不同 seed 或者不同增强强度的模型推理时用 WBF加权框融合合并结果。WBF 比 NMS 更能保留密集场景下的框行人车辆检测里车辆密集排列时 NMS 容易误删。from ensemble_boxes import weighted_boxes_fusion import numpy as np # 假设三个模型的输出已经转成 [x1, y1, x2, y2, score, label] 格式 boxes_list [model1_boxes, model2_boxes, model3_boxes] scores_list [model1_scores, model2_scores, model3_scores] labels_list [model1_labels, model2_labels, model3_labels] fused_boxes, fused_scores, fused_labels weighted_boxes_fusion( boxes_list, scores_list, labels_list, iou_thr0.55, skip_box_thr0.1, weights[1, 1, 1] )iou_thr0.55是融合时的 IoU 阈值比 NMS 的 0.7 低因为 WBF 会加权平均框坐标阈值低一点能合并更多框。skip_box_thr0.1过滤掉低置信度的框避免噪声参与融合。三个模型权重相同如果某个模型在验证集上明显好可以调高它的权重。我自己做行人车辆检测的习惯是训练前一定跑一遍数据统计脚本确认类别分布和小目标占比训练时开 TensorBoard 盯val/box_loss和train/box_loss的差距训练后在验证集上做阈值搜索不直接用默认值。这套流程走下来4500 张图训出的模型在真实场景里的漏检率能控制在可接受范围内。希望帮到你。本文还有配套的精品资源点击获取