ARTICLE DETAIL

资讯详情

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

车辆行人检测数据集:三种标签格式转换与YOLOv8训练实战

车辆行人检测数据集:三种标签格式转换与YOLOv8训练实战 简介这套车辆行人检测数据集面向自动驾驶视觉感知、车辆不礼让行人检测、车辆与行人闯红灯检测等实际项目也适用于课程作业、算法比赛和毕业设计。数据集共5930张真实场景图片标注类别覆盖行人、轿车、大巴车、卡车四类并提供VOC(xml)、YOLO(txt)与JSON三种标注格式可直接接入主流检测框架训练图像目标分布均匀、场景多样性充足且经过作者多次训练迭代筛选校准质量可靠。压缩包整体约922.74MB内含近1.8万个文件以jpg原图、xml标注和txt标注为主体方便按需选用。目前已有501人学习下载可作为模型训练、验证与调优的基准数据帮助快速验证算法效果、缩短数据预处理周期。1. 车辆行人检测数据集5930 张图三种标签格式拿过来就能训练做车辆行人检测项目的人最容易卡死的环节往往不是调模型而是找数据。公开数据集要么只有 COCO JSON 没有 YOLO 标签要么类别杂到没法直接拿去跑你的检测头。这份车辆行人检测数据集一共 5930 张图片类别只有四类行人、轿车、大巴车、卡车标签文件一次性给了 VOC XML、YOLO TXT、JSON 三种格式拿到手可以直接喂给 YOLO 系列也可以转成其他框架需要的格式。数据集作者按多次训练迭代的结果做过筛选校准目标分布均匀标注质量在同类小数据里算能直接上手的不需要再花两天做清洗。适合正在做智能交通课程作业、毕业设计或者拿真实道路场景验证 YOLOv5/v8 检测效果的开发者。下面我从文件结构、标签格式、格式转换、训练参数到踩坑记录按我实际拆包跑通的顺序写一遍。2. 先拆数据集文件命名、三类标签格式与四类目标分布2.1 文件名里的来源线索COCO 命名风格与底图质量打开解压后的文件夹先看到的是 000000210273.txt、000000576187.txt 这种纯数字命名的标签文件完整文件列表有 5930 条。这种 9 位纯数字编号是 COCO 数据集的标准命名习惯COCO 原始图片就是按 000000000xxx.jpg 这种规律编号的。所以可以合理推断这份数据集的底图大概率来自 COCO 训练集里与 person、car、bus、truck 相关的子集然后经过一轮重新筛选和校准标注。这个推断不是随便猜的它直接影响训练结果。COCO 底图的光照、遮挡、尺度多样性在公开数据里算相当好的模型在这种分布上见过足够多「远处的小人」「被车挡住一半的行人」「傍晚路面上的大巴车」迁移到你自己的道路监控数据上时才不会一上来就过拟合。反过来要是自己从某一路口的监控视频里截图做标注一天标两三百张图不难但场景单一、角度固定模型泛化到别的路口基本要打对折。另外一个容易被忽略的信息这个数据集的四类是行人、轿车、大巴车、卡车。也就是说类别名和英文标注可能是 person、car、bus、truck没有把摩托车、自行车、交通标志这些「路人干扰项」混进去。做车辆不礼让行人检测或者闯红灯抓拍时类别越少越不容易互相串尤其是 YOLO 训练时类别之间如果出现强视觉相似性比如 bus 和 truck模型容易在边界框附近摇摆四类刚好是个比较稳的设置。2.2 三种标注格式逐行对照VOC XML、YOLO TXT、COCO JSON三种格式的本质差别是坐标体系和类别表示方式。我拆包后习惯先列一张对照表确认手里拿到的东西长什么样维度VOC XMLYOLO TXTCOCO JSON文件形态每张图一个 .xml每张图一个 .txt全数据集一个 .json坐标体系左上角和右下角绝对像素值 xmin/ymin/xmax/ymax中心点 宽高归一化值0~1左上角绝对坐标 宽高 x/y/w/h类别表示字符串名称如 person/car/bus/truck从 0 开始的整数类别 ID通过 categories 映射的整数 ID适合的训练器Faster R-CNN、SSD、Detectron2YOLO 全系、UltralyticsMMDetection、Detectron2、COCO API接下来用一小段代码把三种格式各读一遍方便对照。实际项目里这一步能快速发现标签有没有坏行import xml.etree.ElementTree as ET import json # 1) 读 VOC XML tree ET.parse(Annotations/000000210273.xml) root tree.getroot() for obj in root.iter(object): name obj.find(name).text bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) print(name, (xmin, ymin, xmax, ymax)) # 2) 读 YOLO TXT with open(labels/000000210273.txt, r) as f: for line in f.readlines(): cls, x_center, y_center, w, h line.split() print(class, cls, bbox, x_center, y_center, w, h) # 3) 读 JSONCOCO 结构 with open(annotations.json, r) as f: coco json.load(f) for ann in coco[annotations][:5]: print(ann[category_id], ann[bbox])这段代码的逻辑很简单但有两点值得说明。第一XML 里拿到的 xmin/xmax 是像素绝对值OpenCV 画框时可以直接用YOLO TXT 里的坐标是归一化到 0~1 的相对值画框必须乘回图片宽高新手在这上面画错位框的几率最高。第二COCO JSON 的 bbox 是 [x, y, w, h] 结构x/y 是左上角坐标w/h 是宽高不是右下角坐标。很多人在这一步把 w/h 当成 xmax/ymax 去算中心点出来的标签全部偏移模型训练时还不会报错属于最难排查的隐性错误。2.3 分布与适用场景从礼让行人到闯红灯检测数据集说明里提到「目标分布均匀标注精准算法拟合较好」这句话落到训练层面是有实际意义的。四类目标如果数量相差悬殊比如 car 有 2 万个框而 person 只有 3000 个模型对行人这一类别的召回率会明显偏低变现为 val 集里 person 类的 mAP 比 car 类低 10 个百分点以上。分布均匀意味着这个数据集不太需要额外做类别重采样直接用默认的 class weights 就能跑出比较均衡的结果。适用场景上我实际接触过的项目大致有三类。第一类是车辆不礼让行人检测相机架在斑马线侧面需要同时检测行人和车辆这个数据集的类别正好覆盖第二类是自动驾驶视觉感知前融合车和行人同时出现四类目标对城市道路够用第三类是路口闯红灯检测典型机位是电警杆俯拍远距离目标较多训练时可以考虑把 imgsz 从默认 640 提到 960。选型建议是如果你做的是高速公路场景需要额外补 truck 和 bus 之外的车型类别如果你做的是园区或者小区内部道路行人密度远高于车辆建议再补充一部分行人密集图否则 val 里 person mAP 会偏低。3. 格式统一把 XML / JSON 标签转成 YOLO 并划分训练集3.1 标注校验先花三分钟体检数据集拿到数据集先别急着训练。我第一次拆这种混合标签格式的数据时直接跳过了校验步骤结果训练到第 40 轮才发现有 30 多张图的标签坐标全是负数白跑了两天。现在固定流程是先抽 10 到 20 张图把标签画出来看一眼。import cv2 img cv2.imread(images/000000210273.jpg) h, w img.shape[:2] with open(labels/000000210273.txt, r) as f: for line in f.readlines(): cls, x_c, y_c, bw, bh map(float, line.split()) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check.jpg, img)画框逻辑是把归一化中心点坐标乘回实际宽高再算出左上角和右下角。这一步检查三个点框有没有明显偏移目标、有没有两个框重叠在同一个目标上、类别编号是不是和预期对应。如果发现 person 的框标在 car 上多半是 XML 转 YOLO 时类别列表顺序和原数据集不一致而不是标注本身的问题。3.2 XML 转 YOLO脚本实现与归一化坐标计算VOC XML 转 YOLO TXT 是这个数据集里最常见的操作因为很多人手里跑的是 YOLOv5 或 YOLOv8但拿到的标签是 VOC 格式。转换公式是固定的x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height对应脚本如下import xml.etree.ElementTree as ET from pathlib import Path # 类别名称顺序要和后续 data.yaml 里的 names 完全一致 CLASSES [person, car, bus, truck] def voc_to_yolo(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() # 用 XML 里 size 节点的宽高而不是自己 cv2.imread size root.find(size) W int(size.find(width).text) H int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center ((xmin xmax) / 2) / W y_center ((ymin ymax) / 2) / H w (xmax - xmin) / W h (ymax - ymin) / H lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) xml_dir Path(Annotations) txt_dir Path(labels) txt_dir.mkdir(exist_okTrue) for xml_file in xml_dir.glob(*.xml): voc_to_yolo(str(xml_file), str(txt_dir / (xml_file.stem .txt)))注意脚本里有个容易忽略的细节XML 的宽高取自 size 节点而不是用 OpenCV 重新读图计算。标注工具在写 XML 时记录的就是它当时看到的尺寸如果训练前图片被压缩过再用 cv2.imread 算出来的宽高去归一化坐标就会整体偏移。CLassES 列表的顺序也不能乱person 必须是 0car 必须是 1否则训练出来的模型会把行人认成车这类错误在 loss 曲线上完全看不出来。3.3 JSON 转 YOLO处理 categories 与 bbox 的坑JSON 转 YOLO 比 XML 转 YOLO 更容易翻车。COCO JSON 的 category_id 不一定连续可能是 1、3、5、7 这种跳跃编号而 YOLO 要求类别编号必须从 0 开始连续排列。直接把 category_id 写进 txt训练时会报 class index out of range或者静默地把 4 类当成 8 类训练。import json from collections import defaultdict from pathlib import Path coco_path annotations.json out_dir Path(labels_json) out_dir.mkdir(exist_okTrue) with open(coco_path, r) as f: coco json.load(f) # 建立 image_id - 文件名的映射 img_map {img[id]: img[file_name] for img in coco[images]} # 关键把 category_id 重映射为 0 开始的连续索引 cat_list sorted(coco[categories], keylambda c: c[id]) cat_map {cat[id]: i for i, cat in enumerate(cat_list)} # 按 image_id 分组 annos defaultdict(list) for ann in coco[annotations]: annos[ann[image_id]].append(ann) for image_id, ann_list in annos.items(): # images 是列表不是字典不能直接按下标拿 img_info next(item for item in coco[images] if item[id] image_id) W img_info[width] H img_info[height] lines [] for ann in ann_list: cls_id cat_map[ann[category_id]] x, y, w, h ann[bbox] x_c (x w / 2) / W y_c (y h / 2) / H w_norm w / W h_norm h / H lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w_norm:.6f} {h_norm:.6f}) out_path out_dir / (img_info[file_name].split(.)[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))这段脚本里最需要记住的是 cat_map 的重映射逻辑。COCO 的 category_id 是标注人员的自由编号跟 YOLO 需要的 0~N-1 没有必然关系不重映射必炸。另外 images 字段是列表查找 image_id 要用遍历或 next 匹配不能用字典下标硬取我见过不少人在这个位置 IndexError。提示转换完成后抽 3 张图把 txt 里第一列的最大值打印出来。如果是四类数据集最大值必须是 3只要出现 4 以上说明 category 重映射没做干净。3.4 划分数据集并生成 data.yaml标签统一成 YOLO 格式之后下一步是划分训练集和验证集。YOLOv8 推荐的目录结构是 images/train、images/val、labels/train、labels/val对应关系靠文件名匹配不需要额外生成 train.txt 索引文件。import random from pathlib import Path import shutil random.seed(42) images list(Path(images).glob(*.jpg)) random.shuffle(images) val_ratio 0.1 val_count int(len(images) * val_ratio) for split, imgs in [(train, images[val_count:]), (val, images[:val_count])]: img_dir Path(fimages/{split}) label_dir Path(flabels/{split}) img_dir.mkdir(parentsTrue, exist_okTrue) label_dir.mkdir(parentsTrue, exist_okTrue) for img in imgs: shutil.copy(str(img), str(img_dir / img.name)) label_src Path(labels) / (img.stem .txt) if label_src.exists(): shutil.copy(str(label_src), str(label_dir / label_src.name)) print(ftrain: {len(images) - val_count}, val: {val_count})划分比例我用的是 9:15930 张图分出来验证集约 593 张。如果你的场景对验证集稳定性要求高可以改成 8:1:1 再拆一个 test 集但对于四类目标、单模型训练来说9:1 足够用了。random.seed(42) 保证每次跑出来的划分一致后续换参数对比实验时训练集不会变实验才可复现。最后生成 data.yaml这是 YOLOv8 训练时唯一读取的数据配置path: D:/datasets/vehicle_person # 改成你本地数据集的绝对路径 train: images/train val: images/val names: 0: person 1: car 2: bus 3: trucknames 里的顺序和 txt 标签里第一列的数字必须一一对应。这里错一个训练不会报任何错误但 val 时的混淆矩阵会显示 person 和 car 互相串。我现在的习惯是训练前先打印一遍 data.yaml 的内容再随机抽 5 张图人工核对类别编号确认无误才启动训练。4. YOLOv8 训练实战与排查从 data.yaml 到 best.pt4.1 训练参数epochs、batch、imgsz、patience 怎么设数据准备完接下来是训练。我用 Ultralytics YOLOv8 做示例因为目前 YOLOv5 已经停止维护v8 生态对 ONNX 导出和部署支持得更好。命令如下yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ batch16 \ imgsz640 \ patience20 \ projectrun_train \ nameperson_vehiclemodel 参数这里有个实际经验你放 yolov8n.pt 时Ultralytics 会尝试自动拉取预训练权重如果内网限制拉不动可以手动下载对应的 .pt 文件放到项目目录再把 model 参数改成 modelyolov8n.pt 的本地路径效果一样。第一次跑通流程建议用 yolov8n参数量最小、训练快等确定数据没问题再换 yolov8s 或 yolov8m 提精度。epochs 设 100 对这个数据量级是够的。5930 张图四类目标一般在 60 到 80 轮之间 val loss 就趋于平稳。如果到第 100 轮还在缓慢下降可以加到 150。batch 根据显存来定6G 显存用 812G 用 1624G 可以上 32。当显存不够时优先降 batch 而不是降 imgsz因为 imgsz 降到 416 之后小目标检测能力会明显下降。imgsz640 是默认值但如果你的应用场景是路口俯拍、目标普遍偏小建议直接上 960代价是显存占用增加约一倍。patience20 表示验证集 mAP 连续 20 轮不涨就早停防止过拟合也节省时间。4.2 训练日志与结果解读results.csv 和混淆矩阵训练结束后所有结果都在 run_train/person_vehicle/ 目录下重点看三个东西weights/best.pt、weights/last.pt、results.csv。很多人只看训练完成时的 mAP 数字我一般会用 Pandas 直接把 results.csv 尾部拉出来看趋势import pandas as pd df pd.read_csv(run_train/person_vehicle/results.csv) print(df.tail().to_string())主要关注两列val/box_loss 和 val/cls_loss。如果这两列在最后 20 轮里还在明显下降说明训练没收敛可以加大 epochs 继续训如果 loss 曲线反复震荡、不降反升可能是学习率偏大或者 batch 太小。另外 confusion_matrix.png 值得花 30 秒看一眼它把四类目标的互相误判画得很直观。我遇到过 person 和 car 混淆率偏高的情况当时第一反应是数据质量不行最后排查下来发现是 data.yaml 的 names 顺序和 txt 标签不一致属于低级错误但影响极其隐蔽。4.3 三个高频踩坑记录从 loss 为 nan 到类别索引越界训练阶段最容易踩的坑我按亲身经历排个序每条按现象、原因、解决写清楚。现象一训练两三个 epoch 后 loss 变 nan训练中断。原因标签里有越界坐标比如归一化后的 x_center w/2 大于 1 或者出现负值常见的来源是 XML 转 YOLO 时 xmax 和 xmin 填反、或者某些图片边框超出了原图宽高。解决训练前跑一遍检查脚本扫描所有 txt 的字段数和坐标范围from pathlib import Path for txt in Path(labels).rglob(*.txt): for i, line in enumerate(txt.read_text().strip().splitlines()): parts line.split() if len(parts) ! 5: print(f[字段数错误] {txt} 第 {i 1} 行: {line}) continue vals list(map(float, parts)) if any(v 0 or v 1 for v in vals[1:]): print(f[越界] {txt} 第 {i 1} 行: {line})现象二训练正常但 val 时 person 类的 mAP 明显低于其他三类。原因四类目标内部数量比例失衡。虽然数据集说明写了「分布均匀」但实际拆包后还是建议自己数一遍各类别框数量确认哪个类偏少。解决如果比例差超过 3 倍给少类别的图片做重复采样或者在训练命令里开启 cls 损失权重调整。统计代码很简单from collections import Counter from pathlib import Path counter Counter() for txt in Path(labels).rglob(*.txt): for line in txt.read_text().strip().splitlines(): cls_id line.split()[0] counter[cls_id] 1 print(counter)现象三用训练好的模型做 predict 时报 class index out of range。原因txt 标签里的最大类别编号大于模型类别数常见于 JSON 转 YOLO 时没做 category 重映射COCO 的 category_id 直接写进了 txt而 YOLO 只认 0~3。解决回到 3.3 节那段脚本用 cat_map 重映射之后重新生成全部 txt再训练。5. 从模型到应用用 best.pt 做推理验证与后续方向训练完不是终点先拿几张没参与训练的图片验证一下模型的实际表现。注意不要用验证集的图从网上随便找两张城市道路照片或者自己用手机拍的路口照片这样测出来的结果才真实。from ultralytics import YOLO model YOLO(run_train/person_vehicle/weights/best.pt) results model.predict(test_images/street.jpg, saveTrue, conf0.35) print(model.names) # 期望输出 {0: person, 1: car, 2: bus, 3: truck}conf0.35 是把置信度阈值调低一点便于把所有漏检目标都暴露出来。如果 model.names 打印出来不是 person、car、bus、truck 的顺序说明 data.yaml 里的 names 和训练时的标签编号不一致这个模型实际上是个错位模型前面的训练白搭了。验证通过之后再谈下一步。后续方向我建议按项目需求从两个点切入。第一是数据增强参数微调YOLOv8 默认的 hsv_h 0.015、hsv_s 0.7、hsv_v 0.4 对自然场景预留了较大扰动空间如果你的目标是夜间路口抓拍建议把 hsv_v 调低到 0.2 左右减少亮度抖动对夜间小目标的干扰。多尺度训练方面imgsz960 对俯拍小目标有明显收益代价是训练时间增加约 40%在显存允许的前提下值得一试。第二是模型部署YOLOv8 导出 ONNX 很简单一行命令model.export(formatonnx, imgsz640, dynamicFalse, simplifyTrue)导出后可以用 ONNX Runtime 跑 CPU 推理或者转成 TensorRT 在 Jetson 上部署整个过程控制在半小时以内。最后说一个我自己的教训。有一版数据集我在 JSON 转 YOLO 时把 bbox 的 [x, y, w, h] 误当成了 [x1, y1, x2, y2] 去算中心点训练时 mAP 曲线一路走高看起来很漂亮结果拿真实照片一测所有预测框都整体偏向右上方。那一版我训了两天最后排查下来只是转换脚本里一个符号错误。从那以后我每次拿到新数据集第一件事就是抽 20 张图把框画出来看一遍再进训练管线这个动作帮我躲开了至少两次白训两天的尴尬。希望帮到你。本文还有配套的精品资源点击获取
返回列表