news 2026/10/4 1:00:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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 色调抖动(h=0.015, s=0.7, v=0.4)、随机平移缩放(scale=0.5)、水平翻转(flipud=0.0, fliplr=0.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( data='dataset/data.yaml', epochs=150, # 课堂数据量小,轮次给足 imgsz=960, # 小目标必须放大输入 batch=8, # 显存不够就往下调 device=0, # 单卡写 0,CPU 写 'cpu' workers=4, # 数据加载进程数 patience=30, # 30 轮无提升就早停 lr0=0.01, # 初始学习率 lrf=0.01, # 最终学习率 = lr0 * lrf mosaic=1.0, # 开启 mosaic 增强 fliplr=0.5, # 水平翻转概率 flipud=0.0, # 垂直翻转必须关掉 hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, project='runs/train', name='classroom_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 帧的类别,取众数作为当前状态。跟踪用 ByteTrack,YOLOv8 内置支持,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(maxlen=WINDOW) 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 服务。

我一般建两个 app:detection管推理调度,dashboard管展示。核心模型三张表:

# detection/models.py from django.db import models class Classroom(models.Model): name = models.CharField(max_length=64) camera_url = models.CharField(max_length=255, blank=True) class BehaviorRecord(models.Model): classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE) track_id = models.IntegerField() behavior = models.CharField(max_length=16) # listen/bow/lie/raise/turn confidence = models.FloatField() created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [models.Index(fields=['classroom', 'created_at'])]

BehaviorRecord上加联合索引是必须的,否则按教室和时间查询报表时会全表扫描,数据量一上来页面直接卡死。这是新手最容易忽略的一步。

4.2 推理结果写入与接口

推理进程通过 Django 的 ORM 批量写入,别一条一条save(),用bulk_create:

from detection.models import BehaviorRecord def save_batch(records): # records 是 [(classroom_id, track_id, behavior, conf), ...] objs = [BehaviorRecord( classroom_id=r[0], track_id=r[1], behavior=r[2], confidence=r[3]) for r in records] BehaviorRecord.objects.bulk_create(objs, batch_size=500)

接口用 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_id=classroom_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_id=cid, created_at__gte=start_time) .values('behavior') .annotate(total=Count('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 到 800,workers在 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 存成表格,和上一版对比。没有对比就没有判断,凭感觉调参是最大的时间黑洞。这套系统从数据到上线,我踩过的坑基本都在上面了,希望帮到你。

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

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

C# WinForm + FluentFTP 上位机文件回传实战:断点续传与进度回调

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

作者头像 李华
网站建设 2026/10/3 23:56:43

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

作者头像 李华
网站建设 2026/10/3 23:50:52

Jev本地部署指南:AI Agent执行框架如何驱动自动化工作流

这几天“Jev”在网上的热度确实有点夸张。我的信息流里,前三天还在讨论Codex的新功能,第四天突然全是“Jev本地部署”“Jev在Codex里跑起来了”“斯坦福教授用Jev构建数据系统”这些话题。说实话,我一开始是带着“又一个被包装出来的爆款工具…

作者头像 李华
网站建设 2026/10/3 23:44:06

反无人机自动防御系统:多传感器融合与智能反制全链路指南

1. 项目概述与需求解读1.1 低空安全威胁为什么突然成了真问题我自己做低空安防这一行快十年了,前些年跟人聊反无人机,对方第一反应往往是“至于吗”。但从消费级四旋翼遍地开花、物流无人机、植保无人机、航拍无人机大规模普及之后,这个问题的…

作者头像 李华
网站建设 2026/10/3 23:34:52

设置页设计的核心细节:分组、状态与控件选型实战

先说一个我自己的真实经历。前两年我给一个工具类产品做改版,需求方对首页、列表页、详情页提了一大堆视觉方案,唯独设置页只留下一句话:“设置页不用动,就那些开关。”结果上线后一周内,用户反馈里最密集的话题全挤在…

作者头像 李华
网站建设 2026/10/3 23:27:00

Python编程练习全攻略:从环境搭建到实战项目的进阶路线

打开招聘网站随手翻一圈,Python 相关的岗位还是那么多,从后端开发、数据分析到自动化测试,到处都是。但真正让我觉得 Python 值得花时间好好练的,不是岗位多,而是它上手快、反馈及时,特别适合用来培养编程手…

作者头像 李华