简介这是一套基于深度学习与Python开发的课堂专注度行为识别系统面向高校人工智能、计算机、电子信息等专业学生及毕业设计开发者可用于课堂场景中学生抬头、低头、专注状态等行为的自动识别与分析。压缩包共2000个文件主要涵盖Python源码、Java与Vue构成的界面工程、C/C与HPP底层模块、模型权重及ONNX/TRT推理相关文件同时包括XML、JSON、YAML等配置描述和Markdown说明文档整体约114.36MB目录结构便于按模块查阅。项目说明与设计文档齐全经过测试可正常运行既适合直接作为课程设计、毕业设计或项目演示也适合在此基础上调整功能、扩展其他课堂行为分析场景。当前已有78人学习下载对需要快速上手深度学习项目或完成毕设的同学有一定参考价值。1. 课堂专注度行为识别一套深度学习Python方案能解决什么问题很多做教育信息化的人第一次看到“基于深度学习python开发的课堂专注度行为识别系统”这个标题时以为就是把摄像头画面喂给一个神经网络输出“专注”或“不专注”。实际做下来你会发现真正的难点不在分类模型而在课堂场景本身学生坐得远、画面里人多、遮挡频繁、光照不均任何一个因素都会让单一网络直接失效。这套系统的成熟做法是两阶段先做人体的姿态估计让每个学生变成一组骨架关键点再对骨架序列做时序行为分类判断专注、分心、举手、趴桌等状态最后按时间窗口聚合成整节课的专注度曲线。这篇文章想说的就是从拿到这样一个项目包开始环境怎么搭、数据怎么备、模型怎么训、坑在哪让新手能跟下来让做过类似方案的也能对一对边界。适合做教育类产品原型的开发者也适合需要从零搭建行为识别Demo的工程预研或课设深化。2. 为什么先提骨架再判行为架构拆解与选型逻辑2.1 两阶段方案为什么比端到端视频分类更省事先看最常见的替代路线直接用3D卷积或视频Transformer对连续帧做端到端分类。这条路线听起来很省事录好视频打上标签就能训但它有三个问题在课堂场景里是致命的。第一它需要海量的标注视频一个类别至少几千个样本才能体现优势而课堂视频数据往往只有几节课、几十节课的量级远远不够。第二3D卷积的输入是原始像素背景、课桌、黑板上的字全都进网络模型很容易学到“这个教室的窗帘颜色”而不是“这个学生在做什么”换个教室就失效。第三多人场景下端到端模型很难输出“哪个学生正在做什么”只能给出整帧的一个类别对课堂这种需要逐人统计的场景基本不可用。两阶段方案姿态估计时序分类把这些问题逐个拆掉了。第一阶段只做关键点回归输出的是一组人体关节点坐标比如头顶、肩膀、手肘、手腕。第二阶段输入的已经不是像素而是骨架序列模型学的全是动作的几何形态。这样背景信息被彻底丢弃换教室、换摄像头位置都不影响骨架的语义每个学生的骨架是独立抽取的所以第二阶段的分类天然就是逐人的想统计全班每个学生的状态只需要在第二阶段对每个人的骨架序列分别过一遍模型。从工程角度看这个路径还有一个隐性优势两段可以分开调优。姿态估计不准时单独换一个更强的模型就行行为分类不准时只调时序模型不用整条链路重训。这对课堂这种长尾场景非常友好。我自己在实际项目里比较过同样的数据量端到端方案在训练集上能到92%换一个教室录的验证集直接掉到70%以下两阶段方案即便姿态估计用的是轻量模型跨教室验证集也能稳定在85%左右。这个差距来自特征本身——骨架是抽象过的高层表征天然屏蔽了场景噪声。做课堂专注度这类任务我一般都会先选两阶段路线除非你有上万小时的同一机位数据才值得回头考虑端到端。2.2 单帧姿态估计怎么选轻量库、高精度库与课堂场景的匹配姿态估计这个阶段业界可选的开源方案大概分成三档。第一档是以MediaPipe为代表的轻量级方案靠CPU就能跑单帧推理在几毫秒到十几毫秒量级检测的是全身33个关键点精度在近距离大目标上表现不错远距离小目标会掉点。第二档是以OpenPose为代表的经典多人姿态估计方案精度和多人鲁棒性都更好但模型体积大、计算量高单帧在GPU上也要几十毫秒起步部署成本明显上了一个台阶。第三档是HRNet这类以精度为目标的骨干网络效果最好但训练和推理成本都最高适合实验室做基准不适合直接塞进一个要实时出结果的课堂系统。课堂场景的选型逻辑其实很清晰摄像头固定在教室前方拍摄距离跨度大后排学生的人体区域可能只有几十像素高同时系统通常需要边录边分析推理速度决定了能不能实时生成课堂报告。两相权衡我一般建议默认选第一档轻量方案跑通全流程先把数据管道和行为分类做起来中间如果发现后排小目标关键点频繁丢失再单独把姿态估计替换成更高精度的方案或在预处理里加一步目标检测裁剪。这样做的原因是课堂系统的开发瓶颈往往在数据标注和行为分类不在姿态估计——你换一个再强的姿态模型后排几万像素的模糊目标一样难救反而不如通过预处理和跟踪逻辑来解决。选型时还要留意一个容易被忽略的点姿态估计的输出格式。不同方案的输出定义差异很大比如关键点顺序不同、是否带置信度、是否有左右手索引的区分。这些细节会在第二阶段的特征构造里引入大量对齐工作。别等代码写到一半才发现两个库的关键点索引对不上回头改特征工程比换模型还要费时间。2.3 专注度状态怎么定义从骨架坐标到可学习特征有了骨架关键点下一步就是把“专注”这类抽象概念变成可计算的数值。如果你直接拿33个关键点的原始坐标丢给时序模型模型当然能学但收敛慢而且对摄像头位置、画面尺寸极度敏感。我一般会先做一版手工特征把定义明确的行为信号提出来头部俯仰角低头还是抬头、头部偏转角左顾右盼、手部移动速度频繁小动作、手腕与桌面的相对距离举手、趴桌。这些特征本身就有很强的语义时序模型只要在它们上面学组合模式就会少走弯路。下面是一个从单帧骨架坐标构造特征的典型代码片段以常见轻量姿态估计库输出的33点格式为例import numpy as np def build_single_frame_feature(landmarks, frame_w, frame_h): # landmarks: 按库输出顺序排列的33个关键点每个点是 x, y, z, visibility def pt(i): return np.array([ landmarks[i].x * frame_w, landmarks[i].y * frame_h ]) nose pt(0) left_shoulder pt(11) right_shoulder pt(12) left_eye pt(5) right_eye pt(6) left_wrist pt(15) right_wrist pt(16) shoulder_mid (left_shoulder right_shoulder) / 2 eye_mid (left_eye right_eye) / 2 # 头部俯仰角眼睛中点相对肩部中点的纵向偏移 pitch np.arctan2(eye_mid[1] - shoulder_mid[1], eye_mid[0] - shoulder_mid[0] 1e-6) # 头部偏转角双眼连线与水平线的夹角转头时明显变化 yaw np.arctan2(left_eye[1] - right_eye[1], left_eye[0] - right_eye[0] 1e-6) # 手部活跃度双手相对肩部的距离波动需跨帧计算 hand_dist (np.linalg.norm(left_wrist - shoulder_mid) np.linalg.norm(right_wrist - shoulder_mid)) / 2 return np.array([pitch, yaw, hand_dist], dtypenp.float32)这段代码有几个地方值得说明。pitch和yaw是用角度而非原始坐标目的就是去掉画面尺寸的影响让特征在讲台近景和后排远景下量纲一致。hand_dist单帧看只是手的绝对位置但连续30帧拼接后它的方差就能反映“手在频繁动还是安静放着”这个统计量对分心检测特别有效。实际使用时我还会把连续两帧之间的关键点位移量也拼进特征作为速度维度因为很多课堂小动作转笔、玩手机在位置特征上不明显但在速度特征上峰值非常突出。特征维度控制在6到10维就够了不需要把33个点全塞进去维度太多反而会让时序模型懒得去学结构化模式。3. 最小可运行环境与数据准备把课堂视频喂给模型前要做的事3.1 创建虚拟环境与依赖安装顺序先PyTorch还是先装姿态估计库拿到项目包之后最忌讳的是直接用全局Python环境跑课堂项目依赖的深度学习框架、视觉库、数值计算库之间经常存在版本咬合问题。我通常的习惯是新建一个独立虚拟环境再按固定顺序装依赖顺序错了后面会花大量时间解决冲突。推荐先建虚拟环境然后先装深度学习框架再装计算视觉和姿态估计相关库最后补装数据处理工具。原因在于姿态估计库的底层依赖了特定版本的numpy和opencv如果先把视觉库装好回头再装深度学习框架很可能把numpy版本直接顶掉导致视觉库间接损坏。python -m venv class_env source class_env/bin/activate # Windows 下用 class_env\Scripts\activate pip install --upgrade pip # 先装深度学习框架CPU 版本优先用来验证流程 pip install torch torchvision # 再装姿态估计与视觉处理库 pip install opencv-python mediapipe # 最后装数据处理与训练辅助库 pip install numpy pandas matplotlib scikit-learn这里有几个参数层面的注意点。torch默认会装最新的稳定版本如果显卡驱动较老建议安装时明确指定CUDA版本对应的安装源否则会出现“安装成功但import报错”的尴尬情况如果你手头只有CPU机器装CPU版就够了姿态估计跑得动训练只是慢一点不影响验证流程。mediapipe装完后建议立刻跑一个import mediapipe as mp打包好的项目有时会带旧版环境配置你不主动验证就不知道冲突在哪个库上。第一个Demo跑通之前别急着装高精度姿态估计库先把最小链路打通比什么都重要。3.2 自制课堂数据集抽帧、标注格式与防泄漏划分课堂行为识别的通用公开数据集很少场景和国内教室差异也大所以多数项目最终都要自己录一段课来标注。录视频的时候注意三点分辨率至少720p帧率24或30即可摄像头尽量放在教室前上方正对人群录完按每节课存一个文件别一录就是几小时的大文件不好管理。视频拿到后第一步不是标注而是抽帧。抽帧的目的不是减少工作量而是降低相邻帧的冗余度让标注出来的样本更有信息量。import cv2 import os def extract_frames(video_path, out_dir, sample_fps5): os.makedirs(out_dir, exist_okTrue) cap cv2.VideoCapture(video_path) # 读取原始帧率决定隔几帧抽一帧 original_fps cap.get(cv2.CAP_PROP_FPS) skip max(1, int(round(original_fps / sample_fps))) frame_idx 0 saved_idx 0 while True: ret, frame cap.read() if not ret: break if frame_idx % skip 0: cv2.imwrite(os.path.join(out_dir, f{saved_idx:05d}.jpg), frame) saved_idx 1 frame_idx 1 cap.release() print(fsaved {saved_idx} frames from {video_path}) # 用法示例 extract_frames(lecture_01.mp4, ./data/frames/lecture_01, sample_fps5)抽帧率sample_fps5是我验证过比较稳的起点5帧每秒能覆盖一个抬手、转头的完整过程又不会让相邻帧几乎一样。如果你想保留更多运动细节可以调到8到10帧每秒但标注量会跟着涨。标注格式我推荐最简单的CSV或者JSON每一行是一个人、一个帧号、一个动作标签标签建议用数字编码训练时少一层转换。标注工具用常见的开源标注工具导出即可格式再自己转成下面的样子person_id, frame_id, state(0专注,1分心,2举手,3趴桌), landmark_file。注意每个学生的关键点结果要单独存文件或者直接在标注时统一跑一遍姿态估计预处理存成骨架序列文件后面的训练阶段就不用再回头读原始视频了。数据划分是课堂项目里最容易翻车的环节。很多项目随手按文件列表随机划分训练集和验证集结果同一节课的帧同时出现在两边模型相当于提前看到了答案。正确的做法是按“课”维度划分训练集用前几节课验证集用完全没参与训练的另一节课这样才能测出模型换课堂的真实表现。划分代码很简单但这一条不遵守后面所有指标都是虚高的。3.3 课堂场景数据增强亮度、遮挡、模拟远景三种扰动课堂数据的多样性主要来自三个方向不同时段的自然光照变化、学生举手或转头时产生的短暂自遮挡、以及座位远近带来的尺度差异。数据增强就应该针对这三类扰动来做而不是照搬常规图像分类的那套随机裁剪和翻转。随机裁剪在课堂场景要慎用因为裁剪掉头部区域会让姿态估计直接失败产生的反而是坏样本。我一般在数据管线里做三类增强。亮度扰动用随机gamma变换模拟上午、下午不同光照范围取[0.7, 1.3]太大画面就失真了。关键点噪声给标注的骨架坐标加小幅度高斯噪声模拟姿态估计在模糊帧上的抖动幅度控制在关键点归一化坐标的±0.02以内。局部遮挡模拟随机把画面中某个同学的区域盖住几帧逼模型学会在短暂遮挡下也能维持状态判断这个对课堂场景特别实用因为一个学生低头系鞋带的瞬间他旁边同学很容易被挡住。import numpy as np def augment_landmarks(landmarks, noise_scale0.02, dropout_prob0.1): # 输入: 单帧关键点坐标序列 shape(33, 2) # 输出: 增强后的坐标模拟姿态估计抖动与局部关键点丢失 aug landmarks.copy() # 高斯噪声模拟轻量姿态估计在运动模糊下的抖动 aug np.random.normal(0, noise_scale, sizeaug.shape) # 随机丢弃部分关键点模拟自遮挡 dropout_mask np.random.rand(33) dropout_prob aug[dropout_mask] 0.0 return aug这段增强逻辑里的两个参数值得细调。noise_scale如果设得太大骨架会变得扭曲时序模型学到的是“抖动分心”这种错误关联设得太小又起不到正则化作用。我一般先在验证集上试几个值观察loss曲线选择让验证loss最平稳的那个。dropout_prob也别超过0.2关键点大面积丢失后特征本身就不可信靠增强硬造这种样本只会增加训练难度。数据增强不是越多越好对课堂这个场景保住头部和手部关键点是底线。4. 训练专注度分类模型序列拼法、损失函数与超参设置4.1 滑窗构造序列样本序列长度30帧的依据行为识别和图像分类最大的不同在于模型需要看到一段变化过程才能做出判断。你给她一张静止帧她低头看手机和低头看书在视觉上几乎无法区分但连续看3秒手的动作模式完全不同。所以训练样本不能是单帧而是一段固定长度的骨架序列。我一般用滑窗把长序列切成固定长度的短序列窗口长度选30帧配合前面5帧每秒的抽帧率刚好覆盖6秒的上下文。这个长度是经过对比得出的短于15帧时模型分不清“临时低头”和“持续趴桌”长于60帧时样本数量锐减而且一个窗口里可能包含两三个动作切换反而把标签搞混。30帧是一个折中点既能捕捉完整动作又能保证一个窗口里大概率只有一个主要状态。def build_sequences(person_feats, seq_len30, stride10): # person_feats: 某个学生按时间排列的特征序列shape(总帧数, 特征维度) # 返回: 滑窗后的样本序列与对齐标签 sequences, labels [], [] for person in person_feats: feats person[features] label_seq person[labels] n len(feats) if n seq_len: continue for start in range(0, n - seq_len 1, stride): sequences.append(feats[start:start seq_len]) # 标签取窗口内出现次数最多的类别 window_labels label_seq[start:start seq_len] labels.append(max(set(window_labels), keywindow_labels.count)) return np.array(sequences), np.array(labels)这里有两个参数需要理解透。stride10决定了相邻窗口之间的重叠度重叠越大训练样本越多但样本之间相关性也越高容易造成过拟合课堂数据本来就少我一般保留较大的重叠来扩充样本量。窗口标签用投票而非取首帧或尾帧是因为一个窗口里动作可能有小幅波动多数投票能让标签更贴近窗口的主体状态。如果你发现模型对某个类别特别迟钝可以先检查一下是不是窗口标签的投票策略把少数派样本全归到了多数类。4.2 样本不均衡怎么办加权损失的表现与边界课堂数据的天然后果是类别严重不均衡一整节课里“专注”可能占70%“举手”只占百分之几。如果直接拿原始分布去训模型的最优策略是“永远预测专注”然后整体准确率还很好看。这也是很多项目报告里准确率90%以上、现场一测就露馅的根源。我建议优先尝试加权交叉熵给少数类更高的损失权重。权重可以按训练集类别频率的倒数做归一化再乘一个缩放系数。import torch import torch.nn as nn # 按训练集统计出的类别占比: 专注0.68, 分心0.17, 举手0.06, 趴桌0.09 class_freq torch.tensor([0.68, 0.17, 0.06, 0.09]) class_weights 1.0 / class_freq class_weights class_weights / class_weights.mean() criterion nn.CrossEntropyLoss(weightclass_weights)这里面最需要调的就是class_weights的缩放。权重比拉得太大少数类在训练集上被强行记住换到新课堂反而误报激增太接近1.0又起不到纠偏作用。我的经验是权重均值控制在1.5到3.0之间先跑10个epoch看验证集上少数类的召回率是否在逐步上升如果没有再整体放大权重而不是单独放大某一个类。加权损失解决不了的问题下一步再用Focal Loss或对少数类做时间轴上的过采样但那是后话别一开始就上复杂方案。4.3 训练曲线怎么看过拟合信号与早停参数时序模型比图像模型更容易过拟合因为样本量小、特征维度低模型记忆训练序列的速度非常快。判断过拟合不要只看验证准确率要看“训练损失持续下降、验证损失在某一步开始反弹”的剪刀差。课堂项目里训练损失降到很低而验证损失回升通常发生在第20到第30个epoch之间早停就是抓这个转折点。下面是一组我常用的训练配置适合小数据集和轻量时序模型参数推荐值说明序列长度30帧约6秒上下文batch size64样本量小时别太大影响收敛稳定性学习率1e-3配合Adam优化器weight decay5e-4防过拟合的关键项early stopping patience10个epoch验证损失连续10轮不降就停学习率衰减每10轮乘0.5降到最后收敛会更稳训练过程中我会额外关注每一类的召回率而不是全局准确率。课堂系统的可用性由少数类决定举手没检测到老师复盘时就发现整节课没人互动趴桌没检测到课堂管理就失去了意义。建议每两个epoch打印一次混淆矩阵重点看“分心”被预测成“专注”的比例那才是真实的漏报率。如果验证集上少数类召回率迟迟不动先别急着堆模型复杂度回到数据层面确认一下是不是标注本身就有歧义——比如“发呆看窗外”有人标分心、有人标专注模型学到的边界自然也是乱的。5. 课堂场景五大翻车点从现象到原因的排查清单5.1 现象后排小目标的关键点频繁丢失骨架断成几截教室后排学生的人体区域在画面中往往只有几十像素高轻量姿态估计库在这种目标上的检测置信度断崖式下降表现就是后排学生的骨架时有时无行为分类完全失去依据。原因有两个层面一是目标像素太少关键点回归的输入信息不足二是课堂背景复杂后排常贴着窗帘、黑板、标语干扰了检测器的注意力。解决这个问题的常见做法是增加一个目标检测前置步骤。先做一次行人检测把每个学生框出来再把框裁剪放大后送进姿态估计相当于给后排学生单独开了一个“特写镜头”。代价是每帧多一次检测的算力开销但对课堂这种固定机位场景非常值得。另一个工程技巧是提高姿态估计的输入分辨率很多轻量方案默认输入是256或320课堂场景可以调到640后排关键点完整率能有明显提升代价是推理耗时会增加一到两倍。预算有限的话先做检测裁剪再调输入分辨率两个手段叠加效果最好。5.2 现象两个学生座位相邻骨架突然串到另一个人身上遮挡是课堂里绕不开的问题。一个学生伸手拿东西胳膊正好挡住旁边同学的肩膀姿态估计的输出就会在两个ID之间跳跃要么同一个人的骨架突然多了一只手要么两个人的轨迹直接互换。这类问题单靠调姿态估计参数解决不了因为遮挡造成的视觉歧义对任何单帧算法都是难题。解决思路要从时序上找。我的做法是在姿态估计后面加一个人体跟踪ID的平滑模块核心是“上一帧的每个人在当前帧先找最接近的框距离在阈值内就延续ID超阈值才认为是新的人”。这个逻辑不复杂但要写对否则会出现ID频繁切换这个更麻烦的新问题。还有一个补救手段行为分类只输出“当前状态”不做跨帧的身份绑定最终课堂统计时用跟踪ID把骨架串成轨迹轨迹中间丢掉的部分用前后状态插值补齐。这样即使单帧偶尔串人时序统计层面也不会放大错误。5.3 现象训练集里只有正面视角换到教室侧面的摄像头指标暴跌课堂摄像头的位置不是统一的有的在正前方有的在侧面墙角。姿态估计对视角有一定鲁棒性但行为分类模型完全是数据驱动的——训练集里全是正面视角的“转头”模型学到的特征偏向于左右偏转一旦换成侧面视角同一个动作在骨架坐标上的模式完全变了模型直接失效。这是典型的训练分布和推理分布不一致。解决要从数据准备阶段下手。录数据时尽量多角度采集正面、左侧、右侧至少三个视角。如果已经训好了模型才发现视角问题退而求其次的办法是在训练集里针对关键点坐标做归一化处理以肩部中点为原点、两肩距离为单位长度把骨架坐标变换到人体自身坐标系。这样不同视角下的同一动作会在归一化空间里重叠度更高模型泛化能力明显增强。但要注意这个归一化必须用在训练集和推理时保持一致不能只在一边做。5.4 现象趴桌被识别成低头专注手撑脸被识别成举手这类误判最容易发生在动作边界的过渡状态。趴桌的初始阶段和低头的确很像都是头部大幅下沉、手部前伸手撑脸和举手也共享同一条“手部抬离桌面”的轨迹。问题不在于模型笨而在于标签定义本身有重叠区。很多标注规范里只给了四个类别却没有定义“过渡帧算哪个状态”。解决这个问题的思路一是调整标注粒度和标签定义给状态加细分比如把“低头”和“趴桌”在标注时用头肩距离做区分定义阈值写进标注规范二是给模型加后处理规则预测结果先过一帧级平滑再用动作持续时间过滤比如“手撑脸”通常持续很久而“举手”一般几秒内就放下按持续时间长短做二次分类。这个后处理规则不增加训练成本还能挡住一大半边界误判。5.5 现象离线测试没问题接入实时视频流后帧率上不去离线跑视频时模型处理速度50帧每秒看起来够用接上实时摄像头后实际体验卡顿严重。问题通常不出在模型而出在数据管线上。摄像头采集、姿态估计、跟踪、行为分类、结果绘制如果全在同一个进程里串行执行任何一个环节抖动都会拖累整条链路姿态估计偶尔一次耗时翻倍后续所有帧都被阻塞。离线测试用的是本地视频文件磁盘读取是稳定的掩盖了这个问题。解决方法是把姿态估计放到独立线程单帧画面到达后如果上一帧还没处理完就丢弃当前帧而不是阻塞采集线程。课堂场景的行为识别不需要逐帧分析抽样分析完全够用牺牲一点帧率换来整体流畅度非常划算。另一个常见瓶颈是结果可视化往画面上叠加骨骼、状态标签、统计数字都要做图像绘制这个操作比想象中耗时建议画布分辨率单独调低一档只在主画面上画人框状态面板每隔半秒刷新一次不要每帧重绘全部内容。6. 从单帧预测到整节课的可视化专注度曲线与状态时间线6.1 用滑窗聚合加指数平滑生成专注度曲线模型输出的是每个学生每一帧的状态但课堂上老师和管理者真正需要的是“这节课整体专注度怎么样”“第10分钟出现了什么波动”。这需要把状态预测按时间窗口聚合成一条曲线。我的做法是每10秒为一个统计单元计算这个窗口内专注类状态占比作为该时刻的专注度再用指数平滑把相邻窗口的突变拉平过滤掉个别误判产生的毛刺。import numpy as np def smooth_curve(raw_curve, alpha0.3): # raw_curve: 按时间窗口统计的原始专注度序列 # alpha: 平滑系数越大曲线越贴近原始值越小越平滑 smoothed np.zeros_like(raw_curve, dtypefloat) smoothed[0] raw_curve[0] for i in range(1, len(raw_curve)): smoothed[i] alpha * raw_curve[i] (1 - alpha) * smoothed[i - 1] return smoothedalpha这个参数的语义要理解透它决定曲线对突发变化的响应速度。设得太小某一段真实的分心潮会被抹平设得太大个别误判又会形成假的波谷。我一般设0.2到0.4之间具体看你的时间窗口长度。曲线生成后再配上状态时间线每个学生的状态变化甘特图课堂复盘时就非常直观老师能看到“第25分钟全班专注度骤降”对应的原始记录里正好有一批学生出现小动作这个联动对课堂质量分析是最有价值的输出。6.2 在视频帧上叠加实时状态面板最终交付给使用方的往往不是一条离线曲线而是实时画面加状态标注。给每帧画面叠加学生框和状态标签能让在场的人第一时间知道系统正在做什么、判断准不准。这个界面我习惯保持极简避免干扰原始画面也避免因为绘制开销拖慢推理。用一个固定位置的状态面板展示当前全班统计比在每个人头上都画标签更清爽。import cv2 def draw_panel(frame, stats, students_info): # stats: {focus: 32.5, distract: 5.2, handup: 2} # students_info: 每个学生的框和状态标签列表 h, _ frame.shape[:2] panel_w int(frame.shape[1] * 0.22) overlay frame.copy() cv2.rectangle(overlay, (0, h - 80), (panel_w, h), (0, 0, 0), -1) frame cv2.addWeighted(overlay, 0.35, frame, 0.65, 0) cv2.putText(frame, fFocus: {stats[focus]:.1f}%, (10, h - 50), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.putText(frame, fDistract: {stats[distract]:.1f}%, (10, h - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) for info in students_info: x1, y1, x2, y2, state info color (0, 200, 0) if state focus else (0, 0, 255) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) return frame这个叠加逻辑里绘制操作放在推理线程之外面板只在画面底部固定区域更新避免大量绘制阻塞主循环。alpha0.35的叠加权重是调试出来的太透明面板看不清太实又挡住学生动作。注意stats里的百分比要在独立线程里按固定周期刷新不要每帧重算全班状态那会白白浪费算力。整套系统跑通之后你会意识到课堂专注度识别的价值不在于“测出谁在走神”而在于把模糊的课堂观察变成了可回放、可对比、可复盘的数据序列——这才是投入这个方向最值得的部分。最后说一个自己踩过的教训有一版实验验证集划分时没按“课”隔离结果同一节课的一部分帧进了训练集另一部分留在验证集指标漂亮得惊人换了一间教室立刻打回原形。从那以后凡是涉及课堂数据的项目第一件事就是锁死数据划分规则先划好验证集再碰任何模型。数据划分这种枯燥的事反而是整个系统里最不能省的一环。希望帮到你。本文还有配套的精品资源点击获取