基于YOLOv8的铁路轨道检测系统:从训练到PyQt5部署全流程
2026/9/24 0:34:54 网站建设 项目流程

简介:本资源为基于YOLOv8的铁路轨道检测系统完整项目包,面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师,适合作为毕业设计、课程设计或大作业的参考方案,也可供初学者进阶学习目标检测全流程。包内共97个文件,以70个Python源码文件为核心,辅以4个pt权重文件、5个xml配置、12个pyc缓存及txt说明文档等,压缩包约24.21MB,涵盖模型训练、推理检测、可视化界面与部署说明等模块。项目已通过运行测试,可生成核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图,并附带可视化页面与视频检测示例,便于直观展示检测效果。目前已有46人学习下载,读者可据此快速复现铁路轨道检测流程,理解数据组织、模型训练与界面交互的实现思路,并在此基础上修改扩展功能。

1. 铁路轨道检测为什么值得用 YOLOv8 重做一遍

铁路轨道检测这个方向,过去几年在工业视觉圈里一直是个"看着简单、做起来全是坑"的活。传统做法要么靠人工巡检,要么用 OpenCV 那套边缘检测加霍夫变换硬拟合钢轨直线,白天光照均匀还行,一到隧道口、雨雾天、道砟反光或者扣件阴影叠上来,阈值调到头也压不住误检。YOLOv8 这类单阶段检测器把"找轨道"从几何拟合变成了目标检测问题,扣件缺失、轨道异物、轨面伤损都能统一到一套标注和训练流程里,泛化能力比手写规则强一个量级。

这套"基于 YOLOv8 的铁路轨道检测系统"打包了源码、完整数据集、可视化界面和部署教程,定位很清楚:让毕设或课程设计的人不用从零攒数据、不用自己搭 PyQt 界面、不用啃 Ultralytics 源码,拿到就能跑通一条从训练到推理的完整链路。适合两类人——一类是刚接触深度学习检测、需要一个能跑通全流程的工程模板的学生;另一类是想把轨道巡检从规则方案迁到模型方案、但不想在数据标注和界面开发上耗时间的工程师。下面按"环境怎么搭、数据怎么处理、模型怎么训、界面怎么接、坑在哪"的顺序拆开讲。

2. 环境搭建与数据集结构:先把地基打平

2.1 用 conda 隔离 YOLOv8 运行环境

铁路轨道检测这套代码依赖 Ultralytics 官方库,而 Ultralytics 对 PyTorch 版本比较敏感,直接装在系统 Python 里很容易和已有的 torch 版本打架。我一般用 conda 建一个独立环境,Python 版本锁在 3.9 或 3.10,这两个版本和 PyTorch 2.x 的兼容性最稳。

# 创建独立环境,Python 3.10 对 torch 2.x 支持最好 conda create -n rail_yolov8 python=3.10 -y conda activate rail_yolov8 # 安装 PyTorch,CPU 版本先用官方源,有 GPU 再换 cu118/cu121 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 Ultralytics 和界面依赖 pip install ultralytics opencv-python pyqt5 numpy pandas matplotlib

这里有个参数要留意:--index-url后面跟的地址决定了装 CPU 版还是 GPU 版。如果你机器上有 NVIDIA 显卡,把cpu换成cu121(对应 CUDA 12.1),装完用torch.cuda.is_available()验证一下返回是不是 True。没有显卡也别慌,YOLOv8n 这种小模型在 CPU 上跑单张 640×640 推理大概 100~200ms,做毕设演示完全够用,只是训练会慢很多,建议训练阶段借一台带显卡的机器或者用云上的按量实例。

装完 Ultralytics 后,终端敲yolo checks会打印环境自检信息,重点看三行:Python 版本、torch 版本、CUDA 是否可用。如果 CUDA 显示不可用但你确实有显卡,八成是 torch 装成了 CPU 版,重装对应 CUDA 版本的 wheel 即可。

2.2 数据集目录必须按 YOLO 格式摆

YOLOv8 对数据集的目录结构有硬性要求,摆错了训练脚本直接报"no labels found"。标准结构是这样:

rail_dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 训练集标签,与图片同名 .txt │ └── val/ └── data.yaml # 数据集配置文件

