拓冰建站拓冰建站
首页 / 资讯中心 / 正文

YOLOv8课堂行为分析系统实战:从数据标注到可视化

简介基于YOLOv8的智慧教室学生课堂行为分析系统是一套完整可运行的计算机视觉毕业设计项目面向高校计算机、人工智能、自动化等相关专业学生适用于课堂学生行为目标检测与教学质量分析等场景。压缩包共8个文件约15.91MB包含3个Python源码模块、3个预训练权重文件以及2份txt说明文档其中源码分别对应可视化界面设计、模型训练与视频检测权重文件用于加载不同YOLO模型推理txt文档内含部署说明和项目备注。配套功能可输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果与标签分布图覆盖从训练到评估的完整链路项目代码已经过测试运行成功便于直接运行或二次修改适合作为毕设、课设或项目初期演示使用。目前已有64人次浏览学习资源包体量小、结构清晰适合需要快速上手课堂行为分析系统的学生和开发者参考与实践。 《基于YOLOv8的智慧教室学生课堂行为分析系统》这类标题在毕业设计和课程设计里出现频率非常高。它解决的问题其实很具体把教室监控画面里的“人”变成可统计的状态比如听课、睡觉、玩手机、举手、站立最后在可视化界面上给出每一类的占比。YOLOv8负责最核心的检测部分但真正让系统像样的是行为类别的定义、后处理和统计逻辑这条完整链路。很多刚拿到这种压缩包的同学第一反应是赶紧把demo跑通这没错但跑通之后常会被同一个问题卡住模型框出了“人”却说不清他在干什么。原因就在于行为识别类系统不能只靠一个目标检测权重包打天下。数据怎么标、类别怎么定、UI怎么接每一步都有细节。这套方案适合三类人想快速完成毕设、需要把课堂视频自动统计成报表的学生想用最小成本做课堂参与度分析的教学管理人员以及第一次接触YOLOv8想通过完整链路入门的目标检测新手。它不需要昂贵显卡CPU环境也能走通训练和推理只是参数要懂得收敛。2. 先看懂这套系统的技术栈YOLOv8、行为分类、可视化界面怎么分工2.1 YOLOv8的输出只有框和类别行为识别要靠第二段逻辑补如果你找过“yolov8网络结构图”会看到主干网络里有C2f、SPPF颈部是PAN-FPN检测头是解耦的。这些结构决定了YOLOv8的推理速度和对小目标的敏感性但对使用者来说最重要的结论只有一个模型最终输出的是目标框、置信度和类别ID它不会直接告诉你“这个学生正在专心听讲”。所以所谓“基于YOLOv8的学生课堂行为分析”通常不是只靠一个权重做完整判断。有人把“睡觉”“举手”“玩手机”直接当作检测类别来训练框住整个人贴上行为标签。这种做法的好处是模型轻、流程简单坏处是行为概念容易被场景绑架。比如斜靠在椅背上和趴桌子在画面里看起来差不多纯检测模型很容易翻车。另一种做法是两段式先用YOLOv8把学生检测出来再把每个人的区域裁剪下来交给一个分类器判断行为。这种结构更稳数据标注也更符合直觉。这个压缩包如果带的是“检测分类”两段式代码跑通后你会发现真正的可调参数不是YOLOv8的模型结构而是第二段分类的置信度阈值。先看一个最简单的调用方式确认环境里YOLOv8能跑起来from ultralytics import YOLO import cv2 model YOLO(yolov8n.pt) frame cv2.imread(classroom.jpg) results model.predict(frame, conf0.35) for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy [round(v, 2) for v in box.xyxy[0].tolist()] print(cls_id, conf, xyxy)这段代码做的事情很简单读一张课堂图片用预训练权重检测物体打印每个框的类别ID、置信度和坐标。conf0.35的意思是置信度低于35%的框直接丢掉。预训练模型的类别是COCO的80类里面只有“person”和学生相关所以第一次跑的时候你只会看到人框不会看到行为标签。这很正常别急着怀疑代码下一步就是换自己的best.pt。2.2 行为类别怎么定答辩时才不会被老师反复追问行为类别是整个项目里最容易被低估的部分。很多人拿到数据集就开训结果训练完发现“听课”和“站立”在画面里长得差不多“玩手机”和“趴桌子”更是分不开。我在做这类系统时习惯先按“是否肉眼可分辨”来定类别。下面这个表是我常用的粒度行为类别建议标注区域检测难度listening 听课本人/buster全身低sleeping 趴桌子上半身或头部低hand_raise 举手带手臂的上半身中phone 玩手机身体加上手部小区域高stand 站立全身低提醒一个关键点行为类别的数量不要贪多。毕设系统控制在5到8类最合适超过10类之后类别之间边界会很模糊混淆矩阵上会出现一大片非对角线数值。如果你的数据集只有几千张把“低头看书”和“玩手机”分成两类模型大概率会在两者之间反复横跳。如果你拿到的是已经标注好的数据集先花十分钟统计每一类的样本数。我在第5章会讲到类别不均衡才是课程设计翻车的第一大来源。样本最多的类和最少的类差距超过5倍时要么做正样本增强要么干脆合并行为。2.3 可视化界面与部署形态PyQt5桌面端和Web端怎么选标题里的“可视化界面”是另一个隐藏工作量。常见实现有两种PyQt5桌面程序和Flask网页端。界面方案适合场景改动成本PyQt5本地答辩演示、双击运行、读视频文件方便中Flask Web浏览器访问、可部署到服务器、适合远程展示低我一般建议以答辩场景为准。如果你是在教室或实验室里用一台笔记本现场跑视频PyQt5更稳妥因为它不依赖浏览器和端口。反过来如果老师想在自己的电脑上直接打开系统地址查看Flask就更合适。典型课设界面长这样左边是视频播放区域右边是实时检测结果列表下面是一张课堂行为占比饼图或随时间变化的折线图。数据流向是“视频帧 → YOLOv8检测 → 行为标签 → 统计模块 → 界面刷新”。搞清楚这条链路后面改UI才不会把自己绕进去。3. 在Ubuntu 20.04上搭CPU版YOLOv8环境数据转换与训练全过程3.1 用conda搭CPU版YOLOv8环境环境配置顺序很重要搜索“ubuntu20.04搭建yolov8环境cpu版本”的人多半是没有独立显卡的笔记本用户。CPU环境不是不能跑而是要把安装顺序和线程控制做好。我常用的命令序列如下# 1. 创建独立虚拟环境Python不要太新 conda create -n classroom python3.9 -y conda activate classroom # 2. 先装CPU版PyTorch再装ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install ultralytics pip install labelme opencv-python pandas第一步创建虚拟环境时Python版本我推荐3.9或3.10。太新的Python版本有些依赖包没有编译好的wheel安装时会现场编译容易报错。第二步最有讲究。如果直接执行pip install ultralyticsPyPI上默认的torch依赖可能是CUDA版体积大而且会尝试安装数百MB的nvidia相关包。先指定CPU源安装torch再装ultralytics能避免后面95%的依赖冲突问题。装完之后用一个最小命令验证环境yolo predict modelyolov8n.pt sourceclassroom.jpg第一次运行会自动下载预训练权重。如果下载慢或反复中断解决办法是让脚本自己下载一次然后把缓存目录里的yolov8n.pt复制到当前工作目录。不要手动去其他渠道找权重版本不匹配反而会带来一堆黑匣子报错。3.2 用Labelme标注课堂行为数据再转成YOLO训练格式如果你的数据集不是现成的YOLO格式而是Labelme标注的一堆JSON文件那么训练前必须做一步转换。常见目录结构如下classroom_datasets/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ └── val/ └── labels/ ├── train/ │ ├── img_0001.txt │ └── ... └── val/Labelme标注时每张图对应一个同名的JSON文件里面用points记录多边形或矩形坐标。YOLO训练需要的是每张图对应一个txt文件每行格式为“类别ID 中心点x 中心点y 宽度 高度”所有值都归一化到0到1之间。下面是我常用的转换脚本只处理矩形标注import json from pathlib import Path def labelme_to_yolo(json_dir, out_dir, names): json_dir Path(json_dir) out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) for json_path in json_dir.glob(*.json): data json.loads(json_path.read_text(encodingutf-8)) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in names: continue points shape[points] if len(points) 2: continue xs [p[0] for p in points] ys [p[1] for p in points] x1, y1 min(xs), min(ys) x2, y2 max(xs), max(ys) # 过滤掉过小的标注可能是误操作 if x2 - x1 5 or y2 - y1 5: continue cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{names.index(label)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_path out_dir / (json_path.stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) print(f转换完成输出目录{out_dir}) if __name__ __main__: names [listening, sleeping, hand_raise, phone, stand] labelme_to_yolo(labels_json/train, classroom_datasets/labels/train, names) labelme_to_yolo(labels_json/val, classroom_datasets/labels/val, names)这段代码的核心是把标注框的左上角和右下角坐标换算成YOLO的中心点加宽高格式。names列表的顺序就是类别ID顺序比如listening对应ID 0。这个顺序必须和训练用的YAML完全一致否则后面模型学到的类别全是错的。转换完成后抽查一个txt文件看看内容里面每一行的第一列是类别ID后四列是归一化坐标。如果第一列有数字大于等于类别总数说明某个JSON里的标签没有在names里对齐要么补标签要么处理脚本里的映射关系。3.3 训练参数怎么设epochs、batch、imgsz、早停与损失曲线训练自己的数据集前先建一个YAML文件path: /home/ubuntu/classroom_datasets train: images/train val: images/val names: 0: listening 1: sleeping 2: hand_raise 3: phone 4: standpath建议写绝对路径因为YOLO在训练时会自动拼出图片路径相对路径一旦换目录就找不到了。然后执行训练yolo detect train dataclassroom.yaml modelyolov8n.pt epochs100 imgsz640 batch8 devicecpu patience15这条命令里有几个参数值得细说。modelyolov8n.pt是官方预训练权重用迁移学习方式训练自己的课堂行为数据集比从零训练快很多。imgsz640是训练分辨率如果CPU环境内存吃紧可以改成imgsz480速度明显提升但小目标如手机可能会更难检测。batch8在CPU上已经不小了内存不够时先降到batch4。patience15表示验证集损失连续15轮不下降就早停这是防止训练时间过长最有效的后悔药。训练过程中的损失曲线不需要另外写脚本。训练结束后进入runs/detect/train/目录打开results.png里面已经自动画好了train/box_loss、val/box_loss、precision、recall等曲线。这就是很多人搜“yolov8画损失函数曲线图”找的东西。看这条曲线有一个习惯要养成不要只看训练损失不断下降还要看验证损失。如果训练损失在降验证损失在某个epoch后开始回升那就是过拟合了早停会帮你自动停下来而不是继续浪费算力。训练完成后用验证命令看整体指标yolo detect val modelruns/detect/train/weights/best.pt dataclassroom.yaml输出里有mAP50和mAP50-95前者对课程设计来说已经够用后者对行为识别类任务会偏低因为行为类别的框尺寸差异很大属正常现象不用过度焦虑。4. 把训练好的best.pt接进可视化界面Flask接口与课堂统计4.1 PyQt5还是Flask直接按演示场景选拿到带“可视化界面”的源码包时先看一眼UI是用什么写的。如果是PyQt5会有一堆.ui文件和main_window.py之类的东西如果是Flask通常只有一个app.py加上模板目录。两种界面的体验差别很大。PyQt5适合“双击运行、导入视频、看统计”的本地工具型产品Flask适合“浏览器输入地址就能访问”的服务型产品。课程设计通常选PyQt5因为界面更接近真实软件但Flask更适合没有GUI编程经验的新手代码量少问题也容易排查。如果压缩包里自带界面最好的策略是先不重写界面而是把界面和后端推理接口的调用关系弄懂。大多数UI写得很直白无非是“按钮触发一个函数函数调用 model.predict再把结果画到画布上”。4.2 Flask YOLOv8 的最小推理接口这里给一个能直接跑的Flask接口示例演示如何把一个训练好的best.pt包成HTTP服务from flask import Flask, request, jsonify from ultralytics import YOLO import cv2 import numpy as np model YOLO(runs/detect/train/weights/best.pt) behavior_names [listening, sleeping, hand_raise, phone, stand] app Flask(__name__) app.route(/detect, methods[POST]) def detect(): file request.files[image] img_bytes np.frombuffer(file.read(), np.uint8) frame cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) results model.predict(frame, conf0.4, iou0.45, verboseFalse) boxes [] for box in results[0].boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy [round(v, 2) for v in box.xyxy[0].tolist()] boxes.append({ behavior: behavior_names[cls_id], conf: conf, box: xyxy }) return jsonify({count: len(boxes), results: boxes}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)这段代码的关键点有三个。第一模型在Flask启动时加载了一次而不是每次请求都重新加载否则每个HTTP请求都要卡几秒钟。第二上传的图片用np.frombuffer和cv2.imdecode解码兼容前端传来的二进制数据比直接保存图片再读路径更省事。第三conf0.4这个阈值过滤掉了低置信度框避免界面上出现一堆噪音。前端只需要用requests.post上传一张图片就能拿到检测结果。如果你想直接出可视化效果可以在服务端用result.plot()生成带框的图再通过接口返回给前端显示。4.3 课堂行为统计置信度过滤、窗口平滑与CSV导出检测模型输出的是每一帧的行为框但“课堂行为分析”最终要的是统计报表。我见过很多课设在这里犯同一个错误直接按单帧检测结果画饼图结果一秒钟内同一学生在“听课”和“趴桌子”之间来回跳统计数字完全没有说服力。解决办法是做时间窗口平滑。最常见的做法是对连续5帧的检测结果做多数投票5帧里3帧以上是“sleeping”才把这一段时间判为“sleeping”。下面是一个极简实现from collections import Counter def smooth_predictions(preds, window5): smoothed [] for i in range(len(preds)): start max(0, i - window 1) chunk preds[start:i 1] smoothed.append(Counter(chunk).most_common(1)[0][0]) return smoothed这个函数的参数只有一个window。窗口越大行为状态越稳定但反应越迟钝。对课堂场景5到10帧就够。统计结果导出时我习惯用CSV而不是Excel因为CSV可以被任何工具打开而且写入依赖少import csv rows [ {frame: 1, behavior: listening}, {frame: 2, behavior: listening}, ] with open(classroom_summary.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[frame, behavior]) writer.writeheader() writer.writerows(rows)用utf-8-sig编码是为了让Excel直接打开中文表头时不乱码。这是很小的细节但在答辩演示时很加分。5. 课堂行为分析系统避坑指南从训练到界面最容易翻车的5个地方5.1 现象检测到人框却分不出行为这是最常见的翻车现场用训练好的best.pt跑视频能框出每个学生但所有框都被标成同一个类别比如全是“listening”。原因很可能是训练数据里大部分框都集中在“listening”上其他类别样本少模型干脆摆烂把所有目标都判成高频类别。解决思路有两个方向。一是做数据均衡把少量类别的图片用翻转、亮度调整、裁剪等方式增强到接近多数类。二是从任务设计上妥协比如“玩手机”这一类实在难分就把它并入“其他”或者“低头”减少类别数。课程设计的核心是系统闭环不是挑战极限分类精度。5.2 现象CPU训练卡死或内存爆满进程直接被kill用CPU版YOLOv8训练时经常跑到第一轮就整机卡死或者提示内存不足。原因通常是PyTorch在CPU上默认调用所有CPU核心而batch又设得太大数据加载线程同时读图内存直接顶满。解决方法是限制线程数并调低资源占用。启动训练前执行export OMP_NUM_THREADS4然后把训练参数改成batch4 imgsz480 workers0。workers0表示数据加载不再使用额外进程速度慢一点但稳定很多。如果机器内存只有4G我建议不要本地训练直接用自己的数据跑推理验证即可训练放到云端或实验室机器上。5.3 现象Labelme转YOLO后类别标签错乱标注时用的是中文名“玩手机”训练YAML里写的是英文phone转换脚本里names顺序又和YAML不一致最后模型输出的类别ID和标签完全对不上。解决方法是转换后立刻抽查。终端里先看几个txt文件head -n 3 classroom_datasets/labels/train/img_0001.txt然后写一段小脚本统计所有label文件出现的类别ID集合from pathlib import Path classes set() for txt_path in Path(classroom_datasets/labels/train).glob(*.txt): for line in txt_path.read_text().splitlines(): if line.strip(): classes.add(line.split()[0]) print(sorted(classes, keyint))如果输出的ID里有5以上说明某个JSON里的标签没有在names里定义或者混入了其他数据集的标注。先修标签再谈训练。5.4 现象视频推理太卡画面根本拖不动把YOLOv8接到视频文件后逐帧跑检测结果画面像幻灯片卡到没法演示。原因是在UI线程里同步执行了模型推理模型每处理一帧要200毫秒以上整个界面都在等它自然卡顿。解决方法是把推理和显示拆开。推理线程只负责读取视频帧并调用模型用队列把结果传给显示线程。通常还会跳帧处理比如vid_stride5每5帧检测一次中间帧沿用上一帧结果for result in model.predict(sourceclassroom.mp4, streamTrue, vid_stride5): frame result.plot() # 把frame交给UI线程显示不在推理线程里画streamTrue会让模型按生成器方式逐帧返回而不是一次性把整段视频全算完。加上vid_stride5之后界面响应会明显变快课堂行为统计本身也不需要每一帧都给出新结果。5.5 现象pip install ultralytics 反复报错在Windows或Ubuntu上直接装ultralytics有时会因为OpenCV编译问题、Python版本不兼容、torch版本冲突而报错。解决方法是严格按环境章节里的顺序来先创建Python 3.9的conda环境再装CPU版torch最后装ultralytics。如果还需要做图像标注用opencv-python-headless而不是opencv-python两者同时存在会产生符号冲突。实在装不上就检查一下现有Python是不是3.11以上。很多依赖库的wheel还没跟上新版本换到3.9环境里基本一次通过。6. 验证系统是否能用混淆矩阵、行为时间线与一个长期习惯训练完成后不要只看mAP数字要真正跑一遍验证集打开confusion_matrix.png。这张图能告诉你哪些类别之间最容易混淆。如果phone这一行的误判大量落在listening上说明标注时“玩手机”和“听课”的画面边界太模糊不是模型玄学问题是数据问题。更直观的验证方式是做行为时间线图。把视频按每150帧分成一个时间桶统计每个桶里各类行为的数量画一张堆叠面积图就能看到一节课的走势前10分钟听课比例高中间有波动快下课时“趴桌子”比例升高。这类图对毕设答辩非常有说服力因为它证明系统做的不是单帧截图的玩具而是能反映课堂节奏的产品。你可以在classroom_summary.csv基础上直接画import pandas as pd df pd.read_csv(classroom_summary.csv) df[time_bin] df[frame] // 150 pivot df.pivot_table( indextime_bin, columnsbehavior, aggfuncsize, fill_value0 ) pivot.plot.area() pivot.figure.savefig(behavior_timeline.png)time_bin的分母来自视频帧率。25fps的视频里150帧是6秒这样一个时间桶代表6秒内的行为分布一节课40分钟大概会有400个点曲线足够平滑。我自己的习惯是每训练完一版模型后先不急着调参而是多花半小时去验证集视频里找30个误判最严重的片段看它们到底错在哪里。这些片段才是数据集的真实边界。场景遮挡严重、标注矛盾、类别定义不清晰都会在这些片段里暴露出来。很多看起来是模型的问题归根结底都是数据和后处理的问题把这个观念立住后面做RK3588板端部署或者换其他模型时才不会走弯路。希望这些实战里的流程和坑位能帮到你整套系统的成败从来都在细节里。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门