news 2026/10/1 3:04:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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_ok=True) 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(f"converted: {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个没有样本,训练时这些空类会干扰损失计算。正确做法是统一写成0,data.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(f"bad 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(f"check 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.yaml:

path: /data/bus_voc train: images/train val: images/val names: 0: bus

path必须写绝对路径,尤其是你不在项目根目录执行命令的时候,相对路径经常导致训练脚本找不到图片。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 data=bus_data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0

model=yolov8n.pt会从官方仓库自动下载预训练权重,如果你在离线环境,需要提前把权重文件放到当前目录。选yolov8n而不是yolov8x是有原因的:VOC里抽取出的bus类实例数量在几百的量级,属于典型小数据集,用小模型起步能更快验证流程是否通畅,也不容易在几十轮之后就过拟合。先跑通nano,再根据精度缺口换yolov8s或yolov8m,这个路线最省时间。

超参方面,epochs=100对几百张图的单类检测通常够用,如果你自己补充了数据可以加到150;imgsz=640是精度和速度的默认平衡点,但公交车场景有个特殊性——远处的小目标占比高,显存允许的话可以试试imgsz=960,输入分辨率变大后小目标的召回率一般会涨。batch=16在8G显存上跑nano没有压力,如果显存吃紧就改8或者4,配合默认的optimizer='auto'也能正常收敛。

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看一眼这个CSV:

tail -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 data=bus_data.yaml model=runs/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 现象:解压完只有JPEGImages,Annotaions目录不存在

这是从网盘流传资源时最常见的现象。有人分享数据集时只打包了图片,或者打包时漏掉了标注目录,导致你按2.2节的脚本批量转换时直接提示找不到XML文件。

原因很简单:原始整理者自己就把两个目录分开放了,转发时只传了图片。解决方法是先看压缩包内文件列表,确认Annotations和JPEGImages都存在再解压;如果只有图片,就只能找另一份资源,花时间重标一套公交车数据完全不划算。

5.2 现象:训练到十几轮时loss突然变nan,或报出BN相关的崩溃

很多人在YOLO公交车数据集上遇到的第一个硬故障是训练中途loss变成nan,日志里能看到类似Convergence to 0的BN层相关报错。此时模型基本已经废了,再跑下去只会越跑越歪。

原因通常有三类:学习率过大,默认0.01对小数据集来说偏高;batch size太小导致BN统计量不稳定;数据里存在宽或高为0的标签,回归目标出现无穷值。解决方法是先清理标签,用脚本过滤掉w<=0 or h<=0的行;然后调低学习率,启动参数加lr=0.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-iou:

yolo predict model=runs/detect/train/weights/best.pt source=test_video.mp4 conf=0.35 nms-iou=0.5

conf=0.35表示只输出置信度高于0.35的检测框,阈值越高误检越少但漏检越多;nms-iou=0.5表示两个框的交并比超过0.5时合并成一个,值越低对重叠框的合并越激进。如果你的场景是路侧监控,车辆密集且相互遮挡,nms-iou调到0.5左右能明显减少同一个车被输出两个框的情况;如果你发现大量真实车辆因为置信度低被滤掉,就把conf降到0.25甚至0.2,然后用视频帧间平滑或跟踪器来消除抖动。

我自己的习惯是先用默认conf=0.25跑一段视频,统计误检和漏检的分布,再针对性调参——误检多为护栏、公交车尾部的相似纹理,就把conf上调;漏检多出现在远处小目标,就下调conf并同步检查是不是该换更大输入分辨率。这样一轮下来,模型的落地表现通常比直接拿最高mAP的阈值配置要稳得多。

最后补一句血泪经验:我最早拿纯VOC数据练出来的模型,在测试集上mAP50有0.92,觉得已经能交差,结果一放到真实道路视频里,白天逆光场景直接漏掉一半公交车。后来花了两天时间专门补了几百帧自己场景的标注数据,重新训练后才把漏检率压下来。数据集的起点决定了模型的上限,但真正决定能不能用的,是你愿意为它补多少真实场景的样本。希望这篇笔记能帮你少走这几段弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 3:01:59

AI工业控制系统搭建实战:从架构设计到模型部署的完整指南

1. 从"AI工业控制"这个组合词说起&#xff1a;它到底在解决什么问题"AI工业控制系统"这个词这两年出现的频率越来越高&#xff0c;但很多人第一次听到时的反应是&#xff1a;工业控制不是已经有PLC、DCS、SCADA了吗&#xff0c;再加个AI是要干什么&#xf…

作者头像 李华
网站建设 2026/10/1 3:01:48

靠谱的AI搜索推广品牌企业用户力荐

衡水亚云科技有限公司&#xff0c;作为一家深耕数字化营销领域十二年的全国连锁一站式企业服务公司&#xff0c;始终聚焦于企业获客与品牌传播的核心需求&#xff0c;通过短视频运营、互联网推广及AI智能营销三大核心板块&#xff0c;为各类企业提供适配性强、落地性高的一站式…

作者头像 李华
网站建设 2026/10/1 3:01:44

温州企业GEO代理服务 南方网通网络技术开发 提供多账号管理及关键词排名诊断工具

当AI搜索逐渐占据超过70%的用户决策场景&#xff0c;传统网络营销正在迎来新一轮的重构与洗牌。对于实体企业、本地服务商而言&#xff0c;过往依赖付费投流、人工优化的获客模式&#xff0c;正在面临成本高企、转化低迷的困境——AI搜索结果里找不到自身品牌信息、产品优势无法…

作者头像 李华
网站建设 2026/10/1 3:01:44

13.RK3588 的 8K 编解码能力,在真实产品里怎么用?

RK3588 的 8K 编解码能力&#xff0c;在真实产品里怎么用&#xff1f;摘要&#xff1a;8K 是 RK3588 宣传页上最醒目的参数之一&#xff0c;但产品经理更关心&#xff1a;8K 解码在我的设备里到底解决什么问题&#xff1f;多路并发怎么算&#xff1f;本文从真实产品视角拆解 RK…

作者头像 李华
网站建设 2026/10/1 3:01:19

Python使用RotatingFileHandler实现日志自动切割

Python使用RotatingFileHandler实现日志自动切割 程序运行时间长了以后&#xff0c;日志文件可能不断增大&#xff0c;不仅占用磁盘空间&#xff0c;打开和检索也会越来越慢。Python自带的 RotatingFileHandler 可以按照文件大小自动切割日志&#xff0c;并保留指定数量的历史文…

作者头像 李华
网站建设 2026/10/1 3:00:20

RL-算法演进史05:Model-Based强化学习算法演进路线04

下一节: 5.15 MCTS与Planning路线:从Dyna-Q到AlphaGo、AlphaZero、MuZero 重点分析: 为什么Model-Based RL存在两条路线: Dreamer路线(学习Policy) MuZero路线(搜索Planning) MCTS数学原理; AlphaGo如何结合深度网络; AlphaZero如何去掉人类棋谱; MuZero如何在未知…

作者头像 李华