☰
YOLOv5煤矿传送带异物检测实战:从训练到部署全流程
2026/10/1 1:10:03 网站建设 项目流程

简介:基于YOLOv5的煤矿传送带异物检测系统,面向矿业安全与工业视觉开发者,解决传送带锚杆、石块等异物实时识别问题,提供PyQt5图形界面便于现场操作,有助于及时发现障碍、预防故障,对提升生产线安全水平有实际价值。压缩包共80个文件,大小9.22MB,主要包含可直接运行的源码、推理模型、评估指标曲线、测试图片、模型说明与界面设计资源,目录结构清晰,既适合快速部署,也便于算法调试与二次开发。已有705人学习下载。配套资料覆盖完整检测流程,并给出Windows10、Anaconda3、Python3.8及对应PyTorch版本的运行环境说明,方便复现调参;同时内置样本图片和界面模块,可帮助开发者理解模型调用、结果可视化和交互设计,有效降低工业落地门槛,还能辅助后续算法优化与隐患预警。

1. 煤矿传送带异物检测:这个 YOLOv5 资源到底能帮你省多少事

煤矿传送带上的异物(锚杆、道木、大块矸石、铁丝网残片)一旦卡进转载点,轻则划伤皮带、重则撕裂整条输送带,检修一次动辄停产数小时。这类项目最麻烦的不是算法本身,而是从模型训练到能跑的完整链路:数据集怎么标、训练参数怎么设、pytorch 模型怎么转成 onnx、GUI 界面怎么把推理结果实时展示出来。这份资源把这几环打包在一起——YOLOv5 完整源码加训练好的 onnx 权重加评估指标曲线加一套能直接操作的 GUI 界面,正好覆盖了从「训练自己的数据集」到「现场可演示」的全流程,适合正在做煤矿智能化改造的算法工程师,也适合拿 YOLOv5 做毕业设计、需要快速产出完整系统的同学。

我的建议是:别把它当成现成产品直接上生产,而是当成一套可以拆开用的工程模板。先跑通 GUI,再换自己的数据集重训,最后按现场工况调阈值——这套流程走完,你就掌握了 YOLOv5 落地工业检测的标准路线。

2. YOLOv5 训练自己的数据集:先把数据组织和超参这两件事做对

2.1 数据集的目录组织与标注格式

YOLOv5 对数据集的目录结构有固定要求,第一次用的人最容易在 labels 的路径和格式上报错。拿到这份资源后,先别急着跑 train.py,把数据目录搭对才能省掉一半的排错时间。标准结构是这样:

datasets/ ├── coal/ │ ├── images/ │ │ ├── train/ │ │ │ ├── 001.jpg │ │ │ └── 002.jpg │ │ └── val/ │ │ ├── 101.jpg │ │ └── 102.jpg │ ├── labels/ │ │ ├── train/ │ │ │ ├── 001.txt │ │ │ └── 002.txt │ │ └── val/ │ │ └── 101.txt │ └── coal.yaml

images 和 labels 必须同级对应,文件名要一致,后缀可以不同但主名必须相同。每个 txt 文件里存的是归一化后的标注坐标,格式是「类别 x_center y_center width height」,全部是 0 到 1 之间的小数,不是像素坐标。新建一个 coal.yaml 来指定类别,这是训练入口的第一步。

# coal.yaml train: datasets/coal/images/train val: datasets/coal/images/val nc: 3 names: ['anchor', 'wood', 'gangue']

train 和 val 的路径是相对于你执行 train.py 所在目录的,如果你把 datasets 放在项目根目录下,一般不用改。nc 是类别数量,names 列表里的顺序必须和标注 txt 里的类别数字一一对应——比如你标注时把锚杆编为 0,那 names 列表第 0 项就是 'anchor',顺序乱了损失曲线会直接崩掉。

2.2 煤矿场景的数据标注意见

煤矿传送带场景对标注的要求比通用目标检测要苛刻得多。传送带上的异物往往被煤粉覆盖,轮廓边界不清晰;皮带还在持续运动,容易产生运动模糊。这两类样本在标注时,我一般会把边界框画得比物体实际可见轮廓略微外扩一到两个像素,而不是严格贴边——这样能让模型学到「煤粉覆盖下物体的真实范围」,而不是只学到露出来的那部分。同时,每个类别至少要有 1000 个以上的实例框,且要覆盖不同光照、不同皮带速度、不同煤流量下的形态。如果只标一个时间段的数据,模型的泛化能力会非常差。

还有一类样本是很多人会漏掉的——负样本,也就是完全没有任何异物的正常皮带画面。负样本对降低误报率很关键,没有它模型会把煤流纹理误判成异物。我一般建议在训练集里混入 15% 到 20% 的纯正常样本,对应空标注的 txt 文件(内容是 0 字节)。

2.3 训练参数怎么设:从基础配置到超参调整

这份资源里的训练脚本是基于 YOLOv5 官方仓库改的,训练入口还是经典的 train.py。先用默认配置把流程跑通,再按自己的显卡调整关键超参。一条典型的训练命令如下:

python train.py \ --data coal.yaml \ --weights yolov5s.pt \ --img 640 \ --epochs 200 \ --batch-size 16 \ --patience 30 \ --cache ram

--img 640 是输入分辨率,传送带异物检测我用 640 而不是 1280,原因是现场部署的机器大多是老旧的工控机,CPU 或低端 GPU 跑不动大分辨率;而且锚杆、道木这类异物尺寸不算小,640 输入足够检出。--batch-size 16 是给 12G 显存的中端卡留的余量,如果你显存只有 6G,降到 8 甚至 4。--patience 30 是早停的耐心值,连续 30 个 epoch 在验证集上没有提升就自动停止训练,这个参数能帮你省时间——不要傻等满 200 个 epoch,模型通常在第 60 到 100 个 epoch 之间就收敛了。

YOLOv5 的超参文件里有一个 hyp.scratch-low.yaml,里面包含学习率、数据增强系数等配置。对于煤矿皮带场景,我通常会把 hsv_h、hsv_s 这两个颜色增强参数略微调低,因为传送带异物的颜色特征本来就很有限,过度的颜色扰动反而会削弱模型对煤粉背景下物件的判别能力。另一个值得改的是 mosaic 参数,默认是 1.0,如果你发现小目标(比如细铁丝)老是漏检,可以尝试把 mosaic 降到 0.8,让模型多看一些完整的小物体,而不是被马赛克拼接切掉一半。

2.4 训练过程的监控与结果检查

训练启动后,重点看两个东西:一个是终端里每个 epoch 打印的 loss 曲线,另一个是 runs/train/exp 目录下生成的 results.png 和混淆矩阵。

这里有一个关键判断:如果发现 loss 在前 20 个 epoch 内快速下降,随后进入平台期并轻微波动,这是正常现象,说明模型在学习特征,不要为了追求 loss 绝对下降而无限增加 epoch——过拟合通常在第 120 个 epoch 后开始显现,此时训练集指标继续上涨、验证集指标开始掉头。训练结束后,验证脚本 evaluate.py 会输出 precision、recall 和 mAP@0.5 三个核心指标。对这个场景,我更看重 recall——因为漏过一个异物可能导致整条皮带报废,代价远高于多报几次警让巡检去看一眼。mAP 可以不太高,0.7 以上即可投入试用。

3. PyTorch 转 ONNX 与部署推理:别在模型转换这一步翻车

3.1 导出 ONNX 的命令与参数选择

训练好的权重是 .pt 格式,这在现场部署时很不友好——工控机上通常没有完整的 PyTorch 环境,装起来又慢又占磁盘空间。ONNX 格式的好处是运行时只需要一个 onnxruntime 库,CPU 就能跑,而且推理速度比 PyTorch 的 eager 模式快不少。导出命令在 YOLOv5 项目里已经封装好了:

python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --opset 12 \ --img 640

--opset 12 是 ONNX 的算子集版本,我建议固定在 11 到 12 之间。onnxruntime 对这两个版本的支持最成熟,太新的 opset(比如 15 以上)在老版本 onnxruntime 上会报「unsupported operator」错误。--img 640 必须和训练时的输入尺寸一致,否则导出的模型输入 shape 对不上,后续推理会出错。

导出成功后终端会打印模型文件的路径和输入输出的 shape,通常是 input 维度为 [1, 3, 640, 640]。如果你需要支持动态分辨率,可以在导出命令里加上 --dynamic,但我不推荐——动态 shape 会让 CPU 推理变慢 20% 左右,而且煤矿场景固定输入尺寸完全够用。

3.2 ONNX Runtime 推理代码:预处理与后处理要对齐

拿到 onnx 文件之后,推理代码是整个部署链路里最容易出细节问题的地方。YOLOv5 的预处理有三个关键步骤:letterbox 缩放、BGR 转 RGB、归一化到 0 到 1。任何一个环节和训练时不一致,检测精度都会明显下降。推理脚本的核心逻辑是这样:

import cv2 import numpy as np import onnxruntime as ort def letterbox(img, new_shape=(640, 640), color=(114, 114, 114)): shape = img.shape[:2] r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r))) img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) dw = (new_shape[1] - new_unpad[0]) / 2 dh = (new_shape[0] - new_unpad[1]) / 2 top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) return img, r, (dw, dh) def infer(session, img, conf_thres=0.25, iou_thres=0.45): im, ratio, pad = letterbox(img) im = im[:, :, ::-1].transpose(2, 0, 1) # BGR -> RGB, HWC -> CHW im = np.ascontiguousarray(im, dtype=np.float32) im /= 255.0 im = im[None] # 增加 batch 维度 inputs = session.get_inputs().name outputs = session.run(None, {inputs: im})[0] # shape: [1, 25200, 5+nc] # 后处理:把框还原回原图坐标 boxes, scores, class_ids = post_process(outputs[0], ratio, pad, conf_thres, iou_thres) return boxes, scores, class_ids

这段代码里参数的关键点在于:letterbox 的填充色必须是 114,114,114——这是 YOLOv5 训练时的默认填充值,改成 0 或 127 都会导致边缘区域的检测效果变差。BGR 转 RGB 用切片操作完成,如果你用的是 OpenCV 读取图像,默认是 BGR,不转换的话模型看到的颜色通道全反了,anchor 和 wood 的区分度会明显降低。归一化一定是除以 255.0,要注意数据类型是 float32,不能是 uint8。

onnxruntime 的 Session 初始化也需要在意两件事,模型加载时的 providers 顺序和线程数配置直接决定推理速度:

import onnxruntime as ort sess_options = ort.SessionOptions() sess_options.intra_op_num_threads = 8 sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL providers = ['CPUExecutionProvider'] if 'CUDAExecutionProvider' in ort.get_available_providers(): providers.insert(0, 'CUDAExecutionProvider') session = ort.InferenceSession('best.onnx', sess_options=sess_options, providers=providers)

intra_op_num_threads 是 CPU 推理时使用的线程数,我习惯设为 8,再高对提升帮助有限。graph_optimization_level 设为全部开启,onnxruntime 会自动做一些算子融合优化,能让模型体积看起来没变但实际推理快 10% 到 15%。如果你用的是 GPU 推理, CUDAExecutionProvider 要放在列表第一位,否则 onnxruntime 会优先用 CPU 而让你误以为 GPU 没生效。

3.3 输出后处理:解码 25200 个预测框并做 NMS

ONNX 模型的原始输出维度是 [1, 25200, 5+nc],其中 25200 是三个检测尺度(80x80、40x40、20x20)的预测框总数。这 25200 个候选框里有 95% 以上都是低置信度的背景框,后处理要做的就是筛掉它们并抑制重叠框。核心是 NMS,我常用的是 PyTorch 的 torchvision.ops.nms 或者 OpenCV 的 cv2.dnn.NMSBoxes。如果你不想引入 torch,纯 numpy 实现也不复杂。关键点是坐标还原时要把 letterbox 加上的 padding 减掉,再用 ratio 缩放回原图尺寸:

def scale_boxes(boxes, ratio, pad): # boxes: [x1, y1, x2, y2] 在 640x640 坐标系下 dw, dh = pad boxes[:, [0, 2]] = (boxes[:, [0, 2]] - dw) / ratio boxes[:, [1, 3]] = (boxes[:, [1, 3]] - dh) / ratio return boxes.round().astype(int)

注意这里减 padding、除 ratio 的顺序不能反,先减后除才正确。如果你看到检测框偏到了目标物体的左上角或右下角,十有八九就是这一步的数学没做对——这是我在部署时踩过最频繁的坑。

4. 精美 GUI 界面的实现思路:推理线程分离与参数动态调整

4.1 GUI 框架选择与界面布局设计

这份资源里的 GUI 界面基于 PySide6 实现,继承自 PyQt5 的生态,既有成熟的控件库,又能用 QSS 样式表把界面做得美观。界面布局上分为四个区域:左上角是视频源预览(支持本地视频文件和摄像头实时流),右上角是模型状态和检测结果的实时信息面板(当前帧率、检出异物数量、每类别的置信度),底部左侧是参数控制区(置信度阈值滑块、IOU 阈值滑块、报警开关),底部右侧是日志区(记录每次告警的时间、类别和坐标)。这个布局逻辑很直接——现场的巡检工不需要理解算法,他们只需要看到画面、看到数字、看到报警。

在 PySide6 里实现滑块与标签的联动非常简单,核心价值在于把阈值参数暴露到界面上,而不是写死在代码里:

from PySide6.QtWidgets import QSlider, QLabel def setup_conf_slider(slider: QSlider, label: QLabel): slider.setRange(10, 90) # 对应 0.10 ~ 0.90 slider.setValue(25) # 默认 0.25 slider.valueChanged.connect( lambda v: label.setText(f"置信度: {v / 100:.2f}") )

参数范围上,置信度滑块我限制在 0.10 到 0.90,因为低于 0.10 时画面全是噪声框,高于 0.90 时真正的小目标会被漏掉。IOU 滑块同理限制在 0.20 到 0.70,默认 0.45。这两个参数是现场调试时用得最频繁的旋钮,做成滑块比改代码重启程序要高效得多。

4.2 推理线程与界面的分离:QThread 加队列

GUI 程序最容易犯的错是在主界面线程里直接跑推理循环,这会导致界面完全卡死——视频画面一帧一帧地跳、滑块拖动无响应。正确的做法是创建一个独立的推理线程,推理线程只负责取帧、推理、返回结果,主线程只负责把结果画到控件上。中间用队列通信。这是实现方式的标准做法,我依照一个简单的 QThread worker 模式来写:

import queue import threading from PySide6.QtCore import QObject, Signal class InferWorker(QObject): frame_ready = Signal(object, object, object, object) def __init__(self, session, input_queue): super().__init__() self.session = session self.input_queue = input_queue self.running = True def run(self): while self.running: try: frame = self.input_queue.get(timeout=0.05) except queue.Empty: continue boxes, scores, class_ids = infer(self.session, frame) self.frame_ready.emit(frame, boxes, scores, class_ids)

这里 frame_ready 信号把原始帧和检测结果一起发回主线程,主线程拿到之后再用 QPainter 或直接在 QLabel 上画框。input_queue 是主线程往 worker 喂帧的通道,用队列的好处是当推理速度跟不上视频帧率时,新帧会自然堆积在队列里,而不是把主线程阻塞住。timeout=0.05 保证退出信号发出后,worker 最多 50 毫秒内就能跳出循环,不会造成程序关闭时的卡死。

在这个场景里,推理线程的处理速度是最核心的性能指标。如果你的工控机是 6 核 8 线程的 CPU,640 输入分辨率下单个模型推理大约需要 40 到 60 毫秒,对应每秒 15 到 20 帧。这个帧率对皮带检测来说完全足够,因为皮带运行速度通常低于 4 米每秒,摄像头视角下异物在画面中停留的时间至少有几秒,15 帧的采样率已经能保证不会漏掉。

