ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5煤矿传送带异物检测实战:从训练到部署全流程

YOLOv5煤矿传送带异物检测实战:从训练到部署全流程 简介基于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.yamlimages 和 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 和 mAP0.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, interpolationcv2.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, valuecolor) return img, r, (dw, dh) def infer(session, img, conf_thres0.25, iou_thres0.45): im, ratio, pad letterbox(img) im im[:, :, ::-1].transpose(2, 0, 1) # BGR - RGB, HWC - CHW im np.ascontiguousarray(im, dtypenp.float32) im / 255.0 im im[None] # 增加 batch 维度 inputs session.get_inputs().name outputs session.run(None, {inputs: im})[0] # shape: [1, 25200, 5nc] # 后处理把框还原回原图坐标 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_optionssess_options, providersproviders)intra_op_num_threads 是 CPU 推理时使用的线程数我习惯设为 8再高对提升帮助有限。graph_optimization_level 设为全部开启onnxruntime 会自动做一些算子融合优化能让模型体积看起来没变但实际推理快 10% 到 15%。如果你用的是 GPU 推理 CUDAExecutionProvider 要放在列表第一位否则 onnxruntime 会优先用 CPU 而让你误以为 GPU 没生效。3.3 输出后处理解码 25200 个预测框并做 NMSONNX 模型的原始输出维度是 [1, 25200, 5nc]其中 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(timeout0.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 喂帧的通道用队列的好处是当推理速度跟不上视频帧率时新帧会自然堆积在队列里而不是把主线程阻塞住。timeout0.05 保证退出信号发出后worker 最多 50 毫秒内就能跳出循环不会造成程序关闭时的卡死。在这个场景里推理线程的处理速度是最核心的性能指标。如果你的工控机是 6 核 8 线程的 CPU640 输入分辨率下单个模型推理大约需要 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_recall0.95): data np.load(metrics_path, allow_pickleTrue).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_recall0.95))脚本逻辑很直白从低置信度往高置信度扫找到第一个让 recall 掉到 0.95 以下的置信度取它的前一个值作为部署阈值。我在煤矿项目上跑这个脚本时发现测试集上的最优阈值是 0.37而不是训练默认的 0.25。默认阈值会让误报率翻倍因为 0.25 到 0.37 之间有一大批低置信度的「疑似煤流纹理」预测框拉低了 precision。用脚本跑完阈值后最后再把这套数值回填到 GUI 界面参数和告警逻辑里。从那以后我每次对接新的皮带现场都会先把资源里的评估脚本跑一遍用 P/R 曲线定初始阈值而不是直接用默认值上线等现场反馈再来回调。这样既省去了频繁改代码的麻烦也能给现场交付一个更接近最优状态的系统。希望帮到你。本文还有配套的精品资源点击获取
返回列表