ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLO公交车检测数据集:从VOC到训练避坑全指南

2026/10/1 3:04:02 拓冰建站 浏览量
YOLO公交车检测数据集:从VOC到训练避坑全指南 简介这套数据集面向目标检测与智慧交通方向的开发者从PASCAL VOC 2012训练验证集中筛选出全部含公交车类别的图像并整理为YOLO可直接使用的单类别数据集。全部467张实拍图片均配有jpg原图、txt边界框与xml详细标注共1402个文件压缩包约55.82MB标签分为简洁版txt与完整版xml两种格式对应YOLO训练所需的类别ID、坐标及目标尺寸信息便于快速开展实验。已有643人学习使用。图像覆盖城市道路、交叉口、公交站等多种环境场景多样便于模型泛化。资源适合用于公交专用道检测、车辆识别等场景读者拿到后即可按VOC标准组织训练集与验证集配合YOLOv3/YOLOv4等框架调参训练txt与xml并存的结构也为不同标注习惯的工程转换提供了参考可减少数据清洗与格式适配的时间直接进入模型训练和mAP评估环节。1. 拿到bus_VOCtrainval2012.zip别急着开训先搞懂这个YOLO公交车检测数据集是什么很多人第一次接触YOLO公交车检测数据集都是从网盘或群文件里拖下来一个叫bus_VOCtrainval2012.zip的压缩包解压就扔进训练脚本然后被 no labels found 或者标签格式报错折腾一个下午。这个zip看起来很不起眼但它解决的是一个真实痛点你想训练一个能识别公交车的YOLO模型却没有标注数据。自己用标注工具一框一框画几百张图就要大半天而PASCAL VOC 2012这个公开数据集里本来就有带bus类别标注的图片把这个子集抽取出来、按YOLO需要的格式重新整理打包就成了这个文件。这套数据集适合三类人做公交车检测相关课题、毕设的学生想快速验证YOLO训练流程但不打算先花两天标注的工程师以及做车载感知、路侧监控、智慧公交这类场景的算法人员。VOC2012的标注质量经过多年验证远比爬虫抓来的野标注干净用来做起步数据集相当稳。但要提醒一句从第三方渠道下载的压缩包解压后先检查再训练这个习惯能帮你避开后面九成的问题。2. 解压后先过检查清单目录结构、标签格式与XML转YOLO脚本2.1 三分钟核对目录images与labels缺一不可先把压缩包解压到一个干净目录然后按下面的命令检查结构mkdir -p /data/bus_voc cd /data/bus_voc unzip /path/to/bus_VOCtrainval2012.zip # 查看目录层级 find /data/bus_voc -maxdepth 2 -type d | sort # 统计图片和标签数量 find /data/bus_voc/images -name *.jpg | wc -l find /data/bus_voc/labels -name *.txt | wc -l期望看到的结构是images/train、images/val、labels/train、labels/val四个目录且images和labels下的文件数量能对上。大多数第三方整理的VOC子集包也会保留JPEGImages、Annotations、ImageSets/Main这种VOC原版目录但真正能直接喂给YOLO的是上面这种划分好的形态。这个检查动作能过滤掉一批“阉割版”资源。常见坑是zip里只有JPEGImages没有Annotations说明有人只抽了图片没带标注或者图片数量与标签数量差几十个多半是某几张图在整理时丢了XML配对。数量对不上就别继续往下走先补文件或者换一份资源硬训练的后果是模型在缺失样本上静默学歪。2.2 把VOC XML标注转成YOLO txt坐标归一化别写反如果你拿到的zip是VOC原版结构只有Annotations下的XML文件那就需要自己做格式转换。这里给出我常用的转换脚本import os import xml.etree.ElementTree as ET from pathlib import Path # 按PASCAL VOC官方类别顺序排列bus在index 5 VOC_CLASSES [aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor] def voc_xml_to_yolo(xml_path, out_txt): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width float(size.find(width).text) height float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text # 本数据集只保留bus类其余类别直接跳过 if name ! bus: continue # 单类数据集的类别ID统一写0而不是沿用VOC里的5 class_id 0 box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 归一化中心点坐标 x_center (x1 x2) / 2 / width y_center (y1 y2) / 2 / height w (x2 - x1) / width h (y2 - y1) / height lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) # 批量转换示例遍历Annotations目录 ann_dir Path(/data/bus_voc/Annotations) out_dir Path(/data/bus_voc/labels) out_dir.mkdir(exist_okTrue) for xml_file in ann_dir.glob(*.xml): txt_file out_dir / (xml_file.stem .txt) voc_xml_to_yolo(xml_file, txt_file) print(fconverted: {xml_file.name} - {txt_file.name})这里最关键的逻辑是归一化坐标的计算方式。YOLO的txt标签格式是class x_center y_center width height所有值都在0到1之间表示相对图片尺寸的比例。写成(x1 x2) / 2 / width而不是先加再除再除顺序上数学等价但直接连着除不容易漏括号。w和h用右下角减左上角再除以图片宽高注意x2、y2在VOC里是包含边界在内的坐标不用额外减一因为训练时差一个像素对结果没有实质影响。另一个容易被忽略的参数是类别ID。VOC官方20类的顺序里bus排在第5位但单类数据集里如果沿用这个ID你的data.yaml里names就得写20个类别其中19个没有样本训练时这些空类会干扰损失计算。正确做法是统一写成0data.yaml里只声明一个bus类别。如果后面要合并自己的数据这一点特别重要合并时所有来源的bus都必须是同一个ID否则模型会把同一个类当两个类学。2.3 转完先画框验证5分钟可视化检查误标漏标XML转txt之后不要直接训练花5分钟把标签画回图片上看看。这一步能发现大量低级错误import cv2 def draw_yolo_box(img_path, txt_path, out_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(fbad line in {txt_path}: {line.strip()}) continue _, cx, cy, bw, bh map(float, parts) # 反归一化还原像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(out_path, img) print(fcheck image saved: {out_path}) draw_yolo_box( /data/bus_voc/images/val/000001.jpg, /data/bus_voc/labels/val/000001.txt, /data/bus_voc/check_000001.jpg, )画框脚本本质上做的是归一化坐标的逆运算用中心点和宽高反推出左上角与右下角再乘回图片原始宽高。跑完后打开输出的check_*.jpg重点看三类问题框是否明显大于实际车辆、框是否偏移到车身一半以外、以及是否有漏掉的目标。如果出现“一个公交车只被框了半边”的情况问题多半出在XML的bndbox坐标本身就是残缺的这种脏数据直接删掉对应图片和标签不要留在训练集里。3. 用YOLOv8在本地把这批公交车数据练起来data.yaml与训练命令3.1 data.yaml里最容易写错的两个参数path和names训练前的配置文件是YOLO系列绕不开的一步。以当前最常用的YOLOv8为例新建一个bus_data.yamlpath: /data/bus_voc train: images/train val: images/val names: 0: buspath必须写绝对路径尤其是你不在项目根目录执行命令的时候相对路径经常导致训练脚本找不到图片。train和val是相对path的目录不要在前头加/否则会拼出绝对路径错位。names用键值对形式足以推断类别数量不需要再写nc如果你手多写了一个nc: 1和names冲突也不会报错但多一事不如少一事。在写names前先想清楚一件事zip里如果是按VOC原始类别顺序转换的那标签文件里bus可能是5如果转的时候已经统一成0这里就写0: bus。检查方法很简单随便打开一个txt标签文件看第一列数字是什么。3.2 训练命令与超参第一次训练别上来就yolov8x配置写好后用下面的命令启动训练cd /data/bus_voc yolo detect train databus_data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0modelyolov8n.pt会从官方仓库自动下载预训练权重如果你在离线环境需要提前把权重文件放到当前目录。选yolov8n而不是yolov8x是有原因的VOC里抽取出的bus类实例数量在几百的量级属于典型小数据集用小模型起步能更快验证流程是否通畅也不容易在几十轮之后就过拟合。先跑通nano再根据精度缺口换yolov8s或yolov8m这个路线最省时间。超参方面epochs100对几百张图的单类检测通常够用如果你自己补充了数据可以加到150imgsz640是精度和速度的默认平衡点但公交车场景有个特殊性——远处的小目标占比高显存允许的话可以试试imgsz960输入分辨率变大后小目标的召回率一般会涨。batch16在8G显存上跑nano没有压力如果显存吃紧就改8或者4配合默认的optimizerauto也能正常收敛。3.3 训练时看什么box_loss、cls_loss、dfl_loss与P/R曲线训练开始后终端会滚动输出每轮的结果但很多人不知道进度条最下面那行数字的含义。box_loss是边界框回归损失衡量预测框和真实框的位置偏差cls_loss是分类损失单类任务里这个值通常很小dfl_loss是分布焦点损失YOLOv8用它来细化边界框的回归精度。这三个损失在正常训练中应当整体下行如果 loss 从一开始就来回震荡不收敛多半是前面数据检查没做透。训练过程中的完整指标记录在runs/detect/train/results.csv里包括每个epoch的precision、recall、mAP50和mAP50-95。相比盯着终端日志我习惯每隔一段时间用tail看一眼这个CSVtail -20 /data/bus_voc/runs/detect/train/results.csv如果发现mAP50在某个epoch后不再增长甚至下降说明模型开始过拟合这时要做的是减小epochs或增加数据而不是继续跑下去。训练结束后best.pt和last.pt会同时保存在weights目录里后续验证和推理一律用best.pt。4. 用val.py验收公交车检测模型mAP与混淆矩阵怎么看4.1 验证命令与结果文件不只盯着mAP50看训练完成后执行验证得到一套完整的评估结果yolo detect val databus_data.yaml modelruns/detect/train/weights/best.pt输出目录在runs/detect/val里面有confusion_matrix.png、F1_curve.png、PR_curve.png和results.csv。绝大多数人只看终端打出的mAP50一个数字这不够。对单类公交车检测mAP50到0.9以上只能说明框大体能落上真正要判断模型能不能用要看recall召回率——远处的小公交车漏检率才是这个场景的痛点。PR_curve.png能看到模型在不同置信度阈值下的精度-召回率曲线曲线和坐标轴围成的面积就是mAP。如果你发现曲线右下角塌陷说明存在大量低置信度的真目标这些车通常是小尺寸或被遮挡。下一步要么补数据要么调低推理时的conf阈值。4.2 混淆矩阵总和不是100%先弄懂YOLO混淆矩阵的行列含义不少人第一次看到YOLO输出的混淆矩阵都会有一个疑问矩阵里所有格子加起来为什么不是100%或者1这不是bug而是混淆矩阵的归一化方式造成的。YOLO的混淆矩阵默认按行归一化也就是说每一行代表一个真实类别该行所有格子的和是1或100%表示这个类别的所有真实样本被分别预测成了哪些类别。这样设计是合理的你关注的是“真实的公交车有多少被正确检出、多少被漏成背景”。但有个副作用在多类模型里矩阵右下角有一个background列专门统计被漏检成背景的样本比例所有行的数值加总后自然不等于1。如果你在验证集上得到每行总和刚好是1说明行归一化是对的如果你看到的总和是别的数那可能是工具版本不同导致列归一化。诀窍很简单按行解读召回率按列解读精确率。看单类模型的混淆矩阵时最该关注的是第一行的对角格子和它右边的格子。如果对角线比例低、background列比例高说明大量公交车被模型当作背景放过了这是小目标漏检的典型信号。5. 公交车数据集的五个坑训练崩了、指标虚高的排查经验5.1 现象解压完只有JPEGImagesAnnotaions目录不存在这是从网盘流传资源时最常见的现象。有人分享数据集时只打包了图片或者打包时漏掉了标注目录导致你按2.2节的脚本批量转换时直接提示找不到XML文件。原因很简单原始整理者自己就把两个目录分开放了转发时只传了图片。解决方法是先看压缩包内文件列表确认Annotations和JPEGImages都存在再解压如果只有图片就只能找另一份资源花时间重标一套公交车数据完全不划算。5.2 现象训练到十几轮时loss突然变nan或报出BN相关的崩溃很多人在YOLO公交车数据集上遇到的第一个硬故障是训练中途loss变成nan日志里能看到类似Convergence to 0的BN层相关报错。此时模型基本已经废了再跑下去只会越跑越歪。原因通常有三类学习率过大默认0.01对小数据集来说偏高batch size太小导致BN统计量不稳定数据里存在宽或高为0的标签回归目标出现无穷值。解决方法是先清理标签用脚本过滤掉w0 or h0的行然后调低学习率启动参数加lr0.001最后把batch从16提到32如果显存允许。按这个顺序排查绝大多数BN崩溃都能定位到数据问题。5.3 现象训练日志提示某些标签被跳过或者loss反复横跳这条和5.2有强关联。归一化脚本如果没有过滤零宽高框转换出的txt里会出现0.000000 0.500000 0.000000 0.200000这种宽度为0的行YOLO训练时读到这种标签无法计算正常loss。原因是在VOC原版XML里个别bndbox的xmin和xmax写成了同一个值属于标注者手误。解决方法是写脚本批量检测txt里任何一行的w或h小于等于0就删除该文件或者把该样本从数据集目录中移出。注意不要只删行不删图否则图片和标签数量对不上后面的验证阶段会报样本不匹配。5.4 现象验证集mAP很高一到实际视频里检测效果却明显偏差这个坑最容易迷惑新手。mAP50高达0.95模型看起来完美但拿到一段真实街景视频里跑公交车频繁漏检。典型原因是验证集和训练集的内容重叠度过高——如果这两部分图片来自同一批VOC场景的相邻帧或者是从一段视频里按时间顺序硬切出来的验证集基本等于开卷考试得出来的指标没有任何参考价值。正确的划分方式是按场景分组保证同一辆公交车、同一路口的图片全部落在训练集或全部落在验证集。判断自己是否踩了这个坑可以随机抽几张验证集图片在训练集目录里搜同名或相似时间戳的文件如果命中率很高那就重新划分。5.5 现象把自己标注的数据合进来后整个模型精度反而下降你辛辛苦苦标了300张自己场景的公交车图合并进数据集训练后mAP反而掉了这种现象我见过不止一次。原因几乎都是类别ID冲突。自己的标注工具里可能把bus存为类别0但下载的数据集转换脚本里沿用了VOC的ID写了5两批标签混在一起同一个类被当成两个不同的类学模型就混乱了。解决方法是统一类别映射在合并前写一个脚本把两边的标签第一列全部改成同一个ID然后重新生成train.txt和val.txt划分。这个检查应该在合并前做而不是在训练失败后。6. 从“能跑通”到“能上线”补充公交车数据与推理NMS参数调优6.1 用一周时间给数据集补几类难例夜间、雨天、侧后视角VOC2012里的公交车图片质量整体不错但场景以白天的城市道路为主夜间、雨天、黄昏这些条件几乎空白侧后方视角的车辆也很少。用这套数据集训练出的模型在白天前向视角场景表现尚可到夜间或雨天上路就会打回原形。解决方向是补充难例数据从自己的监控视频里抽帧用LabelImg或Roboflow这类工具标注导出YOLO格式后并入原数据集。合并时最省事的做法是保持“单类”不变所有新标注的bus类别ID写0与下载的数据集一致然后重新划分train/val。补多少量级看效果一般先从100帧夜间难例开始验证集上夜间场景的召回率有明显提升就继续没有提升就检查标注质量。6.2 推理时NMS和置信度怎么调按场景调conf与nms-iou模型部署推理时两个参数对最终效果影响很大一个是conf一个是nms-iouyolo predict modelruns/detect/train/weights/best.pt sourcetest_video.mp4 conf0.35 nms-iou0.5conf0.35表示只输出置信度高于0.35的检测框阈值越高误检越少但漏检越多nms-iou0.5表示两个框的交并比超过0.5时合并成一个值越低对重叠框的合并越激进。如果你的场景是路侧监控车辆密集且相互遮挡nms-iou调到0.5左右能明显减少同一个车被输出两个框的情况如果你发现大量真实车辆因为置信度低被滤掉就把conf降到0.25甚至0.2然后用视频帧间平滑或跟踪器来消除抖动。我自己的习惯是先用默认conf0.25跑一段视频统计误检和漏检的分布再针对性调参——误检多为护栏、公交车尾部的相似纹理就把conf上调漏检多出现在远处小目标就下调conf并同步检查是不是该换更大输入分辨率。这样一轮下来模型的落地表现通常比直接拿最高mAP的阈值配置要稳得多。最后补一句血泪经验我最早拿纯VOC数据练出来的模型在测试集上mAP50有0.92觉得已经能交差结果一放到真实道路视频里白天逆光场景直接漏掉一半公交车。后来花了两天时间专门补了几百帧自己场景的标注数据重新训练后才把漏检率压下来。数据集的起点决定了模型的上限但真正决定能不能用的是你愿意为它补多少真实场景的样本。希望这篇笔记能帮你少走这几段弯路。本文还有配套的精品资源点击获取