4.3 报警逻辑:怎么减少误报对现场巡检的干扰

GUI 界面里的报警功能如果设计得太粗暴,比如每检出一次异物就立刻报警,现场会有大量误报——煤流中的一小块反光、水蒸气形成的阴影都会被识别成异物。合理的做法是引入「连续 N 帧确认」机制:只有当同一区域连续 3 到 5 帧都检出同类异物时,才触发声光报警。这个机制在 GUI 代码里是一个简单的计数逻辑:

def update_alert_state(class_track: dict, frame_id: int, new_dets: list): for cls, conf, frame_record in class_track.items(): if frame_id - frame_record["last_seen"] <= 5: frame_record["count"] += 1 else: frame_record["count"] = 1 frame_record["last_seen"] = frame_id for item in new_dets: cls = item["class"] if class_track[cls]["count"] >= 3: trigger_alarm(cls, item["confidence"])

这里 class_track 是一个字典,每个类别记录最近一次出现的帧号和连续出现计数。判断条件是帧间隔超过 5 帧就重新计数,避免把两个不同的异物误当成同一个持续目标。count 达到 3 才报警,这样能把单帧的偶然误检过滤掉。实际现场部署时,这个 N 值需要根据皮带速度调整,皮带越快,N 越小(否则异物已经过去还没触发报警),皮带越慢,N 可以适当增大。

5. 部署踩坑与常见问题排查:五个最容易翻车的地方

5.1 模型转换后推理结果与 PyTorch 原版不一致

  • 现象:同一个测试图片,用 .pt 模型跑出来的检测框和置信度,和 .onnx 模型跑出来的结果对不上,框的位置偏移,分数也变了。
  • 原因:99% 的情况是推理代码的预处理和后处理与训练时不一致。最常见的是 letterbox 的填充色用了默认 0 而不是 114;其次是 BGR 和 RGB 顺序没转;还有一个隐蔽点是归一化时用了 PIL 的 Image / 255.0 而 OpenCV 的 BGR 矩阵顺序不同。
  • 解决:在导出 onnx 前后,用同一张图分别在 PyTorch 和 ONNX Runtime 上跑一次前向,对比输出 tensor 的数值。误差超过 1e-3 就说明预处理不一致。我在项目调试时会把预处理写成独立函数,两个框架共用同一个函数,这样能从源头避免偏差。

5.2 GUI 推理导致界面卡死,程序无响应

  • 现象:启动 GUI 后点击「开始检测」,界面立刻白屏或显示「未响应」,过几秒弹窗提示强制退出。
  • 原因:把模型推理的 for 循环直接写在了主界面的槽函数里,比如在「开始检测」按钮的 clicked 事件里循环调 infer。主线程被推理阻塞,无法处理窗口绘制和鼠标事件,系统判定程序已无响应。
  • 解决:严格按照 4.2 节的方式,把推理放到 QThread 或 Python threading 中,主线程只做信号接收和 UI 更新。检查方法很简单:在推理循环里加一个 time.sleep(0.1),如果 GUI 滑块依然流畅,说明线程分离是正确的。

5.3 检测框位置整体偏移,在画面中不在目标上

  • 现象:模型检出了异物,但画出来的框偏在物体的左上角或右下角,有时甚至整个框跑到物体旁边的背景区域。
  • 原因:推理完成后,框的坐标是在 640x640 的 letterbox 坐标系里,直接画到了原图上,没有做「去 padding、按比例还原」的操作。坐标偏移量的大小正好等于 letterbox 填充的黑色区域边缘到原图边界的距离。
  • 解决:在用框坐标绘图前,严格按 3.3 节的 scale_boxes 函数执行还原。验证方法:随便取一帧检测结果,把还原后的框坐标与原图上的人工标注画在一起对比,两者应该基本对齐。

