YOLOv8+PyQt5课堂行为检测实战:从数据集到界面集成

发布时间:2026/10/9 20:02:35
YOLOv8+PyQt5课堂行为检测实战:从数据集到界面集成 简介一套基于YOLOv8与PyQt5的课堂行为实时检测系统完整工程包面向教育技术开发者、计算机视觉初学者及需要快速搭建课堂管理原型的研究人员。系统以YOLOv8为检测核心结合PyQt5构建友好界面可识别学生出勤、专注度、互动等行为并实时反馈操作简便无需编程基础即可完成配置与监控适合智慧教室、在线课堂等场景。包内共2000个文件、约720MB以txt说明文档为主1975个另有Python脚本、XML配置、YAML模型配置和CSS样式文件覆盖数据标注转换、数据集划分、界面实现与启动配置等环节便于按模块学习与二次开发。已有109人学习下载。资源包含可直接运行的工程代码、实用工具脚本与界面样式可帮助读者系统掌握YOLOv8目标检测与PyQt5桌面应用结合的完整思路同时节省环境搭建与数据处理的时间适合作为课程设计或科研项目的参考基础。1. 课堂行为实时检测YOLOv8 加 PyQt5这套组合解决的项目痛点教室里举手、趴桌、玩手机、交头接耳这些动作想在监控画面里被自动框出来、实时计数并留下时间戳过去要么靠人工盯屏要么用老式检测模型在低帧率下硬撑。这套基于 YOLOv8 PyQt5 的课堂检测系统核心是把 YOLOv8 的推理能力封装进 PyQt5 图形界面做成一个能接摄像头、能看实时画面、能统计行为数据的桌面应用。操作上不需要写代码就能启动检测适合老师做课堂行为观察也适合做教学硬件集成的开发者和刚接触目标检测的新手。顺着数据准备、界面集成、训练调参这条路走完你会发现“数据集、YOLO、学生行为”这三件事串起来并没有想象中那么玄乎。2. 学生行为数据集准备YOLO 标注格式、类别设计与训练划分2.1 课堂场景对检测模型的三个硬性要求课堂场景和通用目标检测不太一样。第一视角固定画面基本是教室前排到后排的广角或者讲台旁的斜俯视角度学生的头、肩、手在画面里只会在有限的尺度范围内变化不会像自动驾驶那样出现突然逼近的极端尺度。第二目标之间高度重叠“趴桌”和“低头写字”在画面上可能只有细微差别头部边缘被前排遮挡是常态数据标注时如果只框头部模型很难区分动作状态。第三对实时性敏感检测环节拖到每秒十帧以下界面上的学生动作就会明显发虚后端的统计也会跟着失真。YOLOv8 在同类模型里属于速度和精度权衡很实用的选择n、s、m、l 几档覆盖了从 Jetson 板卡到普通台式机的部署区间。做课堂行为检测我通常会先用 YOLOv8s 做起点它在 640 分辨率下能跑到 50 FPS 左右同时保留足够的特征表达能力去区分坐姿和趴桌的差异。如果你确认部署设备是低功耗板卡再降一档用 n如果画面里学生人数经常超过二十人且教室纵深长建议直接上 m小模型对密集小目标的召回率明显不够。很多公开的课堂行为数据集往往只覆盖“举手、阅读、写字”这几类高频动作真正部署时还会碰到“打哈欠、交头接耳、玩手机”这些占比不小但样本稀少的行为。所以做这个项目时我建议按“动作状态”而不是“人物身份”来定义类别类别之间靠姿态和遮挡关系区隔能减少模型在相似类上的混淆。数据集里至少包含八类举手、低头、趴桌、玩手机、写字、看书、站立、交头接耳。多一类就多一个行为维度后面做统计时价值会高很多。2.2 YOLO 格式标签与数据集目录组织搞课堂检测第一步不是写模型代码而是把手上的视频素材转成 YOLO 能吃的格式。课堂项目里最常采用的是 images 和 labels 分目录的布局训练和验证互不干扰dataset/ ├── images/ │ ├── train/ │ │ ├── frame_0001.jpg │ │ └── frame_0002.jpg │ └── val/ │ ├── frame_0100.jpg │ └── frame_0101.jpg ├── labels/ │ ├── train/ │ │ ├── frame_0001.txt │ │ └── frame_0002.txt │ └── val/ │ └── frame_0100.txt └── classroom.yaml对应的标注文本 .txt 里每行是一条检测框格式是五个浮点数类别 ID、中心点 x、中心点 y、框宽、框高。注意全部是归一化坐标除以图片宽高后的结果取值范围 0 到 1。举例说明3 0.642 0.183 0.120 0.290 0 0.224 0.324 0.095 0.220这里第一行表示类别 3 的一只手机中心点落在图片横向 64.2%、纵向 18.3% 的位置第二行是类别 0 的举手框的宽度占图宽 9.5%高度占图高 22%。写标注的时候最容易犯的错是把“趴桌”的人框成整个上半身而“低头写字”只框头部区域这两类框的纵横比不一致训练时模型会拿头部的特征去套整个上半身最后检测框跳来跳去。2.3 数据增强与自动划分让训练集更接近真实课堂标注完成后我习惯用一段脚本先检查类别分布这是最容易暴露问题的环节from pathlib import Path labels_dir Path(dataset/labels/train) txt_files list(labels_dir.glob(*.txt)) class_count {} for txt in txt_files: lines txt.read_text(encodingutf-8).strip().splitlines() for line in lines: cls_id line.split()[0] class_count[cls_id] class_count.get(cls_id, 0) 1 print(类别分布, sorted(class_count.items(), keylambda x: x[0]))这段脚本把训练集里所有标注文件的类别 ID 统计一遍输出形如[(0, 3205), (3, 412)]的结果。参数方面不需要改什么关键是跑完脚本后肉眼看一下各类别占比。如果某一类占比不足 5%训练出来的模型会倾向于把这类样本直接归并到相似类。比如“玩手机”和“低头”在画面里确实像数据少就更容易混。划分数据集时我一般做 80/10/10 的 train/val/test 比例。有个细节不能省不能把同一个训练视频里的相邻帧同时分进 train 和 val否则验证集里全是高度相似的画面评估结果会虚高一上真实摄像头就原形毕露。常见做法是从视频里按 2 到 3 帧每秒抽帧保证同一批素材进入数据池时已经天然降低了时间相关性。增强策略直接用 YOLOv8 内置 mosaic 和随机透视就够不需要额外堆花活。有一个禁忌不要在增强里开左右翻转。教室里有黑板文字、门窗朝向左右翻转会造成“向左看”和“向右看”的语义混乱类别特征变得不稳定。如果你用的是 YOLOv8 默认增强注意关闭 hsv 之外的几何随机裁切强度课堂里密集人群被随机裁切后很容易把单个学生切成碎片喂给模型。数据准备完回到 classroom.yaml 里把 path、train、val、names 写好names 的顺序必须和标注文件的 class_id 一一对应不能改。这一步错位了后面所有训练和评估都会莫名其妙地失效我把这个坑留到第 5 章详细说。3. PyQt5 界面集成双线程架构与实时视频流处理3.1 UI 线程和推理线程的职责边界很多人第一次写 PyQt5 检测界面习惯把模型推理直接塞进界面的事件循环里结果一运行就卡死。原因是模型推理在 CPU 上跑一次 640 分辨率的推理需要 30 到 80 毫秒而 PyQt5 的界面刷新和按钮事件都在同一个主线程里排队。推理一次阻塞一百毫秒鼠标点击和窗口拖动全部跟着延迟看起来就像界面“假死”了。正确的思路是用 QThread 把视频读取和推理放到工作线程主线程只负责画界面和接收信号。代码骨架大致是这样from PyQt5.QtCore import QThread, pyqtSignal import cv2 class DetectThread(QThread): frame_ready pyqtSignal(object) result_ready pyqtSignal(object) def __init__(self, model, source0): super().__init__() self.model model self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running: ok, frame cap.read() if not ok: break results self.model(frame, imgsz640, conf0.25, verboseFalse) if results is not None: self.frame_ready.emit(frame) self.result_ready.emit(results) cap.release()run 方法是 QThread 的入口while 循环里做“读一帧、推理一次、发两个信号”。frame_ready 用来把原始画面送给界面显示result_ready 用来把检测结果送给统计面板。注意工作线程里不要直接调用任何 QLabel 或 QTableWidget 的操作界面控件的更新必须通过信号槽回到主线程否则线程不安全会导致画面闪烁甚至崩溃。3.2 视频流接入帧缓冲与跳帧策略摄像头输入通常走两种USB 摄像头直接给设备索引比如 0 表示第一路网络摄像头走 RTSP 地址。PyQt5 里一般不直接处理 RTSP而是交给 OpenCV 的 VideoCapture 去解再把解出来的帧交给推理线程。这个分工让界面代码保持简洁同时也方便以后把视频流换成离线视频做回放测试。实际部署课堂场景时摄像头一般是 1080p 三十帧。YOLOv8s 在普通台式机上推理一帧大约 20 到 30 毫秒勉强能跟上但一旦教室人数多、检测框多后处理的时间会陡增。这时候我一般会做跳帧frame_skip 2 frame_count 0 while self.running: ok, frame cap.read() if not ok: break frame_count 1 if frame_count % (frame_skip 1) ! 0: continue results self.model(frame, imgsz640, conf0.25, verboseFalse)frame_skip 等于 2 表示每三帧只做一次推理另外两帧直接丢弃不进入模型。这种方式能明显降低 CPU 占用代价是输出帧率变成原来的三分之一。课堂行为检测不需要像目标跟踪那样逐帧连续三帧一跳完全够用。要注意跳帧跳的是“推理”不是“显示”界面显示仍可以通过缓存队列保持流畅这一点后面还会提到。读取 RTSP 时容易遇到连接不稳定常见做法是给 VideoCapture 设置超时参数cap cv2.VideoCapture(rtsp://your_camera_ip/stream) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)CAP_PROP_BUFFERSIZE 设成 1 能减少因解码积压导致的延迟。如果画面花屏或黑屏先检查网络带宽再检查摄像头编码格式不要一上来就怀疑模型。3.3 检测结果叠加与统计面板刷新拿到 results 之后要把框画到画面上再交给 QLabel 显示。这里的关键是坐标体系推理产生的框是基于模型输入的 640x640 分辨率而显示区域是原始帧的宽高直接画会错位。YOLOv8 的 results 会返回原图坐标系下的 xyxy 坐标前提是传入的 frame 本身没有经过额外缩放这一步要特别留意。def draw_results(frame, results, names, threshold0.25): for box in results.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) if conf threshold: continue x1, y1, x2, y2 [int(v) for v in box.xyxy[0]] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) label f{names[cls_id]} {conf:.2f} cv2.putText(frame, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) return frame这段代码把每个框的类别、置信度和坐标取出来画矩形和文字。threshold 建议和推理时的 conf 保持一致否则画面上明明有框统计里却对不上数。画完框之后要把 OpenCV 的 BGR 图像转成 Qt 能识别的 QImage再贴到 QLabel 上rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label_video.setPixmap(QPixmap.fromImage(qimg))format 用 RGB888 才能避免颜色通道错乱。如果你的 QLabel 是固定尺寸不要用 setScaledContents(True)那会让图像拉伸变形。正确做法是手动缩放成与显示区域等比例的画面再贴图确保检测框和标注文字不错位。统计面板的逻辑是每帧对检测结果做一次分类计数但不要每帧都刷新表格否则 QTableWidget 会闪得厉害。我会设一个 QTimer每 500 或 1000 毫秒触发一次刷新用缓存下来的累计计数更新界面。4. YOLOv8 训练与推理超参设置、曲线解读与模型导出4.1 训练入口与关键参数数据准备好之后训练入口直接走 ultralytics 的命令行就能跑起来yolo detect train dataclassroom.yaml modelyolov8s.pt epochs120 imgsz640 batch16 lr00.01这几个参数里data 指向 classroom.yamlmodel 用官方预训练权重做迁移学习epochs 设 120 轮imgsz 640。CPU 想快一点可以把 imgsz 降到 480但课堂里小目标多降分辨率会掉召回。batch 大小取决于显卡16GB 显存跑 s 模型没问题显存不足就减到 8不要硬开大 batch否则第一个 epoch 就报 CUDA out of memory。classroom.yaml 的内容大致这样path: dataset train: images/train val: images/val names: 0: raise_hand 1: looking_down 2: sleeping 3: using_phone 4: writing 5: reading 6: standing 7: turning_aroundnames 顺序必须与标注文本里的 class_id 完全一致。增删类别时要同步改标注脚本和这个 yaml否则模型训练时的类别映射就乱了。训练过程中会自动保存 last.pt 和 best.ptbest.pt 是按验证集指标挑选出来的权重后边部署一律用 best.pt。4.2 训练过程loss 曲线和 mAP 到底信哪个训练时大多数人盯着终端里跳动的 loss 数字其实更该看验证集的 mAP。loss 下降只能说明模型在拟合训练数据课堂数据集样本量有限模型很容易把训练集背下来loss 继续降但验证集精度反而下滑。我记录过一次比较典型的训练过程三个阶段的验证集指标变化如下训练轮次mAP50mAP50-95400.620.38800.780.511200.810.54可以看到 80 轮以后 mAP50 涨幅放缓从 0.78 到 0.81 只涨了三个点。如果再跑 160 轮训练 loss 可能还会降但验证集的 mAP50-95 大概率横盘甚至回落那是过拟合信号。课堂这个场景120 轮左右基本够用不必盲目贪多。判断模型是否定版我的习惯是看 mAP50-95 而不是只看 mAP50。mAP50 对边界框位置的要求宽松只要求 IoU 大于 0.5 就算命中而 mAP50-95 是多个 IoU 阈值下求平均更能反映框位是否准确。课堂里“举手”和“低头”边界距离很近框偏了哪怕一点统计结果就从举手变成低头这种误差在应用层很难察觉。4.3 模型从训练权重到 PyQt5 可加载的格式训练出来的 best.pt 可以直接加载但桌面应用里直接跑 PyTorch 权重会比较慢。我一般会导出一份 ONNX再用 onnxruntime 做推理CPU 上能快不少from ultralytics import YOLO model YOLO(runs/detect/train/best.pt) model.export(formatonnx, imgsz640, opset12)导出后生成 best.onnx推理会话的加载方式变成import onnxruntime as ort import numpy as np session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name output session.run(None, {input_name: preprocessed_img})preprocessed_img 需要是形状为(1, 3, 640, 640)的浮点数组像素值归一化到 0 到 1。不同导出参数下输出格式不完全一样onnx 模型通常把边界框、置信度、类别概率合并成一个大数组解析逻辑要按实际形状处理不要照搬上一份代码。如果你只想快速跑通系统不追求那 20% 到 40% 的速度提升直接用 pt 文件最省事PyQt5 里加载方式不变后续换模型也简单。5. 避坑指南课堂检测实测中的典型问题与排查记录5.1 戴眼镜学生边框漂移现象戴眼镜的学生在斜照光下检测框忽大忽小有时只框了半个头人脸和镜框区域被反复切割。原因训练样本里白天自然光照片比例过高傍晚教室灯光偏黄时模型对低照度下的人脸头部特征响应不稳定。解决在训练增强里提高亮度扰动和色相扰动幅度我常用参数是hsv_h0.015, hsv_s0.7, hsv_v0.5。另外从教室里补拍一段傍晚灯光的视频抽帧合入训练集比任何参数调整都直接有效。5.2 PyQt5 界面假死不响应现象摄像头画面停在最后一帧窗口拖动时出现白色残影点击关闭按钮反应延迟两秒以上。原因推理代码放在了界面主线程里模型推理一次 60 毫秒加上图像编码和绘制界面事件被阻塞。解决把推理逻辑拆到 QThread主线程只接收 signal 更新画面。另外一个细节是如果在按钮槽函数里直接调用模型推理同样会阻塞需要把耗时操作丢给线程池或再开一个工作线程别贪方便写在 clicked 处理里。5.3 检测框和画面错位现象人在讲台左侧框却偏到讲台右侧置信度数值正常就是画错位置。原因推理时把输入图缩放到了 640x640而显示时又把画面拉伸到 QLabel 的尺寸两者的宽高比不一致坐标还原自然出错。解决显示区不要用 setScaledContents 直接拉满先按原图宽高比计算出居中缩放区域再贴 QPixmap。推理结果回传时统一用归一化坐标也就是把 xyxy 除以原图的宽高到界面端再按实际显示区域反算像素位置两头对得上才不会错位。5.4 验证集 mAP 挺高现场漏检玩手机现象最后一轮验证集 mAP50 到 0.8 以上但拉去真实课堂做一节课测试“玩手机”行为的漏检率明显高于其他类别。原因课堂数据里玩手机样本占比低且验证集和训练集来自同一批视频的相邻帧模型表面的验证指标被数据冗余撑高了。解决划数据时按视频场景分组同一段拍摄素材的帧不能同时进入 train 和 val。对少数类样本可以做适度复制或者把两张含手机的小图拼接到同一张训练图里让模型多看到这类目标重新训练后再看现场表现。5.5 摄像头自动曝光导致置信度抖动现象教室靠窗户光线变化快时画面亮度一抖置信度从 0.8 直接掉到 0.3行为日志里出现大量“消失又出现”的空档。原因摄像头自动曝光和自动白平衡在持续调节帧间亮度变化剧烈时模型的置信度被拉低。解决固定机位下尽量手动关闭自动曝光OpenCV 里对应cv2.CAP_PROP_AUTO_EXPOSURE设为 0.25 左右同时锁定白平衡。如果设备不支持手动控制可以在送入模型前做帧间亮度平滑把连续三帧的均值作为当前帧输入注意这样会增加一定延迟但在固定场景里效果明显。6. 进阶验证视频回放构建行为时间线模型部署进系统只是第一步真正要说服使用者需要让检测结果变成可查证的时间线。最常见的做法是录制一段十分钟的教室视频用系统离线回放把所有检测框和置信度导出成 JSON再做标注对比。import json from collections import defaultdict def export_timeline(results, fps15): timeline defaultdict(list) for frame_id, dets in enumerate(results): t round(frame_id / fps, 1) for cls_id, conf, box in dets: if conf 0.25: timeline[cls_id].append({ time: t, conf: conf, box: box }) return json.dumps(timeline, ensure_asciiFalse)参数方面fps 是视频帧率课堂回放通常取 15conf 阈值要和界面里保持一致否则统计结果对不上。导出的 JSON 可以直接喂给数据统计脚本做累计时长、出现频次、时间分布的情况分析学校用来做课堂观察报告也够用。验证时我的习惯是抽 30 帧做人工复核计算每一类的 precision 和 recall而不是只看界面效果。人眼主观感受具有迷惑性真正把 ground truth 拉出来对比才发现漏检主要集中在后排小目标。那次调试我印象很深第一次用视频回放做验证发现“看书”和“低头写字”互相跳变换了更严格的 conf 阈值后才稳定下来。从那以后我每次给课堂检测系统做演示之前都会强制走一遍离线回放加上 JSON 导出的流程确认时间线和统计口径都对齐了再连摄像头实拍。这也算是我给自己定的“后悔药”步骤。课堂行为检测的门槛不在模型而在数据颗粒度和验证流程够不够细。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询