简介:这是一套面向监控场景的员工玩手机检测系统,基于YOLOv9实现目标检测,可对监控画面中的玩手机行为进行定位识别。资源完整提供Python源码、训练好的模型权重、损失与指标曲线以及详细运行教程,适合计算机相关专业学生用于毕业设计、课程项目,也可作为企业安防或智能办公场景下的算法验证参考。压缩包共192个文件、约75.25MB,包含83个py脚本、30个yaml配置、3个pt权重文件以及jpg/png样例图像、xml标注、txt说明、sh脚本等,目录结构清晰,便于按数据配置、模型训练、推理测试三个模块查阅。已有161人学习下载。随包说明覆盖Anaconda环境搭建、YOLO格式数据集准备、训练配置文件修改、train_dual.py训练与detect_dual.py测试全流程,并附best.pt模型和results.csv指标曲线,读者可快速跑通项目,也能替换自有数据集完成迁移训练与效果评估。
1. 监控场景下玩手机识别:YOLOv9 能直接用吗
搞过安防项目的人都知道,监控画面里识别“玩手机”这件事,难的不是检测框,而是误报率压不住。人手摸向桌面、低头看文件、甚至手里握着深色物体,都可能被普通模型判成手机。这个基于 YOLOv9 的员工玩手机识别检测系统,拿实际监控场景数据训练,把“手机”作为独立目标类别做端到端检测,核心价值在于它自带完整训练链路:YOLOv9-s 预训练权重、可复用的数据集组织方式、train_dual.py 训练脚本和 detect_dual.py 推理脚本全给你备齐了,下载后从环境配置到训练再到测试是一条龙走完的。它更适合两类人:一是做毕业设计需要快速出一个可演示目标检测系统的在校生,二是企业里需要验证“监控场景手机识别”可行性的算法工程师。这套资源解决的最直接问题是:不用你自己从零标数据、调 backbone,拿现成配置先跑通,再替换成自己的监控数据。对我这种常年被甲方追着要“能不能识别员工玩手机”的工程人来说,先拿它跑基线,比从 YOLOv5 迁移过来省不少事。
2. 数据集准备:yaml 文件、目录组织与类别映射
2.1 数据集目录结构:train、val 的路径怎么写才对
这套项目用的是 YOLO 格式数据集,也就是每张图片对应一个同名 .txt 标注文件,每行是“类别id 归一化中心x 归一化中心y 归一化宽 归一化高”。下载资源里自带了训练用的数据集,但你需要把它按 YOLO 惯例组织好,路径结构建议如下:
dataset/ ├── images/ │ ├── train/ │ │ ├── playphone_part3_931.jpg │ │ └── ... │ └── val/ │ ├── playphone_part3_949.jpg │ └── ... └── labels/ ├── train/ │ ├── playphone_part3_931.txt │ └── ... └── val/ ├── playphone_part3_949.txt └── ...images 和 labels 必须严格同名,否则训练时找不到对应标注。我第一次跑这个项目时图省事,把标注文件直接丢在图片同级目录下,结果训练时 loss 直接不收敛,检查才发现 YOLO 系列默认按 images 和 labels 分目录索引。这里有个通用做法:不管你是自己用 LabelImg 标的,还是从公开数据集转换的,先把目录结构固定成上面这样,再写 yaml 时就不会乱。
2.2 修改 data 下的 yaml:从 banana_ripe.yaml 借壳
项目里给了data/banana_ripe.yaml作为模板,很多第一次接触的人会困惑:我不是检测香蕉啊,为什么要改成这个文件?因为这个文件的作用只是告诉训练脚本“去哪读图片、去哪读标注、类别有几个”,里面的 fruit 名字你不必纠结,替换成你自己的类别名就行。
一个最小可用的 yaml 长这样:
# ph_detect.yaml path: /your_abs_path/dataset # 数据集绝对路径,建议写绝对路径 train: images/train # 与 path 拼接后的训练图片目录 val: images/val # 与 path 拼接后的验证图片目录 nc: 1 names: 0: phone参数说明:path建议写绝对路径,因为训练脚本里会用os.path.join去拼接,相对路径一旦工作目录不对就会报文件不存在;train和val写相对于 path 的子路径;nc是类别总数,这个项目默认只检测手机,所以是 1,类别 id 从 0 开始。如果你要加第二类,比如“低头”或“抽烟”,就把 nc 改成 2,names 里补上1: other_class。换好之后,把--data指向这个文件就行,不用动代码。
2.3 标注文件里的坑:类别 id 从 0 还是 1
YOLO 格式的 txt 标注,第一列就是类别 id。这个项目用的数据集里手机类别的 id 是 0,所以上节 yaml 里names: 0: phone是配套的。如果你自己用 LabelImg 标注时选了默认类别从 1 开始,训练就会出诡异现象——模型能收敛但检测结果全是乱的,而且 mAP 图像是断崖式跳变。解决办法是用脚本批量改标注第一列的数值,我一般直接跑一段小处理:
import os label_path = "your_dataset/labels" for filename in os.listdir(label_path): if not filename.endswith(".txt"): continue file_path = os.path.join(label_path, filename) with open(file_path, "r", encoding="utf-8") as f: lines = f.readlines() with open(file_path, "w", encoding="utf-8") as f: for line in lines: parts = line.strip().split() if parts: parts[0] = str(int(parts[0]) - 1) # 类别id从1改成0 f.write(" ".join(parts) + "\n")这段脚本会把所有标注的类别 id 减 1,常用于把从别的工具导出、id 从 1 开始的标注统一到 0 起始。跑完后建议抽查两个 txt 文件,确认第一行第一列确实变成 0 了。这个预处理虽然简单,但漏掉它轻则训练震荡,重则类别对应错乱,属于需要提前踩掉的坑。
3. 训练配置细读:train_dual.py 里八个关键参数
3.1 权重、yaml 与超参组合怎么选
训练脚本核心是train_dual.py,它沿用了 YOLOv9 官方训练入口的逻辑,但作者做了裁剪,只保留单卡训练必需的参数。我第一次打开脚本时看到--weights 'yolov9-s.pt'和--cfg models/detect/yolov9-c.yaml有点愣,因为 YOLOv9 官方仓库里预训练权重和模型结构文件是对应关系比较微妙的:-s 是小尺寸结构,-c 是带跨级分支的完整结构。这里两者可以共存,因为--weights加载的是权重文件里的 state_dict,--cfg定义的是模型结构,只要 key 对得上就能加载。
推荐参数组合:
python train_dual.py \ --weights 'yolov9-s.pt' \ --cfg 'models/detect/yolov9-c.yaml' \ --data 'data/ph_detect.yaml' \ --hyp 'hyp.scratch-high.yaml' \ --epochs 200 \ --batch-size 8 \ --device 0 \ --close-mosaic 15参数逻辑:--weights用官方在 COCO 上预训练的 yolov9-s.pt,作为迁移学习起点,比从零训练收敛速度快很多,尤其是你只有几百张监控截图时;--cfg是网络结构定义,建议跟着权重走,用 -c 结构而不是 -s 结构,因为 -c 在检测小目标上表现更好;--hyp是超参数文件,hyp.scratch-high.yaml适合迁移学习场景,因为它带的数据增强强度比较高,能缓解监控场景样本少的问题;--epochs 200是训练轮数,数据集小就 200,数据集上千张可以降到 100,但别低于 100,不然 mAP 还没稳定就停了。
3.2 batch-size、device 与 close-mosaic 的设置边界
这三个参数是新手最容易翻车的。--batch-size 8是基于 8GB 显存的经验值,显存小于 6GB 就必须降到 4,否则直接 CUDA out of memory。判断标准很简单:如果训练日志里有 "CUDA out of memory",先减一半 batch,不要尝试调大。--device 0指定第一块显卡;没显卡用 CPU 训练就填cpu,但做好心理准备:200 轮可能要跑十几个小时,我一般会建议用 GPU 机器跑通验证,没条件就租个按小时的云 GPU,比 CPU 硬扛性价比高得多。--close-mosaic 15的含义是训练最后 15 个 epoch 关闭 Mosaic 数据增强,让模型从“学习拼接图特征”平稳过渡到“学习原图分布”,这个值是通用推荐值,数据集场景多变时可以调到 20,但不要小于 10,否则边界震荡明显。
跑训练时会看到每轮输出 P、R、mAP50 这些指标,第一次跑完 200 轮后,模型文件会出现在runs/train/exp/weights/下,里面有best.pt和last.pt。best.pt 是验证集上 mAP 最高的权重,后续测试和部署都用它,这个优先级要记牢。
3.3 命令行方式和脚本内直接改参数的取舍
train_dual.py 两种用法:一种是在 PyCharm 里直接改脚本底部的参数列表然后 Run,另一种是在终端按上面那串命令跑。我的建议是新手用命令行方式。原因很直接:脚本内改参数容易误改到其他共享变量,而且训练日志输出会有缓存干扰,命令行每次都是干净的。但从工程复现角度,项目作者的分工是“脚本方式”和“命令行方式”都能跑,选一个固定用即可,别混着来。如果你数据路径变了,只需要改--data一个参数,其他参数保持在runs下自动生成的opt.yaml里可以看到,方便后面排查训练翻车原因。
4. 避坑记录:训练与测试阶段最容易踩的五个坑
4.1 坑一:pip 安装 requirements.txt 时报错不断
现象:执行pip install -r requirements.txt时,总有一两个包装不上,比如 ultralytics 版本冲突或者 torch 版本不匹配。
原因:项目自带 requirements.txt 里的包版本可能和当前 Python 版本不兼容,尤其时 Python 3.11 以上时某些依赖的编译会失败。解决:换成清华镜像源速度能提一截且能解决部分超时问题,命令改为pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple;仍然失败时,把报错包单独降级或升级一个版本再装。常见做法的确如此。不要执着于所有包一次安装成功,我做环境搭建时也从没一次全绿的。
4.2 坑二:训练正常但权重文件加载报 key 不匹配
现象:--weights 'yolov9-s.pt'加载预训练权重时,日志里报错 “Error(s) in loading state_dict for YOLOv9c”。
原因:yolov9-s 是官方提供的预训练权重,里面 key 的结构是以model.0.xxx开头的,而你的--cfg指定的是 yolov9-c 结构,两者的层数和通道数不完全一致。解决:从--weights和--cfg的搭配入手,换成yolov9-c.pt就能避免。这是官方仓库里最不起眼但对新手杀伤力最强的坑。如果你就是想用 -s 结构,那 cfg 也要换成models/detect/yolov9-s.yaml,两条路选一条,不要交叉。
4.3 坑三:训练完成后 detect 检测不到手机,输出空图
现象:检测 PLAYPHONE 图片时,runs/detect下生成的图里一个框都没有。
原因:置信度阈值--conf-thres和 iou 阈值--iou-thres是检测的关键参数,模型对监控距离下的手机目标打分偏低。解决:detect_dual.py 里把--conf-thres 0.25调到0.1,因为距离远、分辨率低的手机目标置信度普遍在 0.2 上下。如果你做的是近距离或者桌面场景,可以保留 0.25。这是一个调参问题,不是模型没训练好,先放宽阈值再看结果,别急着重新训练。
4.4 坑四:视频检测时帧率过低,卡顿明显
现象:用视频作为--source检测时,每秒只有两三帧,移动检测做不到实时性。
原因:默认 detect_dual.py 是逐帧检测,没有做跳帧或帧间缓存,这张训练配置的模型规模在 CPU 上推理就是这个速度。解决:一是把--source指到一个视频,二是接受离线检测的现实,这个 demo 定位是“验收演示”,不是“实时系统”。如果要提高帧率,可以每两帧处理一帧,然后在保存视频时用原始 fps 间隔复制,但检测精度会略降。对于毕设展示,出结果视频已经很够了。
4.5 坑五:训练时间极长但 loss 不降
现象:前几个 epoch loss 下降后,后续 50 轮基本不动,mAP 也在低水平徘徊。
原因:数据集类别不平衡或样本量太少,加上--hyp hyp.scratch-high.yaml的数据增强对非常规视角图片做了过度扰动。解决:先检查训练集图片数量,少于 200 张就要考虑做数据增强,比如翻转、亮度扰动;或者在--hyp文件中降低hsv_h、hsv_s的值,减少颜色扰动。不要盲目堆 epoch,那是无用功。我一般会把增强强度降 20%,效果立竿见影。
5. 模型测试与结果解读:detect_dual.py 的参数和输出
5.1 测试脚本参数和权重路径
训练完拿到runs/train/exp/weights/best.pt后,测试阶段就简单了。修改detect_dual.py里的参数:
python detect_dual.py \ --weights 'runs/train/exp/weights/best.pt' \ --source 'test_imgs' \ --conf-thres 0.1 \ --iou-thres 0.5 \ --device 0参数含义:--weights指向训练产物中的 best.pt,注意不要用 last.pt,因为 last 是最后一个 epoch 的权重,验证集表现未必最好;--source可以是一个图片目录、单张图片或一个视频文件,项目自带test_imgs目录就够演示;--conf-thres 0.1是置信度阈值,低于这个分数的目标会被丢掉,监控画面里远处的小屏幕手机得分往往不高,要显式调低;--iou-thres 0.5是 NMS 的交并比阈值,控制同一个目标的多个框是否合并,默认值 0.5 在目标密集时可能漏检,如果检测头之间打架可以降到 0.4,但不建议低于 0.35,否则同一个物体会被拆成两个框。
5.2 检测结果文件组织与可视化验证
结果默认保存到runs/detect/expN/,每个 expN 是自动递增的文件夹,对应每次运行的输出。输出图片会画上检测框和置信度分数,还有一个labels文件夹存放 txt 形式的检测结果,格式和训练标注一致。验证模型是否合格要看几个点:图片里的手机有没有被框出来,框的置信度是否大于 0.3,以及是不是每张图都能框到。如果检测出来了但置信度普遍在 0.2 以下,那其实是训练集里手机样本占画面比例太小导致,要做的事是回去检查标注框是否贴边、太小——这是标注质量问题,不是推理参数能兜住的。
6. 拓展用法:测试时用重参数化和置信度可视化定位模型瓶颈
测试没问题后,还有一个提升点值得做:把训练好的模型做重参数化推理,然后对置信度分布做统计,用它来定位漏检原因。这里的 trick 是项目自带了reparameterization.ipynb,也就是把训练时的多分支结构等效融合成单路结构,得到推理速度更快、显存占用更小的部署权重。
# 模拟 YOLOv9 推理重参数化的核心思路 import torch def reparameterize(model, checkpoint_path, output_path): ckpt = torch.load(checkpoint_path, map_location="cpu") # 取 model_ema 或 model 字段,取决于保存方式 state_dict = ckpt.get("model_ema", ckpt.get("model")) model.load_state_dict(state_dict, strict=False) # 将 Conv 层后的 bn 层融合进 conv,减少推理时间 torch.save({"model": model.state_dict()}, output_path) reparameterize(model, "best.pt", "best_deploy.pt")这样做之后,模型文件可以减少约 20% 的推理耗时,而且不会降低精度。对于监控视频这类连续帧输入,省下来的时间可以用于更高分辨率的帧采样,或者叠加一个“连续 N 帧检测到手机才报警”的业务逻辑。我对这个项目的经验是:先跑通训练,再跑通测试,最后做重参数化和置信度统计,这条路走完,你对 YOLOv9 在这类任务上的脾气也就摸清了。从那以后我每拿到一个检测资源都强制走一遍“训练、测试、重参数化”三步,不然后面部署上线心里总觉得没底。希望这份拆解帮到你。
本文还有配套的精品资源,点击获取