标签文件是每行一个目标的 txt,格式为类别id 中心x 中心y 宽 高,后四个值都是归一化到 0~1 的相对坐标。这一点和 VOC 的 XML、COCO 的 JSON 都不一样,很多人第一次转格式就栽在这——忘了归一化,或者把左上角坐标当成中心坐标写进去,训练时 loss 死活不降。

data.yaml是训练入口的配置文件,内容长这样:

# 数据集根路径,建议写绝对路径避免相对路径踩坑 path: /home/user/rail_dataset train: images/train val: images/val # 类别数和类别名,顺序必须和标注时的 id 对应 nc: 4 names: 0: rail # 钢轨 1: fastener # 扣件 2: missing_fastener # 扣件缺失 3: obstacle # 轨道异物

ncnames的对应关系是整个数据集里最容易出错的地方。标注时如果扣件缺失标成了 id 2,这里 names 里 2 就必须是 missing_fastener,错一位模型学出来的类别就全乱套。改完 yaml 建议用 Ultralytics 自带的校验跑一遍,它会检查图片和标签是否一一对应、坐标是否越界。

2.3 用脚本批量校验图片与标签配对

拿到数据集后别急着开训,先跑一段校验脚本,把"有图无标签""有标签无图""坐标越界"这三类问题筛出来。这三类问题在训练时不会直接报错,但会让模型学到噪声,表现为 loss 震荡或者某类 AP 异常低。

import os from pathlib import Path img_dir = Path("rail_dataset/images/train") lbl_dir = Path("rail_dataset/labels/train") img_stems = {p.stem for p in img_dir.glob("*.jpg")} lbl_stems = {p.stem for p in lbl_dir.glob("*.txt")} # 找出不配对的样本 only_img = img_stems - lbl_stems only_lbl = lbl_stems - img_stems print(f"有图无标签: {len(only_img)} 个 -> {list(only_img)[:5]}") print(f"有标签无图: {len(only_lbl)} 个 -> {list(only_lbl)[:5]}") # 检查坐标是否越界 bad = [] for txt in lbl_dir.glob("*.txt"): for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: bad.append((txt.name, "字段数不对")) continue _, cx, cy, w, h = map(float, parts) if not all(0 <= v <= 1 for v in (cx, cy, w, h)): bad.append((txt.name, f"坐标越界: {cx},{cy},{w},{h}")) print(f"异常标签: {len(bad)} 个") for name, reason in bad[:10]: print(f" {name}: {reason}")

这段脚本干三件事:用集合差集找出图片和标签不配对的样本;逐行读标签检查字段数是不是 5;检查归一化坐标有没有超出 0~1。跑完如果only_imgonly_lbl都是 0、bad也是 0,说明数据集结构干净,可以进训练环节。如果有问题,only_img里的图要么补标注要么删掉,坐标越界的标签要回标注工具里重新框。

提示:图片格式建议统一成 jpg,png 和 jpg 混用虽然 Ultralytics 能读,但某些版本在缓存标签时会因为扩展名不一致漏读,统一格式能省掉一类玄学问题。

3. 训练配置与参数调优:让模型真正学到轨道特征

3.1 从预训练权重起步,别从零训

铁路轨道检测的数据集规模通常不大,几千张到一两万张量级,从零初始化训练很容易过拟合,而且收敛慢。标准做法是加载 COCO 预训练的 yolov8n.pt 或 yolov8s.pt 做迁移学习,让骨干网络已经学到的边缘、纹理特征直接复用。

# 从预训练权重开始训练,epochs 设 100,imgsz 640 yolo detect train \ model=yolov8n.pt \ data=rail_dataset/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/rail \ name=exp1

参数逐个说清楚:model指定预训练权重,n 是最小的 nano 版本,速度快适合毕设演示,追求精度可以换 s 或 m;epochs是训练轮数,100 轮对几千张图通常够,配合patience=20做早停——20 轮验证指标不涨就自动停,省得白跑;imgsz=640是输入分辨率,轨道检测里小目标(扣件)比较多,如果显存够可以提到 768 或 896,小目标召回会明显改善;batch=16是批大小,显存不够就往下调,调到 8 甚至 4 都行,但太小会让 BN 层统计不稳,建议不低于 8;lr0=0.01是初始学习率,迁移学习场景下这个值比较稳,如果 loss 一开始就炸到 nan,降到 0.001 再试。

