最近帮一位做智慧监考方向的朋友梳理了一套“基于深度学习的考试作弊检测系统”,他把整个项目从数据准备、模型训练到 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 在作弊检测这种中小目标密集的场景下综合表现最好。从原理上理一下各个版本的区别:YOLOv5:anchor-based 时代最有代表性的版本。项目成熟、社区资料多、部署工具最完善,在小数据集场景下不容易过拟合,转 ONNX 后几乎所有设备都能跑。YOLOv8:anchor-free 架构,C2f 模块替代了原来的 C3,对不同尺寸目标更友好。同时自带实例分割能力,虽然作弊检测用不太上,但如果你打算扩展分析“手机和手部是否在同一框内”,v8 的 mask 输出比单纯检测框更可靠。YOLOv11:anchor-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 以上级别显卡)epoch:300,配合 early stoppingbatch size:16(8G 显存建议 8,否则容易 OOM)optimizer:SGD,momentum 0.937,weight_decay 0.0005warmup:3 epoch,初始 lr 0.01开启 AMP(混合精度训练)这几组参数至少有一半是“试错试出来的”。比如 optimizer,第一次直接用 AdamW,loss 降得挺快,但收敛后 mAP 反而不如 SGD。原因也好理解:这个数据集规模不大,AdamW 的逐参数自适应学习率在小数据集上容易过拟合;SGD + 合理 warmup 收敛慢,但泛化更稳。各类别的 loss 权重也要调。项目初期 phone 类召回率很高,paper 类却一直上不去。原因是 paper 目标太薄、太小,而且在不同光照下颜色差异巨大。后来在 loss 里给 paper 类单独加了权重,同时把 paper 类的正样本通过复制粘贴增强(在图片随机位置贴小纸条图块)扩充了一倍,才算把召回率拉到可用水平。关于类别不平衡,有个土办法很管用:打印每个 batch 里各类目标的数量,就很容易定位哪一类样本不够。不要光看最终 mAP,mAP 对稀有类别的表现不敏感,要看每个类别的 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 model=yolov8n-cheating.pt format=onnx dynamic=Truedynamic=True 保证输入尺寸可以动态调整。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 震荡。检查学习率是否过大,我试过 lr=0.1 直接爆掉,后来 lr=0.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、给评委或客户现场演示,用这个方案基本一个周末就能跑出比预期好不少的效果。
最近帮一位做智慧监考方向的朋友梳理了一套“基于深度学习的考试作弊检测系统”,他把整个项目从数据准备、模型训练到 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 在作弊检测这种中小目标密集的场景下综合表现最好。从原理上理一下各个版本的区别:YOLOv5:anchor-based 时代最有代表性的版本。项目成熟、社区资料多、部署工具最完善,在小数据集场景下不容易过拟合,转 ONNX 后几乎所有设备都能跑。YOLOv8:anchor-free 架构,C2f 模块替代了原来的 C3,对不同尺寸目标更友好。同时自带实例分割能力,虽然作弊检测用不太上,但如果你打算扩展分析“手机和手部是否在同一框内”,v8 的 mask 输出比单纯检测框更可靠。YOLOv11:anchor-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 以上级别显卡)epoch:300,配合 early stoppingbatch size:16(8G 显存建议 8,否则容易 OOM)optimizer:SGD,momentum 0.937,weight_decay 0.0005warmup:3 epoch,初始 lr 0.01开启 AMP(混合精度训练)这几组参数至少有一半是“试错试出来的”。比如 optimizer,第一次直接用 AdamW,loss 降得挺快,但收敛后 mAP 反而不如 SGD。原因也好理解:这个数据集规模不大,AdamW 的逐参数自适应学习率在小数据集上容易过拟合;SGD + 合理 warmup 收敛慢,但泛化更稳。各类别的 loss 权重也要调。项目初期 phone 类召回率很高,paper 类却一直上不去。原因是 paper 目标太薄、太小,而且在不同光照下颜色差异巨大。后来在 loss 里给 paper 类单独加了权重,同时把 paper 类的正样本通过复制粘贴增强(在图片随机位置贴小纸条图块)扩充了一倍,才算把召回率拉到可用水平。关于类别不平衡,有个土办法很管用:打印每个 batch 里各类目标的数量,就很容易定位哪一类样本不够。不要光看最终 mAP,mAP 对稀有类别的表现不敏感,要看每个类别的 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 model=yolov8n-cheating.pt format=onnx dynamic=Truedynamic=True 保证输入尺寸可以动态调整。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 震荡。检查学习率是否过大,我试过 lr=0.1 直接爆掉,后来 lr=0.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、给评委或客户现场演示,用这个方案基本一个周末就能跑出比预期好不少的效果。