简介:地铁警戒线检测数据集围绕地铁安全监控场景构建,主要面向目标检测算法开发者、科研人员以及交通视觉方向学习者,用于训练与评估警戒线(warning_line)识别模型。压缩包共2000个文件,容量约116.34MB,主要包含jpg图片、VOC格式的xml标注文件和YOLO格式的txt标注文件;标签种类仅1类,标注框总数为1635个,均采用矩形框,用于目标检测识别中的警戒线定位。图片清晰度较高且未做增强处理,可较真实反映地铁现场画面,便于按需自行扩充或测试模型对原始数据的拟合能力。标注文件按JPEGImages、Annotations、labels三个文件夹分类存放,并附有说明文档,可快速了解目录结构,直接接入常见目标检测框架。目前已有159人学习下载,统一规范的VOC+YOLO双格式标注可显著减少数据整理时间,适合作为地铁警戒线检测方向的训练或验证数据。
1. 地铁警戒线检测数据集:1125 张双格式标注,能不能直接拿去训练?
地铁站台的黄色警戒线,是巡检、辅助驾驶和安防系统里最容易忽略却最难做好的检测目标。它细长、反光、透视畸变大,还经常被候车人群挡掉一半。做过这类项目的人都知道,标注成本比训练成本高得多,而「数据集地铁警戒线检测数据集yolo+voc格式1125张.zip」解决的就是从零标注的第一道坎:图片集好了,YOLO 和 VOC 两种格式出好了,拿到手可以直接开训。这篇笔记从拆包、校验、训练到部署,把这个数据集的用法和坑一次说清楚。适合刚拿到这个包、准备训 YOLO 模型、不确定标注格式对不对、参数怎么设的工程师。
2. 先看清 1125 张的底牌:检测难点与 VOC/YOLO 双格式选型
2.1 警戒线为什么难检:细长目标、透视畸变与光照干扰
地铁站台的警戒线是一条 10-20 厘米宽的黄色条带,从监控或车载相机视角看过去,变成一个窄长的四边形。它的标注框长宽比通常在 1:5 到 1:20,属于典型的细长目标,和 COCO 里那些接近正方形的目标完全不是一类分布。YOLO 的锚框设计默认覆盖常见宽高比,遇到这种极端比例的框,特征图下采样到 20x20 那层时,整条线只剩几个像素,能不能捡回来全看浅层特征和上采样质量。这也是这类数据集不能像通用目标检测那样把 imgsz 设太低的原因,我一般从 640 起步,验证集里小目标占比高就提到 960。
透视畸变是第二个难点。站台同一根警戒线,近端在画面底部占半幅,远端在画面上部缩成一条短线,同一个类别同时呈现超大目标和极小目标两种尺度。举个具体例子:1920x1080 的站台监控画面里,近端警戒线可能占 800x60 像素,远端只有 60x8 像素,框面积差了近 100 倍。这种尺度跨度下,模型如果只在单一特征层做预测,小目标那端很容易丢。YOLOv8 的多尺度输出能缓解,但 P5 层对 60x8 的目标依然不友好,所以很多做警戒线的项目会把图像切成两半分别推理,或者把 imgsz 拉到 960、1280 再训。这在通用检测里是过度配置,在细长目标场景里是常规操作。
第三个难点是光照。地铁站荧光灯下黄色漆面会反光,地面潮湿时倒影把线体「染」成橙色,夜间或应急照明下还会严重偏色。所以拆包后第一件事不是急着训练,而是随机抽 20 张,看这 1125 张里到底覆盖了多少种视角、光照和遮挡情况。这个判断直接影响你后面要不要补数据、要不要加强色彩增强。
2.2 VOC 与 YOLO 格式的差异:坐标系统与命名规则
VOC 格式是每个图片对应一个同名 XML,里面用 xmin、ymin、xmax、ymax 四个像素坐标描述框的位置,类别名写在 object/name 节点。它适合人读,是 labelImg 这类标注工具的默认导出;YOLO 格式则是每个图片对应一个同名 txt,每行写 class_id x_center y_center width height,四个数值全部归一化到 0-1,是 YOLO 系列训练直接读取的格式。
| 对比项 | VOC (XML) | YOLO (txt) |
|---|---|---|
| 坐标含义 | 左上/右下像素坐标 | 中心点 + 宽高,归一化 |
| 文件扩展名 | .xml | .txt |
| 每张图对应 | 1 个 XML | 1 个 txt,多行 |
| 人读性 | 好,适合检查 | 差,适合机器 |
这套双格式的关键在于两种格式必须描述同一个标注结果。很多双格式数据集是一次标注后跑转换脚本生成的,转换过程中可能出现 off-by-one、越界框过滤、取整误差。VOC 是给人读的,YOLO 是给模型读的,两者对不上,就会出现「看 XML 觉得没问题,训出来一塌糊涂」的错觉。拿到包以后别急着把 XML 删了腾地方,留着它做校验。另外注意类别命名,VOC 里可能写「警戒线」或 "yellow_line",YOLO 的 class id 从 0 开始编号,映射关系要以 data.yaml 或 class_names.txt 为准。还要看一眼 XML 的 size 节点是否和图片实际分辨率一致,有些包批量 resize 过图片但 XML 没同步改,这是最容易出事的点。
2.3 1125 张的量级怎么评估:单类检测不需要等 COCO
单类检测任务里,1125 张是个「够起步」的量级。公共数据集如 COCO 虽然量大,但地铁站台是封闭场景,相机位置固定、背景单一,域差异比数量更致命。真正合适的数据几百到一千张,配合预训练权重,YOLOv8s 就能训出能用的模型。所以不用因为 1125 张听起来少就怀疑它,重点看内部的多样性分布。如果 900 张来自同一个站台同一个时间段,那有效场景只有一个,模型泛化到其他站台大概率翻车;如果覆盖了不同站台、不同朝向、不同时段,可用性就很高。
量不够的时候不要硬上大模型。yolov8n 或 yolov8s 在千级数据集上是性价比最高的选择,配合 mosaic、hsv 增强把样本多样性撑起来。警戒线每张图一般只有 1-2 个目标,类别区分容易,真正的瓶颈在小目标命中,所以数据增强里 mosaic 的收益比加大模型容量更明显。这个判断也决定了后面 4.3 节的参数设置,先小模型跑通,再决定要不要换大的。
3. 拆包与质量校验:双格式数据集的标准体检
3.1 先看目录结构:两种格式怎么共存在一个 zip 里
解压之后先别急着开训练,花两分钟把目录结构看清楚。常见打包方式是三个目录并列:images 放原始图片,xml 或 Annotations 放 VOC 标注,labels 放 YOLO 的 txt,外层可能还有一个 train.txt / val.txt 写好了划分名单。不同来源的包目录名会变,但思路一致,先摸清三件事:图片后缀是 jpg 还是 png、xml 和 txt 是否和图片同名、划分名单存不存在。
unzip 数据集地铁警戒线检测数据集yolo+voc格式1125张.zip -d metro_data cd metro_data find . -maxdepth 2 -type d | sort ls images | head -5 ls xml | head -5 ls labels | head -5这段命令先列出目录树,再抽查三种文件的前几条。重点看文件名前缀是否一致:图片如果叫 IMG_0001.jpg,xml 和 txt 必须叫 IMG_0001.xml、IMG_0001.txt,任何一方多了前缀或后缀,后面的训练都会报找不到标签。图片后缀大小写也得留意,Windows 解压工具经常保留 .JPG 大写,YOLO 匹配标签时按原始名找文件,大小写不一致直接找不到。
注意:先确认 train.txt / val.txt 里写的划分名单和图片文件名完全一致,不一致时按 4.2 的脚本自行重新划分。
3.2 XML 与 txt 的一致性校验:一段脚本兜底
双格式数据集最常见的隐患是两套标注不一致。我收到这类包的第一件事,就是跑一遍一致性校验,把 XML 转成归一化坐标和 txt 逐行比对。脚本不长,但能省下后面好几次训练翻车的时间。
import glob, xml.etree.ElementTree as ET def xml_boxes(xml_path): root = ET.parse(xml_path).getroot() w = float(root.find('size/width').text) h = float(root.find('size/height').text) boxes = [] for obj in root.findall('object'): b = obj.find('bndbox') xmin, ymin = float(b.find('xmin').text), float(b.find('ymin').text) xmax, ymax = float(b.find('xmax').text), float(b.find('ymax').text) boxes.append(((xmin + xmax) / 2 / w, (ymin + ymax) / 2 / h, (xmax - xmin) / w, (ymax - ymin) / h)) return boxes def txt_boxes(txt_path): boxes = [] for line in open(txt_path): parts = line.strip().split() if len(parts) == 5: boxes.append(tuple(map(float, parts[1:]))) return boxes tol = 0.005 bad = 0 for xml_path in sorted(glob.glob('xml/*.xml')): name = xml_path.split('/')[-1][:-4] try: xb = xml_boxes(xml_path) tb = txt_boxes(f'labels/{name}.txt') except FileNotFoundError: print('缺 txt:', name) bad += 1 continue if len(xb) != len(tb): print('数量不一致:', name, len(xb), len(tb)) bad += 1 continue for a, b in zip(xb, tb): if any(abs(x - y) > tol for x, y in zip(a, b)): print('坐标不一致:', name) bad += 1 break print('问题文件数:', bad)脚本逻辑分三段:先从 XML 里读出图像宽高和所有 bndbox,转成归一化的 x_center、y_center、width、height;再从同名 txt 读出每一行后四列;最后按数量和坐标逐项比对。tol 取 0.005 是因为 XML 里是整数像素坐标,除以大图宽高后本身有一丁点取整误差,超过 0.01 基本可以断定是导出时出了问题。跑完会输出三类问题文件:缺 txt、目标数量不一致、坐标不一致。数量不一致通常意味着转换时过滤了越界框或小于阈值的框,这类框在 VOC 里看得到、在 YOLO 里参与不了训练。
3.3 类别与框分布统计:训练前唯一值得做的体检
一致性校验通过之后,再做一次框分布统计,判断这套数据的难点在哪里。单类数据集不会在类别上出问题,但框的长宽比和大小分布直接决定 imgsz 和增强策略。
import numpy as np, glob ratios, areas = [], [] for txt in sorted(glob.glob('labels/*.txt')): for line in open(txt): p = line.strip().split() if len(p) != 5: continue w, h = float(p[3]), float(p[4]) if w * h > 0: ratios.append(max(w / h, h / w)) areas.append(w * h) print('框数:', len(areas)) print('长宽比中位数:', np.median(ratios)) print('面积分位数:', np.percentile(areas, [10, 50, 90]))长宽比中位数超过 5,说明细长目标占主导,训练时锚框和 NMS 的宽容度都要相应调整;面积分位数里如果 10% 分位小于 0.01,说明存在一批小目标,imgsz 640 可能不够,要提到 960,或者做切片推理。这两个数字是后面选参数最直接的依据,比看十篇经验贴都管用。
4. 用 YOLOv8 把这套双格式数据训成模型
4.1 PyCharm 里搭 YOLOv8 环境:装库、验 GPU、配解释器
常见做法是用 conda 建独立环境,装 ultralytics 这一个包就能带起 YOLOv8 的训练、验证、导出全套。PyCharm 里只需要把项目解释器指到这个环境,剩下全部在终端跑。装完之后先验 GPU,很多翻车案例都是在 CPU 上跑了半天才发现 torch 的 CUDA 版本没配对。
conda create -n metro_yolo python=3.10 -y conda activate metro_yolo pip install ultralytics python -c "import torch; print(torch.cuda.is_available())"打印 True 再往下走。False 的原因通常是 torch 装成了 CPU 版,重装对应 CUDA 版本的 torch 再验一次。只有 CPU 也能训,但迭代速度会慢一个量级,调参体验很差。我的建议是至少找一张支持 fp16 的显卡再开始刷实验,哪怕只是 6GB 显存的小卡,yolov8s + batch 8 也跑得动。
4.2 目录重组与 data.yaml:别让 YOLO 找不到标签
YOLOv8 默认读取 dataset/images/{train,val} 和 dataset/labels/{train,val} 四层目录。如果这个包没有按这个结构组织,或者没有给划分名单,就自己拆。我用固定随机种子保证每次重跑划分一致,这也是复现实验的前提。
import os, random, shutil from glob import glob random.seed(42) imgs = sorted(glob('images/*.jpg')) random.shuffle(imgs) val_n = max(1, int(len(imgs) * 0.1)) for i, img in enumerate(imgs): split = 'val' if i < val_n else 'train' name = os.path.basename(img)[:-4] dst_img = f'dataset/images/{split}/{name}.jpg' dst_txt = f'dataset/labels/{split}/{name}.txt' os.makedirs(os.path.dirname(dst_img), exist_ok=True) os.makedirs(os.path.dirname(dst_txt), exist_ok=True) shutil.copy(img, dst_img) shutil.copy(f'labels/{name}.txt', dst_txt)这段按 9:1 划分,复制而不是移动,原始包保留着,后面校验还能用。划分完成后写 data.yaml,注意 path 用绝对路径最省心,names 的 id 必须和 txt 第一列数字对应。
path: /home/you/metro_data/dataset train: images/train val: images/val names: 0: warning_lineVOC 里类别名可能是「警戒线」或 "yellow_line",到 YOLO 这边统一映射成 id 0,名字叫什么不影响训练,只影响日志可读性。如果 txt 里出现过 id 1 而 yaml 只写了 0,训练不会报错,但验证指标会一路飘零,这类问题在第 5 章单独说。
提示:复制而不是移动原始文件,训练完还要回头做标注核查时,原始包就是后悔药。
4.3 训练命令与参数:从 s 模型起步,看着 loss 回调走
yolo detect train \ data=/home/you/metro_data/dataset/metro.yaml \ model=yolov8s.pt \ epochs=120 \ imgsz=640 \ batch=16 \ patience=30 \ device=0 \ flipud=0.0 \ fliplr=0.3 \ hsv_h=0.02 \ hsv_s=0.5参数分两类说。模型与资源:model 用 yolov8s.pt 预训练权重起步,120 轮配合 patience=30 做早停,足够覆盖大部分收敛情况;imgsz 按 3.3 的统计结果来,小目标多就改 960;batch 显存不够就减半。数据增强:flipud 必须关,站台场景上下翻转会让警戒线跑到天花板上,语义直接错乱;fliplr 保留 0.3,水平翻转对细长目标影响小;hsv_h 和 hsv_s 加的幅度比默认稍大,因为地铁黄色漆面在不同光照下色偏明显,这一点是这类场景的血泪经验。
训练日志里每轮会同时打印 box_loss、cls_loss、dfl_loss 三个损失项,YOLOv8 的总损失就是这三项的加权和。细长目标上 box_loss 降得慢是正常的,框的宽高对几个像素的偏差都很敏感;cls_loss 如果一直贴在很低的位置,说明类别太好分,重心要放到定位和召回上去。别单独去调损失权重,先看验证集 mAP 的走势再决定。
5. 避坑排查:双格式数据集训练翻车的 5 个典型问题
下面五条是从我跑同类「双格式 + 特定场景」数据集时踩过的坑里挑出来的,每条按现象、原因、解决三步写,遇到类似症状可以直接对照。
5.1 现象一:XML 里有 3 个目标,txt 里只有 2 行
现象:训练正常跑完,抽检标注时发现同一张图 XML 和 txt 的框数对不上。
原因:转换脚本通常会把面积过小、超出图像边界的框当作无效框过滤掉。XML 保留原始标注,YOLO 格式是过滤后的导出结果,两套数据天然不一致。
解决:用 3.2 的校验脚本把数量不一致的图片名捞出来。如果缺的是有效框,说明导出阈值设得不合理,重新导一次或手工补标;如果缺的本来就是无效框,在 README 里备注清楚,别让后来的人再踩一遍。
5.2 现象二:loss 正常下降,val 的 mAP50 始终是 0
现象:训练日志一切正常,第一次验证时 mAP50 稳在 0,怎么调都是 0。
原因:data.yaml 的 names 和 txt 里的 class id 对不上,或者 txt 坐标被当成像素坐标又归一化了一次,导致所有框都跑到图像外面去了。
解决:单张图 debug,把 txt 里的四列反算成像素坐标画出来,看框是否落在图内;再打印 txt 第一列的最大值,如果出现过大于 names 数量的 id,说明类别编号有问题。这两步做完,问题基本能定位。
5.3 现象三:警戒线被识别成栏杆或排水沟盖板,两类混检
现象:推理时同一个物体在不同帧交替输出两个类别,边界框也忽大忽小。
原因:标注时把警戒线附近视觉相近的物体一起框进去了,正样本不纯;更常见的是框体没贴线体,整条线加地砖一起框,导致模型学到的特征是「黄色区域」而不是「黄色线条」。
解决:检查标注规范,只框线体本身;把易混淆的物体作为负样本补进训练集做 hard negative mining。这一步对安全类检测尤其重要,误报消耗的是维保人员的信任。
5.4 现象四:一启动就报 No labels found in images/train
现象:训练刚开始,控制台提示标签目录为空,或 loss 恒为 0。
原因:图片后缀 .JPG 和标签同名文件 .jpg 大小写不一致;或者标签目录结构是 labels/ 下面平铺 txt,而 YOLOv8 期望 labels/train/ 和 labels/val/ 分目录。
解决:批量把图片后缀统一成小写,再用 3.2 脚本重新核对一遍文件对应关系;目录结构按 4.2 的重组脚本走一遍。
5.5 现象五:验证集 mAP 很高,拿到现场拍视频几乎全 miss
现象:测试集指标亮眼,部署到真实站台视频上一帧都检不出来。
原因:数据集和实拍之间存在域偏移:采集设备白平衡不同、站台灯带反光、雨天地面倒影把黄色线「染」成橙色,训练集里根本没出现过这些情况。mAP 高只能说明模型在训练分布上表现好,不能说明它能扛住现场分布。
解决:用现场视频抽 50-100 帧做校准集,先跑一遍看失败模式,再做色彩增强或直接拿这批图微调。训练集和验证集同分布是评估的前提,但这个前提在现场不成立。
6. 从 mAP 到上线:验证指标和部署参数一起看
6.1 用混淆矩阵和 PR 曲线判断警戒线检测的真实水平
训练完成别只看 last.pt 的 mAP 数字,跑一次完整的验证并把图表输出,看混淆矩阵和 PR 曲线。命令里 conf 要设到 0.001,目的是让所有低置信度框都参与绘图,得到完整的 PR 曲线,而不是只看到默认阈值下的一个点。
yolo detect val \ model=/home/you/metro_data/runs/detect/train/weights/best.pt \ data=/home/you/metro_data/dataset/metro.yaml \ plots=True \ conf=0.001跑完在 runs/detect/val/ 下找 confusion_matrix.png 和 PR_curve.png。混淆矩阵里背景误报占比如果超过目标类别的一半,说明模型在「像黄色但不是线」的东西上犯迷糊,回到标注规范上去修。警戒线检测属于安全类任务,漏检比误报危险,所以选阈值时往召回高的一段走,哪怕误报多一点也能接受。
6.2 导出 ONNX 前后的参数差异
部署时模型格式通常是 ONNX 或 TensorRT,注意导出的模型不再读训练时的 conf 和 iou 阈值,这些参数交给推理侧决定。导出命令很简单,但导出后一定要用同一张图对比 PyTorch 和 ONNX 的输出,防止算子兼容性把框坐标弄偏。
yolo export model=best.pt format=onnx imgsz=640 opset=12导出之后,onnxruntime 推理时把 conf 设在 0.15-0.25,iou 设在 0.5,具体按 6.1 里 PR 曲线选的阈值来。如果部署目标是像 AGX Orin 这样的边缘设备,输入尺寸可以降回 640 甚至 416 换帧率,单类检测对分辨率不那么敏感,警戒线又是大面积色块目标,降分辨率损失可控。我自己的习惯是,拿到任何双格式数据集都先跑一遍一致性校验再开训,这套流程救过我很多次;跑通之后也别把 1125 张当终点,到现场按真实视角补 100 张,比调一周参数都管用。希望帮到你。
本文还有配套的精品资源,点击获取