简介:这是一套面向目标检测学习者的YOLO斑马线数据集,真实道路场景采集,数据场景丰富,包含1000张高质量图片,使用LabelImg标注,标注框质量高。压缩包内共2000个文件,主体为VOC格式的xml标签与YOLO格式的txt标签,各约1000份;另附COCO标签、3个Python划分脚本、6个环境搭建与训练教程页面及yaml配置,xml/txt为标签文件,py负责数据集划分,html与yaml提供环境配置和运行指引,整体约200.4MB。配套教程覆盖Linux与Windows双平台的YOLO环境搭建、训练案例修改与数据集划分流程,帮助用户从零快速上手。附带的划分脚本支持按需生成训练集、验证集和测试集,并可自动整理图片与标签到新文件夹,适合课程设计、毕业设计或算法对比实验。已有320人学习,适合希望直接获得标注数据与完整训练链路的中初级目标检测开发者。
1. 斑马线检测为什么值得单独做一个YOLO数据集
拿到这个标题第一反应可能是:斑马线也值得单独做个目标检测数据集?值得。斑马线在图像里往往是一条大面积、高重复纹理的地面标记,和行人、车辆这类闭合轮廓目标完全不同——它没有固定形状,被透视拉成长条后边界模糊,阴雨天和晚间反光会让对比度骤降。通用目标检测模型在类别一多时,很难给"斑马线"这种弱纹理目标留出足够容量,专类数据集能把这一类做得很稳。这个资源包给的是人工标注好的1000张斑马线图片,配套VOC、COCO、YOLO三种格式标签,外加划分脚本和训练教程,拿到手可以从数据体检直接做到训练出模型,省掉标框、转格式、排目录这些琐碎活。适合两类人:一是做辅助驾驶、智慧交通,需要快速拿到一个斑马线检测基线的开发者;二是第一次接触YOLO,想用一套完整数据走通"格式→训练→验证"全流程的初学者。
2. VOC、COCO、YOLO三种标签格式:字段怎么存、怎么转换才不出错
同一个标注框,在三种格式里分别长成XML节点、JSON对象和TXT文本行。很多人在这一阶段吃过暗亏:拿着COCO的json直接丢给YOLO训练器,报错后四处找原因,最后才发现格式和坐标约定全都对不上。这一章把三种格式逐层拆开,再给一个可视化验证方案,看完能少踩一半坑。
2.1 VOC格式的XML:object与bndbox是核心
PASCAL VOC是最老也最直观的标注格式,LabelImg这类目标检测常用标注工具默认就存成它。每张图片对应一个同名XML,根节点是annotation,里面记录图片尺寸、来源和一个个object。每个object包含name、pose、truncated、difficult字段,最核心的是bndbox子节点,给出左上角(xmin, ymin)和右下角(xmax, ymax)。坐标是像素值,直接落在原图上,人眼很容易核对。
import xml.etree.ElementTree as ET xml_path = "zebra_0001.xml" tree = ET.parse(xml_path) root = tree.getroot() # VOC XML 里已经带有原图宽高,不需要另外去读图片 img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) print("图片尺寸:", img_w, img_h) for obj in root.findall("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(f"{name}: ({xmin}, {ymin}) -> ({xmax}, {ymax}),宽 {xmax-xmin},高 {ymax-ymin}")逻辑说明:这段代码把XML解析成树结构,先读size子节点里的宽高,再遍历所有object取bndbox四个角点。参数说明:xmin/ymin/xmax/ymax在VOC里都是闭区间整数像素坐标,画框时直接用,不需要任何换算。注意部分标注工具会在XML里写入difficult或occluded字段,训练时一般忽略,但转YOLO格式时不要把difficult误当成类别名。
2.2 COCO格式的JSON:images与annotations靠id关联
COCO把全量信息压缩到一个JSON文件里。顶层通常有info、licenses、images、annotations、categories五个数组或对象。images数组存每张图的id、宽高、文件名;annotations数组存每个框的image_id、bbox、area、iscrowd;categories是类别名字表。读取时靠image_id关联图片,不像VOC那样一个文件对应一张图。
import json with open("annotations.json", "r", encoding="utf-8") as f: coco = json.load(f) # 建立 image_id -> 文件名 的映射 id2name = {img["id"]: img["file_name"] for img in coco["images"]} for ann in coco["annotations"][:5]: img_name = id2name[ann["image_id"]] x, y, w, h = ann["bbox"] # COCO 存左上角 + 宽高 cat_id = ann["category_id"] print(f"{img_name}: class_id={cat_id}, x={x:.1f}, y={y:.1f}, w={w:.1f}, h={h:.1f}")逻辑说明:这段先把图片id映射到文件名,再遍历前5个标注框。参数说明:COCO的bbox是[x, y, width, height]像素坐标,不是VOC的左上右下,转格式时不少人直接拿x+width当xmax,方向没错,但要注意COCO坐标取值是浮点,四舍五入会导致框偏移一两个像素。area字段如果是从VOC转过来的,常常是0或空,U版YOLO训练不需要area,但严格按COCO指标评估的框架需要重算。
2.3 YOLO格式的TXT:五个归一化数字,class从0开始
YOLO的标签是每张图片一个TXT,文件名与图片名相同,扩展名不同。TXT里每行五个数:class_id、x_center、y_center、width、height。后四个数除以原图宽高做了归一化,取值一般在0到1之间,class_id从0开始。最大的坑就在这:中心点坐标。很多人转格式时直接拿左上角坐标填进去,模型能训练,但预测框永远偏在目标右上角。
# 把 VOC 的一个 bbox 转成 YOLO 格式 img_w, img_h = 1920, 1080 xmin, ymin, xmax, ymax = 100, 200, 500, 900 dw = 1.0 / img_w dh = 1.0 / img_h # 先算中心点,再算宽高 xc = (xmin + xmax) / 2.0 yc = (ymin + ymax) / 2.0 w = xmax - xmin h = ymax - ymin # 全部归一化到 0~1 xc_norm, yc_norm = xc * dw, yc * dh w_norm, h_norm = w * dw, h * dh print(f"0 {xc_norm:.6f} {yc_norm:.6f} {w_norm:.6f} {h_norm:.6f}")逻辑说明:中心点取左上和右下的中点,宽高直接做差,最后统一除以图片宽高。参数说明:第一列的0是类别编号,对应data.yaml里names列表的第0个元素,严格从0开始。TXT标签里不保存图片尺寸信息,所以训练器要知道原图宽高,只能靠它自己去读图片文件,这就是为什么图片路径和标签路径必须一一对应。
2.4 转换与验证:把三种格式画到同一张图上,一眼看出对不对
转换脚本写完后,别急着训练,先做可视化验证。常见做法是把同一张图的三种格式bbox分别画出来,叠加或并排对比。如果三个框的位置和大小明显不一致,一定是坐标定义搞错了,而不是画图代码的问题。
import cv2 def draw_boxes(image_path, boxes, color): img = cv2.imread(image_path) for x1, y1, x2, y2 in boxes: cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) return img # 示例:同一张图,分别从 VOC、COCO、YOLO 三种格式提取 bbox # VOC 直接用 (xmin, ymin, xmax, ymax) # COCO 先转换 bbox: [x, y, w, h] -> (x, y, x+w, y+h) # YOLO 先把归一化坐标乘回宽高,中心点转左上角: # x1 = (xc - w/2) * img_w, y1 = (yc - h/2) * img_h # x2 = (xc + w/2) * img_w, y2 = (yc + h/2) * img_h逻辑说明:可视化是格式转换的后悔药,靠它能在十分钟内发现坐标语义问题。参数说明:画框的颜色按格式区分,VOC用绿色、COCO用蓝色、YOLO用红色,排成一列对比。如果三种格式的框轮廓不重叠,优先检查"中心点对左上角""归一化漏乘""宽高写成右下角减左上角"这三类错误。
3. 划分脚本与数据体检:1000张图怎么切train/val/test,切完先查再用
数据集划分看起来是小事,实际上决定了训练结果有没有说服力。如果验证集里混进训练图片,或者某个时序场景的图片全落在训练集,模型指标会虚高,上线后立刻现原形。这一章给出划分脚本的核心写法,以及在划分前必须做的数据体检。
3.1 划分脚本的核心逻辑与可调参数
划分脚本要做的不是按数字砍三刀,而是随机打乱后按比例抽取。比例上常见做法是7:1.5:1.5或8:1:1,1000张图规模下建议train不低于600张、val不低于100张,否则验证指标波动太大。必须固定随机种子,否则每次重跑划分结果不一样,实验对不上。
import os import random from pathlib import Path random.seed(42) # 固定随机种子,划分结果可复现 image_dir = Path("images") label_dir = Path("labels") all_names = [p.stem for p in image_dir.glob("*.jpg")] random.shuffle(all_names) train_ratio = 0.7 val_ratio = 0.15 # test 占剩余 0.15 n_total = len(all_names) n_train = int(n_total * train_ratio) n_val = int(n_total * val_ratio) train_names = all_names[:n_train] val_names = all_names[n_train:n_train + n_val] test_names = all_names[n_train + n_val:] def write_split(split_names, split_name): with open(f"{split_name}.txt", "w") as f: for name in split_names: f.write(f"{name}.jpg\n") write_split(train_names, "train") write_split(val_names, "val") write_split(test_names, "test") print(f"total={n_total}, train={len(train_names)}, val={len(val_names)}, test={len(test_names)}")逻辑说明:先取所有图片的文件名主体,随机打乱,接着按索引切片,最后把每个子集的图片名写入单独的TXT列表文件。参数说明:seed不固定会导致每次运行划分结果漂移;train_ratio和val_ratio两个参数决定三份比例,改成0.8/0.1时test自动占0.1;list文件里写的是图片名而不是绝对路径,后续训练时再由data.yaml前缀拼接。
3.2 切图前先做三查:重复图片、坏文件、空标签
划分脚本跑通后,第一件事不是训练,而是体检。在YOLOv8目标检测数据集处理流程里,这一阶段的坑基本集中在标签没有对齐,常见的有三类:图片损坏打不开、标签文件内容为空、只有标签没有图片。写一个体检脚本一次性查完。
from PIL import Image from pathlib import Path image_dir = Path("images") label_dir = Path("labels") # 第一查:损坏图片 bad_images = [] for p in image_dir.glob("*.jpg"): try: Image.open(p).load() except Exception: bad_images.append(p.name) print("损坏图片:", bad_images) # 第二查:空标签(0字节或没有bbox行) empty_labels = [] for p in label_dir.glob("*.txt"): lines = [l for l in p.read_text().splitlines() if l.strip()] if not lines: empty_labels.append(p.stem) print("空标签:", empty_labels) # 第三查:标签与图片数量对应 label_names = {p.stem for p in label_dir.glob("*.txt")} image_names = {p.stem for p in image_dir.glob("*.jpg")} print("有标签没图片:", list(label_names - image_names)) print("有图片没标签:", list(image_names - label_names))逻辑说明:三查分别覆盖"图片无法解码""TXT里没有任何bbox""文件对不齐"三类问题。参数说明:PIL的load()会把图片真正读进内存,能暴露截断的半张图;TXT按行清洗后判断是否有有效标注,空行和纯注释行不算数。体检结果如果出现异常,优先人工看一眼对应文件,不要直接进训练,否则后半夜的报错都从这里来。
3.3 切完检查目录结构:YOLO官方能直接读的布局
划分脚本跑完后,数据集目录结构要符合训练器的预期。常见做法是镜像目录,图片和标签分别放images与labels两个大目录,下面再按train/val/test分。这样训练器在读取时,会自动把same stem的图片和TXT配对。
datasets/zebra/ ├── images/ │ ├── train/ 约700张.jpg │ ├── val/ 约150张.jpg │ └── test/ 约150张.jpg ├── labels/ │ ├── train/ 约700张.txt │ ├── val/ 约150张.txt │ └── test/ 约150张.txt ├── train.txt ├── val.txt └── test.txt检查时重点看两个地方:一是images/train与labels/train的文件名一一对应,二是三个子目录里图片和标签数量都大于0。有些教程习惯把所有图片平铺在同一个目录,只靠train.txt列表区分,那种布局也能训,但对新手不推荐,因为一旦TXT列表写错路径,排查成本更高。
3.4 配置data.yaml时最容易写错的三个字段
目录结构确认后,写data.yaml。这个文件是训练器的入口,常见错误集中在path、names、nc三个字段上。下面这份以Windows路径为例。
path: D:/datasets/zebra # 数据集根目录,正斜杠最稳 train: images/train # 相对 path 的图片目录 val: images/val test: images/test nc: 1 # 类别数量,斑马线只有一类 names: 0: zebra_crossing参数说明:path写绝对路径省心,但换机器要改;写相对路径时要保证运行命令的工作目录在数据集根目录下,否则找不到。train和val的值是相对path的路径,不需要写前导斜杠。nc是names列表长度,如果names有2个元素但nc写成1,训练不会报错,只是类别名错位,指标照样假。
4. YOLO训练与避坑排查:环境配置到结果验证的5个高频现场
数据准备妥当后进入正式训练。这一章先讲环境怎么搭、参数怎么设,再用5个高频坑覆盖最常见的翻车现场。斑马线是单类目标,训练本身不复杂,真正浪费时间的基本都集中在环境版本和标签对齐两件事上。
4.1 环境配置与预训练权重:版本匹配是第一步
YOLO环境配置的核心原则只有一个:torch、CUDA、训练器三者的版本互相匹配,别装最新版,装组合最稳的版本。常见做法是用conda建独立环境,再安装ultralytics训练器。yolo预训练模型下载这步,训练器默认会自动下载权重,网络不好就手动把.pt文件放到当前目录,它优先读本地文件。
conda create -n yolo python=3.10 -y conda activate yolo # 先按机器CUDA版本装torch,再装训练器 pip install torch torchvision pip install ultralytics安装完先做一件事:确认GPU被正确识别。输入python -c "import torch; print(torch.cuda.is_available())",输出True再继续。如果输出False,说明torch装成了CPU版或CUDA版本不匹配,后面所有训练都会慢几十倍,这时不要急着调参,先回头修环境。
4.2 训练启动命令与参数取值参考:imgsz、batch、epochs、patience怎么设
数据量只有1000张且是单类目标,训练参数不需要激进。下面命令用YOLOv8作为示例,预训练权重选nano版本就够,斑马线这种大纹理目标用大模型收益很低,反而增加过拟合和训练时间。
yolo detect train \ data=zebra.yaml \ model=yolov8n.pt \ imgsz=640 \ batch=16 \ epochs=100 \ patience=15 \ project=zebra_runs参数取值参考表格:
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 640 | 输入分辨率,斑马线属于大目标,640够用,涨到1280收益不大但显存翻倍 |
| batch | 8~16 | 根据显存调整,单卡16G以下先试8 |
| epochs | 100 | 配合early stopping,不必真的等100轮训满 |
| patience | 15 | 验证指标连续15轮不提升就自动停 |
| model | yolov8n.pt | 用预训练权重做迁移学习,比从头训练收敛快很多 |
逻辑说明:从预训练权重开始训属于迁移学习,模型已经会识别通用特征,只需要在斑马线上做微调。参数说明:patience是训练黑匣子里最实用的机制,它会在val mAP不再提升时自动停掉,省时间也防过拟合。imgsz提高会成倍增加计算量,小显存优先保batch而不是保分辨率。
4.3 训练与验证的5个高频坑:现象、原因、处理
以下5个问题覆盖了斑马线数据集训练中大多数踩坑现场,按重要性排序。每条都按现象到原因再到处理来写,可以直接对着排查。
坑1:训练loss在第几十轮突然变NaN
现象:训练日志里loss从正常值突然变成nan,之后一直nan,甚至整个训练崩溃。
原因:最常见两个来源,一是学习率偏高导致梯度爆炸,二是数据体检没做干净,图片里混进了解码异常的黑图或全透明图。
处理:先把lr0从默认值调到0.001左右,复跑一次;如果还是nan,回到2.2的数据体检步骤,把坏图全部剔除后重训。
坑2:mAP一直是0,但loss明显在下降
现象:训练正常跑完,loss曲线很漂亮,但val的mAP50和mAP50-95全部为0。
原因:绝大多数情况是类别编号错位。斑马线数据集的TXT里class_id是1,而data.yaml的names只有0一个类别,所有标签越界,验证时模型不知道学的是什么。
处理:统计所有TXT首列最大值,确认数据集类别编号范围。如果数据集标签里的class_id沿用了VOC的类别编号而不是从0开始,需要写脚本重映射后再训。
坑3:预测框明显偏在目标左上角,位置不对
现象:训练完成后用val图片测试,框能框住斑马线,但整体往左上角偏,框的尺寸也偏小。
原因:YOLO标签没有做归一化,或者归一化时把左上角坐标当成中心点填了。这是格式转换最常见的坑。
处理:抽查一个TXT,把五列数值乘回原图宽高,画到图上对比。如果中心点落在目标外,按2.3的转换公式重写TXT,然后再训一轮。
坑4:刚启动训练就报CUDA out of memory
现象:训练刚开始,进度条没跑几个batch就报torch.cuda.OutOfMemoryError,进程直接退出。
原因:batch或imgsz超出显存,另一个隐藏因素是Windows下显存碎片化严重。
处理:先把batch减半,imgsz降回640;还不行就在训练前设置环境变量,分段显存分配能缓解碎片问题。设置方式在bash里执行export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True。
坑5:每个epoch耗时越来越长,日志像卡死
现象:第一个epoch正常,后面每轮时间暴涨,进度条半天不动。
原因:最常见是num_workers设置不当导致CPU数据加载成为瓶颈,尤其是Windows系统下worker进程频繁重启。
处理:训练命令里加workers=4,Windows机器建议直接设workers=0,让数据加载在主进程跑。另外给训练命令加cache=True,把图片一次性缓存进内存,1000张图占不了多少内存,但训练速度能快好几倍。
4.4 训练产物解读:best.pt、loss曲线和混淆矩阵
训练完,项目目录zebra_runs下会生成一系列文件。你需要关注的就四样:best.pt是验证集上指标最好的权重,last.pt是最后一轮的权重,results.png是loss和mAP曲线,confusion_matrix.png是混淆矩阵。评估时会犯的一个错误是拿last.pt去推理,正确做法是直接用best.pt,它才是整个训练过程中泛化能力最强的版本。
results.png怎么看:上面一行是训练loss,下面一行是验证指标。training loss持续下降但val loss先降后升,就是过拟合信号,训练器的early stopping已经替你拦住了。混淆矩阵在单类目标的数据集上,重点不是看类间混淆,而是看背景那一列——如果背景被大量识别成斑马线,说明训练集里负样本不够,需要补充不含斑马线的图片或加大负样本增强。
5. 验证与改进:mAP、PR曲线之外,斑马线检测的专项优化技巧
5.1 验证方法:不看loss看mAP的三大支柱
loss曲线能反映收敛,但不能直接说明检测效果。验证阶段我会固定看三个指标:mAP50、mAP50-95和PR曲线。mAP50是IoU阈值0.5下的平均精度,斑马线这种单类目标通常能到0.95以上才算合格;mAP50-95是多个IoU阈值的平均,数值会明显低于mAP50,这是正常的,如果它也能到0.7以上,说明框的位置精度很好。PR曲线看的是置信度阈值变化时,精确率和召回率的折中关系。对斑马线这类目标,宁可损失一点精确率也要保住召回率,因为漏检斑马线在辅助驾驶场景里的后果比误检严重得多。
5.2 三个针对斑马线的可复现改进实验
第一,数据增强实验。斑马线是线性纹理目标,训练时把Mosaic增强的mosaic概率保持默认0.5左右,额外加大垂直方向的随机透视变换,模拟不同俯仰角下的观感,能明显提升雨天路口的泛化。第二,检测框长宽比实验。斑马线天然是长条目标,训练时用矩形推理模式,少填充黑边,让输入图片的短边不缩得太小。第三,后处理实验。斑马线容易被遮挡或磨损,推理时把NMS的IoU阈值从默认0.45放宽到0.5,能救回部分被压掉的重复检测框,对密集斑马线效果尤其明显。
这三个实验我一般会每个单独开一版跑,改两个以上参数就不清楚是哪个在起作用。自己曾经因为一次性把batch、imgsz、增强全改了,结果指标涨了但找不到原因,后来白复盘了一场。现在每次拿到新数据集,第一件事永远是统计TXT首列最大值,而不是直接开训,这个习惯帮我省掉了大半的截图返工。希望帮到你。
本文还有配套的精品资源,点击获取