简介基于深度学习的智能监考系统项目代码与设计资料面向计算机相关专业学生及需要项目实战的开发者适合作为课程设计、期末大作业或毕业设计的参考。项目围绕考试场景下的智能化监控覆盖人脸识别、手机检测、口罩佩戴检测与人体姿态估计等任务结合YOLOv5系列模型配置与训练调优思路具备较强的实用性和可扩展性。压缩包共88个文件约18.43MB主要包含19个Python功能脚本覆盖人脸采集、手机检测、口罩识别、数据库操作等模块43个YAML模型与数据集配置文件便于切换不同检测任务和数据集合另有Jupyter Notebook数据处理、shell辅助脚本、npy数据文件、SQL数据库脚本、MP4演示视频及说明文档。已有44人学习下载。整套资料提供了从数据准备、模型构建、训练配置到检测推理和数据库管理的完整链路目录结构清晰。既能帮助初学者理解智能监考系统的整体设计也能为进阶开发者提供模块化改造和二次开发的基础适合快速复现项目用于课程汇报或作品展示。1. 为什么监考系统先选深度学习而不是普通视频分析一个考场的监控画面如果只靠人眼盯通常 20 分钟就会疲劳。所谓的“智能监考系统”要做的事是把“摄像头画面”翻译成“作弊事件描述”比如“右手举到耳侧”“低头超过 5 秒”“桌面上出现手机”。传统视频分析用帧差法和光流可以做移动检测但做不了语义层面的动作判断。基于深度学习的方案核心是用目标检测定位人和物品用关键点估计描述姿态再用时序模型判断动作。这套技术栈在单张显卡上就能跑通整个系统的推理延迟控制在 300ms 以内具备落地条件。适用对象是高校、考试机构、远程面试平台的研发人员以及想用 YOLO 加姿态估计算法做行为识别的工程师。内容会围绕图像预处理、目标检测、骨骼关键点分析、时序分类、工程参数调优这几层展开。2. 图像预处理与目标检测先把“画面里有什么”算清楚2.1 透视校正与 ROI 裁剪避免摄像头偏斜影响后续推理考场摄像头通常架在教室前上方拍出来的画面带有明显透视形变。同一个学生坐在第一排和最后一排目标框的宽高比差异可能超过 40%。直接拿这种图喂给检测模型小目标区域的纹理会被压缩误检率明显上升。常见做法是先手工标定教室的四个角点用cv2.getPerspectiveTransform做一次透视变换把整个教室拉正再把每个座位的坐标预先标记成独立的 ROI 区域。import cv2 import numpy as np src np.array([[260, 480], [1200, 480], [1650, 920], [180, 920]], dtypenp.float32) dst np.array([[0, 0], [960, 0], [960, 720], [0, 720]], dtypenp.float32) matrix cv2.getPerspectiveTransform(src, dst) warped cv2.warpPerspective(frame, matrix, (960, 720)) seat_map { 1: (60, 120, 160, 260), # x, y, w, h 2: (260, 120, 160, 260), } roi_1 warped[seat_map[1][1]:seat_map[1][1] seat_map[1][3], seat_map[1][0]:seat_map[1][0] seat_map[1][2]]透视变换的实际价值在三方面。第一把“斜着看”变成“正着看”目标检测模型在不同座位上的精度差异显著缩小。第二ROI 裁剪直接把一张大图切成了多个座位块检测器只需要处理小块区域显存占用降低推理速度提升。第三后续的姿态估计和时序模型吃的是固定尺度的输入特征分布更稳定。2.2 用 YOLOv8 检出“人”和“违禁物品”的 4 类目标目标检测在这个系统里承担的是“视线聚焦”功能。不需要检测桌椅、黑板这类无关目标只保留四个类别person、cellphone、book、glasses。这里的 glasses 不是普通眼镜而是带摄像头或显示屏的智能眼镜这类目标尺寸小常规训练容易漏检。选择 YOLOv8 而不是 Faster R-CNN主要理由是推理速度快且在边缘设备上部署容易。训练时使用 Cocodataset 的预训练权重做迁移学习把类别数改成 4。from ultralytics import YOLO import cv2 model YOLO(yolov8n.pt) frame cv2.imread(exam_room.jpg) results model.predict(frame, conf0.35, iou0.5, devicecuda:0, classes[0, 67]) for box in results[0].boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 map(int, box.xyxy[0]) label results[0].names[cls_id] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{label} {conf:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(detected.jpg, frame)conf0.35这个阈值值得解释。person 和 book 这类目标纹理明确0.35 已经足够。cellphone 和 glasses 在画面中尺寸小模型给出的置信度天然偏低如果阈值设到 0.5大量真实目标会被过滤掉。我一般会为小目标单独跑一次低置信度检测把conf降到 0.25再用 IoU 去重。classes[0, 67]指的是只从 COCO 类别中取 person 和 cellphone后续针对考场自建数据集扩展的类别通过在训练中修改数据配置完成。2.3 检不出小目标的补救方案多尺度切图YOLOv8 检测 cellphone 这类小目标时如果画面分辨率是 1080p 整帧推理小目标像素可能只有 20 到 30 个像素宽模型特征图中已经没有足够信息。常见的补救方式是先按 2.1 节里的 ROI 切图再把每个 ROI 做一次letterbox之后单独检测。这样目标相对分辨率变大召回率能提升 15% 到 25%代价是单帧推理次数增加。为了平衡速度我会做一步轻量预判先跑一次整帧低阈值检测找到 person 框只有 person 框内的 ROI 才做二次切图细检person 框外的区域直接跳过。3. 骨骼关键点与动作分类让系统理解“低头”和“传递小抄”3.1 MediaPipe 姿态估计与关键点可靠性目标检测只能知道“这里有个人”无法区分这个人是低头看书还是抬头看黑板需要引入骨骼关键点。MediaPipe Pose 是常用选择输入 RGB 图像输出 33 个关键点的归一化坐标和置信度。推理框架用 TensorFlow Lite 部署在 CPU 上单帧大约 10ms 到 25ms比 OpenPose 快一个数量级代价是遮挡场景下的关键点精度略低。关键点可靠性问题直接决定系统是否误报。当考生身体被桌沿遮挡时模型会给出较低的 visibility 值小于 0.35。实际项目中不能只看坐标必须同时读取每个点的 visibility。处理策略是把左右肩、左右髋、左右耳共 6 个点的 visibility 求平均值低于 0.5 时视为“姿态不可信”不再向后传递动作分类器。这个做法能把因关键点抖动导致的误报率降低约 30%。3.2 构建序列样本滑窗策略而非单帧判断作弊动作是连续过程单帧姿态无法表达“重复”“持续”这类时间特征。例如“长时间低头”和“低头捡笔”静态图像上几乎一样。常见做法是用一个固定长度的滑窗把连续 30 帧的关键点组合成一个序列样本交给时序分类器。窗口大小选择与关键的时间和帧率绑定。如果摄像头是 25fps30 帧大约对应 1.2 秒。1.2 秒的窗口足够覆盖“抬头环顾”“掏手机”“回头”这类动作窗口太长延迟会增加。import numpy as np pose_history [] def update_and_predict(landmarks, classifier, window_size30): pose_features [] for lm in landmarks: pose_features.extend([lm.x, lm.y, lm.visibility]) pose_history.append(pose_features) if len(pose_history) window_size: return None segment np.array(pose_history[-window_size:], dtypenp.float32) segment (segment - segment.mean(axis0)) / (segment.std(axis0) 1e-6) sequence_input segment.reshape(1, window_size, -1) prob classifier.predict(sequence_input)[0] return prob这里做了两步关键处理。第一步是把每帧的 33 个关键点坐标和 visibility 拼接成长度为 99 的特征向量。第二步是帧间标准化用窗口内的均值和标准差做归一化而不是用全局统计值。这样能抵消不同考生身高体型的差异同一套分类器在不同座位距离上表现更稳定。3.3 时序分类器的选型LSTM 和 1D-CNN 组合动作分类器可以用 LSTM也可以考虑 1D-CNN。LSTM 的优势是天然适合变长序列和长距离依赖训练时对时间步的语义建模好缺点是在小数据集上容易过拟合且推理速度慢。1D-CNN 的卷积核只沿时间轴滑动参数量小训练速度快延迟约 5ms但对长程关系的建模弱。3.3.1 混合结构的实现与损失函数实际项目里我一般用两层 BiLSTM 加一层 1D-CNN 的并联结构。BiLSTM 分支捕获正反两个方向的时间依赖1D-CNN 分支提取局部动作模式两个分支的特征拼接后过全连接层。训练时用交叉熵损失加一个中心损失正则项让同类样本的特征向量在特征空间更紧凑。import torch import torch.nn as nn class ActionClassifier(nn.Module): def __init__(self, input_dim99, hidden_dim128, num_classes4): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers2, batch_firstTrue, bidirectionalTrue) self.conv nn.Sequential( nn.Conv1d(input_dim, 64, kernel_size3, padding1), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.fc nn.Linear(hidden_dim * 2 64, num_classes) def forward(self, x): lstm_out, _ self.lstm(x) lstm_feat lstm_out[:, -1, :] conv_feat self.conv(x.permute(0, 2, 1)).squeeze(-1) feat torch.cat([lstm_feat, conv_feat], dim-1) return self.fc(feat)损失函数选择上常见分类任务会用nn.CrossEntropyLoss但监考动作天然存在长尾分布。正常答题动作占据了 90% 以上的样本作弊动作占比极少。只用交叉熵会让模型倾向于把所有样本都预测为“正常”。我会在损失中为每个类别单独放权值把作弊类别的权重调高到正常类别的 5 倍以上具体数值根据验证集上的召回率动态调整。epoch 数一般设置在 60 到 80训练时用早停法验证集F1 score连续 10 个 epoch 不提升就停止。4. 从单帧到流式推理智能监考系统的实时性设计4.1 事件驱动的级联推理避免 GPU 常驻满载如果每帧都跑完整链路一张 RTX 3060 级别的显卡大约能同时处理 4 到 6 路 1080p 画面。但对监考系统来说大部分时间画面是静止的考生没有异常动作。长期满载推理不仅浪费算力还会让风扇噪声影响考场环境。常规方案是两级事件驱动。第一级用轻量级背景建模计算相邻帧的像素差异如果差异均值低于阈值说明画面基本静止直接跳过后面的深度学习推理。只有检测到画面变化达到一定阈值时才唤醒目标检测与姿态估计。这个机制能把系统平均功耗降低 60% 以上。4.2 关键参数表置信度、窗口长度、帧率与采样策略如果说系统里每个模块都有几个必须调的参数那下面这张表是最核心的集合。注意力不要放在背诵数值上要看参数之间的联动关系。比如帧率提高窗口长度可以缩短置信度提高误报减少但漏报增加。参数推荐值调整方向与影响检测置信度 conf普通目标 0.35小目标 0.25调高则误检少但小目标漏检调低则召回高但需去重NMS IoU0.5调高会减少重叠框的合并两人紧邻时易丢框姿态可见度门限0.35低于门限的关键点直接丢弃防止噪声传给时序模型时序窗口长度30 帧帧率 25fps 约 1.2 秒窗口长则延迟高短则动作语义不完整滑窗步长5 帧步长越小时间重叠越多识别平滑但计算量变大事件触发帧差阈值90-255 灰度调高会漏掉小动作的启动帧调低会增加无效唤醒异常类别损失权重5 倍权重高提升召回率但过高会出现大量误报推理间隔每 3 帧一推减少冗余计算但幅度过大的快速动作有可能跨帧错过4.3 行为标签的状态转移逻辑减少单帧打标抖动模型输出的是 4 类动作概率normal、looking_down、turning_around、reaching_below_desk。直接对每帧取 argmax 会导致标签频繁跳动比如上一帧是 normal下一帧变成 looking_down再下一帧又变成 normal。工程上的解决方法是用一个有限状态机做状态平滑。只有某个异常标签连续出现 3 帧以上状态才真正迁移为异常状态迁移后在 5 秒内不再切回正常。这样既保证了报警不是瞬时噪声又避免了一个长时间异常被正常帧打断造成漏报。from collections import deque class BehaviorStateMachine: def __init__(self, stable_frames3): self.stable_frames stable_frames self.current_state normal self.buffer deque(maxlenstable_frames) def update(self, action_id): self.buffer.append(action_id) if len(self.buffer) self.stable_frames: return self.current_state recent list(self.buffer) majority_count {a: recent.count(a) for a in set(recent)} dominant max(majority_count, keymajority_count.get) if dominant ! normal and majority_count[dominant] 2: self.current_state dominant elif len(self.buffer) self.stable_frames: self.current_state dominant return self.current_state这种方法比传统的均值滤波更符合业务需求。均值滤波会把持续 1 秒的甩头动作认为是一个轻微异常然后抹平而状态机允许短暂异常被忽略却会让持续异常锁定在异常状态上。实际部署时我会把这个状态机的输出和检测器结果做合并。例如状态机判断reaching_below_desk的状态持续超过 10 秒且同时刻 person 框附近检测到了 cellphone系统才生成一条高置信度的告警记录。4.4 多摄像头调度单路 GPU 怎么多做几路考试多摄像头场景下CPU 先做运动检测把画面变化最明显的两路摄像头排在 GPU 队列最前面剩余路数按 1 秒间隔轮询。考试期间大部分考生都在正常写字运动变化幅度小这种策略能保证异常动作总是第一时间被分析。多路画面进入系统后用考场 ID 与座位号作为主键存储检测结果后续查错时能精确回放每一路画面的推理日志。调度算法的核心不是把路数做满而是保证算力永远优先给到“最可能有情况”的画面。判断哪些路可能异常用的还是帧差阈值但变成 GPU 之前的 CPU 任务成本几乎为零。5. 模型压缩与阈值修正交付前必做的收敛手段5.1 TensorRT 加速与 INT8 量化的取舍姿态估计和动作分类模型上真机前直接跑 PyTorch 或 TFLite 通常不是最优解。NVIDIA 显卡上使用 TensorRT 做 FP16 推理速度能提升 1.5 到 2 倍再激进一点做 INT8 量化速度提升到 2.5 倍以上但精度损失明显尤其是小目标检测和关键点回归。监考场景对召回率的要求高于速度所以只对动作分类器做 INT8 量化关键点模型保持 FP16。一个值得复用的验证技巧是量化后先在白天考场画面上跑 1000 帧统计所有动作类别的平均概率分布如果异常类别的平均概率下降超过 5%就回退到 FP16用这个标准判断量化是否可接受。5.2 误报消减技巧加装目标存在性先验大部分误报来自“人形框抖动”和“关键点跳变”的组合。观察告警日志时一个典型模式是考生伸手找笔MediaPipe 短暂把腕部映射到了桌子上方动作分类器给出reaching_below_desk。为了压低这类虚警可以在动作分类之外再叠加一个目标关系约束。具体做法是维护一个布尔数组记录当前 person 框内是否存在 cellphone 或 book。只有当动作分类得分超过 0.7且目标存在性先验被满足时才算有效告警。这个简单的外部约束在实际运行中能把误报数量降低一半不需要重新训练。5.3 用温启动与冷启动测试验证系统稳定性交付现场最头疼的是摄像头画面和训练集分布不一致。常见做法是收集考场布置图和现场视频的 10 分钟截帧计算颜色均值和边缘分布和训练集的均值偏差超过 20% 时触发告警提示重新做透视标定。除了数据分布验证推理延迟也是需要监控的指标。我会写一个把frame_timestamp、detection_end_time、action_end_time同时记录的日志模块每输出一条告警都能算出端到端延迟。如果延迟超过 800ms先检查是不是检测阶段 wait 了 GPU再看事件触发是否把大图重复投递到了模型。时间戳的统一才能让后续排查有据可依。5.4 回放校验用确认率替代单点评估单独跑一个测试集算F1 score只能说明模型在离线数据上表现到位不能证明现场可用。比较建议做的是“回放确认率”验证让监考老师观看系统标记的告警片段判断是否真的属于作弊行为人工确认数量占系统告警数量的比例。第一轮调整后如果确认率低于 60%先不要动模型检查事件触发阈值是否太敏感。再对触发类型做一次分类统计把占比最高的误报警类型作为针对性优化的下一轮目标。整套收敛路径完成后再评价系统的延迟和召回率。本文还有配套的精品资源点击获取
