简介本资源面向计算机视觉入门与进阶开发者聚焦打电话行为检测这一实际场景提供基于YOLOv5的完整实现方案包含训练好的识别权重与配套数据集标签同时提供txt和xml两种格式并分文件夹存放方便直接用于训练或迁移学习。压缩包共165个文件约433.91MB涵盖34个py源码、32个pyc缓存、27个yaml配置、26张jpg与8张png示例图、4个pt权重、4个ui界面文件以及csv、sh、mp4等辅助内容结构清晰便于按模块查阅。资源内置PyQt可视化界面支持检测图片、视频及调用摄像头采用PyTorch框架与Python代码实现适合课程设计、毕业设计或行为识别项目快速落地。目前已有604人学习下载可帮助读者省去数据采集与标注环节直接获得可运行的检测流程与界面参考并对照检测结果理解模型训练与推理的完整链路。1. 从一通没人接的电话说起YOLOv5 打电话行为检测到底在做什么地铁驾驶室、危化品车间、加油站、考场监控室这些场景里都有一条硬规定当班人员禁止接打手机。但真要靠人盯着几十路监控画面十分钟就走神。我去年帮一个客户做值班室行为规范系统最初想用姿态估计加手部关键点判断结果现场光照一变、人一转身误报率直接飙到没法用。后来换成 YOLOv5 做打电话行为检测配合训练好的模型、自建数据集和 PyQt 界面整套东西在一台带独显的工控机上就跑起来了误报压到可接受范围。这篇笔记讲的就是这条完整链路YOLOv5 怎么标数据、怎么训、训练好的模型怎么落到 PyQt 界面里做实时推理以及现场部署时那些让人翻车的细节。适合两类人一类是手里有监控视频、想快速搭一个行为检测原型的工程师另一类是想把 YOLOv5 从「跑通 demo」推进到「能交付给值班室用」的开发者。数据集、模型权重、界面代码这三块我会拆开讲参数怎么设、坑在哪都按我实际踩过的说。2. 数据集怎么攒打电话行为检测的标注口径与增强策略2.1 为什么「打电话」这个类别比想象中难标目标检测里打电话行为检测的难点不在模型在标注口径。一个人举着手机贴耳朵是打电话举着手机看屏幕是玩手机手放在耳边但没拿手机是挠头这三种在低分辨率监控里长得几乎一样。我一开始图省事把所有「手靠近头部」都标成 phone训出来的模型把捋头发、扶眼镜全认成打电话现场根本没法用。后来定了一套硬口径只有「手持手机且手机贴近耳部或面部」才标 phone 类手机拿在手里看屏幕的单独标一个 use_phone 类手靠近头但没手机的干脆不标。类别数从 1 变成 2标注工作量翻倍但误报直接降了一个量级。如果你只做打电话告警建议至少保留 phone 和 use_phone 两类否则模型学到的边界太模糊。数据来源上公开数据集里专门做打电话行为的很少常见做法是自己从监控录像抽帧。抽帧别按固定间隔无脑抽同一段连续动作抽太多帧会造成样本冗余模型见过一万张几乎一样的图泛化反而差。我一般按「每个打电话片段抽 15 到 30 帧、每个正常片段抽 5 到 10 帧」的比例来保证正负样本别太失衡。2.2 用脚本把视频抽成帧并做初步筛选抽帧这一步我用 OpenCV 写了个小脚本顺手把模糊帧过滤掉省得后面标到一半发现全是糊的。import cv2 import os import numpy as np def extract_frames(video_path, out_dir, sample_gap10, blur_thresh80.0): 从视频抽帧过滤模糊帧 :param video_path: 输入视频路径 :param out_dir: 输出帧目录 :param sample_gap: 每多少帧取一帧 :param blur_thresh: 拉普拉斯方差阈值低于此值判为模糊 os.makedirs(out_dir, exist_okTrue) cap cv2.VideoCapture(video_path) idx, saved 0, 0 while True: ret, frame cap.read() if not ret: break if idx % sample_gap 0: gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 拉普拉斯方差越小越模糊 blur_score cv2.Laplacian(gray, cv2.CV_64F).var() if blur_score blur_thresh: cv2.imwrite(os.path.join(out_dir, f{idx:06d}.jpg), frame) saved 1 idx 1 cap.release() print(f总帧数 {idx}保存 {saved} 张) extract_frames(monitor_01.mp4, frames/monitor_01, sample_gap10)逻辑说明sample_gap10表示每 10 帧取一张25fps 的视频大约每秒取 2.5 张对行为检测够用。blur_thresh是关键参数监控画面普遍偏糊我一般设在 60 到 100 之间太低会把糊图留下太高会把正常帧也滤掉。跑完先抽看几十张确认打电话动作被覆盖到再进标注工具。标注工具用 LabelImg 或 X-AnyLabeling 都行导出 YOLO 格式的 txt。每张图的 txt 里一行一个目标格式是类别id 中心x 中心y 宽 高坐标都是归一化到 0 到 1 的值。标完记得写一个classes.txt类别顺序要和后面训练配置里的names完全一致顺序错一位模型训出来全是错的这个坑我见过不止一次。2.3 数据增强别乱开监控场景有它的脾气YOLOv5 自带 mosaic、mixup、HSV 增强、随机翻转这些。监控场景下我的经验是HSV 增强可以开因为现场光照变化大随机翻转要谨慎左右翻转对打电话动作影响不大可以开mosaic 建议在训练后期关掉因为它会把四张图拼一起小目标容易糊成一团而监控里人本来就小。还有一个容易被忽略的点负样本。很多人只标打电话的图结果模型见到谁都觉得在打电话。我一般会往训练集里塞 20% 到 30% 的纯背景图或者正常行为图txt 留空让模型知道「没有目标」也是一种答案。这一步做完误报率通常能再降一截。3. 训练 YOLOv5从 data.yaml 到权重文件的完整命令3.1 环境准备和目录结构训练环境我一般用 Python 3.8 加 PyTorchCUDA 版本跟显卡驱动对齐。YOLOv5 的仓库拉下来之后目录结构建议整理成下面这样后面配置路径不容易乱yolov5/ ├── data/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── phone.yaml ├── models/ └── runs/images/train和labels/train里的文件名要一一对应比如000123.jpg对应000123.txt。YOLOv5 靠文件名找标签名字对不上会直接报「找不到标签」或者更坑的——静默跳过你以为训了其实一半数据没进去。3.2 data.yaml 怎么写类别顺序为什么是命门phone.yaml内容如下path: /home/user/yolov5/data train: images/train val: images/val nc: 2 names: [phone, use_phone]nc是类别数names的顺序必须和标注时classes.txt的顺序一模一样。我踩过的坑是标注时先写的 use_phone 后写的 phone配置里却按 phone 在前训出来的模型把两类完全搞反界面上显示「打电话」其实是看手机。改配置比重标便宜但排查花了半天血泪经验就是标完先核对一遍顺序。3.3 训练命令和关键参数怎么调基础训练命令python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/phone.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --name phone_exp \ --cache参数说明--img 640是输入分辨率监控里人小的话可以提到 960但显存和速度要权衡--batch 16看显存8G 显存跑 640 一般能到 16爆显存就降到 8--weights yolov5s.pt用预训练权重别从零训收敛快很多--cache把图片缓存到内存数据集不大时能明显加速。训练过程中重点看三个指标mAP0.5、precision、recall。打电话检测这种场景recall 比 precision 更值得盯漏报一个打电话的比误报几次更麻烦。如果 recall 上不去先查标注有没有漏标再考虑加数据或调低置信度阈值。3.4 训练完怎么验证模型不是「假学会」训完在runs/train/phone_exp/weights/下会有best.pt和last.pt。别急着上界面先用验证集跑一遍python val.py \ --data data/phone.yaml \ --weights runs/train/phone_exp/weights/best.pt \ --img 640 \ --conf 0.25--conf 0.25是置信度阈值验证时先用默认值看整体表现。如果 mAP 不错但实际画面误报多多半是验证集和现场分布不一致得补现场数据。我一般还会拿几段没参与训练的现场视频单独跑看真实漏报误报这一步比看 mAP 数字靠谱得多。4. PyQt 界面怎么做把训练好的模型塞进值班室能用的窗口4.1 界面结构视频区、控制区、告警区三块就够PyQt 界面不用做花哨值班室要的是稳定和一眼看懂。我的布局是左边一个大 QLabel 显示视频右边一列放「选择视频/摄像头」「开始/暂停」「置信度滑块」底部一个 QTextEdit 滚动显示告警记录。核心是把 YOLOv5 推理放在子线程里主线程只管刷新界面否则视频一卡界面就假死这是 PyQt 做视觉应用最常见的翻车点。4.2 推理线程和界面刷新的最小实现import sys import cv2 import torch from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget class DetectThread(QThread): frame_signal pyqtSignal(object) # 发送带框的帧 alarm_signal pyqtSignal(str) # 发送告警文本 def __init__(self, weights, source, conf0.25): super().__init__() self.model torch.hub.load(ultralytics/yolov5, custom, pathweights, force_reloadFalse) self.model.conf conf self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break results self.model(frame) # 只取 phone 类触发告警 for *box, conf, cls in results.xyxy[0].tolist(): if self.model.names[int(cls)] phone and conf self.model.conf: self.alarm_signal.emit(f检测到打电话 置信度{conf:.2f}) annotated results.render()[0] self.frame_signal.emit(annotated) cap.release() def stop(self): self.running False self.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(打电话行为检测) self.label QLabel() self.setCentralWidget(self.label) self.thread DetectThread(best.pt, 0, conf0.25) self.thread.frame_signal.connect(self.update_frame) self.thread.alarm_signal.connect(lambda s: print(s)) self.thread.start() def update_frame(self, frame): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape img QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(img).scaled( self.label.size(), Qt.KeepAspectRatio)) if __name__ __main__: app QApplication(sys.argv) win MainWindow() win.show() sys.exit(app.exec_())逻辑说明DetectThread继承 QThread把模型加载和逐帧推理都放在run里通过信号把结果发回主线程避免跨线程操作控件。torch.hub.load直接加载本地best.ptforce_reloadFalse避免每次重复下载。self.model.conf是置信度阈值界面上那个滑块就是改这个值。告警只对phone类触发use_phone可以只记录不告警具体看业务。参数上conf设太低误报多设太高漏报多现场我一般从 0.25 起调根据实际画面微调。视频源source填 0 是默认摄像头填文件路径就是本地视频填 RTSP 地址就是网络流注意网络流要处理断线重连否则跑一晚上第二天发现早断了。4.3 界面卡顿和内存泄漏的两个常见原因界面卡顿十有八九是推理和刷新在同一个线程或者每帧都新建 QPixmap 没释放。上面代码里推理在子线程刷新在主线程基本能保证流畅。内存泄漏常见于results.render()返回的数组没及时释放长时间跑内存会缓慢上涨。我的做法是每隔几百帧手动gc.collect()一次或者把渲染结果转成 QImage 后不再持有原数组引用。5. 避坑与排查打电话行为检测上线前必须过的五道坎5.1 模型在验证集很好现场全是误报现象val.py跑出来 mAP 0.9 以上接到现场视频一看满屏框。原因验证集和现场分布不一致验证集可能是从同一批视频抽的光照、角度、人员着装都太像。解决从现场重新抽一批没参与训练的帧人工看误报集中在哪些场景针对性补负样本再微调。别指望调置信度能根治那只是把漏报换成误报。5.2 界面跑几分钟就卡死现象视频画面停住窗口无响应。原因推理写在主线程或者信号槽连接用了阻塞方式。解决确认推理在 QThread 子线程里信号用默认的队列连接。另外检查有没有在run里直接操作 QLabel跨线程操作控件是 PyQt 大忌。5.3 置信度调来调去总是不满意现象调高漏报调低误报找不到平衡点。原因模型本身区分度不够或者类别定义太模糊。解决回到数据层面把 phone 和 use_phone 的边界标清楚必要时加一个「手靠近头但无手机」的负样本类。模型能力不够时调阈值只是拆东墙补西墙。5.4 换一台机器模型加载报错现象开发机跑得好好的部署到工控机报CUDA out of memory或找不到权重。原因部署机显卡显存小或者 PyTorch 版本不一致。解决部署前确认显存必要时把--img降到 416 或用 yolov5n。权重文件路径用绝对路径别用相对路径工控机上工作目录一变就找不到。5.5 网络流跑一晚上就断现象RTSP 流第二天早上发现早断了界面还显示最后一帧。原因网络抖动或摄像头重连OpenCV 的 VideoCapture 不会自动恢复。解决在读取循环里判断ret为 False 时释放并重新VideoCapture加重试间隔。这个逻辑不加长期稳定性测试根本过不了。6. 让模型更稳的几个进阶技巧从能跑到敢交付模型训出来能跑和敢交付到值班室中间差的是稳定性。我一般会做两件事一是滑动窗口滤波二是告警去抖。滑动窗口滤波的思路是连续 N 帧里至少有 M 帧检测到 phone 才触发告警单帧误检直接被滤掉。实现上维护一个长度为 N 的队列每帧把是否检测到 phone 压进去统计为真的数量超过 M 才发告警。N 取 5 到 10M 取 N 的一半左右具体看帧率和动作持续时间。这个技巧对监控场景特别管用因为真实打电话动作会持续好几秒而误检往往是单帧的。第二件事是告警冷却。同一个人连续打电话不能每帧都弹告警否则告警区刷屏。我一般设一个冷却时间比如 10 秒内同一区域只报一次。实现上记录上次告警时间戳新告警来的时候判断间隔。这两件事做完现场误报能再降一大截值班员才愿意用。还有一个验证习惯别只看 mAP拿一段完整的、没剪过的现场视频跑一遍人工数漏报和误报算一个「每百分钟误报次数」。这个数字比任何指标都直观客户也听得懂。我一般要求自己做到每百分钟误报少于 3 次才敢交付达不到就回去补数据。最后说个我自己的教训早期做这类项目我总想着把模型调得更强后来发现现场问题八成出在数据和部署上模型本身反而没那么关键。数据集标清楚、负样本给够、推理放子线程、网络流加重连这四件事做到位yolov5s 就够用了不必一上来就上大模型。希望帮到你。本文还有配套的精品资源点击获取