☰
基于深度学习的课堂专注度分析与作弊检测系统:Python实现与调优指南
2026/10/2 22:01:28 网站建设 项目流程

简介:这份资源是一套基于深度学习实现的课堂专注度分析与考试作弊检测系统,包含完整Python源码与配套文档说明,面向计算机相关专业正在做毕业设计的学生,以及需要项目实战练习的学习者。项目经导师指导并通过评审,难度适中,源码均经本地编译与严格调试,可正常运行。压缩包共626个文件,约87.63MB,以383个py源码文件为核心,辅以41个md文档、32个yaml配置、25张jpg与21个gif图示,以及cfg、cu、cpp、sh等模型与脚本文件,覆盖数据配置、网络定义、训练推理与结果展示等环节。内容预览中可见yolov3、RetinaFace等模型配置与权重文件,说明系统涉及目标检测与人脸分析等技术路线。目前已有163人学习下载,适合希望快速搭建可运行系统、理解深度学习落地流程并完成毕设或课程实践的读者参考使用。

1. 课堂专注度分析与作弊检测:从一段监控视频到可用告警的完整链路

一间装了摄像头的教室,一节课四十五分钟,视频里同时存在几十张脸。教务真正想要的不是"把视频存下来",而是想知道:这节课有多少人走神了、哪个时间段集体低头、有没有人在考试时频繁转头或传递东西。基于深度学习开发的课堂专注度分析和考试作弊检测系统,本质就是把这段原始视频拆成"人脸检测 → 头部姿态与视线估计 → 时序行为判定 → 告警输出"四段流水线,用 Python 串起来跑。它适合两类人:一类是手里有课堂或考场视频、想快速搭出可演示原型的在校开发者;另一类是拿到一份 Python 源码加文档说明、却卡在环境配置和参数调不对的工程新手。这篇笔记按"先讲清每个模块为什么这么选,再给能直接抄的命令和参数"来写,重点放在能复现、能调参、能排错上,而不是泛泛谈深度学习有多强。

2. 系统拆成四段流水线:为什么不能一个模型端到端硬扛

很多人第一反应是"找个行为识别模型,把视频丢进去输出专注或作弊"。真做起来会发现这条路在课堂场景里几乎走不通:作弊动作样本极少、标注成本极高,端到端模型没有足够数据去学;而专注度本身是个连续量,不是离散标签。所以常见做法是拆成可解释的四段,每段用成熟模型,中间结果可单独调试。

2.1 人脸检测与跟踪:先解决"谁是谁"

第一段负责在每帧里框出人脸,并给同一张脸分配稳定的 ID。检测用轻量模型保证帧率,跟踪用简单的 IoU 匹配或卡尔曼滤波即可,不必上重型 ReID。原因是课堂里人基本不动,跟踪难度低,把算力留给后面的姿态估计更划算。

import cv2 import numpy as np # 用 OpenCV 自带的人脸检测器做最小可跑版本,替换成 YOLO 系检测器时接口保持一致 detector = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) def detect_faces(frame, scale=1.1, min_neighbors=5, min_size=(60, 60)): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # scaleFactor 越小越慢但越不容易漏检,课堂远景建议 1.05~1.1 # minNeighbors 越大误检越少,但小脸容易被过滤,5 是常用折中 faces = detector.detectMultiScale( gray, scaleFactor=scale, minNeighbors=min_neighbors, minSize=min_size ) return faces # 返回 (x, y, w, h) 列表

这段代码里scaleFactor和minNeighbors是最常被调的两个参数。教室后排人脸只有几十像素,min_size设太大直接漏检;设太小又会把窗户、投影仪误判成人脸。我的经验是先用一段真实课堂视频跑一遍,把min_size定在"最远那排脸的实际像素高度再乘 0.8",然后微调minNeighbors压误检。跟踪部分给每个框维护一个 ID 和"丢失帧计数",连续几帧匹配不上才注销,避免人脸被遮挡时 ID 频繁跳变。