5.4 INT8 量化在常见的 CPU 上反而变慢

  • 现象:导出 onnx 后尝试用 onnxruntime 的 quantization 工具做 int8 量化,期望推理速度翻倍,结果在同一台 CPU 上 int8 模型的耗时比原始的 fp32 模型还长。
  • 原因:int8 量化在支持的指令集(如 AVX512 或 ARM 的 DotProd)上才有加速效果。老旧的 Xeon 或赛扬工控机没有这些指令集,onnxruntime 运行时会退化为反量化到 fp32 再计算,多了一步反而更慢。
  • 解决:部署前先用 python -c "import onnxruntime; print(onnxruntime.get_available_providers())" 查看可用的执行提供程序,同时在目标机器上用 benchmark 脚本实际跑一遍对比。如果 CPU 不支持 int8 加速,保持 fp32 模型即可,速度差异通常在 5% 以内,犯不着冒精度下降的风险。

5.5 训练时 loss 正常但验证集 mAP 极低

  • 现象:训练过程中 train loss 平缓下降,图像在训练集上检测效果不错,但在验证集上 mAP 只有 0.3 甚至更低。
  • 原因:这是典型的过拟合,或者数据集划分有问题。煤矿现场的图通常来自同一条皮带、同一个摄像机位,训练集和验证集如果来自同一段视频的连续帧,两者的场景几乎一样,模型看到验证集就「以为」还是训练集。反之,如果验证集用的是另一条皮带或不同角度的相机,模型没见过就不会检测。
  • 解决:按时间和场景划分数据集,比如前 3 天的图片做训练集、第 4 天的做验证集,而不是随机打乱。这样才能验证模型能否应对皮带速度、光照变化和煤流堆积形态的改变。另外,优先使用早停,patience 设为 20 到 30,防止后期过拟合导致验证集指标掉头向下。

6. 进阶用法:用评估指标曲线的 P/R 关系,给现场部署定「后悔药」阈值

部署之后,现场往往还会遇到一个棘手的问题:模型在测试集上的 mAP 挺漂亮,但实际运行起来误报频发或漏报严重。这时候最管用的不是重新训练,而是回到评估指标曲线上去找答案。这份资源里的指标曲线不是摆设,它记录了模型在不同置信度阈值下的 precision 和 recall 变化,这两条曲线的交叉点附近通常就是一个「后悔药」阈值——在这个点上,误报和漏报的代价相对平衡。

如果你需要放大 recall(优先保证不漏检),就取召回率曲线的拐点,通常是 recall 从 0.95 开始明显下滑的位置;如果你需要减小误报(巡检不愿天天被假报警折腾),就取 precision 接近 1.0 的最小阈值。用一段简单的脚本遍历测试集就能找到这个拐点:

import numpy as np def find_best_conf(metrics_path, target_recall=0.95): data = np.load(metrics_path, allow_pickle=True).item() confs = data["conf"] # 所有预测框的置信度 recalls = data["recall"] # 每个阈值下的召回率 best_conf = confs[-1] for conf, rec in zip(confs, recalls): if rec >= target_recall: best_conf = conf break return round(best_conf, 2) # 调用示列:把 GUI 的置信度滑块默认值改成 0.37 print(find_best_conf("metrics_val.npy", target_recall=0.95))

脚本逻辑很直白:从低置信度往高置信度扫,找到第一个让 recall 掉到 0.95 以下的置信度,取它的前一个值作为部署阈值。我在煤矿项目上跑这个脚本时,发现测试集上的最优阈值是 0.37,而不是训练默认的 0.25。默认阈值会让误报率翻倍,因为 0.25 到 0.37 之间有一大批低置信度的「疑似煤流纹理」预测框,拉低了 precision。

用脚本跑完阈值后,最后再把这套数值回填到 GUI 界面参数和告警逻辑里。从那以后,我每次对接新的皮带现场,都会先把资源里的评估脚本跑一遍,用 P/R 曲线定初始阈值,而不是直接用默认值上线,等现场反馈再来回调。这样既省去了频繁改代码的麻烦,也能给现场交付一个更接近最优状态的系统。希望帮到你。

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

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

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

立即咨询