YOLO铁路巡检目标检测:火车轨道手推车数据集训练与部署全流程
发布时间:2026/9/23 22:45:40 锦皓数字建站

简介面向YOLO系列算法目标检测任务这份资源提供了火车、轨道、手推车三类物体的标注数据集共3793张图像适合从入门到进阶的开发者直接用于模型训练与验证。压缩包内共2000个文件包含voc格式的xml标注、yolo格式的txt标注以及data.yaml配置文件文件已按训练集与验证集划分完毕可适配yolov5、yolov8、yolov9、yolov7、yolov10、yolo11等主流算法其中yolo标注采用“类别索引归一化中心坐标宽高”的格式易于读取与转换。包体大小约236.33MB目录结构清晰标签已经过整理无需额外预处理即可开始训练。目前已有100人学习下载适合需要特定场景数据集快速搭建目标检测实验的研究者与开发者。资源附带完整的标签格式说明与类别信息便于理解标注内容、进行格式转换及后续模型调优。1. 铁路巡检为什么要 YOLO三类目标、3793 张图能换回什么刚接触目标检测的人拿到一份「YOLO算法-火车-轨道-手推车数据集-3793张图像带标签」这种压缩包第一反应往往是直接开始训练。但实际做下来你会发现这份数据集的正确打开方式是先看懂标签再动手。3793 张图听起来不多放到铁路场景里却已经覆盖了白天、逆光、弯道、道岔、不同轨枕间距等常见变化加上三类目标互不混淆足够把 YOLO 的完整流程从 labelImg 打标跑到部署验证走通一遍。这个数据集解决的场景很具体铁路沿线要同时盯住「火车来了没有」「轨道区段在哪里」「有没有手推车停留在铁轨上」。手推车出现通常意味着有工人在作业这对安全监测系统来说是需要联动告警的信号。和普通工业质检不同这里的类别有明确的语义关系模型要分清「有车通过」和「有车停留」不是简单框出来就行。适合两类人一是正在做轨道交通视觉检测、需要一份标注质量尚可的数据集来跑通训练和验证流程的工程师二是刚接触 YOLO、想用一份带标签的真实场景数据练手的学习者。接下来按「拆解数据 → 训练参数 → 踩坑 → 部署验收」的顺序讲全程用我自己的做法和踩过的坑说话。2. 打开压缩包后的第一件事核对目录、图片编号与标签是否对齐2.1 先看目录结构和 YOLO 标签格式别急着解压后直接打开训练脚本常见的数据集布局是images/和labels/两个目录平级外加一个classes.txt写明类别顺序也可能按train/、val/划分。我没有见过这份压缩包内部的真实文件名所以拿到手不要猜先解压后用命令看一遍实际结构。如果解压出来是images、labels和classes.txt那是最标准的布局。用下面命令检查unzip YOLO算法-火车-轨道-手推车数据集-3793张图像带标签-火车-轨道-手推车.zip -d railway_data cd railway_data # 统计图片和标签数量是否一致 find images -type f -name *.jpg | wc -l find labels -type f -name *.txt | wc -l # 查看前 5 个标签文件的实际内容 for f in $(find labels -type f -name *.txt | head -5); do echo $f ; cat $f; done这段命令的逻辑是先解压到指定目录用find加wc -l统计两边文件数再随机看几个标签文件内容。数量不一致说明有图片没标或标了没图片这种数据集直接训练会导致训练时报错或者某些图片被静默跳过。YOLO 标签格式每行 5 个数依次是类别 ID、归一化中心点 x、归一化中心点 y、归一化宽、归一化高全部是 0 到 1 的浮点数。比如一行0 0.5123 0.4281 0.3122 0.0845表示这一行标的是类别 0classes.txt 里第 0 行的名字目标中心点在图片水平 51.23% 和垂直 42.81% 的位置宽度占图片宽 31.22%高度占图片高 8.45%。归一化坐标的意义在于标签不随图片分辨率变化训练时不管输入 640 还是 1280标签都不需要重新换算。2.2 写个小脚本可视化标注框这一步能发现 80% 的标签问题很多数据集下载下来图片和标签文件都在但实际跑训练时才发现问题。我习惯在训练前写一个 40 行左右的 Python 脚本把图片和标签画出来直接肉眼看。这一步能发现三类常见问题标签坐标越界、类别 ID 和类别名对不上、手推车这种小目标有没有被漏标。import os import cv2 # 这里的路径改成你解压后的实际路径 img_dir railway_data/images label_dir railway_data/labels class_names [train, track, handcart] # 以 classes.txt 实际内容为准 for img_name in os.listdir(img_dir)[:20]: if not img_name.endswith(.jpg): continue stem os.path.splitext(img_name)[0] img cv2.imread(os.path.join(img_dir, img_name)) h, w img.shape[:2] label_path os.path.join(label_dir, stem .txt) if not os.path.exists(label_path): print(f缺少标签: {label_path}) continue with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) x, y, bw, bh map(float, parts[1:5]) # 还原成像素坐标 x1 int((x - bw / 2) * w) y1 int((y - bh / 2) * h) x2 int((x bw / 2) * w) y2 int((y bh / 2) * h) color (0, 255, 0) if cls_id 0 else (0, 0, 255) if cls_id 1 else (255, 0, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) out_path fvis_{stem}.jpg cv2.imwrite(out_path, img) print(f已生成 {out_path})这段代码把每个目标的中心坐标还原成左上角和右下角的像素坐标再画到图上。注意int()转换时如果出现 x2 大于 w 或者 y2 大于 h说明标签坐标超出了图片范围需要回去修数据。另外第 20 行class_names的顺序必须和classes.txt完全一致否则人工看图时容易把类别看反后面训练阶段再发现就晚了。2.3 统计框宽高和类别分布决定训练时用 640 还是 1280可视化只能看个大概要判断这份数据集的训练难度还得统计标签的分布情况。轨道目标是细长条火车是宽矩形手推车又小又散三种目标的物理尺寸差别很大对应的检测难度也不同。用一个简单脚本跑一遍统计看看每个类别的平均框宽、平均框高和数量。import os import numpy as np label_dir railway_data/labels stats {0: [], 1: [], 2: []} for label_file in os.listdir(label_dir): if not label_file.endswith(.txt): continue with open(os.path.join(label_dir, label_file)) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) bw, bh float(parts[3]), float(parts[4]) stats[cls_id].append((bw, bh)) for cls_id, boxes in stats.items(): if not boxes: continue boxes np.array(boxes) print(f类别 {cls_id}: 数量 {len(boxes)}) print(f 平均宽 {boxes[:,0].mean():.4f}, 平均高 {boxes[:,1].mean():.4f}) print(f 宽 0.5 的比例: {(boxes[:,0] 0.5).mean():.2%})这个统计结果直接影响训练参数。如果手推车的平均框宽不到 0.05即占图片宽不到 5%就说明存在大量小目标训练时imgsz设成 640 基本不可能召回。我会把这些数字记下来作为下一章选择参数的依据。3. 用 YOLOv8 训练自己的数据集从 data.yaml 到跑通一轮的完整命令3.1 整理目录结构并生成 data.yaml类别顺序必须和标签里的 ID 一致训练前先把数据集整理成 YOLOv8 默认接受的目录结构。我一般会建立images/train、images/val、labels/train、labels/val四个目录按 8:2 划分。划分时注意用脚本随机划分不要直接按文件名字母序否则同一时间段拍摄的图片可能全进了训练集验证集就没有代表性。mkdir -p dataset/images/train dataset/images/val mkdir -p dataset/labels/train dataset/labels/val # 这里假设原压缩包里的 image 和 label 在平级目录 python - EOF import os, random, shutil random.seed(42) img_dir railway_data/images label_dir railway_data/labels files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(files) split_idx int(len(files) * 0.8) for i, f in enumerate(files): stem os.path.splitext(f)[0] sub train if i split_idx else val shutil.copy(os.path.join(img_dir, f), fdataset/images/{sub}/{f}) # 标签文件必须和图片同步移动 src_label os.path.join(label_dir, stem .txt) if os.path.exists(src_label): shutil.copy(src_label, fdataset/labels/{sub}/{stem}.txt) else: print(f警告: {stem}.txt 不存在) EOF这段脚本指定了随机种子42保证划分结果可以复现。核心是 11 行的split_idx计算和 15 行的同步复制标签和图片必须在同一个子集否则验证时模型明明识别对了却因为没有对应标签被判定为误检。跑完后检查下dataset/labels/train里的文件数如果出现大量警告说明原始数据集有标签缺失这时候优先把缺失的标签找回来不要直接开始训练。然后是data.yaml这是 YOLOv8 训练时唯一要改的配置文件。文件内容只有四行关键配置路径用相对路径相对于当前运行目录或者直接用绝对路径。path: /home/user/railway_project/dataset # 改成你的 dataset 目录绝对路径 train: images/train val: images/val nc: 3 names: 0: train 1: track 2: handcartnc是类别数量写 3 没问题names的顺序直接对应标签文件里每行第一个数字。这一步最容易翻车的地方是从别人的项目里复制data.yaml忘记改names训练了一个晚上出来发现类别 0 在 classes.txt 里是 train在 names 里却写成了 handcart。建议训练前跑一句验证确认数据能正常加载yolo detect train datadata.yaml modelyolov8n.pt epochs1 imgsz640如果这条命令能在 20 秒内跑完一个 epoch 并且不报错说明数据路径没问题。用最小配置跑通后再回去加正式参数。3.2 训练参数怎么设imgsz、batch、lr0、mosaic 是四个最关键的位置训练命令的完整形式我一般这么写yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs150 \ imgsz1280 \ batch8 \ lr00.001 \ optimizerAdamW \ mosaic1.0 \ patience30 \ device0这段命令用yolov8n作为预训练权重imgsz1280是这份数据集最关键的一个参数。根据前面的标签统计手推车的框在很多图里只有几十像素640 输入下特征图只有 20×20小目标在最高层几乎不可见。升到 1280 之后小目标在特征图上能占 2×2 以上的像素召回会明显改善。代价是显存占用翻倍8G 显存训练 1280 分辨率通常只能开batch216G 以上才能开 8。mosaic1.0表示每张训练图由 4 张图拼成这对小目标是双刃剑一方面不同场景的目标拼在一起模型见过的上下文组合更多另一方面手推车本来就小拼图后可能被裁剪掉一部分。我的习惯是前 50 个 epoch 开mosaic1.0后面关掉让模型在正常尺寸的图上做微调。关掉的方法是在训练命令里写mosaic0.0。损失函数部分YOLOv8 用的是分类损失BCE加边界框回归损失CIoU 和 DFL 的组合这些在训练日志里会显示成cls_loss、box_loss和dfl_loss。不要只看box_loss降没降三个损失总和下降才是真收敛。如果cls_loss一直在 0.5 以上不动先怀疑类别 ID 或者标注漏标而不是调学习率。3.3 训练完成后先验证再导出用混淆矩阵看类别互混情况训练完会在runs/detect/train/下生成best.pt和一系列验证图表。先用官方验证命令跑一遍测试集输出 mAP、每类 precision 和 recallyolo detect val \ datadata.yaml \ modelruns/detect/train/weights/best.pt \ imgsz1280 \ conf0.25conf0.25是置信度门限验证时低于这个阈值的结果会被当作背景丢弃。手册上建议 0.25实际铁路场景我会调到 0.1 看模型上限因为告警系统通常宁可误报也怕漏报上线时再通过后端逻辑过滤。看验证结果时重点看results.png里的混淆矩阵如果 train 和 track 之间有大量互混说明有些图片里火车和铁轨同时出现时模型分不清主次如果 handcart 被检测成背景说明训练时小目标漏检的问题还没解决。确认效果后导出模型yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz1280导出的 ONNX 文件可以直接用 ONNXRuntime 或 TensorRT 跑推理后续部署不再依赖 PyTorch 环境。导出后顺手用onnxruntime跑一张测试图确认输出 shape 是[1, 3, 4, 8400]之类的固定尺寸如果是动态轴部署时还要额外处理尺寸变化。4. 避坑标注 ID 错位、手推车小目标漏检、轨道框变形怎么排查4.1 类别 ID 错位训练时一切正常验证时类别全乱了现象训练损失下降正常但打开验证集预测图发现轨道被标成火车手推车被标成轨道火车反而标成背景。人工看图片时明明能区分三类目标。原因标签文件里每行的第一个数字和data.yaml的names顺序对不上。比如标签文件里0代表类别 0但classes.txt里第 0 行是handcart而data.yaml的 names 第 0 个写的是train模型学到的映射关系就和实际语义产生了偏移。解决训练前用 2.2 节的可视化脚本重新检查重点看第 0 类画出来的框套在什么目标上而不是只数框的数量。也可以在训练集里挑几个 txt 文件直接cat对照classes.txt确认顺序。这个问题一旦发生改完names顺序后必须重新训练模型学过的特征映射已经乱了不能靠微调救回来。4.2 手推车普遍漏检框太小训练时根本没学到有效特征现象验证集 mAP0.5 看着有 0.85但按类别细看handcart 的 recall 只有 0.4 左右。预测结果里中近景的手推车能检测到远处小尺寸的基本全部漏掉。原因手推车在图片里的框宽经常不到图片宽度的 5%YOLOv8 默认输入 640 时目标在特征图上不到 1 个像素模型很难学习到它的判别特征。另外如果训练时开了普通的 Mosaic 增强小目标可能在拼接和裁剪过程中被裁掉加剧了漏检。解决先确认类别统计后再决定策略不要盲目加增强。第一步把imgsz从 640 提到 1280验证集 mAP 一般能提升 5 到 10 个百分点第二步在推理侧做 SAHI 切片推理把大图切成 640×640 的块分别预测再合并这种方式对铁路大图尤其有效因为我需要看清铁轨尽头的细节第三步如果数据集里手推车样本本身很少可以单独做水平翻转和轻微缩放增强不让它被 Mosaic 吞掉。4.3 轨道是细长结构水平矩形框包不住轨道预测框看着很别扭现象轨道预测框不是沿铁轨走向的细长条而是横七竖八的宽矩形有的框甚至同时罩住了两条铁轨。人眼看起来「这也算检测准了」但实际上框的 IoU 很低mAP 虚高是因为验证时只要求 IoU 大于 0.5。原因YOLO 默认输出水平轴对齐的矩形框轨道是典型的细长结构同一个角度的目标在不同图片里朝向变化很大水平框无法紧凑表达模型只能在面积和位置上做折中预测。解决如果只检测轨道区段可以接受粗框后处理里按框的宽高比过滤掉明显不合理的对象。如果业务要求精确知道轨道朝向换用旋转框检测模型YOLOv8 的 OBB 分支或者 mmrotate 都是常见做法。mmrotate 需要额外安装数据标注也要从四点水平框改为带角度参数的旋转框改造工作量不小我先问清楚业务需不需要精确轨道方向不需要就继续用水平框。4.4 损失值变 NAN怎么调参数都救不回来只能回退权重现象训练正常跑了几十个 epoch某一步box_loss突然变成nan之后所有损失都是nan验证结果全部无效。原因常见诱因有三个学习率过大或批次太大导致梯度爆炸图片文件损坏导致读取到非正常像素值标签中有 NaN 或者极端值。这份数据集如果出现过标签坐标越界的情况大概率是坐标换算时除以了 0 或负值。解决训练日志里确认出现 NAN 的 step 数回退到前一步的last.pt把lr0从0.001降到0.0005batch减半先跑 20 个 epoch 看是否复现。同时扫描所有图片用 OpenCV 或 PIL 打开一遍发现无法读取的图片直接移到corrupted/目录把对应标签也移走。这两个操作都做完再重新训练不要在原基础上继续。4.5 全图都是轨道和轨枕漏标成了隐形的「负样本炸弹」现象模型对真实目标检测不错但经常把一片没有手推车也没有列车的空旷轨道区域也预测成某个类别验证集上出现大量假阳性框。原因原始标注如果只标了火车和手推车把大量轨道图像当作背景模型就学到了「凡是这种纹理就算轨道」的错误特征。更隐蔽的是如果标注者漏掉了画面中小尺寸的手推车那些被漏标的手推车在训练时会被默默当作负样本模型就会朝着「手推车不该被检测」的方向优化这比打错标签更致命因为人工检查时几乎发现不了。解决用训练好的模型对训练集图片重新预测一遍把置信度高但没有对应标签的框输出成可疑名单人工抽查后补标或确认。还有一种更省事的方案单独从背景区域裁剪一批不带标注的图片加入训练集在data.yaml里把背景作为一个特殊类别来平衡。这一步在铁路场景里价值很大因为轨枕和道砟的纹理重复度高模型非常容易把纹理当作目标特征。5. 部署前的最后一公里用结果指标和测试视频验收这个模型训练报告上的 mAP 再高也不代表现场能用。我部署铁路视觉模型时习惯先做三个验收动作其中最关键的是让模型看一段「完全没有目标」的空轨道视频。这个测试很多人没做直到上现场才发现模型对轨道纹理的误检率高得离谱而这个事故我在早期项目里真实遇到过后来就固定成了验收习惯。第一个动作是看混淆矩阵。训练生成的confusion_matrix.png里如果背景列的数字偏高说明模型在把大量非目标区域当成了目标这类系统上线后会刷屏式误报。第二个动作是测空背景视频。准备 5 分钟没有列车、没有手推车只有轨道的视频用导出的模型跑一遍统计每帧的平均预测框数。这个数字应该趋近于 0如果一帧能出十几个框说明轨道纹理已经被模型编码成了可检测特征需要回到 4.5 节补背景训练数据。第三个动作是给检测结果加时间域平滑。单帧检测的置信度波动很大手推车在远处时模型可能在连续几帧中一会检出、一会漏掉导致告警系统反复触发。我会写一个简单逻辑连续 3 帧以上都检出同一位置的目标才触发告警连续 3 帧消失才解除。这个逻辑在代码里只有十几行但能让误报率下降一个量级比在模型侧反复调置信度阈值省事得多。置信度门限方面现场设定conf0.35能过滤掉大部分低置信度误检配合时间域平滑后0.25 的阈值也能保持稳定输出。我用这个流程验收过不少模型最大的教训是不要一上来就用yolov8x追求最高精度先拿yolov8n跑通端到端确认数据没坑、指标可信再回去换大模型两个模型之间往往只差两三个点但部署机器的差别很明显。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。