训练启动后终端会打印每一轮的 box_loss、cls_loss、mAP50、mAP50-95。重点盯 mAP50-95,这个指标比 mAP50 严格,能反映框的定位精度。正常情况下前 10 轮 mAP 涨得很快,30 轮后趋缓,如果 50 轮了 mAP50 还在 0.3 以下,八成是数据或配置有问题,别硬等。

3.2 学习率和数据增强的关键参数

YOLOv8 默认开了一整套数据增强,包括 mosaic、mixup、随机翻转、HSV 抖动。轨道检测场景下,mosaic 增强把四张图拼成一张,能提升小目标检测,但轨道是长条状目标,mosaic 拼接后轨道会被截断,有时候反而干扰学习。我的经验是前 80% 的 epoch 开 mosaic,最后 20% 关掉,让模型在接近真实分布的图上收尾。

# 精细调参版本,关闭最后阶段的 mosaic yolo detect train \ model=yolov8s.pt \ data=rail_dataset/data.yaml \ epochs=150 \ imgsz=768 \ batch=12 \ lr0=0.01 \ lrf=0.01 \ mosaic=1.0 \ close_mosaic=30 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4 \ fliplr=0.5 \ project=runs/rail \ name=exp2

lrf=0.01是最终学习率相对初始学习率的比例,也就是从 0.01 余弦退火到 0.0001,这个衰减策略对收敛稳定性帮助很大。close_mosaic=30表示最后 30 轮关闭 mosaic,这是 Ultralytics 内置的参数,比手动改配置省事。hsv_h/s/v控制色调、饱和度、明度的抖动幅度,轨道场景光照变化大,这三个值适当调大能提升鲁棒性,但 hsv_h 别超过 0.02,否则钢轨颜色被抖得不像钢轨,反而掉点。fliplr=0.5是水平翻转概率,轨道左右对称,翻转增强安全,但注意如果数据集里有方向性目标(比如单向的异物),翻转会引入错误标签,这种情况要关掉。

3.3 训练过程监控与损失曲线判读

训练跑起来后,runs/rail/exp2/目录下会生成results.csv和一堆曲线图。results.csv 每行是一轮,列包括 train/box_loss、train/cls_loss、metrics/mAP50、metrics/mAP50-95 等。想自己画损失曲线,用 pandas 读出来画就行:

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/rail/exp2/results.csv") df.columns = df.columns.str.strip() # 列名可能带空格,先清理 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(df["epoch"], df["train/box_loss"], label="box_loss") axes[0].plot(df["epoch"], df["train/cls_loss"], label="cls_loss") axes[0].set_xlabel("epoch"); axes[0].set_ylabel("loss"); axes[0].legend() axes[1].plot(df["epoch"], df["metrics/mAP50"], label="mAP50") axes[1].plot(df["epoch"], df["metrics/mAP50-95"], label="mAP50-95") axes[1].set_xlabel("epoch"); axes[1].set_ylabel("mAP"); axes[1].legend() plt.tight_layout(); plt.savefig("train_curve.png", dpi=150)

判读曲线有几个经验点:box_loss 和 cls_loss 应该整体下降,中间有波动正常,但如果持续上升,多半是学习率太大或者标签有问题;mAP50 和 mAP50-95 应该同步上升,如果 mAP50 涨但 mAP50-95 不涨,说明模型能找对位置但框不准,可以试试提高分辨率或检查标注框是否贴合;如果训练 loss 一直降但验证 mAP 早早停滞甚至下降,就是过拟合了,加数据增强、加 dropout 或者减模型容量。

注意:results.csv 的列名在不同 Ultralytics 版本里可能有细微差异,比如空格、大小写。读之前先 print 一下列名,别硬编码列名导致 KeyError。

4. 可视化界面与推理部署:把模型变成能演示的系统

4.1 PyQt5 界面接推理的最小闭环

毕设和课程设计通常要求有个可视化界面,能选图片、点按钮、显示检测结果。PyQt5 是最省事的方案,核心逻辑就三步:加载模型、读图推理、把带框的结果画到 QLabel 上。

import sys import cv2 from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class RailDetectWindow(QMainWindow): def __init__(self): super().__init__() self.model = YOLO("runs/rail/exp2/weights/best.pt") # 加载训练好的权重 self.setWindowTitle("铁路轨道检测系统") self.label = QLabel("请选择图片") self.btn = QPushButton("选择图片并检测") self.btn.clicked.connect(self.detect) layout = QVBoxLayout() layout.addWidget(self.label); layout.addWidget(self.btn) container = QWidget(); container.setLayout(layout) self.setCentralWidget(container) def detect(self): path, _ = QFileDialog.getOpenFileName(self, "选图", "", "Images (*.jpg *.png)") if not path: return results = self.model(path, conf=0.25, iou=0.45) # 推理 annotated = results[0].plot() # 画框,返回 BGR ndarray rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg).scaled(800, 600)) if __name__ == "__main__": app = QApplication(sys.argv) win = RailDetectWindow(); win.show() sys.exit(app.exec_())

conf=0.25是置信度阈值,低于这个值的框不显示,轨道检测里如果漏检多就降到 0.15,误检多就提到 0.4。iou=0.45是 NMS 的 IoU 阈值,控制重叠框的合并,轨道是长条目标,框之间重叠多,这个值可以适当调高到 0.5 减少误合并。results[0].plot()直接返回画好框的图,省得自己写画框代码,但它默认用类别名做标签,中文类别名需要额外配置字体,否则显示成方块。

4.2 视频流推理与帧率控制

图片检测跑通后,视频检测是自然延伸。核心是把model(path)换成逐帧推理,但要注意帧率控制,否则 CPU 上跑视频会卡成幻灯片。

import cv2 from ultralytics import YOLO model = YOLO("runs/rail/exp2/weights/best.pt") cap = cv2.VideoCapture("rail_video.mp4") fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = max(1, int(fps / 10)) # 目标 10fps 处理,跳帧 idx = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if idx % frame_interval == 0: results = model(frame, conf=0.25, verbose=False) frame = results[0].plot() cv2.imshow("Rail Detect", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break idx += 1 cap.release(); cv2.destroyAllWindows()

frame_interval是关键参数,它决定每隔几帧做一次推理。原视频 30fps,如果每帧都推理,CPU 上根本跟不上,跳帧到 10fps 处理,显示时仍然逐帧播放,视觉上流畅度够用。verbose=False关掉 Ultralytics 每帧的日志打印,不然终端会被刷屏。如果部署到 RK3588 这类嵌入式板子上,思路一样,只是要把 PyTorch 模型转成 ONNX 或 RKNN 格式,用板子的 NPU 加速,这部分转换流程和 PC 端推理是两套东西,需要单独处理。

4.3 模型导出与跨平台部署

训练完的 .pt 权重只能在装了 PyTorch 的环境里跑,要部署到没有 Python 环境的机器或者嵌入式设备,得导出成通用格式。YOLOv8 支持导出 ONNX、TensorRT、OpenVINO 等多种格式。

# 导出 ONNX,动态 batch 和动态尺寸 yolo export model=runs/rail/exp2/weights/best.pt format=onnx dynamic=True opset=12 # 导出 OpenVINO,适合 Intel CPU 加速 yolo export model=runs/rail/exp2/weights/best.pt format=openvino half=True

dynamic=True让导出的 ONNX 支持动态输入尺寸,部署时不用固定 640×640,灵活一些但推理速度略慢。opset=12是 ONNX 算子集版本,12 兼容性最好,别盲目追新。half=True是 FP16 量化,模型体积减半、推理加速,但精度会掉一点点,轨道检测这种对精度敏感的场景建议先对比 FP32 和 FP16 的 mAP 再决定。导出后在同目录会生成对应格式的文件,ONNX 用 onnxruntime 加载,OpenVINO 用 openvino.runtime 加载,推理接口和 Ultralytics 略有不同,需要改几行代码。

5. 避坑与排查:那些训练日志不会告诉你的问题

5.1 现象:训练 loss 正常下降但 mAP 一直是 0

原因:标签类别 id 和 data.yaml 里的 names 顺序对不上,或者标签文件里出现了 data.yaml 没定义的类别 id。模型在学,但学的是错的映射,验证时按正确类别算 AP 自然是 0。

