简介:基于YOLO的课堂行为检测系统.zip 是一套面向课堂教学场景的深度学习实践项目,专为计算机视觉方向的学习者、毕业设计或课程设计人员打造,用来对学生在课堂中的举手、听讲、阅读、写字等行为进行自动识别和实时分析。压缩包共包含27个文件,总大小约26.85MB,其中6个Python脚本分别承担主流程、视频测试和UI交互等任务,1个pt模型权重文件可直接加载运行,1个ui界面文件用于图形化操作,2个Markdown文档给出训练笔记与使用说明,另有若干效果展示图和标注图片辅助验证与展示。系统基于YOLOv8目标检测算法实现,模型训练完成后支持导出为ONNX格式,便于在不同推理引擎和硬件环境中部署。目前已有72人学习或下载该资源。整个项目覆盖了数据标注、模型训练、测试评估到界面集成的完整链路,拿到手后既能快速跑通课堂行为检测演示,也能根据自身场景微调模型或修改代码,作为课程设计及毕业设计的实物基础十分合适。
1. 一台教室监控里的YOLO:为什么课堂行为检测先选目标检测
教室摄像头拍下的画面,比工厂质检线复杂得多。光线忽明忽暗,学生前后排互相遮挡,举手、低头、转身这些动作变化快,而且课堂场景里“行为”不是单一静态物体,而是一连串人与物、人与人的空间关系。基于YOLO的课堂行为检测系统,正是从这个角度切入的:它先用目标检测把画面里的“人”“手机”“书本”“电脑”这些实体框出来,再用空间逻辑去推断行为状态。这个思路最大的好处是,系统不需要一开始就训练一个“行为分类器”,而是把问题拆成“能看到什么”和“怎么组合”两步。在实际教室监控项目里,YOLO的实时性足够追上25帧到30帧的摄像头输出,单张图像推理在边缘设备上能压到几十毫秒,这决定了它适合作为课堂行为分析的前置感知引擎,而不是被塞进一个笨重的多阶段管线。
这篇文章写给两类人。一类是刚接触YOLO的开发者,想用现成代码在本地跑通一整套“图片进、行为标签出”的流程,搞清数据怎么标、训练参数怎么设、模型导出来怎么接业务逻辑;另一类是已经跑通过示例代码、正准备把模型部署到学校机房或教室边缘盒子上的工程师,需要知道哪些设置有坑、误检翻车了从哪里查起。文中所有操作路径都按真实项目里的常见做法来写,不依赖某一份虚构的官方文档。你会看到一份完整的工程侧拆解:数据准备、模型训练、部署套壳、避坑排查,以及最后怎样把检测结果推进到行为判定这一步。
2. 拆解课堂行为检测系统:YOLO在管线里的真实位置
课堂行为检测不是一个“装个模型就能用”的黑匣子。把系统拆开看,它至少包含四个模块:视频采集与抽帧、目标检测、行为推理、结果存储与可视化。YOLO只负责中间最核心的“目标检测”一环,但它的输出质量决定了行为推理的上限。
2.1 为什么用YOLO而不是先做分类或姿态估计
课堂里很多行为本质上与姿态相关,比如“趴桌子”和“举手”,姿态估计似乎更直接。但实操里,姿态估计在多人和遮挡场景下有天然短板。教室中后排学生互相遮挡是常态,姿态关键点在这种画面上会大量丢失。而YOLO这类目标检测器的输出是一组矩形框加类别概率,它对遮挡的容忍度更高——只要人头的上半部分可见,就能给出较稳定的检测框。这是第一个选择理由。
第二个理由是行为定义不总是依赖“骨架”。课堂行为中有一类叫“物品交互行为”,例如看手机、翻书、用电脑,这些行为的判别依据是“人身边有没有某个物体”,而不是人的动作姿态。用YOLO同时检测人和手机、书本、电脑,让行为推理直接基于“人框与物框的空间关系”来做,这比先做人脸识别或姿态估计再额外接一个物体检测器,省掉一整条分支。另外,YOLO的系列迭代里,从V5到V8,检测头的设计越来越适合在边缘设备上做int8量化,这对教室监控常用的Jetson、RK3588盒子很友好。
2.2 把课堂行为拆解成可训练的检测目标
我一般会把课堂行为分成两类。第一类是“纯目标检测型”,例如“学生玩手机”,只需检测手机并关联到人框,不需要任何时序信息。第二类是“状态复合型”,例如“学生举手”,单帧画面中人手抬起是瞬时状态,容易和“伸懒腰”“挠头”混淆。对第二类行为,检测器只提供人框,行为判定交给后置逻辑,通过连续几帧的状态投票来平滑。
拿到一套标注数据时,不要一上来就设十几个行为类别。课堂场景里真正高频且可标注的行为,通常不超过8类。多一个类别,就多一层类别间混淆的风险,比如“看书”和“写字”在画面中经常只是手部位置的细微差别。一个稳妥的做法是第一版模型只检测四类物体:person、phone、book、laptop,再加上一个检测“手部”的类别,然后通过规则引擎组合成人级行为标签。这样既降低了标注成本,也把最不确定的“行为语义”留给后置逻辑处理。
2.3 一套最小的系统结构与数据流向
在开始训练之前,建议先把系统的数据流向定下来,否则后续每个模块各写各的,拼接时容易乱。一套最小可用系统的结构是这样的:
摄像头输出RTSP视频流,通过FFmpeg按每秒2到5帧的频率抽帧,抽帧结果送入YOLO推理模块,推理模块输出每个人框的坐标、置信度和类别。接着行为推理模块拿到同一个人连续多帧的检测结果,维护一个轻量级状态机,例如“手机框与人框的IoU大于0.3且持续3帧以上”,则标记为“正在使用手机”。最后把结果写入时序数据库,并在监控画面上叠加绘制。
这条管线看起来简单,但有一个常被忽略的关键点:抽帧和推理必须分离。如果直接把摄像头帧送到推理线程,网络稍有抖动,系统就会丢帧堆积。常见做法是用一个带缓冲队列的抽帧进程,推理进程只管从队列取最新帧。队列长度控制在60帧以内,超出就丢旧帧,保证推理永远处理的是最新画面。至于行为推理的状态机,用Python的字典就能维护,以person的track_id为键,存储每个学生最近10帧的检测状态。
3. 构建课堂行为数据集:从采集到格式转换的完整路径
很多开发者卡在第一步:下载了预训练模型,有自己的视频素材,但没有数据、不会标。这一章会把从零构建数据集的过程写清楚,包括标签体系设计、VOC转YOLO格式的脚本,以及数据增强的几个关键参数。
3.1 标签体系怎么定:先跑通还是不追求完美
新建数据集之前,先用一周时间定义标签体系,比直接标注更省事。课堂场景建议从“人”和“关键物件”出发,而不是直接标“行为”。原因很简单:行为标签是主观的,不同标注员对“举手”和“伸懒腰”的边界把握不一样。而“手机”“书本”“笔记本电脑”是客观存在的物体,标注一致性高。
一个可用的起始标签表包括:
| 标签名 | 类别ID | 标注规则 |
|---|---|---|
| person | 0 | 全身或上半身可见即标,被遮挡超过50%不标 |
| phone | 1 | 屏幕可见或整机可见,被手完全握住且不可见不标 |
| book | 2 | 封面或内页可见,只露出一角也算 |
| laptop | 3 | 屏幕或键盘可见 |
| hand | 4 | 仅标注“手部与桌面或物品接触”的实例,用于辅助行为推断 |
hand这个类别要慎重。如果资源紧张,可以先砍掉,用手机和人框的关系来判断“玩手机”就够了。加hand类会增加不少标注量,但对“举手”这类行为的准确率提升明显,属于典型的锦上添花项。
3.2 用Python脚本把VOC格式转成YOLO格式
课堂行为数据集的公开样本很少,多数情况是拿到一批VOC格式的标注,或者自己用LabelImg标注后导出为VOC XML。YOLO训练要求每张图片对应一个同名的txt文件,每行是“class_id x_center y_center width height”,其中四个坐标值都归一化到0到1之间。
下面这个脚本能把VOC XML批量转成YOLO格式,并顺手过滤掉那些宽或高小于5像素的无效框:
import os import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(xml_dir, out_dir, class_list): """ 遍历XML目录,生成YOLO格式的txt标注文件 class_list: 类别名列表,索引即类别ID """ os.makedirs(out_dir, exist_ok=True) for xml_path in Path(xml_dir).glob("*.xml"): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_list: continue cls_id = class_list.index(name) bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) # 过滤掉太小的框,这类框在训练中容易变成噪声 if (xmax - xmin) < 5 or (ymax - ymin) < 5: continue # 坐标裁剪到图像范围内,防止标注越界 xmin = max(0, xmin); ymin = max(0, ymin) xmax = min(img_w, xmax); ymax = min(img_h, ymax) # VOC是x1y1x2y2,需要转成x_center y_center w h并归一化 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: out_path = os.path.join(out_dir, xml_path.stem + ".txt") with open(out_path, "w") as f: f.write("\n".join(lines)) class_list = ["person", "phone", "book", "laptop", "hand"] convert_voc_to_yolo("voc_annotations", "labels", class_list)这个脚本有几个细节值得说明。class_list.index(name)省去手动映射类别ID的表格,但要求你严格保证训练配置里的names顺序与脚本里的class_list一致。宽高小于5像素的框被直接过滤,因为训练时YOLO会把这些小目标当作前景,而它们通常只占几个像素,容易拉低precision。坐标裁剪到图像范围内是为了防止标注员手滑把框拉到图像外面去,不裁剪的话训练时某些版本的YOLO会报边界不匹配的错误。
3.3 数据增强与样本均衡:一条配置让模型更抗过拟合
课堂场景的数据有一个典型问题:“看书”类别的样本数量往往是“玩手机”的3倍以上,因为上课时看书是常态,玩手机是少数情况。不处理样本不平衡,模型会把所有低头动作都预测成“看书”,误检率直线上升。
在训练层面,最简单有效的办法是提高少数类别的loss权重。YOLOv8支持在数据配置文件里给每个类别设置独立的权重,这个参数在训练时通过cls系数和box系数间接生效,但更直接的做法是在数据加载时做在线增强——对包含phone和hand标签的图片,随机加大色域变换和随机旋转的角度。
另一个实操经验是不要过度依赖离线增强。课堂场景的摄像头位置固定,画面视角基本一致,不需要做90度旋转或大幅拉伸这类“重型”增强。建议在ultralytics的配置里只开三样:hsv_h从0.015调到0.02,degrees设为10度以内,translate设为0.1。多了反而会让模型学到“歪着头的学生”,部署时直着拍的画面反而识别不准。
4. 训练课堂行为检测模型:环境配置、预训练权重与必调参数
YOLO环境配置是新手第一个翻车点,但其实只要卡准两个版本:Python 3.10以上和PyTorch 2.x。这一章按“最少依赖、迁移学习、结果评估”三段走,把训练跑通并判断质量。
4.1 最小环境配置与依赖安装
如果你的机器有NVIDIA显卡,CUDA就装11.8或12.1,不要追求最新。显卡驱动的计算能力超过CUDA版本没关系,但千万不要让CUDA版本低于你的显卡驱动要求的版本,否则推理阶段会莫名报“CUDA error: no kernel image is available for execution on the device”,这属于最常见的环境坑。
python -m venv yolo_env source yolo_env/bin/activate # Windows下是 yolo_env\Scripts\activate pip install ultralytics==8.2.0 torch==2.2.0 torchvision==0.17.0这里锁了版本。不锁版本的话,一个月后重装环境,ultralytics和torch的API可能已经变化,过去能跑的代码在新版本上可能突然崩掉。ultralytics是YOLOv5到v8的训练与推理统一库,安装它会把yolo命令装进系统,后面训练和导出都用它。
4.2 用预训练权重做迁移学习:训练命令与参数说明
课堂行为数据集的规模一般不会超过几千张,从零训练YOLO不现实。预训练权重的选型上,优先下载YOLOv8n或YOLOv8s的COCO权重,因为COCO里有person、book、laptop这些类别,迁移过来的特征表示对课堂场景有效。
yolo train \ data=classroom.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ workers=4 \ patience=20 \ pretrained=True几个参数值得单独讲。patience=20是早停的耐心值,表示验证集指标连续20轮不提升就停止训练。课堂数据场景里,模型通常在40到60轮之间收敛,但偶尔会有一批容易混淆的样本导致指标波动,耐心值设太短容易提前停。imgsz=640是训练分辨率,不建议一上来就拉高到1280。课堂监控画面通常是1920×1080,但用小分辨率训练能更快遍历数据,模型先学全局特征,后期如果想提升小目标检测能力,再fine-tune到imgsz=960。
classroom.yaml是数据配置,核心结构如下:
path: /path/to/classroom_dataset train: images/train val: images/val names: 0: person 1: phone 2: book 3: laptop 4: hand4.3 用混淆矩阵判断模型能不能上线:看不只看mAP
训练结束后,ultralytics会在训练目录runs/detect/train/里生成confusion_matrix.png、PR_curve.png和results.csv。课堂场景判断模型能不能用,我会优先看混淆矩阵,而不是盯着mAP。原因是mAP是多个类别的平均,可能某个类别很高、某个类别很低,平均值看起来还行,但实际部署时表现就翻车。
看混淆矩阵时抓三个地方。第一,person类有没有被误判成book或hand。如果人框大面积落到其它类别,说明特征提取没学好。第二,hand和phone之间的混淆程度。课堂视频里,很多学生握手机时手部包住屏幕,模型看到的其实是一个“手”的形状,这类混淆是数据标注边界问题,通常需要回头补标注,而不是调整模型参数。第三,background行有没有异常高。背景误检高意味着模型在空桌椅、墙面上产生了大量假阳性框,上线时会把没人坐的位置识别成人。
results.csv里值得看的不是最后的mAP50-95,而是val/cls_loss这条曲线。正常训练时分类loss应该在40轮内下降到0.02以下,如果一直居高不下,多半是类别定义互相重叠。比如“book”和“laptop”都包含“桌面上有矩形物体”的特征,标注时就要规定“书翻开时标book,合上的薄矩形标laptop”这类具体规则。
5. 部署到教室监控的避坑笔记:5个最常踩的坑与排查方法
这一章是整篇文里最“血泪”的部分。下面五个问题都是我在课堂行为检测项目里真实遇见过的,按“现象→原因→解决”的顺序拆给你。
5.1 误检率居高不下:把举手识别成玩手机
现象:模型在测试集上mAP有0.85,部署到教室后,只要学生抬手,系统就报警“玩手机”。原因是测试集里的“hand”类大多是手部握住手机的近景,而教室全景画面里手部只有20×20像素,模型学到的特征跨尺度失效。解决方法是回数据标注阶段,专门把远景画面里手部区域的标注框放大到包含部分手臂,让模型学到“手+手臂”的整体特征,同时训练时把imgsz从640提升到800。这个操作能把误检率下降一半左右。
5.2 训练到一半loss不降反升,BN层崩溃
现象:训练进行到第30轮时,train/cls_loss突然从0.03跳到0.5,之后不下降,同时日志里出现大量RuntimeError: running_mean should contain 10 elements或NaN值。原因是batch size设置偏大,配合默认的BatchNorm参数,在数据里出现个别极端样本时,统计量发生漂移。解决方法是先把batch从16降到8,同时给optimizer增加weight_decay=0.0005。如果已经出现NaN,不需要重头训练,把模型权重回滚到第25轮的checkpoint,再从那里开始训练,但要调低学习率从0.01降到0.005。
5.3 模型在测试集上很好,一到教室画面就翻车
现象:室内模型的mAP很高,但部署到不同朝向的教室后,靠近窗户一侧的座位误检率暴涨。原因是教室靠窗区域亮度过高,加上窗帘光影造成大量类“手机屏幕”的高光区域。此时如果直接加图像增强,效果有限。正确做法是部署预处理环节增加白平衡校正,用OpenCV做灰度世界白平衡,把偏色画面拉回中性色。这属于典型的“模型没问题、场景没伺候好”的坑。
5.4 混淆矩阵总合不唯一,评估结果对不上
现象:训练结束后打印混淆矩阵,发现每行数值加起来不等于该类的样本总数,甚至同一类别在不同epoch下的对角线数值出现波动。原因是混淆矩阵默认基于val集的预处理结果,而验证集本身在做mosaic增强和多尺度测试时,同一个样本会以不同尺寸被推理多次。解决方法是评估时要固定rect=True和imgsz=640,关闭augment模式。推荐用ultralytics的yolo val命令指定split=val,确保评估数据的确定性。
5.5 部署到边缘盒子后帧率掉到个位数
现象:Jetson Nano推理一张640×640的图像耗时200ms,完全无法实时。原因是边缘设备上直接跑FP32模型,而YOLOv8s的参数量和计算量远超边缘盒子的算力。解决方法是导出INT8量化模型,在Jetson上用TensorRT引擎推理,同时把输入分辨率降到480。课堂行为检测不需要检测远处的小手机,imgsz=480足够覆盖教室全景里前排学生的行为。量化和TensorRT导出的命令如下:
yolo export model=runs/detect/train/weights/best.pt format=engine device=0 int8=true imgsz=480这条命令会生成一个best.engine文件,后续推理直接加载这个文件。注意量化需要准备一个校准集,几十张覆盖不同光照条件的教室画面就够,不需要完整训练集。
6. 从检测结果到行为判定:后处理逻辑与长期迭代技巧
YOLO输出的是“框”和“类别”,课堂行为检测系统真正需要的是“某个学生当前在干什么”。所以,最后一环的后处理逻辑才是决定系统“像不像人”的关键。
我通常建议做一个轻量级规则引擎,不引入复杂的行为识别模型。每个person框分配一个track_id,维护一个滑动窗口,例如最近10帧窗口。对每个窗口内该人的检测结果做统计:如果phone类别出现的帧数超过6帧,就判定为“使用手机”;如果hand类别出现且其框中心位于人框的上半区域,判定为“举手”。这个规则能避免单帧误检造成的抖动,又比训练一个LSTM序列分类器简单得多。
为了让行为判定更稳定,还需要一个空间去抖步骤。教室里的学生不是静止的,身体晃动会导致人框和物框的IoU波动。具体做时,我会把同一个人连续帧的人框中心坐标做一次EMA平滑,alpha设为0.3,这样框的位置不会跳变,行为判定也就稳了。另一个小技巧是给每个行为状态加一个“持续时间”阈值,例如必须连续3秒判定为“使用手机”,才真正向监控端发送告警。这能让系统避开学生拿手机看时间这类瞬时动作,大幅减少无效告警。
长期迭代方面,建议每月从部署现场抽100张典型误检图回补训练集。重点挑两类:一类是模型高置信度但实际是误检的图,一类是模型漏检的图。回补时不需要全量重训,只需要用这些新数据做30到50轮的fine-tune,学习率设为正常训练的十分之一。
最后说一个我自己的习惯:每次训练完模型后,不要只存best.pt,把最后一次的权重和训练时用的args.yaml也保留一份。这样三个月后模型效果下降时,能根据当时的超参配置和训练日志,判断是数据分布漂移还是模型性能退化。这个习惯在课堂行为检测这种长期运行的项目里,比多调两个参数值钱得多。希望帮到你。
本文还有配套的精品资源,点击获取