地铁警戒线检测数据集:1125张VOC/YOLO双格式标注与YOLO训练实战
简介本资源为地铁警戒线检测数据集面向从事目标检测算法训练与验证的开发者、学生及研究人员可用于地铁站台安全监控场景下的警戒线识别模型开发。数据集同时提供VOC与YOLO两种标注格式压缩包内包含图片、xml与txt三类文件共2000个文件其中1125个xml标注文件、875个txt标签文件整体约116.34MB图片清晰且未做数据增强标签为矩形框仅含warning_line一类总框数达1635个标注准确合理。已有159人学习下载。读者可直接获得1125张jpg原图及对应双格式标注省去格式转换与清洗成本快速投入YOLO或VOC流程训练适合作为地铁警戒线检测的基线数据也可用于验证模型在单一类别小目标场景下的表现。1. 地铁警戒线检测数据集1125 张双格式标注能不能直接开练做轨道交通场景的视觉算法最头疼的往往不是模型选型而是找不到一份标注干净、格式齐全、拿来就能进训练管线的数据集。这份「地铁警戒线检测数据集」就是冲着这个痛点来的1125 张站台实拍图同时提供 VOC 的 xml 和 YOLO 的 txt 两套标注标签只有一类warning_line总框数 1635。换句话说平均每张图 1.45 个警戒线目标场景聚焦、类别单一非常适合做站台安全监控、越线告警这类单类检测任务的起步训练集。它适合谁刚接手轨道交通视觉项目、需要快速跑通 baseline 的算法工程师以及想拿真实场景练手目标检测的学生和自学者。不适合谁想要多类别、复杂遮挡、密集小目标挑战的人——这份数据集的定位就是「干净、聚焦、可复现」不是刷 SOTA 的竞技场。2. 拆包先看结构VOC 与 YOLO 双格式到底怎么对应拿到压缩包别急着解压进训练脚本先把目录结构和标注逻辑摸清楚。这份数据集的核心价值在于「一份图、两套标注」理解它们之间的映射关系后面转换、校验、训练才不会翻车。2.1 三个文件夹的职责划分解压后你会看到三个平级文件夹这是 VOC 数据集最经典的布局文件夹内容数量作用JPEGImagesjpg 图片1125原始图像训练与验证的输入Annotationsxml 文件1125VOC 格式标注含 bbox 坐标与类别labelstxt 文件1125YOLO 格式标注归一化后的类别与坐标三个文件夹的文件名主干是一一对应的比如sl_images157.jpg对应sl_images157.xml和sl_images157.txt。项目正文里列出的sl_images157.txt、sl_images139.txt、sl_images490.txt这些就是 labels 文件夹里的部分样本名。命名规则统一没有花哨的前缀后缀这对批量脚本非常友好。提示解压后先做一次文件名对齐检查确认三个文件夹的主干名集合完全一致。任何一边多一个或少一个都会在训练时变成静默丢样本的坑。2.2 VOC 的 xml 里有什么VOC 格式的 xml 是「人类可读、机器可解析」的典型代表。打开任意一个 xml你会看到size记录图像宽高object下记录类别名和bndbox的 xmin/ymin/xmax/ymax。这份数据集的类别名固定为warning_line坐标是绝对像素值。annotation folderJPEGImages/folder filenamesl_images157.jpg/filename size width1920/width height1080/height depth3/depth /size object namewarning_line/name bndbox xmin412/xmin ymin806/ymin xmax1503/xmax ymax874/ymax /bndbox /object /annotation这里的关键参数是size里的宽高——YOLO 格式的归一化就靠它。如果 xml 里的宽高和实际图片不一致转换出来的 txt 坐标会整体偏移这是最常见的翻车点之一。2.3 YOLO 的 txt 里有什么YOLO 格式的 txt 每行一个目标格式是类别索引 中心x 中心y 宽 高全部归一化到 0~1。这份数据集只有一个类别所以类别索引恒为 0。0 0.498437 0.777778 0.568229 0.062963换算逻辑是中心 x (xmin xmax) / 2 / 图像宽宽 (xmax - xmin) / 图像宽y 方向同理。你可以用下面这段脚本快速验证 xml 和 txt 是否自洽import os import xml.etree.ElementTree as ET def parse_voc(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) boxes [] for obj in root.iter(object): name obj.find(name).text bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 转成 YOLO 归一化格式 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h boxes.append((name, cx, cy, bw, bh)) return w, h, boxes # 抽查一个样本和 labels 里的 txt 对比 w, h, boxes parse_voc(Annotations/sl_images157.xml) print(f图像尺寸: {w}x{h}) for b in boxes: print(f{b[0]} {b[1]:.6f} {b[2]:.6f} {b[3]:.6f} {b[4]:.6f})这段脚本的作用是把 xml 重新算一遍 YOLO 坐标输出结果应该和 labels 文件夹里对应的 txt 行几乎一致浮点精度范围内。如果差得离谱说明 xml 的 size 和真实图片对不上或者标注本身有问题。参数上唯一需要注意的是float()转换——有些标注工具会写出带空格的文本稳妥做法是加.strip()。3. 从零跑通训练YOLO 格式的目录组织与配置格式看懂了接下来就是把它喂给训练框架。这份数据集已经自带 YOLO txt省掉了转换步骤但目录组织仍然要自己动手。下面以常见的 YOLO 系列训练管线为例把路径、配置文件、划分脚本一次讲清。3.1 组织成 YOLO 训练目录YOLO 训练要求 images 和 labels 分开放且路径结构镜像对应。推荐这样组织dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml把 JPEGImages 里的 jpg 按比例复制到 images/train 和 images/vallabels 里的 txt 同名复制到对应 labels 子目录。注意labels 目录下不能有 images 以外的文件否则训练时会报「找不到标签」或直接跳过。import os import shutil import random random.seed(42) # 固定随机种子保证划分可复现 src_img JPEGImages src_lbl labels dst_root dataset names [os.path.splitext(f)[0] for f in os.listdir(src_img) if f.endswith(.jpg)] random.shuffle(names) split int(len(names) * 0.8) # 8:2 划分 train_names names[:split] val_names names[split:] for subset, subset_names in [(train, train_names), (val, val_names)]: os.makedirs(f{dst_root}/images/{subset}, exist_okTrue) os.makedirs(f{dst_root}/labels/{subset}, exist_okTrue) for n in subset_names: shutil.copy(f{src_img}/{n}.jpg, f{dst_root}/images/{subset}/{n}.jpg) shutil.copy(f{src_lbl}/{n}.txt, f{dst_root}/labels/{subset}/{n}.txt) print(ftrain: {len(train_names)}, val: {len(val_names)})逻辑说明先收集所有图片主干名用固定种子打乱后按 8:2 切分。random.seed(42)是血泪经验——不固定种子每次划分不同实验就没法对比。参数上split控制训练集比例1125 张按 8:2 大约是 900 训练、225 验证对小数据集来说验证集够用。如果你的场景对召回更敏感可以调到 9:1但验证集少于 100 张时指标波动会很大。3.2 data.yaml 怎么写YOLO 的 data.yaml 是训练入口配置这份数据集只有一个类别写起来很干净path: ./dataset train: images/train val: images/val nc: 1 names: 0: warning_linenc是类别数必须和 names 的条目数一致。names的键是类别索引值要和 txt 里的索引对应——这份数据集 txt 里全是 0所以 names 只写 0 即可。常见错误是把nc写成 2 或者 names 里塞了背景类YOLO 不需要显式背景类写了反而会导致索引错位。3.3 启动训练与关键参数配置就绪后一条命令就能开跑yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectruns/warning_line \ namebaseline参数逐个说model选 nano 版是因为数据集只有 1125 张、单类别大模型容易过拟合imgsz640是通用起点如果你的警戒线在图中占比很小可以提到 960 或 1280但显存和速度要权衡batch16在 8G 显存上比较稳爆显存就降到 8patience20表示 20 轮无提升就早停小数据集上能省不少时间。训练过程中重点看mAP50和mAP50-95单类别任务里 mAP50 上 0.9 通常不难难的是 mAP50-95它反映框的贴合精度。注意第一次训练前先用 10 个 epoch 跑个冒烟测试确认 loss 在下降、验证集有预测输出。直接上 100 epoch 发现配置错了浪费的是自己的时间。4. 避坑与排查标注、路径、指标里的五个真实翻车点数据集再干净落地时也有一堆细节能把人绊住。下面这五条是我在实际项目里反复遇到的按「现象 → 原因 → 解决」整理照着排查能省掉大量玄学调试。4.1 训练 loss 正常但 mAP 恒为 0现象训练日志里 box_loss、cls_loss 都在降但验证集 mAP 一直是 0预测框一个都不输出。原因最常见的是 data.yaml 里names的索引和 txt 里的类别索引对不上。比如 txt 写的是 0names 却从 1 开始编号模型学到的类别和评估时对不上。其次是 val 路径写错验证时加载的是空目录。解决先用os.listdir确认 val 目录下图片数量不为 0再抽查一个 txt 的首列确认全是 0最后核对 data.yaml 的 names 键值。三步走完基本能定位。4.2 图片和标签数量对不上现象训练日志提示N images, M labelsN 和 M 不相等或者某些图片被跳过。原因labels 目录里混入了非 txt 文件比如.DS_Store、Thumbs.db或者图片和标签的主干名有细微差异多了空格、大小写不一致。解决跑一段校验脚本把 images 和 labels 的主干名集合做差集import os img_names {os.path.splitext(f)[0] for f in os.listdir(dataset/images/train)} lbl_names {os.path.splitext(f)[0] for f in os.listdir(dataset/labels/train)} print(有图无标签:, img_names - lbl_names) print(有标签无图:, lbl_names - img_names)差集为空才算干净。有图无标签的样本会被当成纯背景有标签无图的直接报错。4.3 警戒线框贴边导致坐标越界现象转换或训练时提示坐标超出 [0,1] 范围或者框被裁掉一部分。原因警戒线常常横跨整个画面标注时 xmin 可能为 0、xmax 等于图像宽归一化后正好是 0 和 1。如果标注工具有 1 像素误差就会算出 1.0003 这种越界值。解决在转换脚本里加一步 clampcx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0)同时检查 xml 里的 xmax 是否大于 width大于就说明标注本身有问题需要回看原图。4.4 分辨率不一致导致归一化错乱现象部分样本的框位置整体偏移像是被拉伸或压缩过。原因xml 里记录的 size 和实际 jpg 的尺寸不一致。有些标注流程会先缩放图片再标注但 xml 里写的是缩放前的尺寸或者反过来。解决用 PIL 读实际图片尺寸和 xml 的 size 对比from PIL import Image import xml.etree.ElementTree as ET img Image.open(JPEGImages/sl_images157.jpg) root ET.parse(Annotations/sl_images157.xml).getroot() size root.find(size) xml_w, xml_h int(size.find(width).text), int(size.find(height).text) print(f实际: {img.size}, xml: ({xml_w}, {xml_h}))不一致的样本要么重新标注要么以实际图片尺寸为准重算坐标。这份数据集摘要里标注了「图片清晰、未增强」理论上尺寸应该一致但抽查一遍不亏。4.5 验证集指标虚高现象验证集 mAP 很高但拿真实站台视频一测漏检严重。原因1125 张图如果来自同一段视频的连续帧相邻帧高度相似随机划分会让训练集和验证集出现「近重复样本」验证指标虚高。这是小数据集最隐蔽的坑。解决如果图片名带帧号按帧号区间划分而不是随机划分或者用感知哈希做去重后再划分。退一步至少把验证集指标当作参考真正验收要用独立采集的测试片段。5. 进阶技巧用 1635 个框反推标注质量与训练策略数据集给了 1635 个框、1125 张图这两个数字本身就藏着信息。平均每张 1.45 个框说明大部分图只有一条警戒线少数图有两条。这个分布决定了你的训练策略不要用针对密集小目标的数据增强比如 Mosaic 开到很高否则会把单条长线切得七零八落反而伤害学习。我一般会先做一次框的宽高比统计判断警戒线的形态分布import os ratios [] for f in os.listdir(labels): with open(flabels/{f}) as fp: for line in fp: parts line.strip().split() if len(parts) 5: bw, bh float(parts[3]), float(parts[4]) if bh 0: ratios.append(bw / bh) ratios.sort() print(f样本数: {len(ratios)}) print(f宽高比中位数: {ratios[len(ratios)//2]:.2f}) print(f宽高比 P10: {ratios[int(len(ratios)*0.1)]:.2f}) print(f宽高比 P90: {ratios[int(len(ratios)*0.9)]:.2f})如果中位数远大于 1说明警戒线普遍是横向长条那么 anchor 或模型的正样本匹配策略要偏向宽扁框。YOLO 系列是 anchor-free 的重点就落在数据增强上可以关掉 Mosaic 的最后 10 个 epoch让模型在真实长条分布上收敛。另一个技巧是用框数分布做分层验证。把「单框图」和「多框图」分开统计指标如果多框图的召回明显低说明模型对同图多目标的分辨能力不足可以考虑提高 imgsz 或增加多框样本的采样权重。还有一个容易被忽略的点警戒线是语义上高度依赖上下文的类别——它总是出现在站台边缘、黄线位置。如果你的部署场景和训练场景的拍摄角度差异大光靠这份数据集可能不够建议用少量目标场景图片做微调哪怕只有几十张也能显著提升实际表现。从那以后我每次拿到单类别数据集都会先跑一遍框分布统计和分层验证再决定增强策略和 imgsz而不是上来就套默认配置。希望帮到你。本文还有配套的精品资源点击获取