news 2026/10/4 5:26:15

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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 卷积或 CNN+LSTM,但课堂行为变化慢,单帧加少量后处理往往性价比更高。

这里有个容易翻车的点:很多教程直接拿公开行为数据集训练,类别是“走路、跳跃、打球”,跟课堂完全不搭。课堂数据必须自己标,标注量按每个类别 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 "select='not(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(weights="IMAGENET1K_V1") model.classifier[3] = nn.Linear(model.classifier[3].in_features, num_classes) # 课堂数据量通常几千到几万,学习率别开大 optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4) criterion = nn.CrossEntropyLoss(label_smoothing=0.1) # 类别不均衡时给趴桌、举手这类小类加权 weights = torch.tensor([1.0, 1.0, 2.0, 3.0, 3.0]) criterion = nn.CrossEntropyLoss(weight=weights, label_smoothing=0.1)

逻辑说明:用 ImageNet 预训练权重做迁移,分类头换成自己的类别数。lr=3e-4是迁移学习的常用起点,数据少于 5000 张时调到 1e-4 更稳。label_smoothing=0.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_length=100) teacher = models.CharField(max_length=50) class_name = models.CharField(max_length=50) class Video(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE) file_path = models.CharField(max_length=255) duration = models.IntegerField(default=0) # 秒 created_at = models.DateTimeField(auto_now_add=True) class BehaviorResult(models.Model): video = models.ForeignKey(Video, on_delete=models.CASCADE) frame_no = models.IntegerField() student_id = models.IntegerField() # 跟踪分配的临时 id behavior = models.CharField(max_length=20) confidence = models.FloatField() bbox = models.CharField(max_length=100) # "x1,y1,x2,y2"

逻辑说明:BehaviorResult一行对应一帧里一个学生的行为,数据量会很大,一节 45 分钟课按每秒一帧、30 个学生算,约 8 万行。所以video和frame_no要建联合索引,查询时按视频和时间段过滤。bbox存成字符串是为了省事,如果要做轨迹分析就拆成四个 FloatField。热搜里django执行查询-删除对象在这个场景就是清理过期视频时用Video.objects.filter(created_at__lt=cutoff).delete(),级联会把结果一起删掉,注意别误删。

3.4 推理结果写库与聚合接口

推理脚本跑完把结果批量写库,别一条条 insert:

from django.db import transaction from myapp.models import BehaviorResult def save_results(video, records): objs = [ BehaviorResult( video=video, frame_no=r["frame"], student_id=r["sid"], behavior=r["behavior"], confidence=r["conf"], bbox=r["bbox"], ) for r in records ] # 批量插入,每批 2000 条,避免单条 SQL 开销 with transaction.atomic(): BehaviorResult.objects.bulk_create(objs, batch_size=2000)

聚合接口用 ORM 的 annotate 做:

from django.db.models import Count, Q def behavior_summary(video_id): qs = BehaviorResult.objects.filter(video_id=video_id) total = qs.values("student_id").distinct().count() stats = qs.values("behavior").annotate(cnt=Count("id")) return {"total_students": total, "detail": list(stats)}

逻辑说明:bulk_create比逐条 save 快一个数量级,batch_size=2000是经验值,太大内存吃紧,太小又失去批量优势。聚合时先按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_th=0.6, win=5): # 低置信度先置为 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_size=win) return [uniq[int(n)] for n in smoothed]

逻辑说明:conf_th=0.6是经验阈值,课堂场景光照变化大,设太高会丢太多有效帧,设太低噪声又进来。win=5对应约 5 秒的平滑窗口,行为变化比这慢,所以不会把真实切换抹掉。medfilt要求输入是数值,所以先做标签到数字的映射,平滑完再映射回去。注意uniq的顺序每次可能不同,如果下游依赖固定标签顺序,就手动指定一个固定列表。

这套方案我从标注到出统计大概花了两周,其中一半时间在调标注规范和跟踪 id。模型本身反而不是最耗时的部分。如果你也要做类似系统,建议先把数据管线和 Django 表结构定死,再动模型,顺序反了会返工。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 5:24:43

RAG检索主链路实战:LangGraph+Milvus+Ollama+SSE流式问答

1. 检索主链路到底在搭什么:从“能聊”到“能查”的分水岭很多人做企业级问答系统,卡在第三章、第四章就停了——模型接上了,Prompt 调通了,单轮对话也能跑,但一旦问它“我们公司去年Q3的差旅报销标准是多少”&#xf…

作者头像 李华
网站建设 2026/10/4 5:23:39

MRAM+ARM Cortex-M4工业存储方案实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 5:23:11

有限元法离散化本质:从物理真实到数值可解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 5:21:21

NeRF转精细纹理网格:自适应表面细化与烘焙全流程

简介:面向计算机视觉与图形学开发者,这份实战项目围绕三维重建中的前沿问题:如何从神经辐射场(NeRF)通过自适应表面细化恢复精细纹理网格。资源完整提供项目源码与流程教程,涵盖数据预处理、NeRF训练、表面…

作者头像 李华
网站建设 2026/10/4 5:16:33

毫米波FMCW雷达中FFT为何等效于相干积累:门信号与信噪比增益解析

从面世那天起,毫米波FMCW雷达的教材里几乎都写着同一句话:“对中频信号做FFT,等效于对回波做相干积累。”我第一次读到这句话时,心里是打了一个问号的。FFT明明是频谱分析工具,怎么就成了“积累”呢?积累不…

作者头像 李华