基于深度学习的课堂专注度监测与作弊识别系统(Python实现)
发布时间:2026/9/26 14:05:35来源:尧图网络
简介面向智慧教室与本科毕业设计场景的课堂行为分析系统基于深度学习实现专注度监测与作弊行为识别双重功能。系统整合面部特征分析、微表情解析与身份认证并结合关键骨骼点轨迹识别头部偏转、视线俯角、物品传递三类异常行为为教学评估提供量化工具。资源包共752个文件、约87.35MB内含382个Python源文件、41个Markdown说明文档、32个YAML配置文件、25张JPG图片、21个GIF演示动画等类型覆盖模型训练、推理部署、文档说明与效果展示。Python脚本覆盖训练、推理及工具封装Markdown与YAML用于使用说明和参数调整JPG/GIF直观呈现演示效果另有C/CUDA辅助代码和模型权重文件构成完整可运行工程。核心组件包括ResNet面部特征提取模型、dlib 68点轮廓定位器及基于随机森林的行为分类器附带setup.py环境配置脚本和demo_inference.py推理演示程序便于二次开发与部署。已有82人学习适合具备一定深度学习基础、需要完整毕业设计项目或智慧教室方案参考的开发者。1. 这个系统到底在检测什么专注度和作弊不是同一个任务基于深度学习的课堂专注度监测与作弊行为识别系统用 Python 实现看起来是一个“智能课堂”项目但真正拆开后会吓你一跳专注度监测和作弊识别是两条完全不同的技术链路。专注度本质上是“对一个人的状态做连续估计”靠面部关键点、头部姿态、闭眼打哈欠这些信号综合打分作弊识别本质上是“在画面里找异常物品和异常行为”靠目标检测和规则判断。前者是一个回归/分类问题后者是一个检测/事件问题。如果一上来就想用一个模型包办全部大概率会在数据标注和实机部署时反复翻车。这篇文章面向正在做课设、毕设或课堂 AI 产品原型的开发者我会按“方案选型 → 数据准备 → 专注度模块 → 作弊识别模块 → 避坑 → 端到端验证”的顺序讲清楚保证每一步都能照着敲进你的 Python 工程里。2. 技术栈拆解与数据准备先定方案再碰代码2.1 专注度监测为什么选面部关键点而不是动作识别课堂专注度没有公开标准不同学校、不同老师对“走神”的定义都不一样。常见做法是把专注度拆成可测量的身体信号头部是否长时间低垂、脸是否长时间朝向侧面、眼睛是否闭合、是否频繁打哈欠。这四个信号都集中在面部因此最稳妥的技术路径是“人脸检测 面部关键点 姿态估计”。你可能会问为什么不用 3DCNN 或者 Transformer 做视频行为识别课堂场景下一个摄像头要覆盖 3 到 5 排学生每个人在 1080p 画面里只有 80×80 到 150×150 像素视频级模型根本看不清细粒度动作而且需要大量标注好的课堂视频数据普通团队根本吃不下。相反MediaPipe FaceMesh 能从单帧直接输出 468 个面部关键点配合 solvePnP 就能算出头部姿态角轻量且成熟CPU 上也能跑到实时。2.2 作弊识别为什么选目标检测而不是视频分类作弊行为很难直接定义成“类别”。考试作弊的典型表现包括桌上出现手机、手里拿着小抄、长时间低头看桌面、频繁侧头看旁边。这些行为里“手机”和“小抄”是具体物体适合用目标检测框出来而“长时间低头”和“频繁侧头”是时序行为可以用专注度模块的姿态角数据做二次判断。如果选择视频分类网络输入是连续帧的堆叠模型要自己学会区分“正常低头写字”和“作弊低头看手机”这对数据量和样本多样性要求极高且解释性很差老师完全不知道模型为什么报警。更怕的是视频分类网络会把整节课的摄像头画面当成一个整体多人同时出现时无法定位到具体学生。因此业界和课设里最常用的方案是“YOLO 系列做目标检测找到手机、书本、人脸再用规则引擎判断是否构成作弊事件”。2.3 数据采集摄像头装哪、画面怎么做才算是能用的数据摄像头安装位置直接决定后面所有模型的上限。我在教室部署时一般会把摄像头装在前黑板正上方离地 3 米左右向下俯视 15 到 25 度。这样既能拍全 3 到 5 排学生又不会被前排同学完全遮挡后排人脸。采集帧率不用太高15fps 足够因为专注度和作弊行为都是秒级事件30fps 只会浪费存储和算力。分辨率建议不低于 1080p。采集视频时尽量覆盖不同教室、不同时间段、不同天气的光线最好包含上午、下午、阴天、拉窗帘等场景。否则模型很容易在下午三点到四点这段时间玄学式掉点因为侧窗阳光照在人脸上关键点会漂移。2.4 标注与增强两类标签体系不能混在一起专注度数据和处理作弊的数据必须分开标注。专注度数据按“时间段 学生”打帧级状态标签比如某学生从第 10 秒到第 20 秒是“低头”第 20 秒到第 30 秒是“闭眼”。标注工具可以用 CVAT导出格式用 CSV 或 JSON。作弊识别数据按目标检测格式标注常见工具是 LabelImg 或 X-AnyLabeling导出 YOLO 格式的 txt 文件每行是“类别 x_center y_center width height”坐标统一归一化。标注过程中最容易犯的错是把“低头”直接标成“作弊”。低头看书、写字、捡笔都是正常行为只有低头同时出现手机或小抄才算作弊。所以类别设计千万不能长成“cheating”这种笼统类而应该拆成“phone”“book”“face”“person”这样的物理对象。数据增强方面目标检测分支用 Mosaic、随机缩放、HSV 扰动专注度分支用亮度对比度调整和轻微的仿射变换模拟摄像头抖动。注意不要做翻转增强因为课堂摄像头是固定视角左右翻转会破坏位置先验。3. 用Python实现专注度监测从关键点到状态打分3.1 用MediaPipe提取面部关键点并计算头部姿态专注度模块的第一步是拿到每一帧的人脸关键点。我会用 MediaPipe FaceMesh它能把人脸对齐到 468 个关键点其中鼻尖、左右眼角、嘴角、下巴这几个点在世界坐标系中的位置相对稳定可以用来 solvePnP 求头部姿态。import cv2 import mediapipe as mp import numpy as np mp_face_mesh mp.solutions.face_mesh face_mesh mp_face_mesh.FaceMesh( static_image_modeFalse, max_num_faces8, refine_landmarksTrue, min_detection_confidence0.5, min_tracking_confidence0.5 ) def get_head_pose(landmarks, img_w, img_h): # 用 6 个关键点在 3D 空间的坐标来解算姿态 ref_idx [1, 199, 33, 263, 61, 291] # 鼻尖、下巴、左眼外角、右眼外角、左嘴角、右嘴角 image_points np.array( [[landmarks[i].x * img_w, landmarks[i].y * img_h] for i in ref_idx], dtypenp.float32 ) # 6 个点对应的 3D 坐标单位是毫米来自通用头部模型 model_points np.array([ (0.0, 0.0, 0.0), (0.0, -63.6, -12.5), (-43.3, 32.7, -26.0), (43.3, 32.7, -26.0), (-28.9, -28.9, -24.1), (28.9, -28.9, -24.1) ], dtypenp.float32) focal_length img_w cam_matrix np.array( [[focal_length, 0, img_w / 2], [0, focal_length, img_h / 2], [0, 0, 1]], dtypenp.float64 ) dist_coeffs np.zeros((4, 1)) ok, rvec, tvec cv2.solvePnP( model_points, image_points, cam_matrix, dist_coeffs ) if not ok: return None rot_mat, _ cv2.Rodrigues(rvec) # 欧拉角顺序是 pitch, yaw, roll pitch np.degrees(np.arctan2(rot_mat[1, 2], np.sqrt(rot_mat[0, 2]**2 rot_mat[2, 2]**2))) yaw np.degrees(np.arctan2(rot_mat[0, 2], rot_mat[2, 2])) roll np.degrees(np.arctan2(rot_mat[0, 1], rot_mat[1, 1])) return pitch, yaw, roll这段代码里max_num_faces8是因为一个教室画面通常会出现 5 到 8 名学生设太小会漏人设太大 MediaPipe 的内存占用会明显上升。min_detection_confidence是检测置信度阈值建议不要低于 0.5否则远处小尺寸人脸会被忽略。get_head_pose内部用 solvePnP 做的是“由 2D 关键点和 3D 模型求相机外参”的过程。焦距直接取图像宽度是一种近似在课堂场景够用如果你发现姿态角系统性偏大可以用教室内标定板做一次相机校准。3.2 状态判定EAR、MAR和姿态角如何加权有了头部姿态角还要加上眼睛和嘴部的状态。EAR眼睛纵横比用来判断闭眼MAR嘴部纵横比用来判断打哈欠。MediaPipe FaceMesh 里左眼关键点是 362、385、387、386、263、373嘴部关键点用 61、39、0、269 等。计算方式很简单。def compute_ear(landmarks, img_h): # 左眼关键点 p1 np.array([landmarks[362].x, landmarks[362].y]) * img_h p2 np.array([landmarks[385].x, landmarks[385].y]) * img_h p3 np.array([landmarks[387].x, landmarks[387].y]) * img_h p4 np.array([landmarks[263].x, landmarks[263].y]) * img_h p5 np.array([landmarks[373].x, landmarks[373].y]) * img_h p6 np.array([landmarks[380].x, landmarks[380].y]) * img_h ear (np.linalg.norm(p2 - p6) np.linalg.norm(p3 - p5)) / (2.0 * np.linalg.norm(p1 - p4)) return ear def compute_state(landmarks, img_h, pitch, yaw): ear compute_ear(landmarks, img_h) # 嘴部开合程度也类似 EAR 的计算 m1 np.array([landmarks[61].x, landmarks[61].y]) * img_h m2 np.array([landmarks[39].x, landmarks[39].y]) * img_h m3 np.array([landmarks[0].x, landmarks[0].y]) * img_h m4 np.array([landmarks[269].x, landmarks[269].y]) * img_h mar np.linalg.norm(m1 - m3) / max(np.linalg.norm(m2 - m4), 1e-6) state normal if pitch 20 or pitch -15: # 低头或过度抬头 state head_down elif abs(yaw) 30: # 明显侧脸 state side_face elif ear 0.2: # 眼睛闭合 state eye_close elif mar 0.7: # 嘴部张大打哈欠 state yawn return state, ear, mar这里的阈值是我在一间 60 人教室、摄像头距第一排 4 米环境下反复调出来的经验值。注意compute_state的优先级很重要如果同时低头和闭眼我会优先判“低头”因为闭眼可能只是眨眼的一瞬间。实际使用时不要对单帧直接判定一定用滑动窗口把最近 10 到 15 帧的状态统计占比占比超过 0.6 才真正切换状态这样可以过滤大量噪声。3.3 阈值怎么调不同教室距离下的建议参数同一个阈值在前后排会差很多。后排学生人脸只有 60 像素宽MediaPipe 关键点抖动更明显姿态角计算值会比实际偏大 3 到 5 度。所以参数必须按画面里的“人脸像素宽度”来分档。表里给出参考值实测时以视频回放为准。场景人脸宽度(px)pitch阈值(度)yaw阈值(度)EAR阈值MAR阈值窗口帧数第一排/单人镜头120 以上18250.200.6010中排80-12020300.200.6512后排/全景60-8025350.220.7015实际调参时我会把专注度打分函数单独跑一遍测试视频输出每位学生每帧的 pitch、yaw、EAR、MAR存成 CSV然后人工看 2 分钟视频对着 CSV 修正阈值。这是最笨但最有效的方法网上说用贝叶斯调参自动找阈值我只在数据量大的时候用小样本下效果和手调差不多。4. 用Python实现作弊行为识别YOLOv8训练与推理4.1 准备自定义数据集类别定义与yaml配置作弊识别需要检测的对象是“手机”“书本”“人脸”。这里有个容易踩的坑人脸检测其实已经由 MediaPipe 做了但 YOLO 里也要标人脸因为后面要用 YOLO 的人脸框与专注度模块的人脸框做匹配从而把手机、小抄归属到具体学生。类别定义不要试图定义“作弊”这个抽象类只定义物理对象。# cheating_dataset.yaml path: ../dataset train: images/train val: images/val names: 0: phone 1: book 2: face每张标注里可能同时包含多个类别。常见的注记错误是把“手里拿书”的 book 框得太大包含整个手臂这样会导致模型学到的“book”带有人手先验误检率上升。正确方式是紧贴书本边缘画框。如果是纯文字代码注意path路径里不要有中文和空格YOLO 的 dataloader 在 Windows 下遇到中文路径会莫名其妙报错。4.2 完整训练流程预训练权重、超参数与训练命令训练推荐使用 YOLOv8 官方 CLI不需要自己写训练循环。我一般从yolov8n.pt或yolov8s.pt开始因为它们能在一个消费级 GPU 上快速跑完适合做课堂视频的实时推理。训练命令如下yolo train datacheating_dataset.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0 patience20如果你只有 16G 显存batch16会有点紧可以降到 8。imgsz640是速度和精度的平衡点但课堂里的手机通常只有 20×30 像素的小框如果发现漏检严重把imgsz加到 960 或 1280 代价很大但收益不是线性的得先试 640。patience20是连续 20 个 epoch 验证集 mAP 没上升就早停防止过拟合。训练完成后验证集效果最好的是runs/detect/train/weights/best.pt不要用last.pt。看训练日志时重点看mAP50课堂场景中手机小目标多mAP50-95往往很难看但只要mAP50能到 0.8实际效果已经可以用了。4.3 推理与后处理检测框如何与专注度模块配合YOLO 推理结果是一堆检测框它们本身不构成“作弊事件”。作弊事件至少是“某个人脸框附近存在手机框且持续了 N 帧”所以推理层要做两件事把同一学生的边界框做分组然后做时间维度上的累计。from ultralytics import YOLO yolo_model YOLO(runs/detect/train/weights/best.pt) def chect_detection(frame, conf0.45): results yolo_model(frame, confconf, verboseFalse)[0] boxes [] for box in results.boxes: cls_id int(box.cls[0]) xyxy box.xyxy[0].tolist() conf float(box.conf[0]) boxes.append({cls: cls_id, box: xyxy, conf: conf}) return boxes # 使用示例把 phone/book 检测框和人脸检测框建立包含关系 # 如果手机框的中心落在某个人脸框扩展 1.5 倍后的矩形里记为该学生的疑似作弊对象这里的conf0.45是置信度阈值。课堂摄像头距离远手机置信度本来就偏低设太低会混入大量黑板反光设太高会漏检。我建议先拿到一段 5 分钟视频输出所有框的置信度分布把阈值卡在分布图的“低谷”处通常会在 0.4 到 0.5 之间。这个值不是大数据玄学完全是看你教室的摄像头画质。5. 避坑课堂场景下模型翻车的五个常见问题5.1 检测框和姿态角跳来跳去现象明明学生没有动M 脸的 pitch、yaw 却在 ±15 度内震荡YOLO 的人脸框也前后两帧对不上。原因单帧检测的回归模型本身就存在抖动MediaPipe 对 80 像素以下的人脸关键点不稳定。解决加卡尔曼滤波或最简单的指数滑动平均对姿态角和检测框中心做平滑。另外专注度状态判定一定要用时间窗口累计不要使用单帧阈值。5.2 手机小目标漏检现象画面里手机赫然可见模型就是没输出框。原因原始 1080p 画面缩放到 640手机目标只剩几个像素特征全丢了。解决先把推理分辨率提高或者在画面里按区域切片检测专业一点的做法是使用 SAHISlicing Aided Hyper Inference。但切片推理很费 GPU我通常先试试imgsz960如果还不行就把摄像头画面从 1080p 改成 4K 再采集数据。5.3 光线变化导致专注度分数崩盘现象下午三点后教室拉窗帘学生低头写字系统开始狂报“闭眼”。原因人脸检测框在暗光下更小MediaPipe 关键点跳变EAR 计算出的值被放大。解决在进入专注度模块前先做直方图均衡化增强暗部细节同时在训练数据增强阶段加入亮度对比度随机扰动。注意增强幅度不要夸张否则正常光线下的数据也会被带偏。5.4 实时推流帧率上不去现象推理线程一旦跑起来显示实时画面只有 5 到 8 帧。原因YOLO 推理 MediaPipe 处理 视频解码都在一个线程里同步执行。解决三个步骤分别开独立线程视频采集线程负责取帧推理线程用队列接帧。或者隔帧推理每 2 帧做一次专注度和作弊推理中间帧直接复用上一帧结果结合卡尔曼平滑视觉上几乎没有延迟。5.5 把低头写作业误判为作弊现象一个学生整节课都在低头做题系统频繁给“作弊”告警。原因专注度模块的“低头”信号碰上了附近桌面上摊开的书本规则引擎直接判定为“低头 书本 抄书作弊”。解决作弊判断必须同时满足两个条件低头要超过 5 秒且手机或小型书本框出现在人脸框下方 30% 的区域。正常桌面上的书本框往往很大应该用“手机检测框”作为硬性条件书本只作为参考线索。6. 端到端验证与部署技巧让两个模块协同输出告警专注度和作弊识别不是两个独立进程而是同一个推理管道里的并行分支。端到端验证时我会先录制一段 5 分钟真实课堂视频包含正常听课、低头写字、玩手机三个典型场景然后离线逐帧跑一遍完整管道统计两个指标专注度状态切换的准确率以及作弊告警的精确率和召回率。一个可用的告警触发策略是作弊行为必须连续出现 5 帧以上且置信度大于 0.45才触发一次告警。课堂环境噪声很多单帧的误检无法避免时间窗口会让你少跑现场很多冤枉路。我部署时会把整条推理管道封装成一个process_frame(img) - states, events函数用 FastAPI 暴露给前端展示。上线前还要做一次“假阳性压力测试”让一个学生全程正常看书、写字、喝水看看系统会不会产生 5 帧以上的作弊告警这一关过了再进真实教室。部署层面如果预算只够买一块 RTX 3060那推理分辨率用 640套餐用 yolov8n 和 MediaPipe 是足够的如果教室长达 50 人三路摄像头同时进就必须考虑 TensorRT 把 YOLO 模型加速到 1ms并借助多线程把三路视频的帧分派到不同硬件流。我一般会把告警阈值和模型权重都放到单独的 JSON 文件里方便现场运维直接改阈值不用重新烧代码。这个习惯让我少开了两次长途。最后再说一句课堂系统最怕的不是模型不准而是误报太多消耗老师的信任所以宁可漏报一半也别乱报一次。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网