考场作弊检测系统落地:YOLOv5/v8/v11/v12训练与Django部署全记录
最近帮一位做智慧监考方向的朋友梳理了一套“基于深度学习的考试作弊检测系统”他把整个项目从数据准备、模型训练到 Web 部署完整跑通还做了演示页面。我借着这个项目把 YOLOv12/v11/v8/v5 几个版本的实际表现、训练细节、Django 集成方案、以及现场踩过的坑从头到尾整理了一遍。这篇文章没什么玄乎的东西就是一套能直接参考的落地记录适合正在做目标检测项目、毕业设计、或者想把手头模型快速接到 Web 端的人。1. 从考场监控到作弊识别这个系统到底做了什么1.1 作弊检测不是“玄学”而是目标检测的落地场景很多人听到“作弊检测”第一反应是行为识别、姿态估计、甚至时序分析但实际做起来就会发现考场上最典型的作弊动作用目标检测就能解决大部分。比如考生低头看手机、手部在桌面下方翻小抄、频繁转头偷看邻座、传纸条等这些动作的共同点是都有明显的“目标对象”手机、小抄、纸条、手部动作。把这些对象从监控画面里框出来按位置和时序做简单判断就能达到较高的预警准确率。本质上这是一个非常标准的二维目标检测问题不需要像行为识别那样处理时空序列也不需要做骨骼关节点检测。这套系统的核心就是把 YOLO 系列模型用在监控摄像头画面里识别出可疑目标并给出告警。整个项目的完整链路是摄像头画面采集 → 视频帧送到 YOLO 模型推理 → 检测到可疑目标后框选并标记置信度 → 后端把结果推送到 Web 页面 → 监考人员在浏览器上实时查看。这个链路里涉及数据、模型、后端、前端四个环节缺一个都跑不通。1.2 系统整体架构模型、后端、前端如何串起来系统的技术选型是 YOLO 做检测Django 做后端服务前端用普通 Web 页面做展示。为什么用 Django 而不是 Flask因为项目还要承载用户登录、历史记录、检测结果统计报表这些功能Django 自带 Admin 后台、ORM 和用户认证省了太多造轮子的时间。架构上分三层模型层用自定义数据集训练 YOLOv5/v8/v11 模型导出权重文件通过脚本做推理测试。服务层Django 负责接收上传图片或视频流调用模型推理保存检测结果。展示层Web 页面实时显示检测框、置信度、告警日志支持阈值调节。这里有一个需要提前想清楚的点检测系统不是“识别作弊”本身而是“识别可疑目标”。模型输出的是“手机”“纸条”“手部遮挡”这类目标框至于这人是不是真作弊了还要靠监考人员判断。这个定位很重要能避免把系统做成一个过度承诺的“自动判罪工具”。实际部署中这套系统承担的是“辅助巡检”角色帮监考老师从几十个画面里快速锁定可疑位置。2. 数据集与预处理检测效果的地基2.1 数据从哪里来仿真自建 公开数据组合这个项目最花时间的不是调模型参数而是数据处理。作弊检测这个场景不像行人检测、车牌识别那样有现成的公开数据集可用公开数据里几乎没有针对考场环境的标注数据。所以项目组用的是“仿真自建为主、公开数据为辅”的组合策略。具体操作是找一间空闲教室按真实考场布置桌椅请同学配合模拟各种作弊动作用手机和摄像头从不同角度录制视频再抽帧标注。为了增加场景多样性刻意在几种光照条件下拍摄上午自然光、傍晚灯光、侧面窗户逆光。这些细节决定了模型在真实考场能不能稳定工作。数据构成大致是自建仿真数据60%的样本覆盖坐姿、手部遮挡、桌面物品等场景公开数据集主要是 COCO 里和手机、水杯可能藏纸条、书本相关的类别用来补充普通物体的泛化能力负样本20%的纯考场正常画面专门用来压制误检负样本这一点很多人会忽略。如果你只给模型看“有作弊行为”的正样本模型会倾向于把一切手部动作都当成可疑目标误检率会高到没法用。加入大量正常考试画面的负样本之后模型才学会区分“手扶着额头思考”和“手在桌下翻东西”。2.2 标注细节与数据增强类别怎么定框怎么打标注类别不能拍脑袋。最初项目组只设了“cheating”一个类别结果训练出来完全没法用目标太小、背景太杂模型学不到有效特征。后来把类别拆分成细粒度目标效果明显提升。最终类别定义phone手机包括拿在手里、放在桌面上、藏在膝盖上paper小抄、纸条、书本主要覆盖桌面和手部附近的纸质物品hand_occlusion手部在桌面以下或遮挡其他目标的异常手势turn_around转头张望这个用目标框其实表达不充分但作为辅助特征还是有用标注工具用的 LabelImg格式转成 YOLO 格式的 txt。需要注意作弊行为的目标通常很小比如手机在画面里可能只有几十个像素标注时框一定要贴边不能像标行人那样留白太多。数据增强方面项目组并没有盲目堆参数。做了以下处理Mosaic 增强在训练前期使用可以显著提升小目标检测能力随机旋转、平移、缩放模拟摄像头角度变化HSV 扰动模拟不同光照不用的增强没有用 Cutout 和 MixUp因为考场场景不能有遮挡太严重的情况试了几轮发现 Cutout 反而让检测更不稳定如果重新做这个项目数据准备阶段可以再快一点先录 2 小时视频抽 500 张图快速验证可行性再逐步扩充到完整的数据集。不要一开始就追求上万张图因为标注成本太高而且前期根本不知道模型能不能收敛。3. YOLOv12/v11/v8/v5 模型选型与训练3.1 四个版本的差异不能只看“新版本更好”项目里同时训练了 v5、v8、v11 和 v12 四个版本不是为了堆工作量而是想弄清在资源有限的条件下哪个版本最实用。这里先说结论新版不一定适合所有场景v8 在作弊检测这种中小目标密集的场景下综合表现最好。从原理上理一下各个版本的区别YOLOv5anchor-based 时代最有代表性的版本。项目成熟、社区资料多、部署工具最完善在小数据集场景下不容易过拟合转 ONNX 后几乎所有设备都能跑。YOLOv8anchor-free 架构C2f 模块替代了原来的 C3对不同尺寸目标更友好。同时自带实例分割能力虽然作弊检测用不太上但如果你打算扩展分析“手机和手部是否在同一框内”v8 的 mask 输出比单纯检测框更可靠。YOLOv11anchor-free C3k2 架构官方宣传是“精度和速度更好的平衡”。实际测试下来小目标召回率确实比 v8 好但模型体积略微增大Django 里做 CPU 推理时延迟会高一档。YOLOv12重点是注意力机制改进作者声称在 COCO 上精度最高。但实际训练中发现作弊检测这种数据集规模不大、背景相对固定的场景注意力机制带来的提升有限反而训练收敛更慢、对参数更敏感。这里说一个很容易被忽略的细节v8、v11、v12 的“anchor-free”不是完全不用锚框而是在解码阶段用中心点 距离回归替代了预设 anchor 的匹配。对应到考试画面里手机的尺寸、长宽比在摄像头俯视角度下相对稳定anchor-based 的 v5 其实已经够用anchor-free 的优势主要体现在目标尺寸变化剧烈的场景。所以如果你只有很小的数据集v5 反而是最不容易翻车的选择。3.2 训练参数与实操过程中的关键调整项目组在训练中踩了不少坑有几个参数调整经验值得记录。首先是图片尺寸。检测作弊场景里的小目标非常多输入分辨率直接决定检测上限。用默认的 640 够了我实际加到 800性能有可感提升但显存也跟着上去了。如果显卡只有 8G 显存建议就用 640 加 mosaic不要贸然加大分辨率。训练参数的参考配置输入尺寸640进阶可以试 800但需要 T4 以上级别显卡epoch300配合 early stoppingbatch size168G 显存建议 8否则容易 OOMoptimizerSGDmomentum 0.937weight_decay 0.0005warmup3 epoch初始 lr 0.01开启 AMP混合精度训练这几组参数至少有一半是“试错试出来的”。比如 optimizer第一次直接用 AdamWloss 降得挺快但收敛后 mAP 反而不如 SGD。原因也好理解这个数据集规模不大AdamW 的逐参数自适应学习率在小数据集上容易过拟合SGD 合理 warmup 收敛慢但泛化更稳。各类别的 loss 权重也要调。项目初期 phone 类召回率很高paper 类却一直上不去。原因是 paper 目标太薄、太小而且在不同光照下颜色差异巨大。后来在 loss 里给 paper 类单独加了权重同时把 paper 类的正样本通过复制粘贴增强在图片随机位置贴小纸条图块扩充了一倍才算把召回率拉到可用水平。关于类别不平衡有个土办法很管用打印每个 batch 里各类目标的数量就很容易定位哪一类样本不够。不要光看最终 mAPmAP 对稀有类别的表现不敏感要看每个类别的 AP 分布。3.3 模型部署时不可不知的几个细节这部分是项目里最容易让人崩溃的环节。模型训练好了要集成到 Django 里有几个细节必须处理好。第一模型加载时机。YOLO 模型加载一次可能要 1-2 秒v11/v12 更慢如果每次请求都重新加载Web 页面基本没法用。正确做法是在 Django 应用启动时预加载模型放到全局变量或者单独写一个 ModelManager 单例。第二GPU 显存管理。如果服务器同时跑训练和推理显存很容易爆。项目组的做法是训练和推理用不同的 conda 虚拟环境推理端直接用 CPU 也能跑出 3-5 FPS 的效果足够应付教室监控的巡检场景。第三模型导出格式的选择。YOLO 官方推理时用的 .pt 权重直接放在 Django 里跑没问题但如果你想优化推理速度可以导出成 ONNX 再用 ONNX Runtime 做推理。导出命令是yolo export modelyolov8n-cheating.pt formatonnx dynamicTruedynamicTrue 保证输入尺寸可以动态调整。ONNX 在 CPU 上的推理速度比 PyTorch 原生快 20% 左右部署体积也更小。v5 转到 ONNX 也非常成熟。第四NMS 参数要按场景调。默认的 IoU 阈值 0.45 在作弊场景可能不够因为你经常会遇到“手部遮挡”和“手机”两个框高度重叠的情况。项目里做了实验IoU 阈值调低到 0.3 后重叠目标的检测率提升明显但代价是同一个目标可能被输出两个框需要在后处理里做一个简单的“框去重”逻辑。4. Django 后端与模型推理集成4.1 视图层设计图片、视频、实时流三条处理链路Django 集成的整体思路是检测逻辑放在独立的 service 模块视图层只负责接收请求和返回响应。这样如果你想换掉 YOLO 换别的检测框架只需要替换 service 内部实现。项目里设计了三个核心接口POST /api/detect-image上传图片返回检测框坐标、类别、置信度POST /api/detect-video上传视频文件按抽帧间隔逐帧检测返回带检测结果的帧列表GET /api/detect-camera实时摄像头画面流使用 HTTP 流式响应逐帧推送检测画面图片检测是最简单的视频文件检测要特别小心中文字符路径和视频编码问题。Django 处理上传文件时的命名我加了一个 uuid 重命名逻辑避免中文乱码导致 OpenCV 读取失败。视频流这里补充说明性能设计摄像头实时流用 OpenCV 读取后每隔 5 帧检测一次其他帧直接透传画面观感是流畅的CPU 占用率也能压得住。如果是 GPU 服务器可以做到每帧检测延迟约 120ms 左右基本可以说实时。4.2 同步阻塞问题为什么视频检测在 Django 里会卡死项目推进过程中踩过最深的坑是视频检测接口一跑整个 Django 服务就“假死”。排查后发现是同步阻塞问题。Django 默认是同步 WSGI 服务一个请求进来之后如果视图里跑了一段耗时的视频检测比如处理一个 20 秒的视频要花 5 秒此时其他请求包括静态资源请求都会被阻塞。浏览器里最直观的表现就是页面转圈甚至第一次请求还没返回第二次请求就超时了。解决办法有几种按推荐程度排列视频抽帧检测改成异步任务前端先拿到任务 ID轮询进度和结果用线程池/进程池把检测放到后台执行视图立刻返回用多 worker 启动 Gunicorn但要注意共享内存模型占用会翻倍项目里选择了方案二用 Python 的 concurrent.futures.ThreadPoolExecutor 做一个简单的检测任务池任务完成前前端轮询进度接口。这个方案实现成本低也不引入额外的消息队列依赖。还有一个相关问题是模型推理线程安全。PyTorch 模型在多个线程里并行推理时如果不做任何处理可能因为 CUDNN 或 autograd 状态异常导致崩溃。我的做法是加一个全局锁保证同一时间只有一个检测任务在跑单并发推理如果你想支持多并发可以在每个线程里加载一份模型副本而不是共享一个模型实例。4.3 检测结果序列化与数据库落盘检测结果是嵌套结构一张图里有多个目标每个目标有类别、坐标、置信度。序列化方案决定前端用起来顺不顺如果设计得不好前端解析时会非常痛苦。项目里定义了一个统一的接口返回结构{ code: 0, data: { image_url: /uploads/xxxx.jpg, detections: [ {class: phone, conf: 0.87, bbox: [140, 200, 260, 320]}, {class: paper, conf: 0.63, bbox: [512, 330, 640, 430]} ] } }这里 bbox 的格式用的是 x1y1x2y2 绝对像素坐标不是 YOLO 的归一化中心格式。这个转换在视图层做前端不用关心模型内部格式。检测记录会存到数据库方便监考人员事后回溯。表结构很简单id、图片路径、检测类别、置信度、创建时间。如果后续做统计报表再按时间段聚合即可。数据库这层有个小坑检测结果可能非常稀疏一节课几百张图只有几张有告警需要给 image_url 和 create_time 建索引否则后期按时间查历史记录时会越来越慢。5. 演示页面设计与交互体验5.1 核心功能与布局让监考人员 3 分钟上手项目里花了不算少精力在前端页面交互上核心目标是监考人员不用看产品说明书打开页面就知道怎么用。页面整体是左右结构左侧是功能面板右侧是检测画面。功能面板按顺序排了四个操作区图片上传检测、视频文件检测、摄像头实时流、参数调节面板。每个操作区都是独立卡片卡片里直接写清楚输入方式和预期结果。参数调节面板做了滑动条实时调节置信度阈值从 0.1 到 0.9 连续可调。滑动的过程里检测框实时刷新前端把当前阈值发给后端后端重新过滤已有的检测结果这样监考老师可以快速看到不同阈值对告警结果的影响而不是被固定阈值卡死。检测画面的展示做了几层信息叠加检测框用颜色区分类别黄色是手机、红色是手部遮挡、蓝色是纸条每个框右上角显示置信度百分比画面底部有一个状态条当出现置信度高于 0.7 的目标时状态条会变成告警色并显示类别和出现时间。这个设计逻辑是“渐进式告警”不管置信度多低所有检测结果都会展示但告警只在高置信度时触发。因为作弊嫌疑的判断存在模糊性把两种信息拆开能避免低置信度的误报警扰得监考人员麻木。前端用的是纯 HTML JS Bootstrap没有上重型前端框架。项目里后续如果要加复杂交互可以换成 Vue 或 React但单就演示系统来说Bootstrap 够用了。如果怎么写 JS 渲染耗性能简单做法是用 canvas 画检测框比频繁操作 DOM 稳定得多尤其画面每秒刷新几帧的情况下。5.2 前端防抖与轮询细节不卡顿、不崩页面页面里最容易出体验问题的地方是实时视频流 检测结果的交互。后端每 0.5 秒推送一个检测结果帧如果前端直接把这帧的检测结果 append 到 DOM整个页面很快会卡死。解决方案是前端做“排队渲染”用一个变量保存最新的检测结果用 requestAnimationFrame 循环去画 canvas而不是每次收到推送就立刻画。这样即使后端推了很多帧前端也只有每一帧渲染周期画一次最新结果大幅降低无效渲染开销。视频文件检测的进度条用了轮询方案。前端每隔一秒请求一次异步任务进度接口拿到进度后更新进度条。注意在轮询时要处理任务失败的情况比如视频文件内部有问题后端标记任务失败前端要能检测到并弹提示否则进度条卡在某一数值会让用户以为服务卡死。摄像头实时流有一个浏览器兼容性的问题如果直接用 WebRTC 拿摄像头画面需要 HTTPS 环境本地开发可以在浏览器安全设置里放行但部署到服务器时必须配 HTTPS。项目里绕开了这个麻烦实时流由后端推送处理后的视频画面前端不做摄像头采集这样就不受浏览器安全策略限制。不过如果你需要在客户端本地做画面预览要注意这个 HTTPS 约束。5.3 多路摄像头支持从单画面到九宫格演示系统最开始只支持单路画面但实际考场有多个摄像头。第二阶段迭代里加上了多路支持后端维护一个摄像头列表IP 摄像头、本地上传的 RTSP 流、U盘视频文件前端一次展示最多 9 路画面按九宫格布局。多路画面的展示不是简单铺几个 canvas 就行需要考虑单页资源占用。项目里做了一个“激活卡片”的机制默认所有画面都播放但只有当前聚焦的一路做实时检测其他路只做视频流展示画面是减速的。用户点击任何一路画面它才变成主动检测模式。这个机制能把多路总开销控制在单路检测的 1.5 倍左右而不是 9 倍。如果后续有人要做多路实时检测的硬件方案建议别再用 Django 这台服务器硬扛了。比较合理的做法是每路摄像头用一个低功耗设备或者边缘盒子做模型推理后端只做结果汇聚和展示。这样不仅延迟更低而且不会出现单点故障影响全部画面。6. 常见问题与排查技巧实录6.1 训练阶段收敛不理想与显存不足训练阶段的坑排在前面因为问题比较早暴露模型不收敛loss 震荡。检查学习率是否过大我试过 lr0.1 直接爆掉后来 lr0.01 配 warmup 就很稳了。如果加了自定义增强先关掉增强试一个过拟合版本看模型本身能不能学到特征。显存不足out of memory。这个在 8G 显卡上很常见把 batch size 从 16 降到 8图片尺寸从 640 降到 512同时开启 AMP基本能压到 7G 以内。如果还是 OOM检查是不是开了过多的 DataLoader worker子进程占显存是很隐蔽的问题。训练后期 mAP 停止提升。先看每个类别的 AP确定是哪个类别拉胯再针对性做数据扩充或者调 loss 权重。盲目加 epoch 没用可能只是过拟合。6.2 部署阶段模型能跑但检测框和实际目标对不上部署后最常见的现象是检测框的位置是对的但目标很小、框框有点歪或者手机在画面里只有 20 个像素时压根检测不到。这两种情况的处理方向不同。框不准通常和数据集标注质量有关。检查标注文件里是否有目标框太小、或者标注框偏移明显的情况。YOLO 的 anchor-based 方法v5对小目标的召回天然偏弱如果小目标占比高考虑换 v8/v11 的 anchor-free 方案。如果确实检测不到小目标先试输入分辨率从 640 提至 768。看显存情况通常提高输入分辨率是小目标检测最有效的办法比改模型架构简单粗暴。帧抽检时漏检率升高的问题实时视频流每 5 帧检测一次如果你看到某一个帧检出来了下一帧没有检测的目标又消失了视觉上会感觉系统很“不稳定”。解决方法是后端把最近一次检测的框做 Buffer在非检测帧中沿用上一个框并用简单跟踪算法平滑位置。具体而言可以对每个目标的中心点做指数滑动平均避免出现剧烈的跳变。6.3 性能与并发系统响应变慢的排查清单系统上线后使用者会反馈“今天怎么这么卡”。排查时先按以下清单走看 Django 进程 CPU 占用率如果接近 100%说明是模型推理占满了单核PyTorch 在多核 CPU 上未必自动并行可以显式设置 torch.set_num_threads看内存占用是否在爬升视频检测时频繁创建帧数组可能造成内存泄漏。重点排查 OpenCV 摄像头视频流是否执行了 release没有 release 的话每路流都是个内存黑洞看数据库写入是否变慢检测结果频繁 insert 时批量提交替代逐条提交能减轻压力看前端页面是不是在疯狂轮询轮询间隔 0.5 秒可能会打爆后端带宽合理值是 1-2 秒这些坑如果不提前处理部署现场往往是大家一起“在线等结果”然后卡死。项目里后期把这套排查清单直接写成了运维文档出了问题按清单排查比看日志瞎猜快得多。6.4 提供一个避坑总结速查表再整理一个速查表方便后续直接抄作业。问题现象可能原因推荐解法训练 loss 震荡学习率过大 / 增强过强lr 降到 0.01调小或关闭增强小目标检测不到输入分辨率低 / 数据里小目标少拉高分辨率到 768补充小目标样本视频检测卡死整个服务Django 同步阻塞检测放线程池前端轮询进度多线程推理崩溃模型实例线程不安全加全局锁或每线程加载独立模型副本检测框和目标偏移标注框不贴边重新检查标注剔除偏移样本阈值调高后召回骤降类别不平衡 / 有效置信度偏低调 loss 权重增加对应类别样本页面刷新很慢重复推断 重复渲染 DOM前端做帧缓存渲染后端做检测频率限制这个表格是项目进入稳定期后总结的基本覆盖了从离线训练到线上使用的 90% 问题。剩下 10% 的问题基本和硬件环境强相关比如 RTSP 流断连、显卡驱动版本和 CUDA 不匹配等这些就得靠部署现场的耐心排查了。最后提一句个人体会如果你准备在类似场景安防、行为识别、智慧校园做目标检测项目不要一上来就追最新最大的模型。项目用下来的直观感受是YOLOv8 的综合性价比最高v5 胜在简单稳定v11/v12 适合数据集比较大、硬件条件不错的场景。先把数据标好、链路跑通再考虑模型升级才是这类项目能持续迭代的正路。Django 和 YOLO 的组合做演示系统非常快如果你要做的是一次性 Demo、给评委或客户现场演示用这个方案基本一个周末就能跑出比预期好不少的效果。