ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

矿用视频监控智能分析:YOLOv5s+PyQt5实现越位报警与停车检测

2026/9/16 18:38:04 拓冰建站 浏览量
矿用视频监控智能分析:YOLOv5s+PyQt5实现越位报警与停车检测 简介基于yolov5s与PyQt5搭建的矿用智能视频监控系统完整源码面向矿山安全管理人员、计算机视觉开发者及工业现场项目爱好者重点解决井下人员越位报警、车辆停放识别、视频清晰度与稳定性检测等实际问题可直接作为相关课题或工程项目的参考基线。压缩包共30个文件涵盖16个Python源码文件、8张测试图片、2个模型权重文件含yolov5s.pt、1个UI设计文件以及依赖列表和说明文档整体约25.9MB代码按检测、界面、控制等模块拆分便于快速定位与修改。目前已有291人学习下载。资源提供了目标检测、PyQt5交互界面、PLC联动控制、异常行为分析等完整实现视频智能分析与越位报警功能均由Python代码驱动可从模型加载、图像处理到报警输出全流程跑通。对于希望快速掌握YOLOv5工程化封装、PyQt5上位机开发或工业安全监控方案的读者这份源码能有效缩短开发周期也可作为算法研究与功能扩展的起点。1. 矿用视频监控为什么把 yolov5s 和 pyqt5 组合在一起井下变电所、皮带巷、卸载站这些区域的摄像头绝大多数时间都只是把画面送到调度室既没人盯着看也没人手动翻录像。真要出问题——比如有人在皮带运行时穿越警戒线或者矿用卡车停在消防通道上——等值班员发现事故和视频都已经过去了。这类系统的价值不在“录”而在“测”和“报”画面模糊了要提醒人员跑到禁入区要报警车辆占道要识别。用 yolo 系列做目标检测、用 PyQt 搭桌面控制端是这类项目里最常见的组合其中 yolov5s 的体量对矿用边缘设备比较友好pyqt5 可以把摄像头预览、报警列表、区域标定放在同一个窗口里。下面按我做这类项目时的技术决策顺序把选型、线程模型、质量检测、报警规则和排错顺序逐个讲清楚。2. 接入 yolov5s 与 pyqt5 的最小工程骨架从安装到线程模型要搭这类系统第一件事不是写界面而是把“yolo 负责分析、pyqt5 负责呈现”这两条线想清楚。很多人上手就写界面回头发现推理一跑窗口就卡死问题恰恰出在线程模型上。2.1 yolov5s 为什么是井下边缘设备的均衡点yolov5s 的参数量约 7.2M权重文件约 14MB640x640 输入在 i5 级别 CPU 上单帧推理大约 80150ms放到带入门级 GPU 的工控机上能压到 2040ms。对比 yolov5mm 的精度提升有限但推理耗时增加三成左右工业现场往往要同时拉 48 路 IPC 流路数一多算力就被摊薄。所以我一般先拿 yolov5s 把整条链路跑通漏检多了再升级模型而不是一上来就用大模型。配套的数据集要注意COCO 预训练权重里的类别是 person、car 这类通用目标井下场景常有防爆车、梭车、皮带异物等特殊目标直接用预训练权重很容易把“人戴安全帽”和“人头”混在一起。最稳妥的做法是拿现场视频抽 20005000 帧标注后做增量微调类别控制在 36 个以内。2.2 pyqt5 安装与 pycharm 配置两个常见的坑先确认 Python 版本PyQt5 对 3.93.11 支持比较好太新的 Python 小版本容易遇到 pyqt5-qt5 依赖解析异常。安装命令如下# 创建独立虚拟环境避免污染系统 Python conda create -n mine_monitor python3.9 -y conda activate mine_monitor # 安装 pyqt5 和常用工具包 pip install pyqt5 pyqt5-tools numpy opencv-python如果装的是 torch 版 yolov5再加pip install torch torchvision如果设备是纯 CPU 或希望降低部署体积可以晚点换 onnxruntime。pycharm 里配置解释器时选 mine_monitor 这个环境就行不需要额外处理 webview 组件。Windows 上常见的坑是 pip 在解析 pyqt5-qt5 时因为自定义源路径失败表现为安装到一半报错退出解决办法是临时切换默认源再装或先升级 pip 再重试。.ui 文件转 .py 也是必会操作。pycharm 里把 pyuic5 配成 External Tool命令行对应的操作是pyuic5 -o mainwindow.py mainwindow.ui这样每次改界面布局后一键重新生成 Python 代码比手写 layout 快得多。要提醒的是生成的 .py 文件不要手动改界面逻辑写在新文件里重新生成时不会覆盖业务代码。2.3 推理线程与界面线程分离QThread 和信号槽把 yolov5 推理直接放进 pyqt5 窗口的主线程窗口就会每帧卡死一次。标准做法是把推理放在 QThread 里跑通过 signal/slot 把结果发回界面线程。下面给出一个最简的推理线程骨架import cv2 import torch from PyQt5.QtCore import QThread, pyqtSignal class VideoAnalyzer(QThread): # 第一个参数传解析结果dict第二个参数传预览用QImage frame_ready pyqtSignal(dict, object) def __init__(self, model_path, video_source, parentNone): super().__init__(parent) self.model torch.hub.load(., custom, pathmodel_path, sourcelocal) self.cap cv2.VideoCapture(video_source) self.running True def run(self): while self.running and self.cap.isOpened(): ret, frame self.cap.read() if not ret: break # 推理统一走640x640预览可以缩到1280以内 results self.model(frame[:, :, ::-1], size640) detections results.xyxy[0].cpu().numpy() # Nx6: x1,y1,x2,y2,conf,cls self.frame_ready.emit({dets: detections}, frame) self.msleep(30) def stop(self): self.running False self.wait()这里的关键是frame_ready信号把检测结果和原帧发到主界面主界面的槽函数只负责绘制不参与计算。模型加载放在__init__里是为了只加载一次避免每次启动视频都重新读权重。self.msleep(30)控制分析频率约为 30ms 一帧实际项目里我通常把推理控制在 1015 FPS因为越位和停车检测对这种时间粒度完全不敏感省下的 CPU 可以留多路视频。results.xyxy[0]返回的六列分别是目标框左上角 x、y、右下角 x、y、置信度和类别 id后面的区域判断都要基于这个格式。3. 视频质量检测在矿用监控里的落地模糊、黑屏与画面冻结矿用摄像头最容易出现的故障不在算法而在成像质量。粉尘附着镜片、补光失效、对焦漂移、夜视切换异常都会让画面变成“能录不能看”。质量检测的目标就是提前把这种异常变成调度台上的告警。3.1 选哪些图像指标来判断画面质量常见的质量检测算法有 BRISQUE 这类无参考评价模型也有基于梯度、亮度统计的轻量方法。井下设备的算力要匀给目标检测所以我不用太重的方法常规做法是检测四类异常模糊、过暗、过曝、画面冻结。模糊用拉普拉斯方差Laplacian Variance判断值越低说明边缘越弱、画面越糊。亮度直接看灰度均值均值落在正常区间之外说明补光或曝光异常。冻结用帧间差判断连续若干帧的像素变化都很小时说明编码流可能停了或摄像头死机。这三个指标组合起来基本能覆盖现场常见的画面问题。3.2 质量检测函数的实现与阈值标定import cv2 import numpy as np def check_frame_quality(frame, prev_frame, fps25): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 1. 清晰度拉普拉斯方差 lap_var cv2.Laplacian(gray, cv2.CV_64F).var() blurred lap_var 15 # 2. 亮度异常灰度均值统计 mean_brightness gray.mean() too_dark mean_brightness 50 too_bright mean_brightness 200 # 3. 画面冻结帧间平均绝对差 frozen False freeze_frames 0 if prev_frame is not None: diff cv2.absdiff(gray, cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)) mean_diff diff.mean() # 连续25帧(1秒)变化值都很低才判定为冻结 if mean_diff 1.0: freeze_frames 1 frozen freeze_frames fps return { blur: blurred, dark: too_dark, bright: too_bright, frozen: frozen, lap_var: lap_var, brightness: mean_brightness, }井下低照度场景里正常画面的拉普拉斯方差往往会比地面场景低一截所以我不会把15当成固定标准。实际操作是在每个摄像头装好后录 10 分钟正常画面的视频计算这段视频的平均拉普拉斯方差再乘以 0.40.6 作为阈值。比如正常值是 25阈值就设在 1015 之间。亮度的正常区间也按白天、夜班分别配置因为井下照度在交接班时段变化很大。3.3 把质量检测结果接入 pyqt5 状态栏在 pyqt5 里做一个 QTimer每隔 2 秒从当前帧做一次质量检查把结果通过信号发到界面状态栏。from PyQt5.QtCore import QTimer quality_timer QTimer() quality_timer.timeout.connect(on_quality_check) quality_timer.start(2000) def on_quality_check(self): # 从最新帧里取一帧做质量检测 latest_frame self.analyzers[0].latest_frame if latest_frame is None: return result check_frame_quality(latest_frame, self.prev_frame) self.prev_frame latest_frame.copy() # 把四个异常项目显示在状态栏颜色区分 if result[blur]: self.status_label.setText(摄像头画面模糊请安排现场擦镜) self.status_label.setStyleSheet(color: red;)质量状态的展示不一定要用复杂组件。有人喜欢把告警做成 HTML 日志面板用 QTextBrowser 的 setHtml 完全够用不需要引入 QWebEngineView 那种重量级依赖。真正的坑在self.prev_frame latest_frame.copy()——如果不 copy下一次循环里 frame 被覆盖absdiff 计算的就是同一帧的变化冻结检测永久不触发。这个细节我在现场调试时踩过冻结检测迟迟不报最后发现是引用问题。各种阈值先记在 config 文件里现场标定后回填不要写死在函数里。4. 视频智能分析的两条业务规则越位报警与停车检测质量检测解决“画面能不能看”这一章解决“画面里的情况要不要管”。矿用监控最常见的两条规则人员进入危险区域要报警车辆在禁停区域停留过久要告警。这两条规则的判定逻辑不同但都跑在同一条检测链路上。4.1 目标检测到业务规则之间的必要一跳跟踪yolov5s 输出的每一帧目标框都是独立的下一帧同一个人的框换了位置算法并不知道它们是同一个人。越位和停车都需要跨帧判断“谁在哪持续了多久”所以中间必须加跟踪层。最简单的是基于检测框的 IOU 匹配复杂一点用 DeepSORT 做外观特征匹配。井下环境光线不稳、粉尘多外观特征容易被干扰我一般用轻量的中心点最近邻匹配就够用了。实现上维护一个字典key 是 track_idvalue 是上一帧的中心点坐标。每帧检测完毕后对每个新检测框计算与上一帧已有目标的欧氏距离距离最小的且低于阈值的就认为是同一个目标。这样后面区域判定就有连续的时间上下文了。4.2 越位报警区域设定与状态机触发越位报警的业务逻辑可以分为三步在界面上画出危险区域多边形、判断目标中心点是否在多边形内、只在状态变化时上报。区域多边形可以预先写在配置文件里格式是一组归一化坐标点。程序启动时读取并还原成图像坐标。判断点是否在多边形内OpenCV 提供了现成的函数。import cv2 import numpy as np from collections import defaultdict class Zone: def __init__(self, name, points): self.name name self.points np.array(points, dtypenp.float32) # [[x1,y1],[x2,y2],...] self.state CLEAR # CLEAR 或 CROSS self.enter_time None def check_crossing(dets, zones, now_ts): alerts [] for zone in zones: in_zone False for x1, y1, x2, y2, conf, cls in dets: # 只对“人”做越位判断类别id按自己的数据集调整 if int(cls) not in (0, 1): continue cx, cy (x1 x2) / 2, (y1 y2) / 2 # 返回值为正:点在多边形内; 0:在边上; 负:在外部 if cv2.pointPolygonTest(zone.points, (cx, cy), False) 0: in_zone True break old_state zone.state zone.state CROSS if in_zone else CLEAR if zone.state ! old_state: if zone.state CROSS: zone.enter_time now_ts alerts.append({type: cross, zone: zone.name, time: now_ts}) return alerts这段代码里最关键的是“状态变化才触发”区域从 CLEAR 变 CROSS 只报警一次CROSS 持续期间不重复发。如果每帧都发报警中心会被刷爆。工程上还要加一个冷却窗口比如某区域 3 秒内只允许产生一条越位告警避免人员站在区域内边缘抖动导致反复触发。中心点判定的优点是计算快、稳定缺点是目标被遮挡时中心点可能暂时移出区域造成漏报如果漏报明显可以改成计算目标框与区域多边形的 IOU超过 0.3 就视为越位。4.3 停车检测中心点位移与停留时长停车检测要区分两类语义一类是“车辆应该停在这里但没停”常见于车场、装载点另一类是“车辆不该停在这里但停了”常见于消防通道、皮带下方通道。两者的判定逻辑一致只是一条取反、一条取正。核心做法是对每个 track_id 追踪中心点位移。位移持续小于阈值且达到设定时长就判定为停车。import math # track_history 保存每个目标最近一帧的中心点 # stop_record 记录目标开始静止的时间戳 MOVE_THRESHOLD 10 # 中心点位移小于10像素视为静止 PARK_SECONDS 15 # 持续15秒触发停车报警 def check_parking(track_hist, dets, now_ts, stop_record): alerts [] current_ids set() for x1, y1, x2, y2, conf, cls, track_id in dets: current_ids.add(track_id) cx, cy (x1 x2) / 2, (y1 y2) / 2 if track_id in track_hist: prev_x, prev_y track_hist[track_id] dist math.hypot(cx - prev_x, cy - prev_y) if dist MOVE_THRESHOLD: if track_id not in stop_record: stop_record[track_id] now_ts elif now_ts - stop_record[track_id] PARK_SECONDS: alerts.append({type: parking, track_id: track_id, duration: now_ts - stop_record[track_id]}) else: # 车辆移动了删除静止计时 stop_record.pop(track_id, None) track_hist[track_id] (cx, cy) # 清理已经消失的目标 for tid in list(track_hist.keys()): if tid not in current_ids: track_hist.pop(tid, None) stop_record.pop(tid, None) return alertsMOVE_THRESHOLD 的取值跟摄像头安装高度和画面分辨率强相关。1080p 画面里车辆停在原地时中心点抖动通常不超过 35 像素我把阈值放宽到 10 像素可以容忍轻度抖动。但如果摄像头装在铲运机上跟着画面移动这个阈值就必须放大或者改用相对坐标。PARK_SECONDS 在现场常设为 1030 秒太短容易被装料、卸料过程误触发。停车告警最好附带一张抓拍的原始帧调度员看照片就能判断是车坏了还是司机离岗不用反复回放视频。5. 源码实战的排错顺序与验证技巧拿到一套这类系统的源码不要急着跑先看目录结构。常见组织方式不会偏离这几个模块config 放摄像头地址、区域多边形、报警阈值detector 放 yolo 模型加载和推理封装可能同时有 torch 版本和 onnx 版本ui 放 pyqt5 界面文件、qss 样式和主窗口代码core 放质量检测、越位/停车规则引擎。先改 config 里的配置跑一遍再动代码是排查问题最快的路径。5.1 模型推理层先确认跑在哪个后端很多源码默认走torch.hub.load加载权重。torch 版本和 pyqt5 版本在同一 Python 进程里共存最容易翻车的是 Python 版本不匹配。如果系统要求免安装部署常见做法是把模型转成 onnx 后改用 onnxruntime 推理一来体积小二来 CPU 推理速度通常更好三来不依赖 torch 版本。转换命令是python export.py --weights best.pt --include onnx --opset 11转完后推理线程里的模型加载部分替换成 onnxruntime 的 InferenceSession输入输出名要看导出后的模型的元信息。在矿用系统里onnx 分支几乎是标配因为现场工控机往往不能联网装大型依赖。5.2 显示与绘制别反复转换 QImagepyqt5 显示视频帧的常见性能瓶颈是把 BGR 转 RGB 后直接 setPixmap 导致内存拷贝太多。正确姿势是 QImage 构造时直接引用合适的数据格式rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) pixmap QPixmap.fromImage(qimg.copy()) self.video_label.setPixmap(pixmap.scaled(self.video_label.size(), Qt.KeepAspectRatio))注意qimg.copy()这一步不能省——QImage 默认引用的是 numpy 数组的内存下一次循环 frame 被覆盖后界面画出来的就是坏帧。绘制检测框时直接在这个 RGB 副本上用 cv2.rectangle 画完再转 QImage比先转 QImage 再调 QPainter 画框省事性能差别不大。5.3 验证整条报警链路的最小脚本调试报警规则时我不想每次都打开图形界面命令行验证反而更快。下面这个脚本把摄像头视频流跑一遍把报警记录输出为 JSON lines方便用 grep 筛选。import cv2 import json import datetime cap cv2.VideoCapture(0) # 0为本地摄像头也可传RTSP地址 track_hist, stop_record {}, {} while cap.isOpened(): ret, frame cap.read() if not ret: break dets run_inference(frame) # 替换成自己的推理函数 alerts check_crossing(dets, zones, datetime.datetime.now()) alerts check_parking(track_hist, dets, datetime.datetime.now(), stop_record) for a in alerts: print(json.dumps({ts: str(datetime.datetime.now()), camera: cam01, event: a}, ensure_asciiFalse))验证越位报警可以找一个有人的视频把危险区域画在行人必然经过的路径上循环播放。脚本应当在人迈入区域的时刻输出一条event: {type: cross}的记录。验证停车报警则把禁停区域画在车辆静止位置附近保证中心点位移小于阈值等待超过 PARK_SECONDS 后查看有没有告警。这种验证方式还能顺手做回归测试——现场换了一个摄像头视角后直接拿新录像跑一遍脚本就能确认规则和阈值需不需要重新标定。本文还有配套的精品资源点击获取