ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于深度学习的课堂专注度分析与作弊检测系统:YOLO+MediaPipe实战

基于深度学习的课堂专注度分析与作弊检测系统:YOLO+MediaPipe实战 简介面向计算机相关专业毕业设计场景的智慧教室项目源码集成课堂专注度分析与考试作弊检测两大功能适合正在做毕设、课程设计或期末大作业的学生参考与二次开发。包内共626个文件、约87.73MB以383个Python源码文件为主体配合YAML配置、Markdown说明文档、JPG/GIF效果展示以及YOLOv3、RetinaFace等模型配置与CUDA扩展源码Python代码覆盖模型定义、数据处理和主流程调用配置类文件用于训练参数与网络结构调整文档和图片辅助理解运行效果。项目经导师指导并调试通过代码结构完整、可直接运行便于读者理解课堂行为识别、目标检测与姿态估计相结合的深度学习方案也方便替换数据集或调整网络参数拓展实验。目前已有346人学习下载可作为同类毕业设计课题的实战参照。1. 智慧教室这个概念听起来很宏大但这套毕设项目源码把基于深度学习的课堂专注度分析和考试作弊检测系统落到代码层面其实就两件事看懂学生的头在哪里以及看懂学生手里和桌上有什么。前者靠姿态估计后者靠目标检测两个都是目前Python生态里非常成熟的深度学习落地方向。适合的人群也很明确做深度学习毕设的学生、准备在智慧教室方向投入的开发者以及想给现有监控系统加一层智能分析的人。这套源码的价值不在于界面多漂亮而在于把“专注度”和“作弊”这种模糊概念拆成了可运行的Python代码。2. 技术选型与源码结构深度学习模型怎么分工目录里每层代码在干什么先聊选型。这类毕设项目最常见的翻车点是一上来就想做个端到端的大模型把专注度和作弊都丢给一个网络去学。实际靠谱的做法是拆成两个独立模型加一个规则引擎目标检测模型负责找物体姿态估计模型负责找人体关键点最后用Python代码把两者的输出组合成行为判断。2.1 为什么用YOLO做作弊检测底座而不是直接上OpenCV作弊检测的核心任务是识别手机、纸条这类小目标。OpenCV做运动检测和颜色分割在固定背景下能跑但教室场景光照变化大学生穿的衣服颜色和桌子颜色都会干扰背景差分。YOLO这类基于CNN的深度学习目标检测模型把特征学习交给网络你只用准备带标注的图片训练完直接拿检测框用这也是目前“深度学习实战项目案例”里最成熟的套路。以项目里常见的YOLOv5实现为例输入一张教室画面输出每个目标的类别、置信度和边界框。源码里一般会包一层detector类把模型加载和推理封装起来。实际项目里我一般会多调一个参数conf_thres置信度阈值默认0.5作弊检测场景建议调到0.6以上因为误报的代价比漏报更大。# src/detection/yolo_detector.py import torch class YOLODetector: def __init__(self, weights_path, conf_thres0.5, deviceNone): # 加载训练好的权重文件device 可以是 cpu 或 cuda:0 self.device device or (cuda:0 if torch.cuda.is_available() else cpu) self.model torch.hub.load(ultralytics/yolov5, custom, pathweights_path, force_reloadFalse) self.model.conf conf_thres # 置信度阈值 self.model.iou 0.45 # NMS 的 IoU 阈值 def detect(self, frame_bgr): # 输入是 OpenCV 的 BGR 帧返回检测结果对象的列表 results self.model(frame_bgr) dets results.pandas().xyxy[0] # 转成 DataFrame 方便过滤 return dets.to_dict(records)这段代码的逻辑很直接构造YOLODetector时指定权重路径和置信度阈值detect方法接收一帧BGR图像返回包含x1,y1,x2,y2,confidence,class的字典列表。两个参数值得注意——conf决定多模糊才算检出iou决定重叠框要不要合并。小目标检测场景里把iou调到0.4到0.45之间用0.5会导致密集座位区相邻的两个手机框被合并成一个。2.2 专注度分析走姿态估计MediaPipe关键点与头部姿态计算专注度分析不需要识别学生是谁只需要知道学生是否在低头、趴桌、频繁转头。级联一下先跑目标检测把人框出来再在每个人的框内做姿态估计。这种级联方式比直接跑多人姿态估计省计算量也是这类毕设源码里最常见的主干设计。头部姿态的输入通常用面部关键点。常见做法是用MediaPipe的FaceMesh模型提取人脸的468个关键点再用PnP解算头部的pitch、yaw、roll三个欧拉角。不过大多数毕设源码不会真的手写PnP而是用MediaPipe自带的头部关键点简化处理。关键要理解三个角度的含义pitch俯仰角低头抬头。低头超过15度通常判为不专注yaw偏航角左右转头。考试场景里频繁转头是作弊嫌疑信号roll滚转角左右歪头。这个角在专注度里一般只做辅助# src/detection/pose_estimator.py 核心片段 import mediapipe as mp class PoseEstimator: def __init__(self): self.mp_face mp.solutions.face_mesh self.face_mesh self.mp_face.FaceMesh(static_image_modeFalse, max_num_faces8, refine_landmarksTrue) def get_head_pose(self, frame_bgr): # 转成 RGBMediaPipe 不接受 BGR 输入 rgb cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) results self.face_mesh.process(rgb) if not results.multi_face_landmarks: return [] poses [] for face in results.multi_face_landmarks: nose face.landmark[1] # 鼻尖点 left_eye face.landmark[33] # 左眼外眼角 right_eye face.landmark[263] # 右眼外眼角 poses.append({nose: (nose.x, nose.y, nose.z), left_eye: (left_eye.x, left_eye.y, left_eye.z), right_eye: (right_eye.x, right_eye.y, right_eye.z)}) return poses这里有个参数容易踩坑max_num_faces。教室后排坐满时摄像头里可能同时出现二三十个脸MediaPipe的FaceMesh默认只能处理少量人脸调太高会严重拖慢帧率。我一般会结合目标检测的人脸框数量动态设置只对检测置信度最高的前6到8个人脸做姿态解算这个取舍在多人场景里非常关键。2.3 从requirements.txt到config.yaml读懂毕设源码的工程骨架拿到一份Python源码包先别急着跑应该先看三个文件requirements.txt、config.yaml或config.py、README.md。这三个文件决定了环境能不能复现、参数在哪里改、模型权重放哪里。这类毕设项目的目录通常长这样不代表所有包都一样但结构高度相似classroom_analysis/ ├── config/config.yaml # 所有可调参数 ├── data/ # 视频、图片、标注文件 ├── models/ # 模型权重, 如 best.pt ├── src/ │ ├── detection/ # 目标检测与姿态估计封装 │ ├── analysis/ # 专注度评分与作弊判定 │ ├── ui/ # PyQt 界面 │ └── utils/ # 画框、日志、数据库等工具 ├── train.py # 训练入口 ├── detect.py # 推理入口 └── requirements.txtconfig.yaml是整份源码的“后悔药”集中地。把模型路径、置信度阈值、摄像头编号、录像保存路径都写在里面二次开发的时候只需要改这个文件不用翻代码。requirements.txt里通常锁了opencv-python、mediapipe、torch、pyyaml这几个基础依赖但注意它很少锁Python版本这是后面环境配置最容易翻车的点。3. 环境配置与最小复现把课堂专注度分析系统在本地跑起来很多拿到源码的人第一步就卡在环境上。pytorch环境配置是深度学习毕设里耗时最长的前置步骤没有之一。下面的步骤我按最少改动、最快跑通来排序优先用CPU也能推理的配置等跑通了再考虑GPU加速。3.1 深度学习环境配置conda、CUDA与PyTorch版本怎么锁常见做法是用conda创建独立环境避免把系统Python搞乱。Python 3.8是这类项目兼容性最好的版本mediapipe和torch都有现成安装包。如果你机器上有NVIDIA显卡先确认驱动支持的CUDA版本再装对应PyTorch没有显卡就装CPU版推理慢一点但能跑完整个流程。conda create -n classroom python3.8 conda activate classroom # 有 N 卡时按本机 CUDA 版本选择例如 11.7 pip install torch1.13.1 torchvision0.14.1 --extra-index-url https://download.pytorch.org/whl/cu117 # 无显卡时装 CPU 版 # pip install torch1.13.1 torchvision0.14.1 --extra-index-url https://download.pytorch.org/whl/cpu pip install opencv-python4.8.1.78 pip install mediapipe0.10.9 pip install pyyaml requests pandas用conda的关键不只是隔离环境而是Python版本和包版本一起锁住。mediapipe对Python版本很敏感3.9以上容易掉进源码编译的坑。PyTorch版本同理torch和torchvision必须配套混装会在import时报Illegal instruction或undefined symbol错。装完第一步不是跑主程序而是先验证torch能不能正常importimport torch print(torch.__version__) print(torch.cuda.is_available())这一步能筛掉八成环境问题。如果cuda.is_available()返回False说明安装的PyTorch和CUDA不匹配别急着继续往下跑先回去重新安装匹配版本。CPU版返回False是正常的继续即可。注意不要照抄网上最新的PyTorch安装命令版本必须与项目requirements.txt锁定的版本一致否则import阶段就会报undefined symbol。3.2 数据集准备标注格式、训练集与验证集划分这个系统要跑起来至少需要两类数据用于目标检测的学生行为数据手机、书本、纸条、举手等以及用于姿态估计的人脸/头部数据。姿态估计一般直接复用MediaPipe的预训练模型不需要自己标注真正需要花时间的反而是第一类。如果是自己标注常见工具是LabelImg或Label Studio导出成YOLO格式的txt标注文件。每一行对应一个目标前一个数字是类别ID后面四个数字是归一化后的中心点坐标和宽高。划分数据集时有一个毕设常见的问题直接用全部图片训练最后测试时反而拿训练集来凑指标。正确做法是把图片按约8:1:1分成train/val/test三份且同一段视频的连续帧要分到同一份里否则模型会“背题”。# 数据集目录结构YOLO 格式 datasets/ ├── images/ │ ├── train/ # 训练图片 │ ├── val/ # 验证图片 │ └── test/ # 测试图片 └── labels/ ├── train/ # 同名 txt ├── val/ └── test/划分之后训练脚本里需要指定数据配置文件的路径。这个配置文件是整个训练流程的枢纽改错一个字段就白跑几小时。# config/cheat_data.yaml train: datasets/images/train val: datasets/images/val nc: 4 # 类别数 names: [phone, book, note, hand] # 类别名, 顺序必须与标注ID一致目录里看不到classes.txt很正常YOLO的类别名统一写在data配置的names字段里。新手最容易犯的错是标注软件里类别顺序和names顺序不一致训练时类别标签整体错位检测出来手机叫book排查方法是用验证集图片手动画框对比。3.3 启动检测流程detect.py参数与摄像头/视频输入切换环境配好、模型权重放到位之后就可以跑推理了。这类毕设项目通常会提供一个detect.py入口支持传入视频文件路径或摄像头编号。命令行参数的写法各家略有不同但基本都有--source、--weights、--conf三个核心参数。# 用本地视频文件测适合没有摄像头的环境 python detect.py --source data/test.mp4 --weights weights/best.pt --conf 0.5 # 用 USB 摄像头实时测0 表示第一个摄像头 python detect.py --source 0 --weights weights/best.pt --conf 0.6 --imgsz 640--source指定输入来源整型数字是摄像头编号字符串是视频文件路径--weights指定模型权重毕设包里通常同时放了yolov5s.pt这个预训练权重和best.pt这个微调权重--imgsz是推理分辨率默认640教室场景建议保持640放大到1280能提高小目标检出率但帧率会明显下降。跑起来的标志是终端里逐条打印检测结果同时弹出一个带检测框的预览窗口。如果只看到窗口没有框先看终端里的检测数量是不是0。常见原因是置信度阈值过高或者权重文件本身是空模型前者把--conf降到0.3就能看到效果后者需要检查权重文件大小小于几十KB的best.pt基本就是没训练成功。4. 核心逻辑二次开发专注度评分与作弊行为规则怎么调推理跑通只是第一步。真正让这套系统从“会画框”变成“能分析”的是专注度评分和作弊行为判定这两个规则模块。这两个模块在源码里通常被封装成analysis包下的两个类输入是检测结果和姿态数据输出是行为标签和分数。常见做法是以下这套设计。4.1 专注度评分头部姿态阈值与滑动窗口的联动专注度不能只看某一帧否则任何一次正常的低头找书都会被误判成不专注。常见做法是引入一个时间滑动窗口统计窗口内低头帧占比再映射成0到100的分数。窗口长度一般取30秒也就是900帧按30FPS算每5秒刷新一次分数。# src/analysis/focus_analysis.py import collections class FocusAnalyzer: def __init__(self, window_seconds30, pitch_threshold18, fps30): self.window_size window_seconds * fps # 窗口总帧数 self.pitch_threshold pitch_threshold # 低头判定阈值(度) self.history collections.deque(maxlenself.window_size) def update(self, pitch_deg): # 每帧调用, pitch_deg 是头部俯仰角, 正值表示低头 is_looking_down pitch_deg self.pitch_threshold self.history.append(is_looking_down) def get_focus_score(self): # 滑动窗口内低头比例越低, 分数越高 if not self.history: return 0.0 down_ratio sum(self.history) / len(self.history) return round(100 * (1 - down_ratio), 1)pitch_threshold这个参数是最值得调的地方。前排学生离摄像头近头部角度天然偏大后排学生远角度偏小。同一个阈值下前排容易误报后排容易漏报。实战中我会用相机标定后的角度作为参考但毕设阶段更实用的做法是取一节课前10分钟的学生pitch均值为基准再加上15度作为个人化阈值。这种“基准固定偏移”的方式实现简单效果远比全局阈值好。4.2 作弊判定检测框、置信度与行为时序三条规则作弊检测比专注度分析更强调“误报代价高”。系统可以说学生不专注但不能轻易说学生作弊否则会引发矛盾。所以源码里的作弊判定通常不会只看单帧而是要求连续多帧满足条件才触发。# src/analysis/cheat_detection.py class CheatDetector: def __init__(self, phone_conf0.6, turn_yaw30, frame_span20): self.phone_conf phone_conf # 手机判定置信度 self.turn_yaw turn_yaw # 转头判定角度(度) self.frame_span frame_span # 连续帧数要求 self.turn_history [] # 记录 yaw 角序列 def judge(self, detections, head_pose, frame_id): events [] # 规则1: 检测到手机且置信度达标 for det in detections: if det[name] phone and det[confidence] self.phone_conf: events.append({type: phone, frame: frame_id}) # 规则2: 规定帧数内 yaw 角变化超过阈值 self.turn_history.append(head_pose[yaw]) if len(self.turn_history) self.frame_span: yaw_change abs(max(self.turn_history) - min(self.turn_history)) if yaw_change self.turn_yaw: events.append({type: head_turn, frame: frame_id}) self.turn_history.clear() return events这段代码刻意做了简化把规则收敛成两条一条管手机一条管转头。真实项目里还会有“长时间低头看桌下”和“手部靠近面部”这类规则但骨架都一样——先看单帧条件再用帧数累积降低误报。frame_span不宜设太小否则一次正常抬头左右看就会被记成作弊嫌疑也不宜太大否则真作弊要半分钟后才发现。20帧左右约0.7秒是一个在课堂场景下相对安全的起点。4.3 结果落库与可视化从检测帧到课堂报表分析完的行为事件和分数最终要落到两个地方数据库和可视化界面。毕设项目里数据库一般用SQLite零配置、单文件、Python内置sqlite3模块直接操作比MySQL省事得多。表结构按事件和时段分数两张表设计就够。# src/utils/db.py import sqlite3 def init_db(db_pathclassroom.db): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS cheat_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, event_type TEXT, confidence REAL, frame_id INTEGER ) ) conn.execute( CREATE TABLE IF NOT EXISTS focus_scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, student_id TEXT, score REAL ) ) conn.commit() return conn落库的关键是时间戳。分析模块跑在独立线程里拿到的frame_id只是视频帧序号必须和墙钟时间对齐否则最后做课堂回顾时根本对不上是哪一分钟发生了什么。界面上常见用PyQt5写一个主窗口左边实时视频流右边两个Tab分别显示作弊事件列表和专注度曲线。这个布局不复杂但PyQt的信号槽和OpenCV的QImage转换之间有个常踩的坑QImage要求图像内存连续OpenCV的frame在多次resize后可能不连续转换前必须先调用np.ascontiguousarray(frame)。5. 避坑与排查真实教室环境里跑这个项目的5个翻车现场这章写的都是实际部署时反复出现的问题每条都按现象、原因、解决来讲不说理论直接说怎么定位。5.1 现象摄像头画面卡顿、推理帧率跌破10FPS原因是视频读取线程和推理线程共用同一个循环摄像头等待一帧的时间被算进推理耗时里。很多毕设源码直接把cv2.VideoCapture.read()和model.detect()写在同一个while循环里USB摄像头在Windows下读取延迟能达到几十毫秒叠加YOLO推理帧率自然崩掉。解决把视频读取和推理拆成两个线程中间放一个限容队列。生产者线程只做read()消费者线程从队列里取帧推理。队列容量设成2到4帧即可太大时延高太小容易丢帧。顺便一提摄像头一般接USB 3.0口接在USB 2.0口上即使型号支持30FPS实际也可能只有15FPS。# src/utils/threaded_reader.py 核心结构 import queue import threading import cv2 class ThreadedReader: def __init__(self, source, queue_size4): self.cap cv2.VideoCapture(source) self.q queue.Queue(maxsizequeue_size) self.running True threading.Thread(targetself._read_loop, daemonTrue).start() def _read_loop(self): while self.running: ok, frame self.cap.read() if not ok: break if not self.q.empty(): try: self.q.get_nowait() # 队满时丢弃旧帧 except queue.Empty: pass self.q.put(frame) def get_frame(self): try: return self.q.get(timeout0.1) except queue.Empty: return None这里丢弃旧帧是关键操作。宁可少处理几帧也不能让推理线程吃到积压的旧画面否则你看到的“实时”画面其实是几秒前的延迟录像。5.2 现象多人场景下专注度标签串位原因是检测框来自YOLO姿态关键点来自MediaPipe两者没有做数据关联。学生A低头框还挂在A身上但MediaPipe把A的脸关键点算到了旁边学生B的框里导致界面显示B正在低头B的专注度分数被拉低。解决做一次IOU匹配。把每个人脸框和每个人体框计算交并比只有重叠度超过0.3才把姿态数据归属到该人体框。没有匹配上的姿态数据直接丢弃不要给任何学生记分。def associate_pose_to_bbox(pose_bboxes, person_bboxes, iou_threshold0.3): matched [] for pose_box in pose_bboxes: best_iou, best_idx 0.0, -1 for i, person_box in enumerate(person_bboxes): iou compute_iou(pose_box, person_box) if iou best_iou: best_iou, best_idx iou, i if best_iou iou_threshold: matched.append((best_idx, pose_box)) return matched这个坑在单人或双人场景几乎不出现一坐到教室后排就暴露了。根源是YOLO的人体框和MediaPipe的人脸框尺寸差异大小脸框落在两个人体框的交界处。把IOU阈值调低到0.2配合距离中心点距离排序效果比只算IOU更稳定。5.3 现象水杯被识别成手机原因是训练集中的手机正样本大多是从网上找的特写图水杯的圆柱形反光表面在特定光照下和手机屏幕很像。模型在教室场景下把水杯、眼镜盒、笔袋误判成手机置信度还接近0.6。解决两步走。第一步把phone类的置信度阈值提高到0.6以上降低误报第二步增加难负样本把真实教室里拍到的水杯、笔袋、橡皮的图片标成background类或者干脆作为空图片加入训练集。数据清洗比堆加更多正样本管用把难负样本从0加到200张后误检率能降掉一半左右。另外一个工程手段是加位置过滤手机检测框的中心点如果落在课桌区域之外比如在走廊或讲台上直接不判定。这个规则不需要模型配合在作弊判定前做一个坐标过滤就行。5.4 现象离线部署时PyTorch依赖冲突原因是目标机器的显卡驱动是新的但系统装的是旧版CUDA或者反过来机器上已经有其他项目装了PyTorch 2.x这个毕设项目需要torch 1.13依赖被覆盖后混在一起import时出现段错误或undefined symbol。解决严格执行两条。第一把torch和torchvision的版本写成完全限定不用“torch1.0”这种范围写法。第二用conda环境锁死Python版本和依赖版本别跟系统Python掺和。检查时先跑torch的import自检有任何异常就动手清理环境重建不要尝试在一个坏环境里修修改改耗时且不可复现。conda create -n classroom python3.8 -y conda activate classroom pip list | grep -E torch|mediapipe|opencv # 确认版本完全匹配后再装项目依赖 pip install -r requirements.txt提示修复环境问题的最快路径不是修补依赖关系而是重建环境一次到位。5.5 现象长时间运行内存暴涨原因是分析模块的滑动窗口历史数据、事件列表、界面显示用的帧缓存都没有设置上限。跑一节课内存从800MB涨到4GB最后进程被杀。最容易忽略的是界面上为了“保留现场”不断往列表控件里append事件行而列表控件不会自动回收。解决所有历史数据结构都要设上限。deque用maxlen事件列表只保留最近500条数据库定期写盘后清空内存缓存。界面上用表格控件配合数据模型分页或者每次刷新只显示最近100条记录。问题本身不难难在排查因为内存是缓慢上涨的跑10分钟看不出跑40分钟就崩了。性能排查用Watchdog脚本定期打印进程的RSS内存每次采样间隔5分钟记录在日志里。如果趋势是单调上升就按上面的经验清理缓存如果是锯齿状说明是垃圾回收行为不用过度担心。6. 进阶验证用你自己的课堂视频检验专注度分析与作弊检测的泛化能力代码能跑不等于项目就做完了。最后一个值得投入的环节是验证泛化能力换一个你没见过的教室、没见过的摄像头角度这套系统还能不能给出靠谱的结果。方法很简单找一个真实的课堂录像按时间轴手动打标把模型的输出和人工标签对齐算一致率。专注度用30秒窗口打分作弊事件用事件级召回率评估。正常课堂上作弊事件本身很少人工打标时故意加几分钟模拟场景能加快验证。做这个验证时我习惯准备三个不同摄像头高度的视频平视、俯视、斜后方。这三个角度对姿态估计的影响非常大MediaPipe在俯视时的头部角度解算误差明显偏大。如果你的使用场景是教室讲台上方的俯视摄像头阈值就得重新校准而不是直接用代码里的默认18度。另外还要测光照下午西晒的教室逆光人脸的检测置信度会掉一大截必要时加gamma校正或改用红外摄像头。我之前接过一个这类项目源码本身没有问题但用户坚持用教室原有的模拟摄像头分辨率只有480P还带噪点。YOLO在640输入分辨率下对这种低质量画面几乎失去小目标检出能力。后来换了一个720P的USB摄像头同样的权重作弊检测的召回率直接从30%拉到70%。这件事给我的教训是调模型之前先确认摄像头质量画面的信息量决定系统的上限深度学习模型只是逼近这个上限。另一个长期有用的习惯是保留每次课堂运行的配置快照把config.yaml、权重文件的MD5、摄像头型号和安装高度都记下来。以后的每一次调整都能回溯哪个参数改变了什么结果而不是靠记忆猜测。这套验证流程走完你对这个毕设项目的掌握程度就远超“能跑通”这个层面了。希望帮到你。本文还有配套的精品资源点击获取
返回列表