2.2 头部姿态与视线估计:专注度的核心信号

专注度最可靠的信号是头部朝向和眼睛开合。头部姿态用 6D 或欧拉角表示,常见做法是检测人脸关键点后做 PnP 解算,或用直接回归姿态的轻量网络。视线估计则看瞳孔相对眼眶的位置。两者结合能区分"低头看书"和"低头玩手机"——前者头低但视线在桌面,后者头低且视线频繁下移。

# 用关键点做头部姿态解算的骨架,landmarks 为 68 点或 5 点 import numpy as np # 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), # 右嘴角 ], dtype=np.float64) def estimate_head_pose(landmarks_2d, frame_size): h, w = frame_size[:2] focal = w # 粗略焦距,真实标定可换成相机内参 center = (w / 2, h / 2) cam_matrix = np.array([[focal, 0, center[0]], [0, focal, center[1]], [0, 0, 1]], dtype=np.float64) dist = np.zeros((4, 1)) ok, rvec, tvec = cv2.solvePnP( MODEL_POINTS, landmarks_2d, cam_matrix, dist, flags=cv2.SOLVEPNP_ITERATIVE ) if not ok: return None rmat, _ = cv2.Rodrigues(rvec) # 从旋转矩阵提取俯仰角,负值通常代表低头 pitch = np.degrees(np.arcsin(-rmat[2, 1])) return pitch

focal用图像宽度近似是常见偷懒做法,误差在可接受范围,但如果要精确到几度,得用棋盘格标定相机内参。pitch的阈值不是固定的:前排学生和后排学生因为俯仰视角不同,同样的低头动作算出来的角度不一样。稳妥做法是按人脸框在画面中的纵向位置做一次线性补偿,或者干脆对每个跟踪 ID 记录一段基线,用相对变化判断。

2.3 时序行为判定:把逐帧信号变成"事件"

单帧姿态没有意义,专注度是时间维度上的统计量。常见做法是滑动窗口:每 1 秒聚合一次,统计窗口内低头帧占比、视线偏离占比、头部转动方差。作弊检测则关注短时间内的异常模式,比如头部在 2 秒内左右摆动超过阈值、或手部区域频繁进入画面下方。

from collections import deque class FocusTracker: def __init__(self, window_sec=30, fps=25): self.window = deque(maxlen=window_sec * fps) self.fps = fps def update(self, pitch, gaze_off): # 低头超过 20 度或视线偏离记为不专注帧 unfocused = 1 if (pitch < -20 or gaze_off) else 0 self.window.append(unfocused) def focus_ratio(self): if not self.window: return 1.0 return 1.0 - sum(self.window) / len(self.window) def is_suspicious(self, turn_events): # 30 秒内头部大幅转动超过 5 次,判为可疑 return turn_events >= 5

window_sec决定告警灵敏度:窗口太短,一次正常低头就触发告警;太长,作弊动作被平均掉。课堂专注度统计我一般用 30 秒窗口,作弊检测用 5 到 10 秒的短窗口。turn_events的计数要在跟踪 ID 维度上做,不能全画面累加,否则一个人频繁转头会污染所有人的统计。

2.4 告警输出与可视化:让结果能被人看懂

最后一段把事件写成结构化记录,并叠加到视频上。输出通常包括:每个学生的专注度曲线、整节课的专注度均值、可疑时间段列表。可视化用 OpenCV 画框和文字即可,不必上重型前端。

