基于YOLOv5与PyQt5的网课专注度检测系统实战
简介本资源是一套基于YOLOv5目标检测的网课专注度检测系统完整源码面向计算机相关专业正在做毕业设计的学生以及需要深度学习项目实战练习的学习者也可用于课程设计与期末大作业。项目采用Python开发结合PyQt5搭建可视化界面通过人脸关键点与目标检测模型判断学习者的专注状态是一份可直接运行、便于二次开发的高分毕设方案。压缩包共122个文件约179.87MB包含39个py源码、43个pyc编译文件、18个yaml配置、3个pkl与1个pt模型权重、1个onnx导出模型以及ui界面文件、mp3提示音、jpg素材和项目计划书文档覆盖训练、推理与界面交互全流程。目前已有112人学习关注。读者可获得完整可运行的工程代码、预训练模型与界面资源并参考项目计划书与目录结构快速理解系统设计思路适合作为毕设答辩与项目实战的参考模板。1. 从网课场景切入YOLOv5 专注度检测到底在做什么线上网课最大的问题是老师看不到屏幕另一端的状态。学生开着摄像头但人在走神、低头玩手机、趴在桌上这些行为在传统考勤系统里完全不可见。Python 基于 YOLOv5 目标检测的网课专注度检测系统本质上就是解决这个问题的用摄像头实时抓取画面通过 YOLOv5 检测学生的姿态和头部朝向再配合 PyQt5 做一个本地可视化界面把「专注 / 走神 / 离开」的状态实时显示出来。这套方案适合做计算机视觉方向的毕设也适合想入门目标检测落地的开发者——它把数据标注、模型训练、推理部署、界面集成四个环节串成了一条完整链路。很多人搜 yolov5 训练自己的数据集、pyqt5 界面设计、yolov5 环境配置其实都是在为这类项目做准备。下面我按实际做过的路径把每个环节拆开讲清楚。2. 数据准备与标注从原始课堂截图到 YOLO 格式数据集2.1 专注度检测的类别定义与标注策略做目标检测项目第一步不是写代码而是想清楚要检测什么。网课专注度检测的类别设计直接决定模型能不能用。我一般会定义四类focus专注正对屏幕、头部抬起、distracted走神低头或侧头、phone玩手机手部持机靠近面部、leave离开画面中无人或只有椅子。这四类覆盖了网课场景下 90% 以上的状态判断需求。标注工具用 labelme 或 labelImg 都行。labelme 装不上 pyqt5 是常见问题原因是 PyQt5 版本和 Python 版本不匹配解决办法是先pip install pyqt55.15.9再装 labelme或者直接用 labelImg 的 exe 版本绕过依赖。标注时注意三点第一focus和distracted的边界要统一建议以头部俯仰角 30 度为分界第二phone类要框住手机和手部区域不要只框手机第三遮挡超过 50% 的目标不标避免引入噪声。每类至少标 300 张总数控制在 1500 到 2500 张之间。数据来源可以是公开课堂视频抽帧也可以自己录几段模拟网课视频。抽帧间隔建议 1 秒 1 帧避免相邻帧过于相似导致训练集冗余。2.2 从标注文件到 YOLO 训练格式的转换labelme 生成的是 JSON 格式YOLOv5 需要的是每张图对应一个 txt每行格式为class_id x_center y_center width height坐标全部归一化到 0 到 1。下面这个脚本是我常用的转换逻辑import json import os from pathlib import Path # 类别映射顺序必须和训练时的 data.yaml 一致 CLASS_MAP {focus: 0, distracted: 1, phone: 2, leave: 3} def labelme_to_yolo(json_path, output_dir, img_width, img_height): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] # 计算边界框中心和宽高并归一化 x_center (min(xs) max(xs)) / 2.0 / img_width y_center (min(ys) max(ys)) / 2.0 / img_height width (max(xs) - min(xs)) / img_width height (max(ys) - min(ys)) / img_height lines.append(f{CLASS_MAP[label]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) out_path Path(output_dir) / (Path(json_path).stem .txt) with open(out_path, w) as f: f.write(\n.join(lines)) # 批量处理 for jf in Path(labels_json).glob(*.json): labelme_to_yolo(str(jf), labels_yolo, 640, 480)这段代码的关键参数是img_width和img_height必须和实际图片尺寸一致否则归一化坐标会错位。CLASS_MAP的顺序要和后面data.yaml里的names完全对应顺序错了模型训练时类别就全乱了。转换完成后按 8:1:1 划分训练集、验证集、测试集目录结构建议如下dataset/ images/ train/ val/ test/ labels/ train/ val/ test/对应的data.yaml写法path: ./dataset train: images/train val: images/val test: images/test nc: 4 names: [focus, distracted, phone, leave]注意nc必须等于类别数names的顺序必须和转换脚本里的CLASS_MAP一致这两个地方对不上是训练时最常见的翻车点。3. YOLOv5 训练与调参让模型在课堂场景真正可用3.1 环境配置与训练命令YOLOv5 的环境配置是新手第一道坎。我一般用 conda 建一个独立环境Python 版本选 3.8 或 3.9PyTorch 按 CUDA 版本装。核心依赖就三个torch、torchvision、requirements.txt里的包。装完之后python detect.py --source 0能调起摄像头就说明环境通了。训练命令我常用这条python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/classroom.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --name classroom_exp1 \ --cache参数逐个说--img 640是输入分辨率课堂场景 640 够用想检测小目标可以提到 1280 但显存翻倍--batch 16是批大小8G 显存跑 yolov5s 用 16 比较稳显存不够就降到 8--epochs 100是训练轮数课堂数据集 2000 张左右 100 轮基本收敛--weights yolov5s.pt用预训练权重做迁移学习比从头训快得多--cache把图片缓存到内存加速数据加载但数据集超过 5000 张就别开内存扛不住。训练过程中重点看三个指标mAP0.5到 0.85 以上基本可用precision和recall差距不要超过 0.15box_loss和obj_loss要持续下降。如果obj_loss震荡不降大概率是标注框有大量重叠或漏标。3.2 超参数调整与数据增强策略YOLOv5 默认的数据增强对课堂场景不一定合适。hyp.scratch-low.yaml里的mosaic增强会把四张图拼成一张对检测小目标有帮助但课堂场景里学生姿态是连续的过度拼接反而让模型学到不自然的组合。我一般把mosaic概率从 1.0 降到 0.5mixup直接关掉。学习率方面lr0默认 0.01如果 loss 一开始就爆炸降到 0.001。lrf是最终学习率比例保持 0.01 就行。warmup_epochs设 3让模型前几轮慢慢适应。如果发现phone类检测效果差两个原因一是样本太少二是手机目标太小。解决办法是把phone类样本单独过采样或者在data.yaml里给这类加权重。YOLOv5 本身不支持类别权重但可以在数据集层面复制样本实现类似效果。训练完成后用val.py跑一遍验证python val.py --data data/classroom.yaml --weights runs/train/classroom_exp1/weights/best.pt --img 640看输出的混淆矩阵如果focus和distracted互相误检严重说明这两类的视觉特征区分度不够需要重新审视标注标准或者增加头部姿态角度的特征。4. PyQt5 界面集成把检测结果做成能演示的系统4.1 界面布局与摄像头线程设计PyQt5 做检测界面核心问题是不能把推理放在主线程否则界面卡死。我一般用 QThread 单独跑推理循环通过信号槽把检测结果传回主界面。界面布局分三块左边是摄像头实时画面右边是状态统计面板底部是日志输出区。主窗口用 QMainWindow中央 widget 用 QHBoxLayout 分左右。左边放一个 QLabel 显示视频帧右边用 QVBoxLayout 放几个 QLabel 显示各类计数和当前专注度评分。专注度评分可以简单定义为focus_count / total_count * 100实时更新。推理线程的核心逻辑from PyQt5.QtCore import QThread, pyqtSignal import cv2 import torch class DetectThread(QThread): frame_signal pyqtSignal(object) # 发送带标注的画面 stats_signal pyqtSignal(dict) # 发送统计数据 def __init__(self, model_path, camera_id0): super().__init__() self.model torch.hub.load(ultralytics/yolov5, custom, pathmodel_path) self.model.conf 0.45 # 置信度阈值 self.model.iou 0.5 # NMS IoU 阈值 self.cap cv2.VideoCapture(camera_id) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: continue # 推理 results self.model(frame) detections results.pandas().xyxy[0] # 统计各类数量 stats {focus: 0, distracted: 0, phone: 0, leave: 0} for _, det in detections.iterrows(): stats[det[name]] stats.get(det[name], 0) 1 # 画框 annotated results.render()[0] self.frame_signal.emit(annotated) self.stats_signal.emit(stats) def stop(self): self.running False self.cap.release()conf阈值设 0.45 是平衡漏检和误检的经验值课堂场景光照变化大太低会误检太高会漏掉低头动作。iou设 0.5 控制 NMS 合并重叠框的力度学生密集时可以降到 0.4。4.2 检测结果可视化与状态判定逻辑光画框不够系统要能给出「专注度」这个结论。我的做法是加一个时间窗口平滑维护最近 30 帧的统计结果取众数作为当前状态。这样避免单帧误检导致状态跳变。状态判定规则条件判定结果界面颜色focus 占比 70%专注绿色distracted 占比 50%走神黄色phone 检测到且持续 3 秒玩手机红色画面中无人离开灰色界面上的 QLabel 用setStyleSheet动态改背景色配合 QTimer 每 500ms 刷新一次统计面板。日志区用 QTextEdit每次状态切换时追加一条带时间戳的记录方便课后回看。提示PyQt5 界面里显示 OpenCV 图像要注意颜色通道转换OpenCV 是 BGRQt 是 RGB忘了转会出现颜色发蓝的问题这个坑我踩过不止一次。5. 避坑与排查从训练到部署的五个血泪教训5.1 训练 loss 不下降mAP 卡在 0.3 上不去现象训练 50 轮后box_loss还在 0.08 以上mAP0.5只有 0.3 左右。原因九成是标注格式问题。要么是归一化坐标算错了要么是data.yaml里的nc和实际类别数不一致要么是图片路径里有中文导致读取失败。解决先用python utils/check_dataset.py检查数据集完整性再随机抽 10 张图用plot_labels.py可视化标注框确认框的位置和类别都对。中文路径问题把数据集移到纯英文路径下即可。5.2 PyQt5 界面卡死无响应现象点开始检测后界面直接白屏任务管理器显示 Python 进程 CPU 占满。原因推理循环写在了主线程里while True阻塞了 Qt 的事件循环。解决把推理逻辑全部放进 QThread 子类通过pyqtSignal回传数据。主线程只负责界面刷新不碰任何推理代码。另外注意cap.read()要加超时判断摄像头掉线时不能让循环空转。5.3 模型在验证集上表现好实际摄像头前效果差现象val.py跑出来 mAP 0.88但接上摄像头后误检率很高。原因训练数据是抽帧的静态图和摄像头实时画面的光照、角度、背景差异大。另外验证集和训练集来自同一批视频分布太接近没有真正测试泛化能力。解决训练时加入随机亮度、对比度、旋转增强验证集单独从不同视频里抽帧。摄像头部署时把conf阈值从 0.25 提到 0.45牺牲一点召回换准确率。5.4 检测框闪烁严重同一目标帧间跳变现象画面上同一个学生的框忽大忽小类别在 focus 和 distracted 之间反复跳。原因单帧检测没有时序平滑模型对相似姿态的置信度波动大。解决加一个简单的跟踪逻辑用 IOU 匹配相邻帧的检测框同一个目标连续 5 帧类别一致才更新显示。或者直接上 ByteTrack 做跟踪但毕设场景用 IOU 匹配就够了。5.5 打包成 exe 后模型加载失败现象PyCharm 里跑得好好的用 PyInstaller 打包后提示找不到yolov5模块或权重文件。原因PyInstaller 不会自动打包torch.hub动态加载的模块权重文件的相对路径在 exe 里也变了。解决把yolov5仓库整个复制到项目目录用本地路径加载模型而不是torch.hub.load。权重文件用sys._MEIPASS获取临时目录路径或者在 spec 文件里用datas参数显式包含。6. 进阶技巧用置信度阈值和 NMS 参数把误检压下去模型训练完之后真正决定系统好不好用的是后处理参数。我做过一组对比实验同一份权重只调conf和iou两个参数误检率能差出三倍。先看conf阈值。YOLOv5 默认 0.25这个值在通用数据集上没问题但课堂场景背景干净、目标明确0.25 会引入大量低置信度的误检框。我一般从 0.4 开始试逐步往上加直到误检明显减少但漏检还能接受。实测下来 0.45 到 0.55 是课堂场景的甜点区。再看iou阈值。这个参数控制 NMS 合并重叠框的力度。学生坐得密集时两个相邻学生的框可能有 30% 到 40% 的重叠iou设太高会把两个人合并成一个设太低又会让同一个目标出多个框。我的经验值是 0.45 到 0.5 之间人特别密集的教室可以降到 0.4。除了这两个还有一个容易被忽略的参数是max_det默认 1000意思是每帧最多保留多少个检测框。课堂场景一帧最多几十个人把这个值降到 50 能减少后处理时间对帧率有提升。验证参数调整效果的方法很简单录一段 5 分钟的模拟网课视频用不同参数组合跑一遍人工统计误检和漏检次数。下面这个表格是我自己测的一组数据供参考confiou误检次数漏检次数综合评分0.250.45478620.450.451211850.450.50913840.550.5061976综合评分是我自己定的一个加权指标误检权重 0.6漏检权重 0.4因为课堂场景里误报比漏报更影响体验。从数据看conf0.45, iou0.45是最平衡的组合。最后说一个习惯每次调完参数把配置写进一个config.yaml文件不要硬编码在代码里。这样换场景时只改配置文件不用动代码。我早期做项目时参数散落在各个文件里后来想复现某个效果都找不到当时的配置这个后悔药不好吃。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →