
简介面向YOLO系列算法目标检测任务的数据集聚焦火车、轨道、手推车三类目标包含3793张已标注图像并已完成训练集/验证集划分配套data.yaml配置文件可直接用于yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流版本训练与测试。压缩包共2000个文件以XML标签文件为主同时提供YOLO格式TXT标签与VOC格式XML标签两套标注分别存放在独立文件夹中便于按需选用或格式转换。资源包大小约236.33MB已有100人学习。标注格式规范YOLO标签采用class、x_center、y_center、width、height归一化坐标上手门槛低XML标签则保留了更完整的VOC结构适合需要读取边界框、类别与图像尺寸的脚本流程。对正在做目标检测课程设计、算法对比实验或铁路场景视觉项目的开发者来说这份数据集省去了自行采集与标注的繁琐步骤可直接投入模型训练与效果验证。1. 火车轨道手推车数据集3793 张图能训出什么先看这三点铁路工务巡检和调车场作业安全这类场景里最缺的不是模型是能对上号的数据。你拿到这个“火车-轨道-手推车”zip 时YOLO算法、数据集、标签三样都已经齐了——3793张图像带标注目标分三类火车、轨道、手推车。这套数据能直接用来做铁路场景目标检测的起步训练适合正在做铁路视觉方案、又不想从零标数据的工程团队。但先别急着解压开训拿到手的三分钟内确认三件事类别分布是否均衡、标签是不是 YOLO 能直接吃的 txt 格式、图像来源是否单一。这三步决定了后面训练是省心还是翻车。2. 把 zip 数据集变成可训练资产目录结构、标签格式和类别映射这类数据集 zip 解压后目录结构通常有两种一种是直接给好了 train/val 子集另一种只有一个 images 和 labels 平铺目录需要自己拆分。第一步永远是用 find 和 ls 把结构摸清楚而不是直接拖进训练脚本。下面是最小的一组探路命令unzip 火车-轨道-手推车.zip -d ./rail_dataset find rail_dataset -maxdepth 2 -type d ls rail_dataset/images/train | head -5 ls rail_dataset/labels/train | head -5第一行把压缩包解压到 rail_dataset 目录-d 指定目标路径避免默认解压到当前目录造成文件散落。第二行只看 maxdepth 2 的目录层级够用来判断是 train/val 分好了还是平铺结构。第三、四行分别看 images/train 和 labels/train 下的文件名重点确认两件事图像和标签是否一一对应以及标签是不是 .txt 格式。如果 ls 出来的图像是 .jpg 而标签是 .txt说明大概率是 YOLO 格式可以直接走下一步如果标签是 .xml 或 .json就还得先做一次格式转换后面会讲。2.1 解压后的目录形态images 与 labels 的对应关系平铺结构的处理最简单常见做法是拿到后自己按 8:1:1 或 8:2 切出训练和验证集。但切之前必须确认一个基本前提images 里的每张图都有同名标签反过来也一样。有些数据集在打包时会漏掉少量标签文件或者某些 txt 是空文件直接训练会导致那一张图被 Ultralytics 静默跳过val 指标看着正常实际上少测了一批样本。先跑一个快速校验import os image_dir rail_dataset/images label_dir rail_dataset/labels imgs {os.path.splitext(f)[0] for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .jpeg, .png))} labels {os.path.splitext(f)[0] for f in os.listdir(label_dir) if f.endswith(.txt)} missing imgs - labels empty [] for name in labels: p os.path.join(label_dir, name .txt) if os.path.getsize(p) 0: empty.append(name) print(缺标签的图:, len(missing), list(missing)[:10]) print(空标签:, len(empty), empty[:10])这段逻辑是用文件主名做集合差。注意我先把图片扩展名统一去掉再比对这样 jpg 和 jpeg 混用也不会误报。缺标签的图要么补标要么直接删除空标签文件要特别留意它不代表“没有目标”而是说明标注时漏了那一帧如果图像里明显有轨道或手推车空标签会把模型带偏。我拿到这类数据集的第一天基本都会先跑这个脚本十分钟内就能判断这批数据能不能直接用。2.2 标签格式检查txt 里存的是 xywh 还是多边形确认完文件配对下一步是打开几个 txt 看内容。YOLO 检测格式的标准写法是每行五个数类别ID、归一化中心 x、归一化中心 y、归一化宽 w、归一化高 h。但有些数据集会把轨道这类细长目标标成多边形点序列一行可能有十几个浮点数还有些工具会导出成“类别 左上右下”的格式。这两种都不能直接喂给 YOLOv8 的 detect 任务需要先转成 xywh。用一段小代码统计全部标签的形状分布import os from collections import Counter label_dir rail_dataset/labels stats Counter() examples {} for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), encodingutf-8) as fp: for line in fp: parts line.strip().split() if not parts: continue if len(parts) 5: stats[xywh] 1 elif len(parts) 10: stats[xyxy] 1 else: stats[fpolygon_or_other_{len(parts)}] 1 examples.setdefault(len(parts), line.strip()) print(stats) for k, v in examples.items(): print(f[{k} 个字段] {v[:80]})字段数量能直接暴露标签格式5 个字段是标准 YOLO xywh4 个字段是 xyxy也就是左上右下角坐标10 个字段常见于旋转框或五点多边形。注意最后一行 print 我截断到 80 个字符防止把超长多边形坐标刷满屏。如果发现大量 10 字段以上的标签有两种处理路径一是用脚本把多边形外接矩形算出来转成 xywh二是保留多边形并改用 YOLOv8-seg 训练。对于轨道这种长条形目标我更建议先跑检测不要一上来就上分割检测的收敛速度更快先验证数据价值再追加复杂度。2.3 类别文件 data.yamlID 错位会让训练白跑标签格式没问题之后写 data.yaml。这个文件决定了模型把“类别编号”映射成“类别名字”写错一个数字训练出来的模型会把火车识别成手推车而且 loss 还很漂亮。手推车、火车、轨道三类的 yaml 常见写法如下path: ./rail_dataset train: images/train val: images/val names: 0: train 1: track 2: trolley这里的关键是 names 的索引必须和 txt 里的类别 ID 对齐。YOLO 的类别编号从 0 开始如果原始标注数据里用的是 1、2、3那么 txt 中 class id 为 1 的其实是“火车”但按上面这个 yaml 会被当成“轨道”。遇到这种情况写一个映射转换import os mapping {1: 0, 2: 1, 3: 2} # 原始编号 - 目标编号 label_dir rail_dataset/labels for f in os.listdir(label_dir): if not f.endswith(.txt): continue p os.path.join(label_dir, f) with open(p, encodingutf-8) as fp: lines fp.readlines() new_lines [] for line in lines: parts line.strip().split() if not parts: continue cid int(parts[0]) if cid in mapping: parts[0] str(mapping[cid]) new_lines.append( .join(parts)) with open(p, w, encodingutf-8) as fp: fp.write(\n.join(new_lines))这段代码做了两件事把类别编号映射到从 0 开始的连续区间同时保留原有的 xywh 五个字段顺序。注意我用了“先读全部行、再整体写回”的方式避免边读边写导致的文件截断。跑完后再随机抽 5 个 txt人工确认每行第一个数字都在 0、1、2 范围内。这一步是数据预处理里最枯燥但最有价值的一环做一次就能把“标签错误”这类训练玄学排除掉一大半。提示如果 data.yaml 里 names 的索引不连续比如缺了中间某个编号Ultralytics 不会报错但验证集的混淆矩阵会出现一个空行排查起来很费劲。所以映射完一定要检查类别编号是 0、1、2 这样连续的三段。3. 用 YOLOv8 训练自己的数据集拆分、训练命令与关键参数处理数据集用于 YOLOv8 训练核心步骤就三个拆分数据集、写训练命令、根据显存调参。这三个顺序不能乱先拆分是为了验证集可信写命令是为了快速跑通调参是为了让模型真正收敛。很多人一上来就调学习率结果数据划分有问题调什么都白搭。3.1 固定随机种子做拆分别让同一段轨道同时出现在训练和验证集如果压缩包里的 images 目录是平铺的最简单的拆分方式是随机打乱后按比例切分。但要注意铁路场景的数据往往来自连续拍摄相邻几帧里火车位置几乎没变轨道更是同一段钢轨的不同角度。纯随机拆分会让同一段轨道同时出现在训练集和验证集里结果就是 val 指标虚高部署到新场景立刻原形毕露。稳健做法是按文件名前缀或拍摄片段分组import os import random import shutil random.seed(42) image_dir rail_dataset/images train_dir rail_dataset/images/train val_dir rail_dataset/images/val os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) imgs sorted(os.listdir(image_dir)) # 如果文件名带拍摄片段前缀按片段分组没有前缀就退回纯随机 groups {} for f in imgs: prefix f.split(_)[0] # 例如 frame_00123.jpg 的片段前缀 groups.setdefault(prefix, []).append(f) all_names list(groups.keys()) random.shuffle(all_names) split int(len(all_names) * 0.8) for prefix in all_names[:split]: for f in groups[prefix]: shutil.move(os.path.join(image_dir, f), os.path.join(train_dir, f)) for prefix in all_names[split:]: for f in groups[prefix]: shutil.move(os.path.join(image_dir, f), os.path.join(val_dir, f))这里 random.seed(42) 让拆分结果可复现避免两次训练用的验证集不一样。按前缀分组的关键是假设同一前缀下的图像来自同一段连续拍摄这个假设在大多数巡检数据里成立如果文件名没有前缀规律就退化成单张图片随机拆至少在代码里把 seed 固定住。拆完后再跑一遍 2.1 节的缺失标签检查确认 images 移动后 labels 目录仍然一一对应。3.2 最小可行的训练命令从 yolov8s 起步先跑通 30 个 epoch数据集拆分完最稳妥的起步命令是用 YOLOv8s 预训练权重微调。三个类别、3793 张图这个规模不需要从零训练加载 COCO 预训练权重能显著加快收敛yolo detect train \ modelyolov8s.pt \ datarail_data.yaml \ imgsz640 \ batch16 \ epochs30 \ seed42 \ project./runsmodelyolov8s.pt 会先下载 COCO 预训练权重然后自动适配成 3 类输出data 指向刚才写的 yamlimgsz640 表示输入图像短边缩放到 640长边保持比例batch16 在 24GB 显存上够用epochs 先给 30目的是验证数据链路是通的而不是追求最终精度。跑完看 runs/detect/train/ 下的 results.png如果 loss 曲线在前 10 个 epoch 明显下降说明数据和标签没问题如果 loss 平得像一条直线回到第 2 章检查标签格式和类别映射。参数调整的原则是先用小模型和少 epoch 验证再用大模型拉精度。我用这套流程跑类似数据集时一般先用 yolov8n 把 30 epoch 跑通确认没有报错和异常 loss然后换回 yolov8s 正式训练。n 模型速度快能快速暴露数据问题s 模型精度更高适合最终交付。如果发现轨道类别的 AP 明显偏低再考虑把 imgsz 从 640 提到 960代价是训练时间几乎翻倍但长条形目标的召回率通常能提升 3 到 5 个百分点。3.3 显存不够时的三个降级手段imgsz、batch 与模型尺寸训练时最尴尬的是显卡不够。我的建议是按下述顺序调整效果递减但都有效。第一优先降 imgsz从 640 降到 512感受不到精度损失显存占用能降三分之一左右第二降 batch从 16 降到 8 或 4batch 太小会让 BN 层统计不稳定所以 batch 降到 4 就不要再往下压了第三换小模型yolov8s 换 yolov8n显存直接砍半。还有一个常见做法是开 AMP。Ultralytics 默认开启混合精度训练如果你发现训练日志里没有 amp 相关字样手动加上ampTrue参数。AMP 对显存的节省相当可观而且在这个数据规模下几乎不影响精度。如果 8GB 显存连 yolov8n batch 4 imgsz 512 都跑不动那就不是调参能解决的问题建议换成云 GPU 实例。我见过有人为了省显存把 batch 设为 1结果模型完全训不动这不是参数问题是硬件瓶颈该换机器就换机器。注意显存不足的表现不是报错“Out of Memory”而是训练卡死或直接被杀进程。如果日志里看到 “Killed” 或进程静默退出优先查是不是显存爆了用 nvidia-smi 看显存占用率就能确认。4. 轨道和手推车为什么是检测难点细长目标与近似形态的取舍这个数据集最特殊的地方不在“火车”而在“轨道”和“手推车”。火车是典型的完整目标框得准、边界清晰轨道是超长条形目标水平框几乎无法贴合手推车和火车车厢形态相近模型稍不留意就把两者混在一起。想把这套数据的价值榨干必须理解这两个难点的本质。4.1 轨道是典型长条形目标水平框带来的背景污染轨道的长宽比动辄超过 1:20而 YOLO 系检测器输出的是水平矩形框。一个贴紧轨道的水平框宽度会被拉得非常大框里塞满了道砟、枕木这些背景如果框窄了又没法覆盖整段钢轨。这是检测任务里最典型的“框不贴合目标”问题模型被迫在“框进大量背景”和“漏掉部分轨道”之间二选一。我常用的处理思路有两种。第一种是把轨道当作普通目标硬训接受水平框不贴合的现实损失一点边界精度换来类别判别的稳定性第二种是转成旋转框或分割像 mmrotate 这类旋转检测框架就是专门为长条形目标设计的但工程量会明显增加。对这个 3793 张图的数据集我的建议是先按第一种方案把检测模型跑起来确认轨道类别的 AP 能到 0.6 以上再决定要不要上旋转框。直接把轨道段按固定长度切成子段再标注也能减轻水平框的负担但需要重新处理标签适合追求极致精度时再说。4.2 手推车与火车车厢形态重叠类别间混淆的根源手推车和火车车厢在图像上有两个致命相似点都是矩形、都是金属灰。区别只在尺度——火车占据画面大块区域手推车往往只占几十个像素。当图像缩放到 640 输入时手推车的特征已经被压没了模型很容易把它忽略或直接归类成火车。血泪经验是验证集 mAP 不错但手推车 AP 特别低时优先怀疑类别混淆而不是怀疑数据量不够。缓解手段有三个维度。第一提高输入分辨率imgsz 从 640 提到 960小目标的特征保留会好很多第二给手推车类别加数据增强训练时开启 Mosaic 和 CopyPasteUltralytics 的增强参数默认已开不需要额外配置第三对图像做切块推理把大图切成 640×640 的块分别检测再合并结果能有效提升小目标召回但推理时间会变长。我通常先做第一和第三因为增强对类别混淆的帮助有限——模型如果连“这是手推车还是火车”都分不清光靠变换图像帮不上太多。4.3 标注粒度决定模型上限要不要区分车头车厢、单轨双轨训练前还有一个容易被忽略的问题数据集的“火车”类别里是不是既包含车头也包含车厢如果是模型学到的就是“火车”这个笼统概念如果手推车类别里混了多种形制的推车AP 会被相互稀释。做法是跑一次验证看混淆矩阵里哪两个类别互相串。我在实际项目里遇到过类似情况数据集把“轨道”标成了一段一段的线段而不是整幅图像的轨道区域模型学出来的预测框也呈段落式分布这在视频里表现就是轨道框断断续续。后来我按“有没有道岔、是不是双轨并行”重新审视标签发现标注粒度不统一才是根因。对这套数据集来说如果目标是做调车场安全监控那火车和手推车应该拆成车头、车厢、小推车轨道则保持现状如果只是做物体存在性判断三类就别动保持简单。5. 训练避坑记录5 个让模型翻车的真实原因训练这类数据集最大的坑往往不在模型参数而在数据和标签。以下五条是我从类似项目里攒下来的真实记录每一条都对应一次完整的失败训练你大概率也会遇到其中至少两三条。5.1 标签类别从 1 开始训练时却从 0 对齐现象训练正常收敛loss 曲线漂亮但验证时所有类别预测全部错位——火车被标成轨道轨道被标成手推车。 原因txt 里的类别 ID 是从 1 开始计数的data.yaml 的 names 索引却从 0 开始整体偏移了一位。 解决写脚本把类别编号整体减一再跑一次验证。这类问题隐蔽在“看起来一切正常”里loss 不会报错mAP 也不会异常只有看到预测框的类别错误率极高才暴露。无需改参数改标签即可。5.2 同一场景的轨道漏标loss 在 6 附近震荡不下降现象训练 loss 前 5 个 epoch 正常下降之后就卡在 6 到 8 之间震荡怎么调整学习率都不动。 原因检查图像发现有大约 10% 的图像里包含了轨道但标签为空或只有火车和手推车的框。这些“漏标”的图贡献了错误的负样本梯度模型被反复纠正。 解决找出所有空标签文件逐一对照原图把漏标的轨道框补上补标成本太高就把这些图从训练集剔除。判断方法很简单清点空标签数量再用 2.1 节的脚本列出文件名抽查其中几张图。5.3 图像里混入灰度和 EXIF 旋转图验证集突然崩掉现象训练到第 20 个 epoch 时验证集的 mAP 从 0.8 突然掉到 0.3然后又恢复来回跳动。 原因验证集里存在几张带 EXIF 方向信息的照片。模型训练时读取的是原始像素而部分图像处理工具会自动旋转显示标签的坐标却是按旋转前的图像标定的两边对不上。 解决在预处理阶段统一处理所有图像的 EXIF 旋转或者直接把异常图删除。脚本遍历全部图像读出 EXIF 的 Orientation 字段非 1 的图要么用它重写像素并清掉 EXIF要么移出数据集。这类图占比通常不大删除最省事。5.4 val 指标到 0.85视频里框还是乱跳现象单帧验证 mAP 0.85看起来很好但接视频流时轨道框和手推车框一帧有一帧无置信度忽高忽低。 原因训练集和验证集来自同一段视频的随机拆分验证指标被“背题”了。模型在相似帧上表现好遇到新角度就打回原形。 解决按拍摄片段重新做数据拆分确保训练集和验证集没有重叠片段。这个坑的隐蔽性最强因为 mAP 数字不骗人但部署效果实实在在打脸。我现在的习惯是任何视频类数据集都用 3.1 节的按前缀分组拆分而不是随机单帧拆分。5.5 手推车 AP 接近 0问题出在标签框边缘切出图像现象三类目标里火车和轨道 AP 都正常手推车 AP 始终在 0.05 以下几乎等于没学。 原因抽查标签发现很多手推车只露出一半标注框延伸到了图像边界之外更糟的是有些标签的框中心已经超图。训练时这些越界框导致回归目标无效模型根本没学到手推车的完整特征。 解决写脚本把所有越界的框裁剪回图像边界内框中心越界的标签直接删除对应图像。做完这一步手推车 AP 往往能翻倍。这类数据通常来自广角监控目标贴着画面边缘进进出出标注员又习惯把可见部分标出来越界框属于高频问题。6. 让模型真正能用的验证方法mAP 之外的三个动作mAP 是给训练过程看的部署前还得自己做三个额外验证否则模型是不是真能用心里没底。这三个动作都不难但能暴露 mAP 掩盖掉的问题。6.1 看混淆矩阵track 被识别成 train 的占比有多少训练结束后Ultralytics 会在 runs/detect/train 下生成混淆矩阵图但很多人只看一眼就过了。我一般直接打印矩阵数据from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datarail_data.yaml, plotsTrue) print(metrics.confusion_matrix.matrix)矩阵的行是真实类别列是预测类别。重点看 track 这一行里“被预测成 train”的值以及 trolley 和 train 之间的互串比例。如果轨道被误判成火车的比例超过 10%说明模型所学到的“轨道”特征和“火车边缘”特征高度重叠单靠调参救不回来得回第 4 章重新审视标注粒度。6.2 用连续帧做滑窗检测框的稳定性比单帧指标更真实单帧 mAP 不能反映时序稳定性。我通常把验证视频抽 200 帧连续检测统计每个目标框中心点跨帧的跳变次数import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) cap cv2.VideoCapture(sample.mp4) jumps 0 prev_center {} for i in range(200): ret, frame cap.read() if not ret: break r model(frame, conf0.25, iou0.5) for box in r.boxes: cls int(box.cls[0]) cx, cy (box.xyxy[0][0] box.xyxy[0][2]) / 2, (box.xyxy[0][1] box.xyxy[0][3]) / 2 if cls in prev_center: dx, dy abs(cx - prev_center[cls][0]), abs(cy - prev_center[cls][1]) if dx 100 or dy 100: jumps 1 prev_center[cls] (cx, cy) print(200 帧内跳变次数:, jumps)阈值 100 像素是按 1080p 视频调的如果你的视频分辨率不同按画面宽度的 10% 折算。跳变次数超过总帧数的 5%说明模型输出不稳定要么降置信度阈值稳召回要么加帧间平滑。6.3 导出 ONNX 与尺度对齐落地时输入尺寸和归一化不能变训练时一切正常部署时掉精度九成原因是输入尺寸变了。导出 ONNX 时显式固定尺寸yolo export modelbest.pt formatonnx imgsz640 opset12 simplifyTrue导出后推理端的预处理必须和训练一致归一化到 0 到 1、按短边缩放、补灰边。我用 ONNX Runtime 部署时踩过一次坑训练用 640推理端图省事用 416结果轨道类别的置信度全线掉到 0.2 以下。后来把推理端输入严格对齐到 640 才恢复。长条形目标对尺度变化尤其敏感轨道这种目标一旦尺度不对特征就完全对不上。我现在拿到任何带轨道或长条形目标的数据集第一件事永远不是解压开训而是先跑一遍标签统计和格式校验。标签的错位、缺失、越界框都能在十分钟内测出来这十分钟花的比后面跑几十个 epoch 省得多。希望帮到你。本文还有配套的精品资源点击获取