交通车辆目标检测数据集:从压缩包到YOLO训练管线全流程
发布时间:2026/9/27 6:39:26来源:尧图网络
简介这份交通车辆目标检测数据集面向自动驾驶感知、智能交通与计算机视觉方向的学习者和开发者聚焦真实道路场景下的多类车辆识别问题。数据采集自实际交通环境覆盖多种光照条件与车辆姿态包含Bicycle、Bus、Car、Motorcycle、Truck五类道路主要参与者标注采用原生YOLO格式提供标准化边界框坐标与类别索引可直接对接YOLOv5/v7/v8等主流框架开箱即用。压缩包共2000个文件以1251个txt标注文件、747张jpg图像为主另含1个yaml数据配置与1份docx说明文档整体约68.65MB目录结构清晰便于按训练集快速检索与加载。数据中保留了车辆遮挡、多尺度目标等实际挑战除基础检测训练外还可支撑车辆计数、轨迹预测等衍生任务。目前已有255人学习下载适合需要真实场景数据验证模型泛化能力的中高级读者参考使用。1. 交通车辆目标检测数据集从压缩包到能跑通的训练管线拿到一个叫「交通车辆目标检测数据集.zip」的压缩包很多人第一反应是解压、翻目录、找有没有现成的data.yaml然后直接yolo train。我见过太多人卡在这一步解压出来一堆images、labels、annotations.json类别名对不上坐标格式是xywh还是xyxy分不清训练一启动 loss 就炸。这个标题背后真正要解决的问题不是「有没有数据」而是「怎么把一份来源不明的交通车辆标注变成 YOLO 系列能直接吃的训练集并且知道它值不值得投入算力」。适合正在做车辆检测、违章抓拍、车流统计、自动驾驶感知预研的从业者也适合手里已经有一个 zip 但还没跑通 baseline 的人。下面按「先看清数据长什么样、再转格式、再训练、再排错」的顺序讲每一步都给能抄的命令和参数。2. 先别急着训练把 zip 拆开看清标注格式和类别分布2.1 解压后先做一次目录体检拿到压缩包不要直接丢给训练脚本。我一般先建一个工作目录解压后跑一遍tree或find把文件类型和数量摸清楚。交通车辆数据集常见的目录结构有三种VOC 风格Annotations/*.xmlJPEGImages/*.jpg、COCO 风格annotations/instances_train.jsonimages/、以及已经切好的 YOLO 风格images/trainlabels/train。这三种的处理路径完全不同先确认再动手。# 建工作区解压统计文件类型 mkdir -p ~/work/vehicle_det cd ~/work/vehicle_det unzip -q ~/Downloads/交通车辆目标检测数据集.zip -d raw find raw -maxdepth 3 -type d | head -30 find raw -type f | sed s/.*\.// | sort | uniq -c | sort -rn这段命令的逻辑是先看目录层级再按扩展名统计文件数量。如果.xml数量约等于.jpg数量基本是 VOC如果只有一个大的.json那是 COCO如果同时出现images和labels且成对出现说明已经是 YOLO 格式可以跳过转换。参数上-maxdepth 3是为了避免深层目录刷屏sed s/.*\.//取扩展名uniq -c做计数。这一步花两分钟能省掉后面半小时的格式报错。2.2 用脚本统计类别名和框的分布交通车辆数据集最坑的地方是类别命名不统一有的叫car有的叫vehicle有的把bus、truck、van分开还有的把person也标进去。类别名直接决定你后面data.yaml怎么写写错了训练时背景全被当成目标。下面这段 Python 同时兼容 VOC 和 COCO把类别和每类框数打出来。import os, json, glob import xml.etree.ElementTree as ET from collections import Counter def scan_voc(root): cnt Counter() for xml in glob.glob(os.path.join(root, **, *.xml), recursiveTrue): tree ET.parse(xml) for obj in tree.findall(object): cnt[obj.find(name).text.strip()] 1 return cnt def scan_coco(json_path): cnt Counter() data json.load(open(json_path, r, encodingutf-8)) id2name {c[id]: c[name] for c in data[categories]} for ann in data[annotations]: cnt[id2name[ann[category_id]]] 1 return cnt # 按实际路径改这里 print(VOC:, scan_voc(raw)) # print(COCO:, scan_coco(raw/annotations/instances_train.json))逻辑说明VOC 走 XML 解析遍历每个object节点取nameCOCO 先建category_id到名字的映射再统计annotations里的category_id。参数上注意encodingutf-8中文类别名不加会乱码。跑完你会得到类似car: 12043, bus: 890, truck: 2100, person: 340的结果。如果person占比很高而你的任务只关心车后面转换时要过滤掉否则模型会花大量容量去学人。2.3 判断这份数据值不值得投入不是所有交通车辆数据集都值得训。我的判断标准有三条第一单类框数是否过千低于 500 的类别基本学不动除非做少样本微调第二框的尺寸分布是否合理如果大量框宽高小于 10 像素小目标检测会非常吃力需要改imgsz或加 P2 层第三是否有明显重复帧行车记录仪抽帧的数据集经常连续几十帧几乎一样直接训会导致验证集泄漏。用下面几行快速看框的宽高分布import glob, xml.etree.ElementTree as ET ws, hs [], [] for xml in glob.glob(raw/**/*.xml, recursiveTrue): for obj in ET.parse(xml).findall(object): b obj.find(bndbox) ws.append(float(b.find(xmax).text) - float(b.find(xmin).text)) hs.append(float(b.find(ymax).text) - float(b.find(ymin).text)) import numpy as np ws, hs np.array(ws), np.array(hs) print(宽 中位数/最小:, np.median(ws), ws.min()) print(高 中位数/最小:, np.median(hs), hs.min()) print(小框(16px)占比:, ((ws 16) | (hs 16)).mean())如果小框占比超过 30%训练时imgsz至少上到 960或者考虑切片推理。这一步做完你对这份数据的底子就有数了再决定要不要继续。3. 把 VOC/COCO 转成 YOLO 格式转换脚本与四个边界坑3.1 VOC 转 YOLO 的完整脚本YOLO 格式要求每张图一个同名.txt每行class_id cx cy w h全部归一化到 0~1。VOC 的xmin/ymin/xmax/ymax是绝对像素转换时要用图片真实宽高做归一化。很多人直接用max(xmax)当宽遇到框超出图像边界就出错。正确做法是从图片读尺寸。import os, glob from PIL import Image import xml.etree.ElementTree as ET CLASSES [car, bus, truck] # 按 2.2 统计结果改 CLS2ID {c: i for i, c in enumerate(CLASSES)} SRC_IMG raw/JPEGImages SRC_XML raw/Annotations DST_IMG dataset/images/train DST_LBL dataset/labels/train os.makedirs(DST_IMG, exist_okTrue) os.makedirs(DST_LBL, exist_okTrue) for xml_path in glob.glob(os.path.join(SRC_XML, *.xml)): stem os.path.splitext(os.path.basename(xml_path))[0] img_path os.path.join(SRC_IMG, stem .jpg) if not os.path.exists(img_path): continue w, h Image.open(img_path).size lines [] for obj in ET.parse(xml_path).findall(object): name obj.find(name).text.strip() if name not in CLS2ID: continue # 过滤不需要的类别 b obj.find(bndbox) x1, y1 float(b.find(xmin).text), float(b.find(ymin).text) x2, y2 float(b.find(xmax).text), float(b.find(ymax).text) # 裁剪到图像范围内防止越界 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{CLS2ID[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: Image.open(img_path).convert(RGB).save(os.path.join(DST_IMG, stem .jpg)) with open(os.path.join(DST_LBL, stem .txt), w) as f: f.write(\n.join(lines))逻辑说明先建类别到 id 的映射遍历 XML过滤不在CLASSES里的目标把坐标裁剪到图像边界内再归一化。参数上:.6f保留六位小数YOLO 官方推荐至少六位位数太少小框会丢精度。convert(RGB)是为了防止灰度图或带 alpha 的 PNG 导致训练时报通道数错误。这段脚本跑完dataset/images/train和dataset/labels/train就成对了。3.2 COCO 转 YOLO 的差异点COCO 的bbox是[x, y, w, h]绝对像素且category_id往往不是从 0 连续。转换时要重新映射成 0 起始的连续 id否则 YOLO 会报类别越界。另外 COCO 的images里有width、height字段可以直接用不必再读图速度快很多。import json, os from PIL import Image data json.load(open(raw/annotations/instances_train.json, encodingutf-8)) cats sorted(data[categories], keylambda c: c[id]) cat2id {c[id]: i for i, c in enumerate(cats)} img_info {im[id]: im for im in data[images]} os.makedirs(dataset/images/train, exist_okTrue) os.makedirs(dataset/labels/train, exist_okTrue) from collections import defaultdict per_img defaultdict(list) for ann in data[annotations]: if ann.get(iscrowd, 0): continue per_img[ann[image_id]].append(ann) for img_id, anns in per_img.items(): info img_info[img_id] w, h info[width], info[height] lines [] for a in anns: x, y, bw, bh a[bbox] cx, cy (x bw / 2) / w, (y bh / 2) / h lines.append(f{cat2id[a[category_id]]} {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}) stem os.path.splitext(info[file_name])[0] with open(fdataset/labels/train/{stem}.txt, w) as f: f.write(\n.join(lines))关键差异iscrowd1的标注要跳过那是人群密集区域YOLO 不适合学cat2id用排序后重新编号保证从 0 连续。图片拷贝部分和 VOC 一样从raw/images复制到dataset/images/train即可。3.3 划分训练验证集时别踩泄漏的坑交通车辆数据如果是视频抽帧相邻帧几乎一样。随机划分会让验证集里出现训练集的近邻帧mAP 虚高。正确做法是按视频片段或时间戳划分同一段视频的帧只进训练或只进验证。如果数据里没有片段信息退而求其次按文件名前缀分组或者用感知哈希去重后再随机分。import os, shutil, random from PIL import Image import imagehash imgs sorted(os.listdir(dataset/images/train)) hashes {} for name in imgs: h imagehash.phash(Image.open(fdataset/images/train/{name})) hashes[name] h # 简单去重汉明距离小于 5 视为重复 kept [] for name, h in hashes.items(): if all(h - hashes[k] 5 for k in kept): kept.append(name) random.seed(42) random.shuffle(kept) val_ratio 0.1 n_val int(len(kept) * val_ratio) for name in kept[:n_val]: stem os.path.splitext(name)[0] shutil.move(fdataset/images/train/{name}, fdataset/images/val/{name}) shutil.move(fdataset/labels/train/{stem}.txt, fdataset/labels/val/{stem}.txt)参数上phash对缩放和轻微压缩鲁棒汉明距离阈值 5 是经验值交通场景可以放宽到 8。这一步做完验证集才可信。4. 用 YOLOv8 跑通第一个 baselinedata.yaml 与训练参数4.1 data.yaml 怎么写才不出错YOLO 训练第一道坎就是data.yaml。路径写相对还是绝对、nc和names对不对、val指向哪里任何一个错都会在启动时报Dataset not found或Label class out of range。我一般用绝对路径避免工作目录切换后找不到。path: /home/user/work/vehicle_det/dataset train: images/train val: images/val nc: 3 names: 0: car 1: bus 2: truck注意names的 id 必须和转换脚本里的CLS2ID完全一致顺序错了模型学到的类别就全乱。nc等于len(names)多一个少一个都会报错。如果后面要加person改names和nc后必须重新转换标签不能只改 yaml。4.2 启动训练的命令与关键参数baseline 先用yolov8n或yolov8s不要一上来就上大模型先确认数据管线通。命令里几个参数决定成败imgsz看小目标占比batch看显存workers看 CPU 核数cache在内存够时开ram能大幅加速。yolo detect train \ data/home/user/work/vehicle_det/dataset/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz960 \ batch16 \ workers8 \ cacheram \ patience20 \ projectruns/vehicle \ namebaseline参数说明imgsz960是因为交通场景小目标多640 容易漏检远处车辆batch16在 8G 显存上跑yolov8s比较稳显存不够降到 8patience20表示 20 轮没提升就早停省时间cacheram要求内存至少能放下整个数据集交通车辆数据集通常几 G16G 内存可以开。训练启动后先看第一轮 loss 是否正常下降如果box_loss一直是nan回去查标签有没有归一化错误或坐标越界。4.3 看训练曲线判断数据质量训练跑起来后重点看runs/vehicle/baseline/results.png里的三条线train/box_loss和val/box_loss是否同步下降metrics/mAP50是否在 30 轮内起来。如果训练 loss 降但验证 loss 升说明过拟合或验证集泄漏如果两者都不降大概率是标签格式错或类别映射错。交通车辆数据集常见的情况是 mAP50 卡在 0.5 左右上不去这时候先别调模型回去看 2.3 的框尺寸分布小目标太多就上imgsz1280或换yolov8m。5. 避坑与排查交通车辆数据集训练中最容易翻车的五件事5.1 现象训练启动报Label class xxx is out of range原因标签里的 class id 超过了data.yaml里nc的范围通常是转换时类别映射没对齐或者过滤类别后没重新编号。解决用awk {print $1} labels/*.txt | sort -u把所有出现过的 id 列出来和names对比多出来的重新映射。5.2 现象mAP 一直是 0loss 也不降原因坐标没归一化或者cx cy w h顺序写成了x1 y1 x2 y2。YOLO 对格式极其敏感顺序错不会报错但学不到东西。解决随机抽一个标签文件手算cx*width看是否落在框的合理位置不对就回去改转换脚本。5.3 现象验证集 mAP 很高实际推理一塌糊涂原因训练验证集划分时同一段视频的帧被分到两边验证集泄漏。解决按 3.3 的感知哈希去重或者按文件名前缀分组划分确保验证集里的场景训练时没见过。5.4 现象显存溢出CUDA out of memory原因imgsz和batch乘积太大或者cacheram把内存吃满导致交换。解决先把batch减半再降imgsz到 640 确认能跑然后逐步加回来找到显存上限。交通车辆数据集如果图很大4K预处理阶段就会爆建议先离线缩放到 1280 长边再训。5.5 现象某些类别永远检不出来原因该类样本太少或者该类框普遍很小。解决先看 2.2 的类别统计少于 500 的类别考虑合并比如van并入car或过采样小目标多就加 P2 检测层YOLOv8 改yaml里的 head 结构或者用 SAHI 切片推理。6. 进阶用切片推理和类别合并把交通车辆 mAP 再抬一截baseline 跑通后真正决定这份数据集值不值得继续投入的是你能不能把 mAP 从 0.5 推到 0.7 以上。我一般先做两件事类别合并和小目标切片。交通场景里car、van、suv在很多数据集里标注边界模糊强行分三类反而拉低整体指标合并成vehicle一类往往 mAP 直接涨 5 到 10 个点。合并后重新生成标签改data.yaml的names和nc再训一轮对比。第二件事是切片推理。如果 2.3 统计出小框占比超过 30%整图缩放到 960 后远处车辆可能只剩几个像素。SAHI 的思路是把大图切成带重叠的小块每块单独推理再合并。训练时也可以用切片增强让模型见过更多小目标上下文。from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathruns/vehicle/baseline/weights/best.pt, confidence_threshold0.25, devicecuda:0, ) result get_sliced_prediction( test.jpg, model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, ) result.export_visuals(export_dirvis/)参数上slice_height/width一般设成训练imgsz的 0.5 到 1 倍overlap0.2 能覆盖跨块目标太大推理慢太小边缘目标被切碎。切片推理的代价是速度下降 3 到 5 倍如果做实时车流统计要权衡。我的习惯是离线评测和标注复核用切片线上实时用整图两套阈值分开调。验证切片有没有用别只看整体 mAP要单独统计小目标子集的 AP。用 COCO 的areasmall口径把验证集里框面积小于 32×32 的挑出来单独算涨了才说明切片真起作用。如果小目标 AP 没动说明问题不在分辨率而在标注质量或类别定义回去查标签。最后说个血泪经验交通车辆数据集最贵的不是模型是清洗和复核。我见过太多团队花两周调模型最后发现是 10% 的标签框偏了 20 像素。训完第一轮后把验证集里 confidence 高但 IoU 低的样本导出来人工看一遍往往比换模型管用。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网