ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv8的智慧教室学生课堂行为分析系统:从检测到课情统计

基于YOLOv8的智慧教室学生课堂行为分析系统:从检测到课情统计 简介一套基于YOLOv8的智慧教室学生课堂行为分析系统面向计算机、人工智能等相关专业的毕设与课程设计场景可完成课堂行为检测、可视化展示与结果评估。压缩包共含8个文件包括3个Python脚本模型训练、视频检测、可视化界面、3个模型权重文件以及2个部署说明文档整体约15.91MB结构简洁便于快速上手。资源内含完整数据集代码均测试通过解压后按照README文档操作即可直接运行目前已有64人学习下载。除基础检测功能外还提供核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果与标签分布图等可视化输出适合答辩展示。读者可在现有代码基础上修改用于课程设计、大作业或项目初期演示也可深入学习YOLOv8目标检测流程。1. 智慧教室学生行为分析YOLOv8检测只是第一步把单帧结果变成课情统计才算完成做课堂行为分析最劝退的时刻往往不在训练而在交付摄像头画面里YOLOv8把人框得漂漂亮亮可一统计“玩手机多久”“睡觉多久”数据全对不上因为单帧检测不等于行为识别。基于YOLOv8的智慧教室学生课堂行为分析系统核心就是把这些“框”变成“表”——先定义六类典型课堂行为再用检测加跟踪把每个学生持续数分钟的动向沉淀成可视化报表。这套方案对毕设和课程设计尤其友好自带数据集、可视化界面、部署教程意味着不用从零造轮子按部就班就能交付一套能演示、能截图、能写进论文的完整系统。适合在校生做毕设也适合想快速验证“检测行为统计”落地路径的工程师。2. 六类行为标签与模型选型判定规则、YOLOv8对比R-CNN以及一条数据的前向路径2.1 六类行为判定规则听讲、写字、举手、玩手机、睡觉、起立怎么标行为分类直接决定模型的可用上限。教室场景下最常见的做法是定六类互斥标签listening听讲、writing写字、hand_raising举手、playing_phone玩手机、sleeping睡觉、standing起立。六类之间既有姿态差异又有遮挡干扰标注时必须有明确的边界规则否则标注同学和训练同学各按各的理解来mAP再高也是假的。类别判定要点典型误标listening目视黑板或教师头部无明显低垂低头记笔记时与writing混淆writing低头执笔桌面有遮挡手部动作在桌面范围手机放在桌面上时误判为写字hand_raising单臂抬起且高于肩部手部清晰可见伸懒腰、扶眼镜playing_phone双手持机于桌面下方或胸前框住“手设备”区域喝水、转笔sleeping趴桌头部枕臂或面部朝下低头看桌洞standing站立目标框高度明显大于周围坐姿中位数起身搬椅子标注时我一般要求一张图只给一个标签多义情况选确定性最高的类别。玩手机这一类尤其特殊不要只框头要把手和设备的区域包含进去因为模型识别手机靠的是手部姿态特征不是靠人脸表情。认真听讲和写字这两类的边界最容易抖建议在标注规范里加一条硬规则笔尖触碰纸面算writing只低头不执笔算listening。2.2 为什么是YOLOv8参数量、速度与内置跟踪的三个理由选YOLOv8而不是YOLOv5或Faster R-CNN核心原因有三个。第一是部署生态YOLOv8由Ultralytics维护训练、验证、导出ONNX、内置跟踪一条命令跑通不需要东拼西凑YOLOv5的推理代码也能用但跟踪和导出需要额外装依赖。第二是精度和速度的平衡Faster R-CNN在密集小目标上精度不差但教师视角下全班三四十人同时推理帧率很难撑住实时演示。第三是anchor-free设计解码端去掉了锚框输出张量形状固定改类别数、导出部署都更省事。常见模型档位如下教室场景按显存和目标帧率二选一模型参数量约计算量约适用场景yolov8n3.2M8.7 GFLOPsCPU演示、嵌入式yolov8s11.2M28.6 GFLOPs单卡训练毕设主力yolov8m25.9M78.9 GFLOPs离线高精度分析我建议毕设至少用s起步n作为最后的界面加速方案。s在GTX1660Ti这类6G显存卡上batch8能跑换到n则CPU也能勉强出图正好对应标题里“简单部署即可运行”的定位。2.3 一张教室图像在YOLOv8里的前向路径从640输入到8400个候选框理解YOLOv8的推理路径排查问题才有方向。输入图像先缩放到640×640进入backbone由C2f模块逐层提取特征接着neck用PAN-FPN把多尺度特征融合让后排的小目标和前排的大目标都能被照顾到最后经过解耦检测头分别输出分类概率和边框回归。整张图会被切分成80×80、40×40、20×20三种网格每个网格预测若干候选合并下来大约8400个候选框再经过NMS去重才得到最终输出。这里的输出向量是“框坐标置信度类别概率”。对六类行为来说每个候选框对应一个长度为11的向量4个坐标、1个置信度、6个类别分数。训练时框坐标用归一化的cx、cy、width、height表示推理时再映射回原图尺寸。知道这个形状后面导出ONNX时就不会被张量维度搞晕。2.4 密集与遮挡的标注先手后排小目标在打标阶段就要处理教室场景最大的干扰是密集和遮挡后排学生人脸只有十几个像素前排高个子学生会挡住后排的举手和玩手机。这些问题是模型训练解决不了的必须在数据准备阶段就干预。第一标注时用原图分辨率操作不要为了省事把图片缩成小图再标小目标会直接丢失第二选640以上的imgsz做训练输入缩得太小后排细节全没第三给sleeping和playing_phone这类易混淆类别多补样本让两个类别在特征空间里真正分开。另外一个实操经验是数据集的划分不能按文件随机切要按“镜头”切。同一个录像片段里相邻帧高度相似随机切会导致验证集泄漏指标虚高后面在真实教室一试就露馅。3. 数据准备与训练labelme转YOLO格式脚本、目录划分与训练命令参数全注解3.1 环境搭建Ubuntu20.04与Windows的Python3.9PyTorch组合环境配置是部署第一道坎也是最容易被版本问题拖住的地方。我习惯用conda隔离环境Python版本固定3.9PyTorch根据显卡驱动选择对应CUDA版本。标题涉及的部署教程一般覆盖Windows和Ubuntu20.04两种系统命令基本一致差别只在驱动底座。conda create -n classroom python3.9 -y conda activate classroom pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 python -c import torch; print(torch.cuda.is_available())第一行创建Python3.9虚拟环境第二行激活第三行安装Ultralytics工具包它会自动带上onnx等推理依赖。第四行安装CUDA11.8版本的PyTorch适合绝大多数30系及以上N卡如果你的机器没有独立显卡就不要安装cu118版本直接装CPU版即可但要做好推理帧率大幅下降的心理准备。最后一行验证torch能否识别到显卡输出True说明环境可用输出False就去检查驱动版本别急着跑训练。环境跑通后顺手验证一下模型能否加载这一步能排除Ultralytics包损坏或依赖冲突from ultralytics import YOLO model YOLO(yolov8s.pt) print(model.names)3.2 数据集目录与YAML配置8:1:1划分并按镜头防泄漏拿到标题所述的数据集后第一件事不是急着训练而是确认目录结构是否符合Ultralytics约定。标准结构是images和labels两个大目录各自下面再分train、val、test三个子目录图片和标签通过文件名一一对应。如果压缩包内数据集不是这个结构直接用脚本重组别手拖。dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classroom.yamlclassroom.yaml是训练入口内容如下path: D:/workspace/dataset train: images/train val: images/val test: images/test names: 0: listening 1: writing 2: hand_raising 3: playing_phone 4: sleeping 5: standingpath必须改为你机器上的实际绝对路径Windows下使用正斜杠不要用反斜杠。names的索引顺序一旦定下来就不能改训练、导出、推理都要沿用这套映射。这里有个细节如果原始数据集的类别和你定义的不一致务必先做类别映射再训练不要指望模型自动对齐。划分比例我采用8:1:1但要强调按镜头划分同一个教室视频的连续帧只进训练集或只进验证集防止画面重复导致的指标虚高。数据集如果只有单场景建议补充一个不同机位的小验证集哪怕只有几十张图也能暴露过拟合。3.3 labelme标注转YOLO格式坐标归一化脚本与三个易错点如果自带数据集还需要补充标注最常见工具是labelme。labelme导出的是JSONYOLO需要的是每张图一个txt、每行“类别编号 归一化中心点坐标”。直接用Ultralytics的转换脚本也行但自己手写一遍能清楚看见三个典型错误源。import json import os def labelme_to_yolo(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] img_name os.path.basename(data[imagePath]) base os.path.splitext(img_name)[0] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) bw x_max - x_min bh y_max - y_min cx (x_min x_max) / 2.0 / img_w cy (y_min y_max) / 2.0 / img_h w bw / img_w h bh / img_h lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, base .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) class_map { listening: 0, writing: 1, hand_raising: 2, playing_phone: 3, sleeping: 4, standing: 5, }这段脚本先读取JSON里的原始宽高再把每个标注框的横向和纵向边界合并成YOLO需要的中心点与宽高。关键点在于归一化必须用data[imageWidth]和data[imageHeight]不能用手动改过的尺寸如果训练前图片做了resize要用resize后的宽高参与计算否则标注框全部偏移。json里的imagePath字段是相对路径还是绝对路径决定了你能不能直接拼出图片文件名转换前先打印出来看一眼这是最常见的翻车点之一。3.4 第一个训练命令epochs、imgsz、batch逐项说明环境、数据都就绪后第一个训练命令这样起yolo detect train \ dataclassroom.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience30 \ cacheTruedata指向上面配好的yamlmodel用预训练权重yolov8s.pt做迁移学习不要从随机权重开始。epochs120是起步值数据量充足但loss还没降平就提到200。imgsz640必须守住降到480会让后排小目标更早失效。batch16在8G显存以上才安全GTX1660Ti这类6G卡需要降到8或者4。device0指定第一张显卡CPU机器改成devicecpu。patience30表示连续30个epoch验证指标没有提升就自动停止防止晚上挂机训练把时间白烧。cacheTrue把图片缓存进内存第一次训练会多花几分钟加载但后续每轮提速明显。训练启动后Ultralytics会在runs/detect/下生成带时间戳的目录里面包含weights/best.pt和last.pt、results.csv、confusion_matrix.png等文件。best.pt是验证集表现最好的权重所有后续导出、推理都以它为准不要手滑用last.pt。4. 训练验证与导出从results.csv画损失曲线到mAP判读再到ONNX推理4.1 超参调节顺序先imgsz后batch最后动epochs很多同学一上来就调学习率这是典型的顺序错误。我的调节顺序是先固定imgsz640再根据显存把batch放到能稳定训练的最大值最后才看loss曲线决定epochs和lr0。教室场景里imgsz对后排小目标的影响远大于lr0的微调imgsz先定下来再去调其它参数才有意义。超参推荐值调整依据imgsz640后排密集小目标低于480精度明显下降batch16/8/4显存不足依次降优先保证能跑完epochs120起步看train/loss是否还在下降lr00.01默认loss震荡时降到0.005patience30防止过拟合后空转degrees/flipud0教室场景不需要旋转和上下翻转增强Ultralytics默认的数据增强里包含随机旋转和翻转对通用目标检测没问题但教室场景里旋转90度会产生大量不合理样本建议在训练首轮后查看增强效果再决定是否关闭。4.2 从results.csv画损失曲线三张图判断训练是否正常训练过程中最直观的依据是results.csv。搜索“yolov8画损失函数曲线图”能翻到不少脚本但很多都输出了带空格的列名导致KeyError。标准写法是先清理列名再绘图import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df.columns [c.strip() for c in df.columns] fig, axes plt.subplots(1, 4, figsize(15, 3.5)) cols [train/box_loss, train/cls_loss, metrics/mAP50, metrics/mAP50-95] for ax, col in zip(axes, cols): df[col].plot(axax) ax.set_title(col) plt.tight_layout() plt.savefig(train_curves.png, dpi200)第一张图看box_loss是否持续下降第二张看cls_loss是否同步收敛第三、四张看mAP是否抬升。健康的曲线是train loss平滑下降、mAP50稳步爬到0.8以上。如果box_loss降了但mAP纹丝不动大概率是类别不均衡或标注边界混乱回第二章对一下判定规则。如果train loss降、val loss回升就是过拟合先砍epochs或加大原始训练数据不要先去动lr0。4.3 指标判读mAP50、混淆矩阵与过拟合信号验证阶段重点看三个东西mAP50、mAP50-95、混淆矩阵。教室场景里mAP50到0.85是及格线0.9以上算可用mAP50-95比mAP50低0.1到0.2属于正常差太多说明框的定位不稳定。confusion_matrix.png直接展示六类互相误判的情况重点看playing_phone和writing这两行的非对角线数值如果互混严重回数据准备阶段补样本而不是盲目调训练参数。检查过拟合的硬信号val loss连续上翘、train loss持续下降、mAP50-95停滞甚至回落。出现这个组合优先做三件事把epochs恢复到验证指标峰值附近的轮数、增加更多真实教室图片、关闭或减弱数据增强。全部做完仍没改善再考虑从s升m换更强模型。4.4 导出ONNXopset配置与onnxruntime推理的最小代码训练验收后下一步是把best.pt导出为ONNX让可视化界面不依赖完整PyTorch环境也能明显压缩推理开销。yolo export modelruns/detect/train/weights/best.pt formatonnx opset12 imgsz640opset锁定12是为了兼容onnxruntime的多版本别为了追求新奇用15。导出完成后用onnxruntime快速验证一次import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name blob np.random.rand(1, 3, 640, 640).astype(np.float32) out session.run(None, {input_name: blob})[0] print(out.shape) # 六类行为下是 (1, 11, 8400) 或 (1, 8400, 11)YOLOv8导出的输出形状因版本而异有的带batch维度在前的形式是(1, 11, 8400)有的版本是(1, 8400, 11)。如果下游要用NMS需要先把输出统一为(1, 8400, 11)的形式也就是把类别维度挪到最后。把这个形状问题在导出时就确认清楚界面上画框时能省两小时排错。5. 部署避坑记录CUDA内存、中文路径、标签偏移等六个真实翻车点这一章按“现象 → 原因 → 解决”的排查顺序写。这套系统的报错九成不是模型问题而是工程细节按下面顺序排查基本能覆盖毕设交付前会遇到的大部分故障。5.1 训练启动即报CUDA out of memory现象训练命令一敲下去epoch 1都没跑完就报显存不足。原因imgsz和batch的组合超出显存容量或者同一张卡上还挂着别的占用程序。GTX1660Ti这类6G卡尤其常见因为默认batch16就是为8G以上显存设计的。解决先把batch降到8仍不足就降到4同时把cacheFalse关掉图片缓存。如果降batch影响到收敛再考虑换yolov8n预训练权重做基线测试。用nvidia-smi确认没有其它进程占用GPU再重跑。5.2 数据集路径含中文训练报Dataset not found现象yaml文件里路径明明写对了Ultralytics就是报找不到数据集。原因Windows下数据集放在带中文或空格的目录里时Ultralytics的路径解析不稳定。有的同学桌面叫“新建文件夹”路径里带了中文训练器直接罢工。解决数据集统一放到纯英文路径下例如D:/workspace/dataset。yaml文件里的路径一律用正斜杠路径值不要加引号结尾不要留空格。改完路径后重启终端不要复用之前的缓存。5.3 labelme转出来的框全部偏移现象训练loss降不下去用标注可视化工具一看框整体偏到某个角落。原因转换脚本里用了经过缩放的图片宽高来做归一化但labelme的json里存的是原始尺寸的坐标两边对不上。另一种情况是把points里的矩形对角当成左上右下但标注时画的是任意多边形。解决回到转换脚本确认归一化分母用的是data[imageWidth]和data[imageHeight]。如果图片在标注前被缩放过在json里补记录真实输入图片尺寸按那个尺寸计算。转换后用一次性可视代码检查20张图别直接开训练。5.4 ONNX导出成功推理结果全为空现象界面接上ONNX后画面里一个框都不出。原因输入没有做letterbox等比缩放或者输出张量没有转置、NMS阈值写错。ONNX的输入必须固定640×640原图直接拉伸会破坏目标宽高比小目标直接消失。解决推理前用letterbox把原图等比填充到640×640记录缩放比和pad偏移量画框时再映射回原图坐标。输出侧先把(1, 11, 8400)转置为(1, 8400, 11)再按conf过滤最后用cv2.dnn.NMSBoxes去重。5.5 密集座位区跟踪ID乱跳行为统计失真现象一个人从举手切换到写字跟踪ID从5跳到17统计里多出一个人。原因conf阈值太低大量低置信度框参与跟踪ID在遮挡后频繁切换。教室场景前后排遮挡严重单人单帧很容易跟丢。解决检测conf从0.25提高到0.45跟踪器用bytetrack.yaml而不是默认的bootsorchybt。统计口径也要调整不按瞬时帧计数而是用滑动窗口取一段时间内出现次数最多的类别作为该学生的行为结论。5.6 CPU机器上摄像头预览像幻灯片现象界面能跑但实时检测只有两三帧看起来一步一卡。原因拿yolov8s在CPU上全分辨率逐帧推理。解决推理模型换成yolov8n.ptimgsz保持640界面端用抽帧策略每3帧送一次检测中间两帧直接复用上一次的框。这样实时性够演示也不会破坏行为统计的连续性。6. 进阶用Streamlit把检测结果变成课堂参与度报表6.1 Streamlit最小可视化界面上传视频、实时画框、统计参与度标题里的可视化界面我的默认实现是Streamlit加Ultralytics的track接口原因是代码量最小、演示效果直观、答辩时浏览器一开就能截图。下面是一个能跑通的最小主程序import streamlit as st from collections import defaultdict from ultralytics import YOLO st.set_page_config(page_title课堂行为分析系统, layoutwide) model YOLO(best.pt) upload st.file_uploader(上传课堂视频, type[mp4, avi, mov]) if upload and st.button(开始分析): with open(tmp_input.mp4, wb) as f: f.write(upload.getbuffer()) cap cv2.VideoCapture(tmp_input.mp4) behavior_seq defaultdict(list) stframe st.empty() while cap.isOpened(): ok, frame cap.read() if not ok: break res model.track(frame, persistTrue, conf0.45, trackerbytetrack.yaml, devicecpu) for box in res[0].boxes: tid int(box.id) if box.id is not None else -1 behavior_seq[tid].append(res[0].names[int(box.cls)]) stframe.image(res[0].plot(), channelsBGR) active_ratio sum( 1 for seq in behavior_seq.values() if max(seq.count(k) for k in set(seq)) / len(seq) 0.6 ) / max(1, len(behavior_seq)) st.success(f检测到 {len(behavior_seq)} 人课堂参与度 {active_ratio * 100:.1f}%)这段代码把track接口返回的轨迹ID当作学生临时编号逐帧累计行为类别视频结束后用“主类别占比超过60%”作为参与度判据。演示时注意两点device按实际环境改cuda或cpu上传视频不要太大压到720p以下Streamlit处理大文件容易超时。6.2 部署提速三板斧模型换小、抽帧推理、TensorRT演示机器的性能参差不齐我通常准备三套加速方案按需切换。第一是模型降档yolov8s换yolov8nmAP50大约降零点几个点肉眼几乎分辨不出差异第二是抽帧推理检测每3帧做一次跟踪在中间帧用卡尔曼补帧帧率直接翻倍第三是TensorRT FP16适合N卡演示机bs1下推理耗时能比ONNX少一半但需要额外装TensorRT环境和engine序列化文件建议作为加分项放论文“部署优化”章节里写。我第一次把系统交给班主任演示时模型mAP50有0.91可当天的课堂参与度报表里“睡觉”时长全算到了“玩手机”上。排查了半天发现是tracker在遮挡后换了ID而我的统计逻辑只看了瞬时类别。从那以后任何行为统计我都坚持“检测框跟踪ID滑动窗口”三层一起看宁可多算一层防抖也不要被单帧的假象带偏。希望这个教训能帮你少走一段弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表