解决:跑一遍标签统计脚本,把所有出现过的类别 id 列出来,和 data.yaml 的 names 逐一核对。统计命令:cat labels/train/*.txt | awk '{print $1}' | sort | uniq -c,输出每个 id 的出现次数,如果出现了 nc 范围外的 id,就是标注时类别选错了。

5.2 现象:推理时框的位置整体偏移

原因:训练时的 imgsz 和推理时的输入尺寸不一致,或者标注时用了非归一化坐标。YOLOv8 推理时会自动 resize,但如果训练用 640、推理传原图不指定 imgsz,内部会按默认 640 处理,长宽比差异大的图会被拉伸,框就偏了。

解决:推理时显式指定imgsz=640,和训练保持一致。如果原图长宽比很极端(比如轨道巡检的窄长图),考虑训练时就用 letterbox 填充而不是直接 resize,Ultralytics 默认就是 letterbox,一般不用改,但要确认推理时没关掉。

5.3 现象:小目标扣件大量漏检

原因:扣件在 640×640 的输入里可能只有十几个像素,YOLOv8 的 P3 特征图 stride 是 8,太小的目标在深层特征图上就消失了。

解决:三个方向——提高输入分辨率到 896 或 1024;换更大的模型(yolov8m/l)利用更丰富的特征;在 data.yaml 里检查扣件类别的标注框是不是太小,如果实际扣件在图中占比就很小,可以考虑切图推理,把大图切成小块分别检测再合并。

5.4 现象:训练到一半突然 OOM 崩溃

原因:mosaic 增强在训练后期会拼接不同尺寸的图,某些 batch 的实际显存占用比预期高;或者 dataloader 的 workers 开太多,每个 worker 都缓存了一批图。

解决:降 batch size,把workers从默认 8 降到 4 或 2;开amp=True混合精度训练,显存占用能降 30% 左右;如果还不行,把cache=True关掉,别把整个数据集缓存到内存。

5.5 现象:PyQt 界面显示检测结果时颜色错乱

原因:OpenCV 读图是 BGR 通道,Qt 的 QImage 默认按 RGB 解析,直接传 BGR 数据过去红蓝通道就反了。

解决:在构造 QImage 之前做一次cv2.cvtColor(img, cv2.COLOR_BGR2RGB),这一步不能省。另外 QImage 构造时要注意 bytesPerLine 参数,传ch * w而不是默认值,否则图片宽度不是 4 的倍数时会出现斜纹。

6. 把 mAP 再往上推一档:几个我反复验证过的技巧

模型跑通之后,大部分人卡在 mAP50-95 上不去,0.6 左右就顶住了。分享几个我在轨道检测场景里反复验证有效的做法。

第一个是难例挖掘。训练完第一版模型后,用它在验证集上推理,把漏检和误检的图挑出来,人工补标或修正后加进训练集,再训一轮。这个循环做两到三次,mAP 通常能涨 3~5 个点。轨道检测的难例集中在隧道口逆光、雨雾天、道岔区域,这些场景在原始数据集里往往样本少,补进去收益最大。

第二个是锚框和损失权重的微调。YOLOv8 是无锚框的,但损失里 box 和 cls 的权重可以调。轨道检测里定位精度比分类更重要(扣件缺失和扣件正常,框的位置差一点类别就错了),可以适当提高 box loss 的权重。在训练命令里加box=7.5(默认 7.5,可以试 10),观察 mAP50-95 的变化。

第三个是测试时增强(TTA)。推理时对同一张图做翻转、多尺度缩放,把多次推理结果融合,能稳定涨 1~2 个点,代价是推理时间翻几倍。Ultralytics 推理时加augment=True就能开 TTA,适合对精度要求高、对速度不敏感的离线检测场景。

技巧预期涨幅代价适用场景
难例挖掘+3~5 mAP人工标注时间有标注人力
box 权重调高+1~2 mAP需重新训练定位精度要求高
TTA 推理+1~2 mAP推理慢 3~5 倍离线检测
提高输入分辨率+2~4 mAP显存和耗时增加小目标多

最后说个习惯:每次改完参数训练,我都会把 results.csv 和权重文件按日期_参数摘要命名归档,比如20250115_imgsz768_closeMosaic30。轨道检测调参是个反复试的过程,没有归档,两周后你根本记不清哪个权重是哪个配置训出来的,想复现最佳结果只能重跑,这个后悔药我吃过不止一次。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询