拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于YOLOv8与Django的课堂学生行为识别系统实战:从数据标注到部署避坑

简介这份资源是面向高校计算机相关专业毕业设计场景的完整项目包主题为基于Python深度学习的上课学生行为识别系统采用Django框架与MySQL数据库开发编程环境覆盖PyCharm、VSCode与MySQL8.0技术栈以YOLOv5和OpenCV为核心并配套论文、开题报告与答辩PPT适合需要完成同类课题或学习目标检测落地应用的学生与开发者参考。系统以后台管理形式实现区分管理员与学生两类权限管理员负责学生注册审核、课程信息维护、行为检测记录查询与通知发布学生端可修改个人信息、查看通知并通过打开摄像头选择当前课堂课程实时检测玩手机、睡觉、举手等课堂行为。压缩包共705个文件约299.74MB包含44个py源码、50个js脚本、13个html页面及21个css样式文件另有224个png、207个jpg与96个gif图像素材以及数据库文件与说明文档便于直接部署与二次开发。目前已有110人学习下载可作为毕业设计选题、系统搭建与深度学习行为识别实践的完整参考方案。1. 从一份毕设需求说起上课学生行为识别系统到底在识别什么课堂场景下的学生行为识别本质上是把一段教室监控视频拆成若干帧对每一帧里的每个学生做检测再判断这个人当前处于听讲、低头写字、趴桌、举手还是扭头说话的状态。它和通用动作识别最大的区别在于目标密集、遮挡严重、动作幅度小而且趴桌子和低头看书在单帧图像上几乎长得一样。这也是为什么很多同学拿现成的 YOLO 权重直接跑教室视频mAP 掉得厉害——模型没见过这种俯拍、小目标、密集排列的分布。这套上课学生行为识别系统通常以 Django 作为 Web 后端Python 做深度学习推理前端展示实时画面、统计报表和告警记录。它适合三类人正在做毕设需要一套完整可跑通方案的学生、想把课堂考勤与专注度分析落地的教育信息化开发者、以及想用 Django 深度学习练手全栈 AI 项目的工程师。下面我按数据怎么来 → 模型怎么训 → Django 怎么接 → 坑在哪的顺序把这条链路讲透。2. 数据与标注课堂行为数据集怎么攒、怎么标才不返工2.1 为什么通用数据集在教室场景会翻车公开的行为识别数据集大多来自影视片段或体育动作视角是平视、目标大、背景干净。教室监控是高位俯拍一个 1080P 画面里可能塞进 40 个学生每个人只占 60×120 像素左右。这种尺度差异会让在 COCO 上预训练的检测头直接失效——小目标的特征在多次下采样后基本被抹平了。我一般的做法是检测阶段用 YOLOv8n 或 YOLOv8s输入尺寸拉到 960 或 1280而不是默认的 640。分类阶段不单独训一个动作网络而是把行为作为检测框的一个属性用多标签分类头输出。这样一套权重同时给出人在哪和在干什么工程上少维护一个模型。类别定义要克制。新手最容易犯的错是列十几类听讲、写字、举手、趴桌、转头、玩手机、交头接耳、站立、走动……实际标注时你会发现相邻类别边界模糊标注一致性极差训出来的模型置信度全卡在 0.4 上下。我建议先收敛到 5 类听讲含看黑板、低头写字/看书/玩手机合并、趴桌、举手、侧身转头说话。等基线跑通再拆细。2.2 标注规范与目录结构标注用 LabelImg 或 X-AnyLabeling 都行导出 YOLO 格式。关键是写一份标注手册把每类的判定标准写死比如头部低于肩线且持续超过 2 秒记为趴桌。多人标注时先标 200 张做交叉验证算一下 IoU 一致率低于 0.85 就回去统一标准。目录按下面这样组织后面训练脚本直接读这个结构dataset/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片 ├── labels/ │ ├── train/ # 与图片同名的 .txt 标注 │ ├── val/ │ └── test/ └── data.yaml # 数据集配置data.yaml内容如下路径写相对路径换机器不用改path: ./dataset train: images/train val: images/val test: images/test nc: 5 names: [listen, bow, lie, raise, turn]提示nc和names的顺序必须和标注时类别 ID 严格对应顺序错了模型不会报错但结果全乱这是最常见的玄学事故。2.3 数据增强里哪些能用、哪些会帮倒忙课堂场景的增强要贴合真实分布。水平翻转可以用因为学生左右对称但垂直翻转绝对不能用——倒立的人不存在模型会学到错误特征。Mosaic 增强对小目标有帮助但拼接后可能出现半个人如果标注没跟着裁会引入噪声。我常用的增强组合是HSV 色调抖动h0.015, s0.7, v0.4、随机平移缩放scale0.5、水平翻转flipud0.0, fliplr0.5。亮度抖动幅度别太大教室灯光相对稳定抖太狠反而让模型对光照过拟合。样本不均衡是另一个坑。举手这类行为在整段视频里占比可能不到 3%直接训会让模型倾向于全预测听讲。解决办法有两个一是在 loss 里给稀有类加权二是对含稀有类的帧做过采样。我一般先用过采样简单直接效果不够再动 loss。3. 模型训练YOLOv8 多标签分类头怎么改、参数怎么调3.1 环境搭建与依赖安装先确认 Python 版本YOLOv8 要求 3.8 以上我一般用 3.10。装依赖时注意 torch 和 CUDA 版本要匹配否则训练时会在.to(device)那一步报错。# 创建虚拟环境避免污染系统 Python python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装 PyTorch按自己的 CUDA 版本去官网选对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics 和训练辅助库 pip install ultralytics opencv-python numpy pandas装完跑一句验证能打印出 True 和版本号就说明 GPU 可用import torch print(torch.cuda.is_available(), torch.__version__)3.2 训练脚本与关键参数下面是最小可跑的训练脚本。我把它写成独立文件train.py方便在服务器上挂后台跑。from ultralytics import YOLO # 加载预训练权重从 COCO 迁移能省大量数据 model YOLO(yolov8s.pt) results model.train( datadataset/data.yaml, epochs150, # 课堂数据量小轮次给足 imgsz960, # 小目标必须放大输入 batch8, # 显存不够就往下调 device0, # 单卡写 0CPU 写 cpu workers4, # 数据加载进程数 patience30, # 30 轮无提升就早停 lr00.01, # 初始学习率 lrf0.01, # 最终学习率 lr0 * lrf mosaic1.0, # 开启 mosaic 增强 fliplr0.5, # 水平翻转概率 flipud0.0, # 垂直翻转必须关掉 hsv_h0.015, hsv_s0.7, hsv_v0.4, projectruns/train, nameclassroom_v1 )参数说明imgsz从 640 提到 960 是这套方案里收益最大的改动小目标召回能涨十几个点代价是显存和训练时间翻倍。batch要根据显存调8G 显存跑 960 大概只能给到 8。patience设 30 是为了防止在验证集上过拟合课堂数据通常几千张150 轮足够收敛。3.3 训练过程怎么看、指标怎么读训练日志里重点盯三个box_loss、cls_loss、mAP50。前两个持续下降是正常的如果cls_loss震荡不降多半是类别标注有冲突。mAP50在验证集上到 0.85 以上基本可用低于 0.7 就要回去查数据。混淆矩阵是最有用的诊断工具。如果低头和趴桌互相误判严重说明这两类在图像上确实难分可以考虑合并或者引入时序信息——单帧分不清连续 10 帧的头部高度变化能分清楚。注意训练时如果发现mAP50前几轮就冲到 0.9 然后不动了八成是验证集和训练集有重叠图片去查一下划分脚本别高兴太早。3.4 从检测到行为序列加一层时序平滑单帧检测会有抖动同一个学生这一帧判听讲、下一帧判低头直接展示到前端会闪。我一般加一个滑动窗口投票对每个跟踪 ID 保留最近 15 帧的类别取众数作为当前状态。跟踪用 ByteTrackYOLOv8 内置支持model.track()直接调用。from collections import deque, Counter # 每个 track_id 维护一个长度为 15 的队列 history {} WINDOW 15 def smooth(track_id, cls_name): if track_id not in history: history[track_id] deque(maxlenWINDOW) history[track_id].append(cls_name) # 取窗口内出现次数最多的类别 return Counter(history[track_id]).most_common(1)[0][0]这段逻辑放在推理循环里每帧对每个跟踪框调一次。窗口长度 15 是按 25fps 估的约 0.6 秒太短压不住抖动太长会让状态切换迟钝。实际部署时按帧率调整。4. Django 后端推理结果怎么存、接口怎么设计4.1 项目结构与模型设计Django 这边不负责推理只负责调度和展示。推理单独跑一个进程结果写进数据库或缓存Django 读出来给前端。这样解耦的好处是推理崩了不影响 Web 服务。我一般建两个 appdetection管推理调度dashboard管展示。核心模型三张表# detection/models.py from django.db import models class Classroom(models.Model): name models.CharField(max_length64) camera_url models.CharField(max_length255, blankTrue) class BehaviorRecord(models.Model): classroom models.ForeignKey(Classroom, on_deletemodels.CASCADE) track_id models.IntegerField() behavior models.CharField(max_length16) # listen/bow/lie/raise/turn confidence models.FloatField() created_at models.DateTimeField(auto_now_addTrue) class Meta: indexes [models.Index(fields[classroom, created_at])]BehaviorRecord上加联合索引是必须的否则按教室和时间查询报表时会全表扫描数据量一上来页面直接卡死。这是新手最容易忽略的一步。4.2 推理结果写入与接口推理进程通过 Django 的 ORM 批量写入别一条一条save()用bulk_createfrom detection.models import BehaviorRecord def save_batch(records): # records 是 [(classroom_id, track_id, behavior, conf), ...] objs [BehaviorRecord( classroom_idr[0], track_idr[1], behaviorr[2], confidencer[3]) for r in records] BehaviorRecord.objects.bulk_create(objs, batch_size500)接口用 DRF 写一个只读的列表视图前端轮询或走 WebSocket 都行。查询时用.values()只取需要的字段别把整个对象序列化出去from rest_framework.decorators import api_view from rest_framework.response import Response from detection.models import BehaviorRecord api_view([GET]) def latest_records(request, classroom_id): qs (BehaviorRecord.objects .filter(classroom_idclassroom_id) .order_by(-created_at) .values(track_id, behavior, confidence, created_at)[:50]) return Response(list(qs))order_by(-created_at)配合索引能走索引扫描[:50]限制返回条数避免一次拉太多。如果要做实时推送把这段逻辑搬到 Channels 的 consumer 里用 group 广播。4.3 统计报表的聚合查询前端要展示本节课各行为占比用数据库聚合比在 Python 里循环快得多from django.db.models import Count stats (BehaviorRecord.objects .filter(classroom_idcid, created_at__gtestart_time) .values(behavior) .annotate(totalCount(id)) .order_by(-total))values(behavior)分组annotate(Count(id))计数一条 SQL 搞定。数据量大时记得给created_at也建索引或者按天分表。5. 避坑与排查这套系统最容易翻车的五个地方现象一训练 loss 正常下降但验证 mAP 一直是 0。原因data.yaml里的路径写错或者 labels 目录下没有对应的 .txt 文件YOLO 静默跳过所有验证样本。 解决训练前手动ls dataset/labels/val | head确认文件存在且和 images 里的文件名一一对应。现象二推理时所有框都判成同一类。原因类别 ID 和 names 顺序错位或者训练时nc写成了实际类别数以外的值。 解决打开data.yaml核对names顺序再去看标注文件里每行第一个数字是不是落在 0 到 nc-1 之间。现象三Django 页面加载越来越慢最后 502。原因BehaviorRecord表没建索引报表查询全表扫描数据到百万级直接拖垮数据库。 解决给(classroom, created_at)加联合索引历史数据定期归档或删除别无限堆积。现象四GPU 显存够但训练报 CUDA out of memory。原因imgsz和batch组合超出显存或者workers太多导致数据加载进程占满内存。 解决先把 batch 减半试还不行就降 imgsz 到 800workers在 Windows 上设 0 或 2。现象五同一学生在相邻帧被分配了不同 track_id行为统计翻倍。原因ByteTrack 的匹配阈值太严遮挡后重新出现被当成新目标。 解决调高track_high_thresh和track_buffer或者在业务层用位置 IoU 做二次关联把短时间内的新 ID 合并到旧 ID。6. 进阶技巧用置信度分层和时序特征把准确率再抬一档基线跑通之后想再往上提点我一般从两个方向下手。第一个是置信度分层处理。检测框的置信度不是只有要和不要两档。对置信度高于 0.7 的框直接采信0.4 到 0.7 之间的框不急着定类别而是把它送进一个轻量分类网络比如 MobileNetV3做二次判定输入是裁剪后的框内图像。这样既避免了低质量框污染统计又比直接丢弃多捞回一部分样本。实测在课堂数据上能把整体准确率抬 3 到 5 个点。第二个是引入时序特征。单帧分不清低头写字和趴桌但连续帧的头部高度轨迹能分清楚。做法是把每个 track 最近 30 帧的框中心 y 坐标和框高拼成一个序列送进一个小的 LSTM 或一维卷积输出最终行为类别。这个模块可以离线训训好后和检测模型串起来。代价是推理延迟增加实时性要求高的场景要权衡。验证方法上别只看整体准确率。按类别拆开看召回尤其是举手这种稀有类整体准确率 95% 但举手召回只有 40% 的情况很常见。再按时间段拆看看课中段和课末段的表现差异如果课末段趴桌误判明显增多说明模型对疲劳姿态的泛化不够得补数据。我自己的习惯是每改一版模型先在固定的 200 张测试集上跑一遍把混淆矩阵和各类 P/R 存成表格和上一版对比。没有对比就没有判断凭感觉调参是最大的时间黑洞。这套系统从数据到上线我踩过的坑基本都在上面了希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门