ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django与深度学习的上课学生行为识别系统实战

基于Django与深度学习的上课学生行为识别系统实战 简介这份资源是面向高校计算机相关专业毕业设计场景的完整项目包主题为基于Python深度学习的上课学生行为识别系统采用Django框架与MySQL数据库开发技术栈涵盖YOLOv5与OpenCV适合需要完成毕设、课程设计或想入门目标检测实战的学生与开发者。系统以后台管理形式实现区分管理员与学生两类权限管理员负责学生注册审核、课程信息维护、行为检测记录查询与通知发布学生端可修改个人信息、查看通知并通过打开摄像头选择当前课堂课程实时检测玩手机、睡觉、举手等课堂行为。压缩包共705个文件包含224个png、207个jpg、96个gif等图像素材44个py源码、50个js与21个css等前端资源以及sql脚本、db数据库与说明文档整体约299.74MB目录结构完整。资源另附论文、开题报告与PPT已有110人学习可帮助读者快速理解系统架构、模型调用与前后端联调思路。1. 上课学生行为识别系统从课堂视频到行为标签一条能跑通的 Django 深度学习链路课堂录播视频越攒越多教务想统计“抬头率”“趴桌率”“举手次数”靠人盯屏幕根本不现实。上课学生行为识别系统要解决的就是这件事把固定机位的课堂画面切成帧用深度学习模型判断每个学生当前是听讲、低头、趴桌还是举手再把结果落进 Django 后台按班级、课程、时间段出统计。它适合两类人一类是手里有课堂视频、想快速搭出可演示系统的学生和初级工程师另一类是想把视觉模型接进 Web 业务、但没想清楚前后端怎么分工的开发者。这篇笔记按“先立住方案、再动手复现、最后讲坑”的顺序走代码和参数都给到能直接抄的程度Django 部分只讲这个场景真正用得到的查询、建模和接口不铺开讲全套教程。2. 方案选型为什么是 Django 扛业务、深度学习管识别2.1 行为识别在这个场景里到底识别什么先把问题定义清楚否则后面模型选型全是玄学。课堂场景的行为识别本质是对每个学生检测框做单帧或多帧分类不是对整个画面打一个标签。常见做法是两段式第一段用人检测模型把画面里每个学生框出来第二段对每个框做行为分类。行为类别一般收敛到 4 到 6 类多了标注成本压不住少了统计没意义。我一般用这套听讲抬头看黑板或老师、低头看桌面或手机、趴桌头低于桌面线、举手、站立。类别定完再决定是单帧分类还是时序分类单帧用 CNN 就够时序要上 3D 卷积或 CNNLSTM但课堂行为变化慢单帧加少量后处理往往性价比更高。这里有个容易翻车的点很多教程直接拿公开行为数据集训练类别是“走路、跳跃、打球”跟课堂完全不搭。课堂数据必须自己标标注量按每个类别 800 到 1500 个框起步否则趴桌和低头这种相近类别根本分不开。2.2 Django 在这条链路里负责哪几件事Django 不碰模型推理它管的是数据落库、任务调度、结果查询和前端展示。具体分工视频上传后Django 存文件路径和课程元信息班级、教师、时间。推理任务异步跑结果按“帧号 学生框 行为标签 置信度”写进数据库。前端按班级和时间段聚合出抬头率、趴桌率曲线。用 Django ORM 做聚合查询比手写 SQL 省事也方便后面加筛选条件。选 Django 而不是 Flask 的理由很实际这个系统天然需要用户、权限、后台管理、ORM 和模板Django 自带 admin 能省掉一整套管理界面开发。热搜里常出现的django创建app、django项目实战新手这些词落到这个项目就是一个 app 管视频和课程一个 app 管识别结果和统计别全塞一个 app 里。2.3 模型侧的最小可用组合检测用 YOLO 系列分类用 ResNet 或 MobileNet这是目前最稳的组合。检测模型负责“人在哪”分类模型负责“这个人在干嘛”。如果算力紧张MobileNetV3 做分类单张 1080Ti 也能跑到实时。训练框架用 PyTorch导出 ONNX 后推理端可以用 onnxruntime部署时不用带整个 PyTorch镜像小很多。提示检测和分类分开训别一上来就搞端到端多任务调参难度会翻倍出问题也不好定位是检测框歪了还是分类错了。3. 动手复现从数据标注到 Django 接口跑通3.1 数据准备与标注格式转换课堂视频先按 1 到 2 秒抽一帧抽太密相邻帧几乎一样浪费标注人力。抽帧命令# 每 25 帧抽一帧假设视频 25fps约等于每秒一张 ffmpeg -i classroom.mp4 -vf selectnot(mod(n,25)) -vsync vfr frames/frame_%05d.jpg抽完用 LabelImg 或 X-AnyLabeling 标人框和行为类别。标注格式建议直接用 YOLO 格式每张图一个 txt每行类别 中心x 中心y 宽 高坐标归一化到 0 到 1。如果拿到的是 VOC 的 xml转 YOLO 的脚本import xml.etree.ElementTree as ET import os # 类别名到 id 的映射顺序必须和训练时一致 classes [listen, bow_head, lie_on_desk, raise_hand, stand] def convert(xml_path, out_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) bnd obj.find(bndbox) x1 float(bnd.find(xmin).text) y1 float(bnd.find(ymin).text) x2 float(bnd.find(xmax).text) y2 float(bnd.find(ymax).text) # 转成归一化的中心点加宽高 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) name os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, name .txt), w) as f: f.write(\n.join(lines))逻辑说明遍历 xml 里每个 object取类别名映射成 id再把绝对坐标转成 YOLO 要求的归一化中心点格式。参数上classes列表顺序一旦定了就不能改训练和推理必须一致否则标签全错位。img_w和img_h要传原图尺寸不是缩放后的尺寸这里最容易填错。3.2 行为分类模型训练的关键参数分类模型输入是检测框裁出来的小图统一 resize 到 224×224。训练脚本核心部分import torch import torch.nn as nn from torchvision import models # 5 类行为输出维度对应类别数 num_classes 5 model models.mobilenet_v3_small(weightsIMAGENET1K_V1) model.classifier[3] nn.Linear(model.classifier[3].in_features, num_classes) # 课堂数据量通常几千到几万学习率别开大 optimizer torch.optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) criterion nn.CrossEntropyLoss(label_smoothing0.1) # 类别不均衡时给趴桌、举手这类小类加权 weights torch.tensor([1.0, 1.0, 2.0, 3.0, 3.0]) criterion nn.CrossEntropyLoss(weightweights, label_smoothing0.1)逻辑说明用 ImageNet 预训练权重做迁移分类头换成自己的类别数。lr3e-4是迁移学习的常用起点数据少于 5000 张时调到 1e-4 更稳。label_smoothing0.1能缓解模型对某一类过度自信课堂数据标注难免有噪声这个参数很有用。weights按类别样本量反比设置趴桌和举手样本通常最少给高权重否则模型会偏向预测“听讲”。训练轮数先跑 30 轮看验证集准确率曲线如果 15 轮后还在涨就加到 50 轮。batch size 设 32 或 64显存不够就降到 16同时把学习率按比例降一点。3.3 Django 建模识别结果表怎么设计结果表设计不好后面统计查询会非常痛苦。核心表三张课程表、视频表、识别结果表。from django.db import models class Course(models.Model): name models.CharField(max_length100) teacher models.CharField(max_length50) class_name models.CharField(max_length50) class Video(models.Model): course models.ForeignKey(Course, on_deletemodels.CASCADE) file_path models.CharField(max_length255) duration models.IntegerField(default0) # 秒 created_at models.DateTimeField(auto_now_addTrue) class BehaviorResult(models.Model): video models.ForeignKey(Video, on_deletemodels.CASCADE) frame_no models.IntegerField() student_id models.IntegerField() # 跟踪分配的临时 id behavior models.CharField(max_length20) confidence models.FloatField() bbox models.CharField(max_length100) # x1,y1,x2,y2逻辑说明BehaviorResult一行对应一帧里一个学生的行为数据量会很大一节 45 分钟课按每秒一帧、30 个学生算约 8 万行。所以video和frame_no要建联合索引查询时按视频和时间段过滤。bbox存成字符串是为了省事如果要做轨迹分析就拆成四个 FloatField。热搜里django执行查询-删除对象在这个场景就是清理过期视频时用Video.objects.filter(created_at__ltcutoff).delete()级联会把结果一起删掉注意别误删。3.4 推理结果写库与聚合接口推理脚本跑完把结果批量写库别一条条 insertfrom django.db import transaction from myapp.models import BehaviorResult def save_results(video, records): objs [ BehaviorResult( videovideo, frame_nor[frame], student_idr[sid], behaviorr[behavior], confidencer[conf], bboxr[bbox], ) for r in records ] # 批量插入每批 2000 条避免单条 SQL 开销 with transaction.atomic(): BehaviorResult.objects.bulk_create(objs, batch_size2000)聚合接口用 ORM 的 annotate 做from django.db.models import Count, Q def behavior_summary(video_id): qs BehaviorResult.objects.filter(video_idvideo_id) total qs.values(student_id).distinct().count() stats qs.values(behavior).annotate(cntCount(id)) return {total_students: total, detail: list(stats)}逻辑说明bulk_create比逐条 save 快一个数量级batch_size2000是经验值太大内存吃紧太小又失去批量优势。聚合时先按behavior分组计数前端再算百分比。如果要按时间段统计加frame_no__range过滤即可。4. 避坑与排查课堂行为识别最容易翻车的 5 个地方4.1 检测框抖动导致行为标签跳变现象同一个学生相邻帧的行为在“听讲”和“低头”之间反复横跳统计曲线像锯齿。原因检测框每帧位置有微小偏移裁出来的图内容变化分类器输出跟着抖。解决对同一个 student_id 的行为做滑动窗口投票窗口取 5 到 7 帧取众数作为最终标签。跟踪 id 用简单的 IOU 匹配或 ByteTrack别用每帧重新编号的方案。4.2 趴桌和低头分不开现象验证集上这两类准确率只有 60% 出头混淆矩阵里互相错。原因两个类别视觉差异小标注时不同人标准不一致。解决先统一标注规范趴桌定义为“头部低于桌面水平线”低头定义为“头部高于桌面但视线朝下”。再在训练时对这两类做更强的数据增强比如随机裁剪和亮度扰动让模型关注头部相对桌面的位置而不是绝对姿态。4.3 Django 查询没加索引统计接口超时现象视频列表页正常一点“查看统计”就转圈十几秒。原因BehaviorResult表几十万行filter(video_id...)没走索引全表扫描。解决在video和frame_no上建联合索引class Meta里加indexes [models.Index(fields[video, frame_no])]然后跑 migrate。加完索引再测同样的查询能降到几百毫秒。4.4 推理和 Web 抢资源页面卡死现象后台跑推理时前端页面响应极慢甚至 502。原因推理进程和 Django 进程在同一台机器上抢 CPU 和内存。解决推理任务用 Celery 丢到独立 worker或者干脆在另一台机器跑完再把结果写回数据库。至少要把推理进程的 CPU 亲和性限制一下别让它吃满所有核。4.5 视频抽帧后时间戳对不上现象统计出来的“第 10 分钟抬头率”和实际画面对不上。原因抽帧时用了-vsync vfr帧率不固定frame_no 和真实时间没有线性关系。解决抽帧时记录每帧的原始时间戳写进结果表加一个timestamp字段统计按时间戳过滤而不是按 frame_no 换算。或者抽帧时强制固定帧率用-r 1明确每秒一帧。5. 进阶技巧用置信度过滤和轨迹平滑把可用率提上去模型跑通只是第一步真正决定这套系统能不能交付的是结果的可信度。我一般会在写库前加两层后处理。第一层是置信度过滤分类置信度低于 0.6 的框直接标成“不确定”不参与统计这样抬头率这类指标不会被低质量预测拉偏。第二层是轨迹平滑按 student_id 分组对行为序列做中值滤波窗口取 5把孤立的跳变点抹掉。这两步加起来代码不到 50 行但能让统计结果从“没法看”变成“能汇报”。验证方法上别只看模型准确率。抽 3 段没参与训练的课堂视频人工数出每个学生的行为次数和系统输出做对比算一个“行为计数误差率”。误差率控制在 15% 以内这套系统就能拿去演示和试用了。如果误差率偏高优先查跟踪 id 有没有频繁切换而不是急着换模型。import numpy as np from scipy.signal import medfilt def smooth_behavior(seq, confs, conf_th0.6, win5): # 低置信度先置为 unknown labels [s if c conf_th else unknown for s, c in zip(seq, confs)] # 把标签映射成数字做中值滤波再映射回来 uniq sorted(set(labels)) idx {u: i for i, u in enumerate(uniq)} nums np.array([idx[l] for l in labels]) smoothed medfilt(nums, kernel_sizewin) return [uniq[int(n)] for n in smoothed]逻辑说明conf_th0.6是经验阈值课堂场景光照变化大设太高会丢太多有效帧设太低噪声又进来。win5对应约 5 秒的平滑窗口行为变化比这慢所以不会把真实切换抹掉。medfilt要求输入是数值所以先做标签到数字的映射平滑完再映射回去。注意uniq的顺序每次可能不同如果下游依赖固定标签顺序就手动指定一个固定列表。这套方案我从标注到出统计大概花了两周其中一半时间在调标注规范和跟踪 id。模型本身反而不是最耗时的部分。如果你也要做类似系统建议先把数据管线和 Django 表结构定死再动模型顺序反了会返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表