def draw_overlay(frame, faces, tracker): for (x, y, w, h) in faces: ratio = tracker.focus_ratio() color = (0, 255, 0) if ratio > 0.7 else (0, 165, 255) cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) cv2.putText(frame, f"{ratio:.2f}", (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return frame

颜色阈值 0.7 是经验值,实际按班级基线调整。输出记录建议同时写 CSV 和 JSON:CSV 给教务做统计,JSON 给前端做回放定位。

3. 环境配置与源码跑通:Python 依赖、模型权重、目录结构

拿到一份 Python 源码加文档说明,第一步不是读代码,而是把环境跑起来。这一步翻车的人最多,血泪经验是:先确认 Python 版本,再装依赖,最后放权重文件,顺序错了会浪费大量时间排查。

3.1 Python 环境与依赖安装

推荐 Python 3.8 到 3.10,太新的版本某些深度学习库轮子还没跟上。用虚拟环境隔离,避免和系统包打架。

# 创建并激活虚拟环境 python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate # 安装核心依赖,版本按源码文档说明为准 pip install opencv-python==4.8.0.76 pip install numpy==1.24.3 pip install torch==2.0.1 torchvision==0.15.2 pip install onnxruntime==1.15.1

opencv-python和numpy版本要匹配,否则会出现numpy的 ABI 报错。torch装 CPU 版还是 GPU 版看机器:有 NVIDIA 显卡且装了对应 CUDA 就装 GPU 版,否则 CPU 版也能跑,只是帧率低。onnxruntime用于加载导出的 ONNX 模型,比直接跑 PyTorch 推理快,适合部署。

3.2 模型权重与目录结构

源码通常不会带大权重文件,需要按文档说明放到指定目录。常见结构如下:

目录用途注意事项
weights/存放检测、姿态、视线模型权重文件名要和代码里加载路径一致
data/测试视频和输出结果视频建议先转成 25fps 统一帧率
configs/阈值、窗口大小等参数改参数优先改这里,别硬编码
logs/运行日志和告警记录定期清理,避免占满磁盘

权重文件缺失时程序一般会在加载处报FileNotFoundError,按报错路径补文件即可。如果文档说明里给了下载方式,按说明操作;没有的话,用代码里指定的模型名去对应框架的官方模型库找同结构权重。

3.3 跑通最小示例

先用一段短视频验证整条链路,别一上来就处理整节课。

python main.py --source data/test_5min.mp4 --output logs/result.csv --show

--source指定输入视频,--output指定结果 CSV,--show打开实时预览窗口。第一次跑建议关掉--show,用--save-video输出带标注的视频,方便逐帧检查。如果报显存不足,把批处理大小调到 1,或改用 ONNX 推理。

4. 参数调优与阈值设定:专注度和作弊判定的边界在哪

模型跑通只是开始,真正决定系统好不好用的是阈值。阈值定错,要么满屏误报,要么什么都检测不到。

4.1 专注度阈值的分层设定

不要用一个全局阈值。按人脸在画面中的位置分层:前排俯仰角天然偏下,后排偏上。可以按人脸框中心 y 坐标分三段,每段用不同的 pitch 阈值。

分层人脸中心 y 范围低头阈值说明
前排画面高度 0~33%-25 度视角偏下,阈值放宽
中排33%~66%-20 度标准阈值
后排66%~100%-15 度视角偏上,阈值收紧

这张表是起点,实际要用自己教室的视频标定。方法是让几个学生做标准低头动作,记录每层的 pitch 值,取平均值再减 5 度作为阈值。

4.2 作弊检测的时序参数

作弊检测靠的是短时间异常,参数比专注度更敏感。核心三个参数:转动角度阈值、时间窗口、触发次数。

CHEAT_CONFIG = { "yaw_threshold": 35, # 头部左右转动超过 35 度记为一次转头 "window_sec": 8, # 8 秒窗口 "trigger_count": 4, # 窗口内转头 4 次触发告警 "hand_region_ratio": 0.3, # 手部进入画面下方 30% 区域 }

yaw_threshold设太小,正常看黑板也会被算成转头;设太大,真作弊动作漏检。trigger_count和window_sec要一起调:窗口短、次数少,误报多;窗口长、次数多,漏报多。我的经验是先用 8 秒 4 次,跑几段真实考场视频看误报率,再微调。

4.3 用验证集校准而不是拍脑袋

有标注数据的话,画一条 ROC 曲线选阈值。没有标注数据,就人工看几段视频,统计误报和漏报,手动调。关键是记录每次调整后的误报数和漏报数,别凭感觉。

5. 避坑与排查:那些让系统跑不起来的常见问题

这一章全是踩过的坑,按"现象 → 原因 → 解决"写,遇到问题直接对号入座。

5.1 检测框疯狂闪烁、ID 频繁跳变

现象:同一个人的人脸框忽大忽小,跟踪 ID 每几帧换一次。原因:检测器在相邻帧输出不稳定,跟踪匹配阈值太严。解决:给检测结果做一次指数平滑,或把跟踪的 IoU 匹配阈值从 0.5 降到 0.3,并允许丢失 5 帧内保持 ID。

5.2 低头阈值在真实教室完全不准

现象:按文档默认阈值跑,全班都被判成不专注。原因:默认阈值是在正面平视的测试视频上调的,真实教室摄像头有俯角。解决:按 4.1 的分层方法重新标定,或对每个跟踪 ID 记录前 10 秒的 pitch 均值作为个人基线,用相对变化判断。

5.3 显存不足或帧率低到无法实时

现象:跑几分钟后程序崩溃,报 CUDA out of memory。原因:批处理太大或模型没释放中间张量。解决:把批大小降到 1,推理时用torch.no_grad(),或导出 ONNX 用 onnxruntime 跑。帧率低的话,把检测间隔改成每 3 帧检测一次,中间帧用跟踪结果。

5.4 作弊告警全是误报

现象:一节课触发几十次作弊告警。原因:转头阈值太低,或没排除正常动作(如捡笔、看时间)。解决:提高yaw_threshold到 40 度以上,增加持续时间要求(转动必须持续超过 0.5 秒),并把手部区域检测加上,只有头部异常加手部异常才告警。

5.5 输出 CSV 中文乱码

现象:用 Excel 打开结果文件,学生姓名或备注是乱码。原因:CSV 默认编码不是 UTF-8 with BOM。解决:写文件时用encoding="utf-8-sig",Excel 就能正确识别。

6. 从能跑到好用:把专注度曲线做成可回放的教学反馈

系统能跑通、告警能出来,只算完成一半。真正有价值的是把逐帧结果变成教师能看懂的反馈。我一般会做两件事:一是把整节课的专注度按分钟聚合成曲线,标出低谷时间段;二是把告警时间点对应到视频时间戳,支持点击跳转回放。

import pandas as pd def build_minute_curve(csv_path): df = pd.read_csv(csv_path) # 假设有 timestamp 和 focus_ratio 两列 df["minute"] = (df["timestamp"] // 60).astype(int) curve = df.groupby("minute")["focus_ratio"].mean().reset_index() # 标出低于 0.5 的低谷分钟 curve["low"] = curve["focus_ratio"] < 0.5 return curve def export_report(curve, out="logs/report.csv"): curve.to_csv(out, index=False, encoding="utf-8-sig")

groupby("minute")把秒级数据聚合成分钟级,low列直接标出低谷,教师一眼就能看到哪几分钟学生集体走神。回放功能用时间戳做锚点,前端拿到告警列表后按timestamp定位视频即可。

验证系统好不好用,别只看准确率数字。找三位老师各看一段带标注的结果视频,问两个问题:告警时间点对不对、有没有明显漏掉的。如果老师觉得告警太频繁,就调高触发次数;如果觉得漏,就降低阈值。这个反馈循环比任何离线指标都实在。

最后说个我自己的习惯:每次调完参数,一定把配置和对应的误报漏报数记在一个表格里,攒够十几组再回头看,就能摸出这套参数在自己场景下的合理区间。别指望一次调对,也别每次凭感觉改。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询