
简介面向计算机视觉、深度学习方向的毕业设计或课程设计提供一套基于YOLOv8的铁路轨道异物侵入多传感器融合报警系统。资源包共8个文件包含3个Python源码文件模型训练、视频检测、可视化界面、3个权重文件yolov8n.pt、best.pt、yolo11n.pt以及2个说明文档README.txt等压缩包仅15.91MB目录结构清晰方便快速部署与二次开发。系统经过运行验证能够生成核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果和标签分布图覆盖从模型训练到多传感器报警的完整流程适合用于毕业设计、课程设计或项目初期演示。适合具备一定Python与深度学习基础的学生学习进阶也可在此基础上扩展其他目标检测功能。目前已有59人学习下载是一份重实用、可落地的工程参考资料。1. 铁路异物检测为什么选了 YOLOv8 这条路铁路轨道上的异物侵入是行车安全里最怕遇到又最难完全靠人盯防的问题。传统的方案依赖人工巡检或者周界摄像头但夜间、雨雾、逆光这些条件下单靠视觉的判断经常出现漏报。这个毕设系统把 YOLOv8 目标检测和多传感器融合放在一起用摄像头做主体识别用雷达或红外数据做交叉验证最后通过可视化界面直接给出报警跑通的是一个可以复制到实际铁路场景的原型。对你来说它最大的价值不是又一个目标检测 demo而是把训练、评估、推理、界面展示、融合判定这整条链路都打通了。适合做毕设或者课程设计的同学也适合想快速上手 YOLOv8 工程化流程、想看清楚目标检测落地时有哪些细节坑的从业者。2. YOLOv8 模型选型与训练流程从 yolov8n 到 best.pt2.1 为什么选 YOLOv8 而不是 YOLOv5C2f 结构与 Anchor-Free 解耦头YOLOv8 在结构上和 YOLOv5 最大的区别在于 Backbone 里的 C2f 模块和 Head 的解耦设计。C2f 把原先 C3 模块的单一分支改成多个梯度流分支每一层都保留不同感受野的特征。对于轨道异物这种目标尺度跨度很大的场景——远处的人可能只有十几个像素近处的落石却能占满画面——这种多分支特征提取能同时抓住小目标和大目标的纹理信息。Head 部分做了三件事一是去掉了 Anchor 机制直接预测目标中心点省去了聚类锚框的步骤二是把分类和回归分成两个独立的卷积分支避免两类任务互相干扰三是引入了 Distribution Focal Loss 来处理边界框回归的不确定性。这套设计在 PyTorch 生态里还没有哪个开源模型能像 YOLOv8 这样做到精度和部署便利性的平衡。模型权重文件yolov8n.pt是 Nano 版本参数量约 3.2M输入 640x640 分辨率时推理速度在 GTX 1660Ti 上能达到 60 FPS 以上项目里的best.pt则是基于这个预训练权重在自己的数据集上 fine-tune 出来的产物。如果你直接用yolo11n.pt做迁移学习起点也完全兼容——Ultralytics 的接口把这两代模型的训练逻辑统一了换权重文件不需要改代码。2.2 数据标注格式与目录组织训练自己的轨道异物检测模型第一步是搞清楚数据目录结构。项目里的数据集是标准 YOLO 格式每张 JPEG 图片对应一个同名 TXT 标注文件内容格式如下0 0.4321 0.5687 0.1234 0.2345 1 0.7234 0.4128 0.0987 0.1765每行五个值依次是类别编号、归一化中心 x 坐标、中心 y 坐标、归一化宽度、归一化高度。类别编号从 0 开始计数和数据集根目录下的data.yaml文件里的names列表严格对应。数据集的目录组织建议按下面的结构摆放这直接影响训练脚本能否直接跑通dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml里必须写清楚路径和类别名一个典型的配置是这样的path: C:/Users/xxx/dataset train: images/train val: images/val test: images/test names: 0: person 1: vehicle 2: obstacle注意path字段建议写成绝对路径因为 Ultralytics 在部分版本里对相对路径的解析有 bug尤其是当你把数据集和项目源码放在不同盘符时会出现FileNotFoundError但报错信息并不会直接告诉你路径找错了。2.3 训练脚本与参数解读项目中的train_mode.py是训练入口。核心代码逻辑如下from ultralytics import YOLO # 加载预训练权重yolov8n.pt 是 COCO 预训练模型 model YOLO(yolov8n.pt) # 训练并指定数据集配置文件 results model.train( datadata.yaml, epochs100, imgsz640, batch16, device0, workers4, lr00.01, patience20, projectruns/train, namerail_exp1, )这里几个参数在实际使用中要特别留意。epochs100对于轨道异物这种类别少、背景相对固定的场景通常是够用的但如果你的数据集只有几百张图建议开启迁移学习并配合patience早停否则后期会过拟合到训练集上。batch16在 8GB 显存的显卡上跑 640 分辨率是安全的显存不够就降到 8如果显存有 12GB 以上可以往上调batch 越大 loss 曲线越平滑收敛也越稳。workers控制数据加载线程数Windows 系统上建议设成 0否则容易报BrokenPipeError。lr00.01是初始学习率YOLOv8 内部会自动做 warmup 和余弦退火所以这个值不需要自己改动太多。训练过程中模型会自动验证并保留验证集上 mAP50 最高的权重写入runs/train/rail_exp1/weights/best.pt。这就是项目里best.pt的来路。需要特别注意训练结束后不要只看 loss 曲线要打开results.png看 mAP 曲线——如果训练 loss 一直在降但 mAP 曲线却在后期掉头向下说明过拟合已经发生了。2.4 训练产物与指标曲线训练完成后runs/train/rail_exp1/目录下会生成一批关键产物文件/目录对应内容答辩/调优时的用途weights/best.pt验证集 mAP 最高的权重推理和部署都用它weights/last.pt最后一个 epoch 的权重断点续训或回滚results.pngloss、mAP、PR 曲线汇总图展示模型收敛过程confusion_matrix.png混淆矩阵分析类别混淆情况F1_curve.pngF1 分数随置信度变化曲线选择最优置信度阈值PR_curve.png精确率-召回率曲线评估模型分类能力val_batchX_pred.jpg验证集预测结果可视化直观展示检测效果labels.jpg标签分布图检查标注质量这些指标图不光是给答辩用的装饰它们各自的场景不一样PR 曲线越靠近右上角越好说明在保持高精确率的同时召回率也高F1 曲线则用来挑置信度阈值——曲线最高点对应的置信度就是理论上 F1 最优的阈值如果你觉得误报偏多就可以在这个值基础上往上加 0.05 到 0.1。混淆矩阵能直接告诉你哪两个类别容易被搞混比如人和电线杆如果频繁互相误判需要回看标注框是否有边界覆盖问题。3. 铁路场景下的多传感器融合相机加雷达的决策级融合思路3.1 单视觉在铁路场景的失效边界纯视觉检测在白天顺光条件下表现不错但铁路沿线的环境复杂程度远高于普通道路逆光时目标轮廓和背景几乎同色夜间靠路灯补光也会有大面积暗区雨雪天气镜头前的水渍会让检测框在帧间剧烈抖动。YOLOv8 再强输入图像质量不够时也会出现连续的漏检和误检这在安全系统里是不可接受的。所以业界通行的做法不是用一个传感器死磕而是引入毫米波雷达或红外相机做交叉验证。毫米波雷达对运动目标的速度和距离感知非常准不受光照影响但角度分辨率低分不清目标的类别视觉正好相反分类能力强但距离信息不准。两者互补就有了融合的必要。3.2 融合架构与数据对齐这个项目采用的融合架构是决策级融合Decision-Level Fusion即视觉和雷达各自独立做检测然后在结果层做匹配。架构可以拆成四个层次传感器层 摄像头 毫米波雷达 | | 特征层 YOLOv8检测 聚类/CFAR检测 | | 目标层 类别 2D框 置信度 位置 速度 RCS | | 融合层 时间同步 → 空间对齐 → 匹配 → 决策输出时间同步是融合系统里最容易踩坑的一环。摄像头帧率和雷达扫描频率不一致是常态比如摄像头 30 FPS、雷达 10 Hz那就要以雷达的一个扫描周期为时间窗口取该窗口内最新的一帧图像来做匹配而不是让两边各跑各的。代码上可以用一个简单的时间戳对齐逻辑import time class SensorFusion: def __init__(self): self.latest_vision_frame None self.latest_vision_time 0 self.latest_radar_tracks [] self.latest_radar_time 0 def vision_callback(self, detections): self.latest_vision_frame detections self.latest_vision_time time.time() def radar_callback(self, tracks): self.latest_radar_tracks tracks self.latest_radar_time time.time() def fuse(self, sync_window0.1): # 只匹配时间差在 100ms 以内的检测结果 if abs(self.latest_vision_time - self.latest_radar_time) sync_window: return self._match(self.latest_vision_frame, self.latest_radar_tracks) return None空间对齐做的是把雷达返回的极坐标系目标位置投影到图像的像素坐标系。核心方法是标定矩阵。先把摄像头内参矩阵 K 和畸变系数标出来再通过雷达与摄像头的外参矩阵 T 把雷达目标从雷达坐标系转到相机坐标系最后用针孔相机模型投影。这一步在毕设里不需要做得像量产项目那么精细用一个简化做法是选几个已知距离的静止目标手动标出它们在画面中的像素位置拟合一个单应性矩阵 H直接做平面映射。单应性矩阵 3x3OpenCV 的cv2.findHomography一行就能算出来。3.3 报警判定逻辑融合之后报警的触发条件要同时满足两个约束才输出高风险报警视觉检测的置信度超过阈值且雷达在该目标方向存在反射回波。这个逻辑能有效压低误报率——比如画面里的影子被 YOLOv8 误判成人但雷达在那个方向没有能量反射就不触发高等级报警只进入疑似状态。报警分级可以这样设计低风险仅视觉检测到置信度在 0.35~0.5 中风险视觉置信度超过 0.5或视觉雷达任一匹配 高风险视觉置信度超过 0.7 且雷达确认目标存在或目标距离低于安全距离在代码实现里直接维护一个目标状态表每个目标 ID 记录它的类别、位置、最近一次被视觉和雷达分别确认的时间戳。超过一定帧数没有联合确认就降级或删除目标避免死目标残留引发重复报警。4. 可视化界面与实时检测模块的实现4.1 Detection_video.py 的核心流程Detection_video.py是实时检测的入口它的流程可以归纳为读取视频流、逐帧推理、在帧上绘制检测框、把结果推送到界面。核心代码结构如下import cv2 from ultralytics import YOLO model YOLO(best.pt) # 加载训练好的权重 cap cv2.VideoCapture(test_video.mp4) # 或者用 0 打开摄像头cap cv2.VideoCapture(0) while cap.isOpened(): success, frame cap.read() if not success: break # 推理conf 是置信度阈值iou 是 NMS 的 IoU 阈值 results model.predict( frame, conf0.35, iou0.5, verboseFalse, devicecuda:0 ) # 在原始帧上绘制检测框和标签 annotated_frame results[0].plot() # 将结果传入可视化界面 cv2.imshow(Railway Detection, annotated_frame) if cv2.waitKey(1) 0xFF ord(q): breakconf0.35是最关键的参数。刚才在 F1 曲线里选出的最优置信度建议直接用在model.predict里不要沿用默认的 0.25。轨道异物检测中误报的代价很高宁可少报也不要频繁误报所以一般把 conf 提到 0.4 以上除非你的场景特别需要抓住小目标。iou0.5是 NMS 的阈值含义是当两个框的交并比超过 0.5 时保留置信度更高的那个。如果场景里目标互相遮挡严重可以把 iou 降到 0.4减少重叠框合并时把小目标吞掉的情况。4.2 可视化页面的功能分区Visual_interface.py是这个项目里界面展示的核心。它不是一个简单的 OpenCV 画框窗口而是把所有监控信息和指标整合在一个面板上。界面大致分成四个区域区域内容实现方式视频显示区实时画面 检测框 目标类别QLabel 刷新 OpenCV 帧报警信息区报警级别、时间、目标类型QTableWidget 表格状态面板FPS、推理耗时、检测目标计数QLabel 实时更新控制按钮开始/暂停/退出、阈值调节QPushButton 信号槽界面主循环的刷新频率建议控制在每 100ms 一次不用每帧都刷新 UI 控件否则 Qt 的事件循环会阻塞界面会表现出卡顿。一个常见的做法是用QTimer定时从检测线程拿最新的结果帧而检测线程只负责把最近的一帧结果存到共享变量里。from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel from PyQt5.QtCore import QTimer class MainWindow(QMainWindow): def __init__(self): super().__init__() self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.timer.start(100) # 100ms 刷一次 def update_frame(self): # 从检测线程取最新帧转为 QPixmap 显示 frame get_latest_frame() # 绘制检测框后显示 self.video_label.set_pixmap(frame_to_pixmap(frame))如果你用的是 PyQt5还要注意 OpenCV 的 BGR 和 Qt 的 RGB 通道顺序不同显示前要做一次cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)不然画面颜色会偏蓝偏红看起来像是白平衡坏了。4.3 与多传感器联动时的界面设计当系统接入雷达数据后可视化界面除了显示视觉检测框还要叠加雷达目标的投影点。这些点可以画成不同颜色的圆圈绿色表示已匹配视觉目标黄色表示仅有雷达回波红色表示高风险目标。这样整个监控画面的信息密度会高很多但也更接近真实产品形态。报警弹窗的设计要克制。高风险的报警可以用弹窗加声音提示但中低风险不能弹窗否则现场操作人员会在没有真正危险时对弹窗产生习惯性忽略。折中的方案是只在报警信息区持续滚动显示高风险时再强制弹窗并把弹窗停留时间控制在 3 到 5 秒。5. 数据集构建、评估指标与答辩重点5.1 数据集的构成与类别设计整个系统的数据质量决定检测精度的上限。这个项目提供的数据集覆盖了铁轨上的典型异物场景侵入的人员、违规停放的车辆、遗落的障碍物。类别设计上建议不要超过 5 类因为轨道场景里的目标外观差异大但每一类的样本数量如果太少模型就学不到该类别的稳定特征。标注时有一个特别容易忽略的点轨道异物检测的数据集正样本不均衡。很多采集素材里正常无目标的帧占了大半如果把这些帧全部加入训练模型会倾向于把所有区域都预测为背景表现为召回率极低。处理方法是人工剔除那些完全没有目标的帧或者在训练时给包含目标的图片设置更高的采样权重。5.2 从权重文件到评估报告指标怎么读答辩时最有杀伤力的不是模型准确率 95%这种含糊说法而是把指标说清楚。目标检测的指标是围绕 IoU 和置信度两个维度展开的。mAP50 的意思是 IoU 阈值为 0.5 时所有类别的平均精确率mAP50-95 则是从 0.5 到 0.95 每间隔 0.05 取一个阈值算出平均值。后者比前者更严格也更接近实际部署的难度。指标含义答辩时怎么说Precision预测出的框中确实是目标的占比误报率低Recall真实目标中被找出来的占比漏报率低mAP50粗粒度定位的检测精度模型整体有效mAP50-95细粒度定位的检测精度模型定位能力F1 ScoreP 和 R 的调和平均模型均衡性F1 曲线读取最优置信度阈值的方法前面说过了这里补一个实操建议答辩前跑一次验证集推理把每个目标的置信度和是否正确的标签存下来然后自己按置信度从低到高计算 F1 值画出一条自定义的 F1 曲线。这比直接贴训练日志里的图更能应对评委提问因为你可以清晰解释为什么选这个阈值。5.3 误报与漏报的平衡策略轨道异物检测里漏报的代价远高于误报但误报太多会导致系统被关闭。所以要分场景调整阈值策略。对高风险区域比如隧道口、弯道可以降低置信度阈值到 0.3宁可多报几次——反正有多传感器交叉验证兜底对低风险区域维持 0.5 以上减少无关报警。还有一个技巧是在后处理阶段加一个目标稳定帧的计数同一个目标需要连续 N 帧被检测到才触发报警。这个 N 可以设为 3 到 5 帧能有效过滤掉因为风吹草动或者镜头抖动产生的单帧误检。代价是报警延迟增加了约 0.1 到 0.2 秒对于最大时速 120 公里的列车来说这个延迟对应约 3 到 7 米的制动距离实际场景里需要和轨道部门确认是否可接受。6. 部署到边缘设备ONNX 导出与推理提速6.1 环境整理与权重导出训练和推理环境建议统一用 Python 3.8 到 3.10 加 CUDA 11.8 的组合。Ultralytics 在 Python 3.11 以上的兼容性虽然已经改善但在 PyTorch 2.0 之后仍有部分算子调度不一致的问题Windows 上更容易碰到所以不要追求最新的 Python 版本。部署的第一步是把 PyTorch 权重导出为 ONNX 格式导出的代码很简单yolo export modelbest.pt formatonnx imgsz640导出后验证一下 ONNX 模型的输出形状。YOLOv8 的 ONNX 输出是一个 1x84x8400 的张量84 是 4 个框坐标加 80 个 COCO 类别概率得来的如果你自定义了类别数量这个维度会相应改变。用onnxruntime验证时输入形状必须是 NCHW 格式且归一化方式要和训练时一致。6.2 RK3588 与 Jetson 上的推理配置在边缘设备上部署是这套系统从毕设走向实际的关键一步。RK3588 是瑞芯微的旗舰 SoC它的 6 TOPS NPU 跑 YOLOv8n 的 INT8 量化模型可以做得很流畅。但 RK3588 的 NPU 不接受直接的 ONNX需要通过 RKNN-Toolkit2 做转换rknn_convert -t rknn -i best.onnx -o best.rknn --quantized量化这一步要准备好一个校准集通常从验证集里抽 100 到 200 张图放到calibration.txt里。校准图片数量不足会导致量化后的模型在某些类别上精度掉得特别狠最常见的就是小目标的召回率断崖式下跌。Jetson 系列的部署路径不太一样因为它有 CUDA 核心可以直接用 TensorRT 加速import tensorrt as trt # 将导出的 ONNX 转换为 TensorRT 的 engine 文件 builder trt.Builder(trt.Logger()) network builder.create_network() parser trt.OnnxParser(network, trt.Logger()) with open(best.onnx, rb) as f: parser.parse(f.read()) # 保存 engine 供运行时加载转换时精度模式建议先用 FP16 试跑如果 mAP 掉得不多就足够满足实时性没必要一上来就上 INT8 量化。TensorRT 的 INT8 需要校准数据而且调起来比 RKNN 的量化更繁琐。6.3 常见坑部署阶段的排错基本围绕三个方向。第一是装了 CUDA 但 PyTorch 不用 GPU这个不是安装问题是 PyTorch 和 CUDA 版本不匹配导致torch.cuda.is_available()返回False排查时直接在环境里输入python -c import torch; print(torch.cuda.is_available())返回 True 才继续往下走。第二是 RKNN 转换报算子不支持尤其是 YOLOv8 里的 SiLU 激活函数和 Split 操作在旧版本 RKNN-Toolkit 里支持不好。处理方式是换新版本的 toolkit或者手动把 SiLU 替换成 ReLU 再训练几轮——大多数场景下精度损失可以控制在 1% 以内。第三是推理时显存占用突然上涨直到 OOM。这通常是因为在循环里每次调用model.predict()都会重新分配张量没有复用显存。解法是把推理封装成一个类只初始化一次模型循环里只传数据进去。class YOLOInference: def __init__(self, weights_path, devicecuda:0): self.model YOLO(weights_path) self.device device def run(self, frame): results self.model.predict(frame, deviceself.device, verboseFalse) return results[0].plot()最后一个值得记下来的点是摄像头输入画面的尺寸。如果你在 4K 输入上直接跑 YOLOv8n推理时间会翻几倍。正确做法是先cv2.resize到 640x640 或 1280x1280检测框乘回缩放比例映射到原图坐标。这个映射关系用两行代码就能算清楚scale_x original_width / 640scale_y original_height / 640预测框的坐标分别乘上这两个比例即可。本文还有配套的精品资源点击获取