尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLO车辆检测数据集处理:从解压到训练的全流程指南

YOLO车辆检测数据集处理:从解压到训练的全流程指南 简介一套面向YOLO系列算法的车辆检测数据集覆盖公交车、卡车、摩托车、行人、自行车、小型车等六类常见交通目标可直接用于目标检测模型的训练、验证与测试。资源包共2000个文件以VOC格式的xml标注文件为主压缩后大小约205.35MB数据已预先划分好训练集与验证集并附带了data.yaml配置文件可无缝对接到yolov5、yolov7、yolov8、yolov9、yolov10、yolov11等主流版本。目前已有97人学习浏览适合目标检测入门者、算法调参人员及自动驾驶场景感知开发者参考。除了VOC标注资源还同时提供yolo格式txt标签两类标签分别存放于不同文件夹其中yolo坐标采用归一化的中心点与宽高方便用户按需选用。解压后即可直接用于模型训练或效果对比省去了自行标注和格式转换的繁琐步骤能有效降低实验准备成本让使用者将更多精力投入模型优化与场景适配尤其适合快速验证算法改进和复现相关实验。1. 拿到这个车辆检测数据集先别急着解压看到《YOLO算法-车辆检测数据集-9767张图像带标签-公交车-卡车-摩托车-行人-自行车-小型车.zip》这个名字多数人的第一反应是把 zip 解压出来然后直接扔给 yolo 训练。这个直觉没错但我处理过好几包类似来源的车辆检测数据顺序不对的话后面折腾的时间是数倍标签格式可能混着 VOC 的 xml图像尺寸可能五花八门六个类别的目标数量可能偏得离谱甚至有 zip 文件本身就没下载完整的情况。这篇笔记就用这包数据结构里最常见的形态讲清楚怎么把 9767 张带标签图像从 zip 变成一条能稳定复现的训练流水线。适合正在做车辆检测项目、又不想在数据准备阶段内耗的从业者。2. 先给 zip 做体检解压前的目录梳理与标签格式确认2.1 解压前用 unzip -l 建立目录索引命令行处理数据集的第一条习惯不是unzip而是unzip -l。这条命令只列出 zip 内的文件清单不解压实际内容能让先看到目录结构、文件数量、以及是否存在“套娃”情况。很多车辆检测数据集打包时会在顶层再套一层同名文件夹直接解压容易把路径弄乱。unzip -l 车辆检测数据集.zip | head -n 30输出长这样Archive: 车辆检测数据集.zip Length Date Time Name --------- ---------- ----- ---- 0 2024-01-15 10:22 vehicle_data/ 0 2024-01-15 10:22 vehicle_data/images/ 1234 2024-01-15 10:22 vehicle_data/images/000001.jpg 0 2024-01-15 10:22 vehicle_data/labels/ 456 2024-01-15 10:22 vehicle_data/labels/000001.txt这一步的逻辑很简单先把目录层级看清楚确定 images 和 labels 是平级还是嵌套后面写数据划分脚本时路径就不会出错。如果发现顶层套了一层目录比如所有文件都在vehicle_data/下解压时建议加上-d指定目标目录避免直接灌进当前工作目录。注意 zip 解压本身有两个售后问题要留个心眼一是 zip 伪加密并不少见表面看要密码实际只是标记位被改了用常规解压工具反而报错二是下载不完整的 zip 在解压到一半才会暴露。稳妥做法是解压前先跑unzip -t做一次完整性校验输出所有文件都显示OK再放开手脚解压。2.2 识别标签到底是 YOLO 的 txt 还是 VOC 的 xml车辆检测数据集的标签格式市面上主要三种YOLO 的归一化 txt、VOC 的 xml、以及 COCO 的 json。标题里明确写了“带标签”但没写格式所以第一步要确认。YOLO 格式每行是class_id cx cy w h四个数值都是 0~1 的归一化坐标VOC 格式是objectnamebus/namebndbox.../bndbox/object这种 XML 结构。用一个几行的小脚本扫描整个目录先统计文件后缀再读取第一个标签文件的前两行判断格式import os from collections import defaultdict label_dir vehicle_data/labels ext_counter defaultdict(int) sample_lines {} for fname in os.listdir(label_dir): ext os.path.splitext(fname)[1].lower() ext_counter[ext] 1 if ext not in sample_lines and ext .txt: with open(os.path.join(label_dir, fname)) as f: sample_lines[ext] [next(f).strip() for _ in range(2)] print(后缀统计:, dict(ext_counter)) for ext, lines in sample_lines.items(): print(f{ext} 样例:) for line in lines: print( , line)逻辑说明先按后缀统计如果出现大量.xml或.json就需要先做格式转换如果全是.txt再看内容是否符合“五个值的归一化坐标”规律。sample_lines只保存每种后缀的前两行采样避免读入整个标签文件耗内存。参数说明这包数据的标签如果是标准 YOLO txt每行第一个数应该是 0~5 的整数类别号对应标题里的六类公交车、卡车、摩托车、行人、自行车、小型车。出现小数或者负数说明标注工具导出时混入了别的字段。2.3 用类别分布脚本看清六个类的不平衡程度车辆检测数据集最常见的坑是类别严重不平衡小型车可能占 60% 的目标摩托车和公交车可能只占 2%。这一点直接决定后面要不要调损失函数、要不要做类别重采样。写一个统计脚本遍历所有标签文件分别统计每个类别出现的目标框总数和被标记的图像数import os import glob label_dir vehicle_data/labels class_names {0: bus, 1: truck, 2: motorcycle, 3: person, 4: bicycle, 5: car} cls_count {i: 0 for i in range(len(class_names))} img_with_cls {i: 0 for i in range(len(class_names))} for label_path in glob.glob(os.path.join(label_dir, *.txt)): seen set() with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) if cls_id not in cls_count: continue cls_count[cls_id] 1 seen.add(cls_id) for c in seen: img_with_cls[c] 1 total_boxes sum(cls_count.values()) print(f目标框总数: {total_boxes}) for i in range(len(class_names)): ratio cls_count[i] / total_boxes * 100 print(f{class_names[i]:10} 框数 {cls_count[i]:6} 包含该类的图像 {img_with_cls[i]:5} 占比 {ratio:.1f}%)逻辑说明seen集合确保每张图只对“是否包含该类”计数一次避免同一张图重复计入。过滤len(parts) 5是因为个别空行或损坏行会导致 split 后字段不足这个防御很重要。输出结果我一般直接粘贴到笔记里作为训练前的基础数据。参数说明占比低于 5% 的类比如摩托车意味着平均每二十个框才有一个。这种情况先别急着上复杂方案优先确认标注本身有没有漏标——很多车辆检测数据集里摩托车被标错成自行车或小型车这类歧义在标注阶段比模型阶段更难发现后面避坑章节会专门讲。2.4 预扫描脏数据越界框、空标签与损坏图片数据集里总有几根刺。YOLO 格式的归一化坐标理论上 cx,cy,w,h 都应该在 0~1 之间但标注工具导出时常出现 w 或 h 超过 1、cx 或 cy 为负数的情况。这种标签丢进训练不会立刻报错但损失函数会异常波动。写一个扫描脚本把越界框和空标签一次性揪出来import os label_dir vehicle_data/labels bad_boxes [] empty_files [] for fname in os.listdir(label_dir): path os.path.join(label_dir, fname) with open(path) as f: lines [l.strip() for l in f if l.strip()] if not lines: empty_files.append(fname) continue for line in lines: parts line.split() if len(parts) ! 5: bad_boxes.append((fname, fields_error, line)) continue cls, x, y, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_boxes.append((fname, out_of_range, line)) if w 0 or h 0: bad_boxes.append((fname, zero_size, line)) print(f空标签文件: {len(empty_files)} 个) for item in empty_files[:10]: print( 空文件:, item) print(f异常标签行: {len(bad_boxes)} 条) for item in bad_boxes[:10]: print( 异常:, item)逻辑说明empty_files记录没有任何标注对象的图像这些图对训练没有贡献建议直接移出训练集不然验证时会被当成漏检去计算损失。bad_boxes里out_of_range是最需要警惕的因为 YOLO 训练时会在数据加载阶段对坐标做缩放越界框会被裁掉一部分等于标注框和真实目标错位。参数说明扫描结果如果异常比例超过 1%是数据质量的红灯不要抱着“模型能自己学出来”的侥幸。异常比例很低时用后面训练的cache缓存机制也能发现问题但显存和内存的额外消耗不划算。3. 从 zip 到 YOLO 训练目录划分与第一个训练命令3.1 YOLO 要求的目录结构与划分脚本YOLO 系列对数据目录有一套约定俗成的结构不同版本略有差异但 v5/v8/v11 都通用这一套images和labels各分train和val两个子目录两边文件名一一对应。9767 张图不算多我一般按 90/10 划分也就是 8790 张训练、977 张验证。先建目录再跑一个按 9:1 随机划分的脚本。随机种子固定住保证每次复现结果一致。mkdir -p vehicle_dataset/images/train vehicle_dataset/images/val mkdir -p vehicle_dataset/labels/train vehicle_dataset/labels/valimport os import random import shutil random.seed(42) image_dir vehicle_data/images label_dir vehicle_data/labels train_img_dir vehicle_dataset/images/train val_img_dir vehicle_dataset/images/val train_lbl_dir vehicle_dataset/labels/train val_lbl_dir vehicle_dataset/labels/val all_images [f for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(all_images) val_count int(len(all_images) * 0.1) val_set set(all_images[:val_count]) train_set set(all_images[val_count:]) for fname in all_images: base os.path.splitext(fname)[0] img_path os.path.join(image_dir, fname) lbl_path os.path.join(label_dir, base .txt) if fname in train_set: shutil.copy(img_path, os.path.join(train_img_dir, fname)) if os.path.exists(lbl_path): shutil.copy(lbl_path, os.path.join(train_lbl_dir, base .txt)) else: shutil.copy(img_path, os.path.join(val_img_dir, fname)) if os.path.exists(lbl_path): shutil.copy(lbl_path, os.path.join(val_lbl_dir, base .txt)) print(f训练集: {len(train_set)} 张, 验证集: {len(val_set)} 张)逻辑说明使用shutil.copy而不是os.rename保留原始数据不动这是数据准备阶段的好习惯——训练过程中如果发现划分逻辑有 bug原始数据还在随时可以重新划分。只复制存在标签文件的图像如果标签文件缺失这图复制过去后训练时就会被跳过等于白占空间。参数说明random.seed(42)不是玄学是保证任何人在同一份数据上跑同一脚本得到完全一样的划分。这对复现实验结果、排查“为什么别人 mAP 高我一截”这类问题至关重要。val_count用int(len * 0.1)小数点后直接截断9767 张 10% 是 976多出的那 1 张留在训练集无伤大雅。3.2 写一个能跑的 data.yaml 参数文件YOLO 训练的数据配置在一个 yaml 文件里。最关键的三个字段path指到数据集根目录train和val分别是两边的相对路径names一行行列出六个类。命名顺序必须和标签文件里的class_id一一对应顺序错了模型学出来的语义就全错了——这是翻车率最高的位置。# data.yaml path: ./vehicle_dataset # 数据集根目录相对当前工作目录 train: images/train # 训练图像目录 val: images/val # 验证图像目录 nc: 6 # 类别总数 names: 0: bus 1: truck 2: motorcycle 3: person 4: bicycle 5: car参数说明names里的顺序不能按中文习惯排必须严格按标签文件里class_id的数字映射。比如标签里 0 是公交车那0: bus就得放第一行。我见过有人在这里按字数排序结果训练出来的模型把卡车识别成公交车损失函数明明在下降预测语义全错。path用相对路径时注意训练命令的工作目录要能指向这个 yaml否则会报 file not found。3.3 预训练模型的选型与下载策略车辆检测这个场景迁移学习是绝对主流没必要从零训练。常见的做法是加载在 COCO 上预训练的权重然后微调。COCO 本身就包含 car、bus、truck、person、bicycle、motorcycle 这些类和我们这六类高度重合迁移效果在众多公开数据集的实践里已经反复验证过。下载预训练权重时最烦的是网络慢或者下载中断。常用的办法是换下载源或者用镜像。命令行里可以直接指定权重路径# YOLOv8 官方权重文件名对应模型大小 # yolov8n.pt 约 6MByolov8s.pt 约 22MB yolo detect train datavehicle_data.yaml modelyolov8s.pt epochs100 imgsz640参数说明yolov8n是 nano 版速度和显存最友好适合先跑通全流程yolov8s是 small 版精度更高。第一次训练建议用 nano 跑哪怕 20 个 epoch 验证数据链路没问题再换 small 正式训练能省下大量排查时间。预训练模型下载失败时先检查网络连通性再换个镜像下载到本地路径把参数从权重文件名改成绝对路径即可。3.4 跑通第一版训练的产出解读训练命令敲下去之后第一次跑的人最容易慌的是不知道“到底跑没跑对”。YOLO 会在runs/detect/train/下实时输出几个关键文件results.csv是每轮训练的指标记录weights/best.pt是验证集上表现最好的权重weights/last.pt是最后一轮的权重还有带预测框的验证图片样例。我的习惯是第一轮先用 20 个 epoch 快速验证重点看三件事训练 loss 是不是在下降、验证集上能不能正确画出几个框、以及有没有报 dataset 相关的错误。yolo detect train datavehicle_data.yaml modelyolov8n.pt epochs20 imgsz640 batch16等到命令跑完打开runs/detect/train/val_batch0_pred.jpg看一眼预测结果。如果三分之一的图上都没有框说明标签或者数据划分之前就出了问题这时候停下来排查比继续跑 100 个 epoch 划算得多。4. 关键训练参数中等规模数据集怎么调才不出怪问题4.1 基础参数表这个数据集的推荐起点9767 张图、六个类别在目标检测任务里属于中等规模和 COCO 那种十几万张的规模差两个数量级。所以训练参数不能照抄大模型的配置要针对这个规模做收敛上的调整。下面这组参数是我在这个体量数据集上验证过的起点放进 data.yaml 同目录的配置文件里即可参数推荐值选择理由imgsz640车辆检测的默认分辨率和预训练权重训练时的尺度一致避免迁移时额外做尺度适应epochs100中等规模数据 100 轮足够收敛继续加轮次在小数据集上反而容易过拟合batch16取决于显存16 是 8GB 显存能跑起的稳妥值显存够可调到 32 加速收敛optimizerSGD小数据集 SGD 收敛稳定AdamW 在数据量大时优势明显但在这类场景容易过早收敛到次优patience30验证指标连续 30 轮不提升就早停省时间cos_lrTrue余弦退火在小规模数据上比阶梯下降更平滑最后几轮不容易震荡cacheTrue数据集只有 9767 张图全量缓存进内存能显著缩短每个 epoch 的时间4.2 yolo 损失函数与类别不平衡的博弈YOLO 系列的损失函数包含三个部分框回归损失CIoU、分类损失BCE、置信度损失BCE。理解到这一步就够了因为车辆检测数据集里更实际的问题是类别不平衡小型车和目标行人占了绝大头公交车和摩托车的样本相当少。yolo 等待损失函数在这个数据集上偏科严重的话最常见的做法有两个方向。一个方向是对样本少的类别做重采样把包含公交车或摩托车的图像在训练时多喂几遍另一个方向是调整分类损失的类别权重。YOLOv8 的配置里没有直接开放cls_weights参数我一般用脚本给损失函数添加权重但这个操作对新手不友好。更实用的办法是做数据层面的平衡把摩托车和公交车样本做离线复制增强。常见做法是用imgaug或 OpenCV 对包含这两类的图像做水平翻转、亮度抖动生成补充样本放进训练集。这个方法朴素但有效模型见到的实例多了检测倾向自然就修正了。4.3 数据增强参数mosaic、hsv 与翻转的取舍yolov8 训练自己的数据集时数据增强的默认值在大数据集上效果不错但在车辆检测这种目标尺度差异大的场景里需要动几个参数。mosaic默认是 1.0也就是每张训练图都有概率由四张图拼接而成这个增强对小目标友好但也会带来标注框被截断的问题。车辆检测里行人和摩托车常出现在图像的角落容易被 mosaic 裁剪出边界训练后期模型会对这些目标看不清。我一般用两组不同的数据增强来对比一组保持默认另一组把mosaic调成 0.5hsv_h、hsv_s、hsv_v调低到默认值的一半减少颜色抖动幅度。原因是车辆检测对颜色敏感性不高但白天的阳光反射会让 HSV 增强后的图像失真增强过头反而降低了真实场景的泛化能力。实践中一个稳妥的操作是训练的后 10 个 epoch 把 mosaic 关闭让模型在干净的数据上做最后收敛这个技巧在不少车辆检测项目里都能稳定提升mAP50零点几个点。在 YOLO 的配置里通过mosaic0.0控制最后阶段的增强比例即可。4.4 用混淆矩阵而不是 mAP 判断模型好坏训练结束后看results.png里的 mAP 曲线是最直观的但这不够。混淆矩阵能告诉你模型具体在哪两类之间打架。车辆检测里最常见的错误就是把公交车认成卡车、把摩托车认成自行车因为外形和比例接近。混淆矩阵训练完自动输出在runs/detect/train/confusion_matrix.png数字越靠近对角线越好。对角线以外的数字要逐格看。比如bus列里truck的比例超过 10%说明这两个类的特征在模型看来太接近。这时候不要急着调模型先检查标签是不是有一部分公交车的标签标的就偏向卡车形态我曾经在一个数据集中发现 300 多张公交车的标注框把车头顶部的空调机组排除在外导致模型只学了半截车身自然和卡车混淆。标注数据的问题混淆矩阵比人眼更早暴露。5. 调试车辆检测数据集的常见坑现象、原因、解决5.1 训练到一半卡在某张图片上报损坏现象训练跑没几个 epoch突然报CUDA error: an illegal memory access was encountered或者直接提示某张图片路径读取失败。训练中断后重启又卡在同一张图附近。原因压缩包下载不完整或者源文件本身就有损坏的图片文件。YOLO 训练时图片通过cv2.imread读取遇到损坏的 jpg 会返回空对象数据加载器在内存里拿到空数据轻则训练步数异常重则直接触发显存错误。解决在数据准备阶段全量校验图片完整性把打不开的图片移出训练目录。import os import cv2 image_dir vehicle_dataset/images/train corrupted [] for fname in os.listdir(image_dir): path os.path.join(image_dir, fname) img cv2.imread(path) if img is None: corrupted.append(fname) print(f损坏图片: {len(corrupted)} 张) for name in corrupted[:20]: print( , name)逻辑说明cv2.imread读取失败返回None用这个特性遍历一遍目录是最快的体检方案。很多压缩包传到一半断掉会在文件尾部丢字节图片文件头完好但数据区不完整这种图能打开但实际内容残缺也要清理。判定这类图片可以在读取后检查形状是否为 0。5.2 标签坐标越界导致损失函数炸掉现象训练 loss 前期正常下降某几轮突然从 0.05 跳到 2.0然后又掉回来像心电图一样。查看训练日志找不到明确报错。原因部分标签文件里坐标值不是 0~1而是像素坐标。yolo 训练时数据加载器会把标签坐标按图像尺寸归一化但错误的标签被当成归一化坐标直接用相当于把标注框放到了图像之外的位置分类和回归的梯度同时失控。解决用前面第 2.4 节的扫描脚本把越界标签找出来然后把像素坐标统一除以图像宽高做一次真实的归一化。注意有些标签文件和图像尺寸不一致这时候优先信一边用最常见尺寸做归一化基准不匹配的单独处理。5.3 标签类别号与 data.yaml 错位现象训练能跑完但验证结果里 mAP50 奇低看混淆矩阵发现行人被识别成小型车看起来模型对文字完全错乱。原因data.yaml里names的顺序和标签文件里class_id的数字不是同一套映射。比如标签里 0 是公交车、5 是小型车而 yaml 里 0 写的是 car、5 写的是 bus整个映射表旋转了一圈。解决写一个脚本读取标签文件里的最大类别号再去核对 yaml 的nc和names。# 快速查看某个标签文件的类别号范围 awk {print $1} vehicle_dataset/labels/train/000001.txt | sort -n | uniq -c | head -n 10参数说明$1取每行第一个字段也就是类别号。sort -n | uniq -c统计每个类别号出现的次数。如果输出里出现了超过 5 的类别号说明标签文件混入了超出六类范围的数据换算回 yaml 的 names 时一定要先纠正这个问题。5.4 验证集 mAP 很高但实拍视频一塌糊涂现象训练完在验证集上 mAP50 有 0.85导出模型跑一段行车记录仪视频检测效果惨不忍睹大量的漏检和误检。原因数据集划分时用了纯随机划分同一个视频源的连续帧被分进了训练集和验证集验证集内容在训练时已经见过类似的画面分数虚高。这类问题叫信息泄漏是车辆检测数据集最常见的负面案例。解决划分数据集时按照视频片段或场景分组不要让同一场景的帧同时出现在训练和验证两边。如果原始数据没有视频编号也没有场景标记可以用图像文件名前缀推测或者用感知哈希给相似帧分组。5.5 验证 mAP 不低但检测置信度普遍偏低现象模型能检测出目标但置信度普遍在 0.3~0.5 徘徊0.5 以上的框很少。业务上要求置信度阈值 0.5 以下就大量漏检。原因数据集的标注风格不一致。有些标注框画得紧贴目标轮廓有些画得松垮包含大量背景模型学到的边界框分布方差大置信度自然被摊薄。解决先框出标注框的宽高分布看看是否存在两种完全不同的标注风格。如果是把宽松的标注结果做一次收缩处理让框贴近目标边缘。这个操作没有统一脚本我用的是读取标签后对 w 和 h 乘以 0.85~0.95 的收缩系数再重新写回。风险是如果标注本来就紧收缩后会把目标局部截掉需要抽样检查。6. 最后一步把模型验证落在真实场景中6.1 用视频抽帧做盲测验证验证集指标是实验室成绩真实场景才是车辆检测的收银台。常见做法是拿一段训练集以外拍摄的道路视频每 5 帧抽 1 帧做推理统计漏检和误检。这个环节能暴露很多训练时发现不了的问题光线变化、遮挡、跟车距离导致的尺度突变。直接用训练好的 best.pt 跑一段视频yolo detect predict modelruns/detect/train/weights/best.pt \ sourcetraffic_clip.mp4 conf0.25 saveTrue跑完之后打开输出的视频文件重点看两个时间段车辆刚出现在画面边缘的时刻以及车辆被前方车尾遮挡一半的时刻。边缘场景是标注数据集普遍薄弱的位置如果这段抽帧盲测里摩托车和行人在边缘大量漏检说明训练数据里这两类在边缘位置的样本不足可以针对性补充数据。6.2 NMS 与 conf 阈值的现场调整逻辑推理时的conf参数是置信度阈值iou参数是 NMS 的交并比阈值。车辆检测场景里这两个值的设置有一定讲究conf 设 0.25 是通用值但公交车和卡车这类大目标在遮挡少时置信度通常很高可以设到 0.4 减少误检行人和摩托车目标小、遮挡多若阈值太高会大面积漏检需要降到 0.15 甚至 0.1。这里有一个实用的做法先按 0.25 跑一遍视频用飞桨或 OpenCV 写个小脚本统计每帧的检测框数量和平均置信度再逐档调整阈值找到一个“漏检率低且误检率可接受”的中间值。没有人能凭空给出最优阈值它是数据和场景共同决定的只能抽帧实测。6.3 导出 ONNX 与一个收尾习惯部署时把 PyTorch 权重转成 ONNX 是标准操作这一步能把推理速度提升不少尤其是在 CPU 推理场景下。YOLO 提供了一键导出命令yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出成功后建议用onnxruntime跑一遍同一段验证视频对比原模型的检测结果确认精度没有掉。如果导出后出现检测框偏移优先检查导出时是否顺手改变了imgsz这是最常见的偏差来源。收尾前我有一个坚持了很久的习惯把训练时用到的 data.yaml、划分脚本、参数配置连同权重一起归档到同一个目录。车辆检测的复现难很多时候不是模型难而是三个月后回来看当时的标签分布、划分比例、增强参数全都不记得了。这套归档习惯能省下二次训练时的大把时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表