ARTICLE DETAIL

建站实战干货

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

监控场景玩手机检测:基于YOLOv9的Python识别系统实战

2026/9/28 17:21:39 拓冰建站 浏览量
监控场景玩手机检测:基于YOLOv9的Python识别系统实战 简介本资源面向计算机、人工智能、自动化等专业学生与开发者提供一套基于YOLOv9的监控场景员工玩手机行为识别检测系统可用于毕业设计、课程项目或企业安防场景的二次开发。压缩包共192个文件约75.25MB包含83个Python源码、30个YAML配置文件、3个pt权重文件以及训练日志、指标曲线图、测试图片与标注数据等覆盖从数据准备、模型训练到推理测试的完整流程。资源内附详细运行教程指导完成Anaconda与PyCharm环境配置、依赖安装、数据集yaml修改、train_dual.py训练参数调整及detect_dual.py推理测试并给出命令行训练示例与常见参数说明。目前已有160人学习关注。读者可获得可直接运行的YOLOv9玩手机检测工程、预训练模型、指标曲线与排错思路便于快速复现结果并迁移到自有数据集。1. 监控场景玩手机检测从 YOLOv9 权重到可复现的 Python 识别系统厂区监控室里值班主管盯着一面 16 路电视墙最头疼的不是有人打瞌睡而是产线工人低头刷手机——等发现时人已经刷了十分钟。靠人盯屏不现实靠通用目标检测模型直接跑也不行手机在画面里往往只有几十像素还经常被手、工服、桌面遮挡模型要么漏检要么把工牌、对讲机、螺丝刀误判成手机。这套「监控场景玩手机检测-基于 YOLOv9 的员工玩手机识别检测系统」要解决的正是这个具体问题用 YOLOv9 训练一个专门识别「人 手机 玩手机姿态」的检测器配上 Python 推理脚本、训练好的模型权重和指标曲线让一套监控视频流能自动标出违规玩手机行为。它适合两类人一类是想把 YOLOv9 真正落到工业安防场景的算法工程师另一类是手里有监控数据、想跑通一套完整检测流程的 Python 开发者。下面按「数据怎么准备 → 模型怎么训 → 指标怎么看 → 推理怎么接 → 坑在哪」的顺序把这条链路拆开讲清楚。2. 玩手机检测的数据集构建与 YOLOv9 标注规范2.1 为什么通用 COCO 模型在监控场景下会翻车先说清楚一个反直觉结论拿 COCO 预训练的 YOLOv9 直接推理监控画面玩手机这一类几乎不可用。原因有三层。第一层是尺度问题COCO 里手机的平均像素面积在 100×100 以上而 1080P 监控画面里一个工人手里的手机可能只有 30×50 像素YOLOv9 的 P3 特征层虽然对小目标友好但预训练权重里根本没有「小手机」这种分布。第二层是场景差异监控是俯视或斜俯视视角手机常处于「被手握住、屏幕朝向身体」的姿态和 COCO 里「手持手机正对镜头」的样本分布完全不同。第三层是类别定义通用模型只检测「cell phone」这个物体而业务要的是「玩手机行为」也就是人 手机 头部低垂姿态的组合单靠一个 cell phone 框无法判定违规。所以正确做法是重新定义检测类别。我一般会设三个类person人、phone手机、playing玩手机行为框覆盖人和手机的整体区域。前两类用于定位第三类用于直接判定违规。这样后处理时只要playing类置信度超过阈值就报警逻辑简单误报也容易控制。如果数据量足够也可以只标playing一类做成单类检测器推理更快但可解释性差一些排查误报时不好定位是手机没检到还是姿态判断错了。2.2 从监控视频抽帧到 YOLO 格式标注的完整流程数据来源通常是现场监控录像或公开的工地、车间视频。第一步是抽帧不要每秒都抽相邻帧几乎一样浪费标注成本。我一般按 2~3 秒一帧抽一个 10 分钟的视频抽 200~300 帧就够。用 OpenCV 写个脚本import cv2 import os # 抽帧脚本每 interval 帧保存一张避免冗余 video_path monitor.mp4 out_dir frames os.makedirs(out_dir, exist_okTrue) interval 60 # 假设 30fps60 帧约 2 秒一帧 cap cv2.VideoCapture(video_path) idx 0 saved 0 while True: ret, frame cap.read() if not ret: break if idx % interval 0: # 可选缩放到 1280 宽减小标注和训练压力 h, w frame.shape[:2] if w 1280: frame cv2.resize(frame, (1280, int(h * 1280 / w))) cv2.imwrite(f{out_dir}/frame_{saved:05d}.jpg, frame) saved 1 idx 1 cap.release() print(f共保存 {saved} 帧)这段脚本的关键参数是interval它决定抽帧密度。30fps 视频里 60 帧约等于 2 秒动作变化能被覆盖又不至于重复。resize到 1280 宽是权衡太小手机像素丢失太大标注和训练都慢。抽完帧后用 LabelImg 或 X-AnyLabeling 标注导出 YOLO 格式每张图对应一个.txt每行是class_id cx cy w h坐标全部归一化到 0~1。标注时有三条血泪经验。第一phone框要贴着手机可见部分画不要把手也算进去否则模型学到的是「手」而不是「手机」。第二playing框要覆盖人头到手机的整体区域不要只框手机否则和phone类混淆。第三遮挡超过 70% 的手机不要标标了反而引入噪声。标完检查一遍类别平衡如果playing样本不到person的十分之一后面训练要加过采样或 focal loss。2.3 数据集划分与 data.yaml 配置标注完成后按 8:1:1 划分训练、验证、测试集。目录结构建议这样组织dataset/ images/ train/ val/ test/ labels/ train/ val/ test/ data.yamldata.yaml是 YOLOv9 训练入口内容如下path: ./dataset train: images/train val: images/val test: images/test nc: 3 names: [person, phone, playing]nc是类别数必须和标注里的class_id最大值一致否则训练时报 index 越界。names顺序要和标注时类别顺序完全对应错一个位置整个模型就学歪。常见错误是把path写成绝对路径后换机器跑不起来建议用相对路径训练时在项目根目录执行。注意如果监控画面是红外或夜间模式白天和夜间样本要分别抽帧并混合进训练集否则模型在夜间几乎全漏。夜间样本至少占 20%。3. YOLOv9 训练配置从预训练权重到指标曲线解读3.1 YOLOv9 选型理由与模型结构关键点YOLOv9 相比 YOLOv8 最大的变化是引入了 PGI可编程梯度信息和 GELAN 结构简单说就是在浅层保留更完整的梯度信息让小目标检测更稳。这对玩手机检测很关键因为手机本身就是小目标。实际选型时如果显存只有 8G选yolov9-t或yolov9-s显存 12G 以上可以上yolov9-m。不要一上来就yolov9-e参数量大、训练慢监控场景的数据量通常撑不起这么大的模型过拟合风险高。预训练权重用官方 COCO 版本做初始化虽然 COCO 没有玩手机类但底层边缘、纹理特征可迁移比从头训收敛快很多。常见做法是冻结 backbone 前几轮先训 head再解冻全量微调。YOLOv9 官方代码里通过--freeze参数控制一般设--freeze 10冻结前 10 层。3.2 训练命令与超参数设置假设你已经拉好 YOLOv9 代码环境训练命令如下python train_dual.py \ --weights yolov9-s.pt \ --data dataset/data.yaml \ --img 640 \ --batch 16 \ --epochs 150 \ --device 0 \ --workers 8 \ --freeze 10 \ --patience 30 \ --name playing_phone逐个参数说明。--img 640是输入分辨率监控小目标建议用 640 起步如果手机还是漏检可以升到 960但显存和速度会明显下降。--batch 16在 8G 显存上跑 yolov9-s 比较稳爆显存就降到 8。--epochs 150配合--patience 30意思是 30 轮验证指标不提升就早停避免无效训练。--freeze 10冻结前 10 层数据量小于 5000 张时强烈建议加。--workers 8是数据加载线程和 CPU 核数相关设太大反而拖慢。训练过程中重点看三个输出box_loss、cls_loss、mAP50。box_loss下降说明框位置在收敛cls_loss下降说明类别判断在变好。如果box_loss降但cls_loss震荡通常是类别不平衡playing样本太少需要加过采样。如果两个 loss 都不降检查学习率YOLOv9 默认 lr00.01小数据集可以降到 0.001。3.3 指标曲线怎么读mAP50、mAP50-95 与 PR 曲线的实际含义训练完在runs/train/playing_phone/下会生成results.png、PR_curve.png、confusion_matrix.png。指标曲线不是用来看「好不好看」的是用来定位问题的。mAP50是 IoU 阈值 0.5 时的平均精度反映「框大致对不对」。玩手机检测里mAP50到 0.85 以上基本可用。mAP50-95更严格要求框位置非常准这个值通常比mAP50低 0.2~0.3如果低太多说明框回归不准可能是标注框抖动大。PR_curve看每个类的曲线如果phone类曲线明显低于person说明手机样本不够或太小。confusion_matrix最有价值能直接看出phone被误判成playing的比例如果这个比例高说明两类定义重叠需要重新界定标注规则。我一般会重点看confusion_matrix的归一化版本横轴真实类、纵轴预测类。理想情况是对角线深、其他浅。如果background列很深说明误报多需要提高推理置信度阈值或补充负样本。提示指标曲线里的val/box_loss如果后期反弹是过拟合信号回退到反弹前的 epoch 权重或者加数据增强mosaic、mixup。4. Python 推理脚本把训练好的模型接到监控视频流4.1 推理环境依赖与模型加载训练完得到best.pt推理侧只需要 ultralytics 或 YOLOv9 官方推理接口加 OpenCV。依赖清单pip install torch torchvision opencv-python numpy如果用的是 YOLOv9 官方仓库推理脚本基于detect.py改如果用 ultralytics 封装版直接YOLO(best.pt)即可。我一般用后者接口稳定、跨平台好。加载模型时注意device参数有 GPU 用cuda:0没有就cpuCPU 推理 640 分辨率大概 5~8 FPS勉强能跑但实时性差。4.2 单帧检测与玩手机行为判定逻辑核心推理代码如下from ultralytics import YOLO import cv2 model YOLO(best.pt) CONF 0.45 # 置信度阈值 IOU 0.5 # NMS IoU 阈值 PLAYING_ID 2 # playing 类索引 cap cv2.VideoCapture(monitor.mp4) while True: ret, frame cap.read() if not ret: break results model.predict(frame, confCONF, iouIOU, verboseFalse) for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 map(int, box.xyxy[0]) if cls_id PLAYING_ID and conf CONF: # 判定为玩手机画红框并报警 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(frame, fPLAYING {conf:.2f}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(detect, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明model.predict返回每帧的检测结果遍历boxes拿到类别、置信度和坐标。只有cls_id PLAYING_ID且置信度超过阈值才判定违规。CONF0.45是经验值太低误报多太高漏报多实际部署时根据现场误报率调。IOU0.5控制 NMS 合并重叠框玩手机场景里人和手机框可能重叠IoU 设太高会保留重复框。4.3 多路视频流与报警去抖单路跑通后实际监控是 8 路、16 路。多路推理有两个做法一是多进程每路一个进程独立跑二是批处理把多路帧拼成一个 batch 送模型。前者简单但吃内存后者效率高但要改推理接口。我一般用多进程每路一个Process通过队列把报警事件汇总到一个主进程写日志。报警去抖很重要。玩手机动作可能持续几秒逐帧报警会刷屏。做法是维护一个状态字典同一路同一区域连续 N 帧检测到playing才触发一次报警N 一般设 15~30约 0.5~1 秒。报警后设冷却时间比如 30 秒内同一区域不重复报。这样日志干净值班人员也不会被轰炸。注意多路推理时 GPU 显存是瓶颈16 路 640 分辨率 yolov9-s 大概需要 10G 以上显存。显存不够就降分辨率到 480 或换更小的模型。5. 玩手机检测落地避坑5 个真实踩坑记录5.1 坑一手机太小导致漏检率居高不下现象训练指标mAP50到 0.88但现场推理时手机漏检严重尤其是远距离工位。原因训练集里手机像素分布和现场不一致抽帧时缩放到 1280 宽但现场摄像头是 1080P 且工人距离更远手机实际像素更小。解决一是训练时开启--img 960或更高分辨率二是抽帧时不要缩放保留原始分辨率三是补充远距离样本。如果还不行可以在推理前对画面做 1.5 倍放大再送模型代价是速度下降。5.2 坑二工牌、对讲机被误判成手机现象模型把胸前工牌、手持对讲机识别成phone进而触发playing误报。原因这些物体在监控画面里都是矩形小目标纹理和手机相似标注时如果没标这些负样本模型没见过就会乱判。解决专门收集一批含工牌、对讲机、螺丝刀的负样本图标注时只标person不标phone让模型学会区分。另外可以在data.yaml里加一个other类把这些干扰物单独标出来效果更稳。5.3 坑三夜间红外画面几乎全漏现象白天检测正常夜间切红外后playing类几乎检不到。原因训练集全是白天彩色样本红外画面是灰度、对比度低、纹理丢失模型分布外泛化失败。解决夜间样本必须进训练集至少占 20%。如果夜间样本难标可以用白天样本做灰度化 对比度扰动增强模拟红外效果但效果不如真实红外样本。5.4 坑四置信度阈值设太高导致漏报现象为了压误报把CONF调到 0.7结果大量真实玩手机行为漏报。原因playing类本身置信度分布偏低因为姿态多样、遮挡多模型给的分普遍不如person高。解决不要一刀切设阈值按类设。person可以 0.5playing设 0.35~0.4配合去抖逻辑压误报。另外看PR_curve选阈值找 F1 最高的点。5.5 坑五换摄像头后模型性能骤降现象同一套模型A 车间跑得好换到 B 车间误报漏报都变多。原因摄像头安装角度、焦距、光照不同画面分布变了。解决每换一个场景至少抽 100 帧做验证如果指标掉超过 10%需要补充该场景样本做微调。微调时学习率调小到 0.0001训 20~30 轮即可不要从头训。6. 进阶技巧用滑动窗口滤波稳定玩手机判定最后一章讲一个我实际部署时最常用的技巧滑动窗口滤波。逐帧判定玩手机有个玄学问题——模型偶尔会在某一帧把手机误判成playing或者真实玩手机时中间有几帧漏检导致报警断断续续。滑动窗口滤波就是用一个固定长度的队列存最近 N 帧的判定结果队列里playing占比超过阈值才最终判定。from collections import deque class PlayingFilter: def __init__(self, window15, ratio0.6): self.window window # 窗口帧数 self.ratio ratio # 触发比例 self.buf deque(maxlenwindow) def update(self, is_playing): self.buf.append(1 if is_playing else 0) if len(self.buf) self.window: return False return sum(self.buf) / self.window self.ratio参数怎么设window15在 25FPS 下约 0.6 秒能滤掉单帧抖动ratio0.6表示窗口内 60% 帧判定玩手机才触发兼顾灵敏度和稳定性。如果现场误报多把ratio提到 0.7如果漏报多降到 0.5。这个滤波器对每路视频单独维护一个实例不要共用。验证滤波效果的方法拿一段已知违规时间的测试视频跑推理并记录每次报警的时间戳和真实违规时间段对比。理想情况是报警时间落在违规区间内且不早于违规开始、不晚于违规结束太多。如果报警频繁出现在违规前后说明window太大滞后明显降到 10 试试。我自己的习惯是任何检测系统上线前先用滑动窗口滤波跑一遍历史视频统计误报率和漏报率两个指标都达标才接实时流。这套玩手机检测方案从数据标注到推理部署最花时间的不是训模型而是标注规则统一和负样本收集。模型本身用 YOLOv9 已经够强真正决定落地效果的是数据质量和后处理逻辑。希望帮到你。本文还有配套的精品资源点击获取