news 2026/10/1 1:47:13

基于深度学习与Django的学生课堂行为识别系统开发实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于深度学习与Django的学生课堂行为识别系统开发实践

简介:一套基于深度学习的上课学生行为识别系统源码包,适合毕业设计、课程设计、大作业或工程实训。系统以YOLOv5模型进行目标检测,配合OpenCV实现实时图像处理,采用Python语言与Django框架搭建,MySQL存储行为数据。功能上分为管理员与学生两个权限:管理员可管理账号、审核学生、维护课程信息、查看行为检测记录、发送通知;学生可修改个人资料、打开摄像头选择课程进行行为检测、查看通知。识别范围涵盖坐姿、站立、写字等典型课堂行为,并能分析专注度与活跃度,即时生成数据分析报告。资源包共718个文件,主要包括Python源码、HTML/CSS/JS前端页面、图片素材、SQL数据库文件以及Word/PDF说明文档,压缩包约304MB,可直接部署运行。当前已有86人学习下载,项目包含可运行源码、sql文件和配套文档,便于参考系统设计思路并在此基础上进行功能扩展。

1. 这个 zip 里装的是什么:先把上课学生行为识别的闭环想清楚

拿到“5p069基于深度学习的上课学生行为识别系统(django).zip”这个压缩包时,大部分人第一反应是解压看代码,但更该做的是先搞清楚这类项目在解决什么问题:教室里的摄像头画面进来,系统要能区分出“正常听讲、低头、睡觉、举手、玩手机、站立”这些行为,并把结果落到后端,让老师能在 Web 页面里看到统计。这种项目是典型的“深度学习模型 + Web 框架”套件,模型负责从视频帧里检测学生行为,Django 负责把检测结果存库、展示、做课堂报告。

这个 zip 适合两类人:一类是做课程设计或毕业设计的学生,需要快速跑通一个能演示的完整系统;另一类是刚入行的后端工程师,想看看 Django 怎么把 PyTorch 模型包成真实可用的 Web 服务。核心价值不在模型精度有多高,而在“视频流 → 模型推理 → 数据库 → 网页展示”这条链路能不能稳定运转。打开压缩包之前,先照着下文的思路把每个环节应该有什么代码、会踩什么坑在脑子里过一遍,比自己翻两天源码有效得多。

2. 从摄像头到网页卡片:行为识别系统的数据流与模块划分

2.1 整条链路拆成四段,每段职责必须单一

先不要急着看代码,把系统按数据流向分成四段:视频接入与抽帧、行为检测推理、结果持久化、Web 展示与查询。绝大多数这类 Django 行为识别系统都是这个骨架,区别只在于每段实现得粗糙还是精细。

视频接入段负责读取教室摄像头 RTSP 流或上传的视频文件,按固定帧率抽帧。行为识别做的是图像识别,不需要每帧都推理,一般每秒抽 1~2 帧就足够,抽多了反而会让 GPU 空转、数据库疯狂写入。检测推理段是整个系统的心脏,输入是一张 640x640 或 1280x1280 的帧,输出是若干检测框,每个框带类别(睡觉、举手、玩手机等)和置信度。这里最常见的做法是用 YOLO 系列模型,因为它是单阶段检测器,速度和精度平衡最好,部署时能转成 ONNX 或者 TensorRT 加速,适合教室这种固定场景的实时监控。

结果持久化段把每一帧的检测框写成记录,至少要保存时间戳、行为类别、置信度、学生位置(归一化坐标或者按照座位编号映射),这样后续才能做课堂专注度曲线。Web 展示段就是 Django 的活:提供今天哪节课哪个班的行为分布、单帧可视化(在图片上画框)、按时间回放。把这四段拆清楚之后,再看压缩包里目录结构会顺手很多。

2.2 常见目录结构与关键文件的功能预判

这类 zip 解压后目录通常长这样,我列一个典型结构并解释每部分的作用,你拿到压缩包后先对照一遍。

project_root/ ├── manage.py # Django 入口,python manage.py runserver ├── config/ # Django 项目配置:settings.py、urls.py ├── apps/ │ ├── behavior/ # 业务 app:模型定义、视图、路由 │ └── detection/ # 推理 app:加载模型、预处理、预测 ├── models/ │ ├── best_yolo.pt # 训练好的 PyTorch 权重 │ └── labels.txt # 类别名,一行一个 ├── utils/ │ ├── video_reader.py # 抽帧模块,OpenCV 封装 │ └── draw_boxes.py # 画框模块 ├── static/ # 前端静态文件 ├── templates/ # Django 模板 ├── dataset/ # 可能附带部分训练数据或标注文件 └── requirements.txt

这里没有标准答案,但有几个文件是一定存在的:manage.py说明 Django 版本是 1.x/2.x/3.x 里的一种,models/目录下一定要有训练好的权重文件,不然整个系统就是空壳。labels.txt内容直接告诉你这个系统到底识别哪几种行为——有些压缩包里写的“睡觉”和“趴桌子”是同一类,只是标注时候叫法不同,跑代码之前先看这个文件,能省很多核对时间。

值得注意的一个细节是:zip 里的代码如果要用 GPU 推理,requirements.txt里通常只列了torch、opencv-python、django这种基础包,CUDA 版本要自己配。如果是纯 CPU 机器,代码里写死了.cuda()就会直接报错,这是后面避坑章节要细说的问题。

2.3 技术选型:为什么偏偏是 Django 而不是 Flask 或 FastAPI

很多第一次接触这类项目的人会问:行为识别系统为什么用 Django?模型推理部分用 PyTorch 就够了,后端用 Flask 不是更轻吗?这里有两个现实原因。第一,这类系统普遍是课程设计或校园项目,要求“前后台管理”,Django 自带 Admin 后台、ORM、模板系统,学生管理、课程记录、行为数据查询这些需求直接用现成组件就能拼出来,不需要自己写登录鉴权和表单处理。第二,Django 的 ORM 对新手友好,写BehaviorRecord.objects.filter(course_id=1)就能查数据,比手写 SQL 再拼 JSON 要少踩一半坑。

另外,Django 的 migration 机制在项目迭代时非常有用。行为识别系统的数据库表结构往往不是一次定死的:一开始只有行为类别和置信度,后来要加课堂专注度、要加摄像头编号,用python manage.py makemigrations和migrate就能平滑迁移。如果是 Flask + SQLAlchemy,配置和迁移也要自己搭,复杂度反而上去了。对于“跑通演示”这个目标,Django 全家桶是最稳的选择,这也是这类 zip 项目几乎清一色选 Django 的原因——不是为了性能,是为了开发效率和可维护性。

3. 用 django 把检测模型包成 Web 接口:最小可跑通的代码

3.1 先建 app 再改配置,照这个顺序五分钟起服务

拿到压缩包后不建议直接python manage.py runserver,因为配置项很可能不匹配当前机器。更稳的做法是先打开config/settings.py,确认 INSTALLED_APPS 里注册了哪个业务 app,数据库配置是 SQLite 还是 MySQL,再决定要不要改。以下是一套最常见的初始化流程。

# 1. 如果 zip 里没有 manage.py,说明压缩包只给了模块代码,需要自己创建工程 django-admin startproject config . # 2. 创建业务 app,app 名字常叫 behavior 或 detection,以压缩包里的目录为准 python manage.py startapp behavior # 3. 把 app 注册进 settings.py 的 INSTALLED_APPS # INSTALLED_APPS = [..., 'behavior', 'detection'] # 4. 迁移数据库:缺这一步打开网页会报 no such table 错误 python manage.py makemigrations python manage.py migrate # 5. 启动开发服务器,浏览器访问 http://127.0.0.1:8000 python manage.py runserver

这段命令里有三个关键参数需要注意。startproject config .最后的点表示在当前目录生成 manage.py 和 config 包,不加点在嵌套目录里会多绕一层,路径写错是新手最常见的翻车点。makemigrations必须紧跟migrate,前者生成迁移脚本,后者真正建表;很多解压后直接跑服务的人报错都是少跑了一条。runserver默认端口是 8000,如果被占用就改成runserver 8001,跟模型推理没有关系,只是 Web 服务端口。

这类项目压缩包里如果没有db.sqlite3文件,说明数据库是空的,必须先迁移再使用。如果有现成的 db 文件,建议直接删掉迁移,否则原本的密码盐值和模型记录可能跟你的 Django 版本不匹配,Admin 后台登录会一直报错。

3.2 定义行为记录表:字段怎么设计才够用

行为识别系统的数据表是整条链路的落点,字段设计决定了后面做课堂统计时是切菜还是剁骨头。我会在behavior/models.py里写下面这样的模型,字段含义和类型写在注释里。

from django.db import models class Course(models.Model): """课程表,记录是哪门课、哪个班级""" name = models.CharField(max_length=100, verbose_name="课程名称") class_name = models.CharField(max_length=50, verbose_name="班级") teacher = models.CharField(max_length=50, verbose_name="教师") def __str__(self): return f"{self.name}-{self.class_name}" class BehaviorRecord(models.Model): """单帧检测结果,一行就是一次推理的一个行为框""" course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="课程") timestamp = models.DateTimeField(auto_now_add=True, verbose_name="检测时间") behavior_type = models.CharField(max_length=30, verbose_name="行为类型") confidence = models.FloatField(verbose_name="置信度") x_center = models.FloatField(verbose_name="归一化中心x", default=0) y_center = models.FloatField(verbose_name="归一化中心y", default=0) frame_path = models.CharField(max_length=255, blank=True, verbose_name="帧图片路径") class Meta: ordering = ["-timestamp"]

这里的设计有几个讲究。behavior_type用CharField而不是IntegerField,因为行为类别在不同模型权重里编号可能不一样,存字符串在展示时可读性更好,也避免类别映射写死带来的麻烦。confidence必须保留,画框和统计时需要按置信度过滤,低于阈值的检测框不应入库。坐标字段存归一化值而不是像素值,因为摄像头分辨率可能调整,归一化坐标在画框时乘以当前帧宽高即可,不会因为分辨率变更而失效。

ForeignKey关联课程表是关键设计:如果不关联课程,所有行为记录混在一起,后面按班级统计出勤率、专注度根本无从做起。很多课程设计项目就是栽在这一步——检测结果能看,但没法按课程维度做报告,导师一问就露馅。

3.3 视图层调用推理模型:同步调用还是队列处理

这是整个 Django 项目里最容易写崩的部分。模型推理是个耗时操作,单帧检测在 GPU 上大约 20~50ms,在 CPU 上可能 300ms 以上。如果直接在视图函数里同步推理,前端请求会一直等到检测完成才返回,整个页面像死了一样。拆解这个问题的思路:如果是演示用单张图片上传,同步推理就能接受;如果是视频流实时检测,必须异步。

下面是一段同步调用的最小实现,适合先跑通再优化。

import torch import cv2 import numpy as np from django.shortcuts import render from django.http import JsonResponse from .models import BehaviorRecord, Course # 模型加载放在模块级别,避免每次请求都重新加载权重 model = torch.hub.load('ultralytics/yolov5', 'custom', path='models/best.pt', force_reload=False) model.conf = 0.4 # 置信度阈值,低于这个值的框会被丢掉 model.iou = 0.45 # NMS 的 IoU 阈值,控制重叠框的保留 model.classes = None # None 表示识别所有类别,也可以写成 [0, 1, 2] 只保留部分类别 def detect_frame(request): """接收前端上传的图片,返回检测结果 JSON""" if request.method != "POST": return render(request, "detect.html") file = request.FILES.get("image") if not file: return JsonResponse({"error": "no image uploaded"}, status=400) # 读图:django 的 InMemoryUploadedFile 不能直接喂给 opencv,先转 bytes img_bytes = np.frombuffer(file.read(), np.uint8) img = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) # 推理:results 对象里包含 boxes、labels、confidences results = model(img) boxes = results.xyxy[0].cpu().numpy() # 每行是 [x1, y1, x2, y2, conf, class] # 组装返回数据 detections = [] for box in boxes: x1, y1, x2, y2, conf, cls = box label = model.names[int(cls)] detections.append({ "bbox": [round(x1, 1), round(y1, 1), round(x2, 1), round(y2, 1)], "label": label, "confidence": round(float(conf), 3), }) return JsonResponse({"detections": detections, "count": len(detections)})

这段代码的关键点有三个。第一,模型必须在模块级别加载,不能放在视图函数里,否则每个请求都要重新加载权重,CPU 机器上光加载就要几十秒,页面必崩;这个坑几乎每个做过的人都会踩一次。第二,file.read()返回的 bytes 不能直接给 OpenCV,必须np.frombuffer加cv2.imdecode两步转换。第三,model.conf和model.iou是 YOLOv5 的推理参数,分别控制置信度阈值和 NMS 的 IoU 阈值——conf调高会漏检,调低会误检,0.4 是教室场景的合理起点,后面按实际效果微调。

但异步是必须考虑的:真实教室摄像头是持续推流的,不可能每帧都跑一次 POST 请求。常见的做法是单独起一个推理进程或线程,从队列里取帧、推理、写库,Django 只负责查询展示。实现方式是threading.Thread配合queue.Queue,或者用 Django 的channels做 WebSocket 推送。同步版本先跑通验证模型没问题,再改异步,不要一上来就追求完美架构。

4. 让模型认得“睡觉、举手、玩手机”:训练数据与超参调优

4.1 行为类别定义:先统一再训练,不然标注返工

打开训练好的权重之前,先确认一个问题:这个系统到底识别几种行为?不同 zip 里的命名差异很大,有的写sleep、hand_up、phone,有的写睡觉、举手、玩手机;同一类行为在不同数据集的标注标准也不同,比如“低头看书”算正常听讲还是算违纪,取决于项目需求。

建议从这三个正交维度定义类别:姿态动作(举手、站立、趴桌)、头部朝向(抬头、低头)、设备交互(玩手机、看平板)。这三个维度互不重叠,标注时不会产生歧义。具体类别控制在 5~8 类最合适,太少没有区分度,太多标注成本和数据量都撑不住。我见过一个压缩包把类别拆到 15 类,结果训练集只有两千张图,每个类别平均一百多张,模型在真实教室里一测,睡觉和趴桌混得不可开交,这就是类别定义失控的典型。

标注完成后生成labels.txt,每行一个类名,顺序必须跟训练时的类别索引一一对应。部署推理时,model.names[int(cls)]拿到的就是这里的名字。压缩包里的 labels.txt 如果跟自己重训后的类别顺序不一致,推理结果就会张冠李戴,这是换权重文件时最容易忽略的细节。

4.2 从零攒数据集:公开来源、自行采集与数据增强的搭配

这类行为的开源数据集其实很零散,没有统一的大规模标准数据集,所以这套系统的数据集通常是“公开行为数据集 + 教室自采 + 数据增强”的混合体。公开数据集优先找校园场景相关的目标检测数据集,比如要识别“学生”,找教室场景的行人检测数据集;要识别“手机”,用手持物体检测数据集。但公开数据集的视角往往跟教室监控视角不一致,视角差会让准确率掉得厉害,所以自己采集一小部分作为补充是很常见且必要的做法。

自行采集时注意三个参数:摄像头安装位置固定在教室前方或后方斜上方,高度 2.5~3 米,角度下俯 30~45 度;采集时间覆盖上午、下午、开灯、关灯不同光线;帧率 2fps 抽帧保存,不要用视频原帧率,否则相邻帧几乎一样,数据冗余会让训练结果偏向重复样本。采集 20 分钟的教室视频,按 2fps 抽帧能拿到 2400 张图,去掉模糊和重复的,留 1500 张左右,再配合公开数据,效果就明显可用了。

数据增强要克制。对这类项目最有效的是随机的亮度扰动、饱和度扰动、轻度随机裁剪和左右翻转。教室场景光线变化大,亮度扰动是刚需;翻转要注意“举手”这种左右手动作翻转后仍算同类别,没问题。但不要用大幅旋转或透视变换,监控视角下学生不会倒着出现,过度增强反而会让模型学到不存在的特征。

4.3 YOLO 训练的关键参数与常见调优方向

如果压缩包里没有带训练好的权重,或者你拿到的是没训练过的代码,就需要自己训。下面这段是 YOLOv5 的训练命令,配合注释说明每个参数的含义,换到 YOLOv8 也基本相同。

python train.py \ --data classroom.yaml \ # 数据集配置文件,内含类别数和数据路径 --weights yolov5s.pt \ # 预训练权重,s 是小模型,适合 CPU 推理 --img 640 \ # 训练输入分辨率,640 是速度和精度的平衡 --batch 16 \ # 显存不够就降到 8 或 4 --epochs 100 \ # 教室场景 100 epoch 能看到收敛趋势 --conf-thres 0.4 \ # 训练时评估用的置信度阈值,跟推理阈值保持一致 --device 0 \ # 0 表示第一块 GPU,没有 GPU 就写 cpu

--img这个参数最容易被忽视。训练分辨率跟推理分辨率保持一致,模型学到的目标尺度才匹配;如果训练用 640,部署时为了加速改成 416,小目标(远处的学生)漏检率会明显上升。反过来训 416 推理 640 也不会更好。这个项目场景里,教室远距离的学生在画面上占比很小,--img 640是底线,有条件用 1280 更好,但显存和推理速度要自己权衡。

训练完成后生成best.pt,只保留这个文件就够了。前面视图代码里torch.hub.load的path参数指向它。验证时看两个指标:mAP@0.5(各类别的平均精度)和每类的 Recall(召回率)。mAP 超过 0.7 在教室场景算不错,但比总精度更重要的是看“睡觉”和“低头”这两类的混淆矩阵——它们外观极其接近,如果召回率低于 0.5,就要考虑加数据或者做类别重加权。

4.4 部署时的模型转换:从 .pt 到 ONNX

Django 视图里直接加载.pt文件可以跑,但不适合长期部署。.pt权重依赖 PyTorch 环境,启动慢,CPU 推理效率也低。常见做法是导出 ONNX,再用 ONNX Runtime 或 OpenVINO 跑推理,这样能脱离 PyTorch 加载模型,顺便获得 2~3 倍的 CPU 加速。

python export.py \ --weights models/best.pt \ --include onnx \ --opset 12 \ --img 640

导出后得到best.onnx,推理端的改动只在加载方式上:ort.InferenceSession("best.onnx")替代torch.hub.load,输入要做一个标准化预处理(/255.0归一化),输出解析逻辑不变。这里有一个坑:ONNX 导出的输出格式跟 PyTorch 原始输出不完全一样,YOLOv5 导出后输出是[1, 25200, 85]的三维张量,需要自己解码候选框,不再有现成的results.xyxy[0]可用。如果只是为了交课程设计,用.pt直接跑就行;如果是要长期部署,ONNX 这一步值得做,它让推理服务环境变成纯 CPU 也能扛住 2fps 的实时检测。

5. 从解压到上线:五个常见问题与避坑记录

5.1 zip 解压失败或提示文件损坏:伪加密不是密码问题

现象:Windows 自带解压工具双击 zip 直接报文件损坏,或者解压到一半报 CRC 错误,再或者提示“需要密码”而你根本不知道密码是什么。

原因:这类 zip 有两种情况,一是压缩时用了特殊编码(中文字符在部分压缩软件里编码不一致),二是做了“伪加密”——zip 格式里有个标志位表示是否加密,伪加密就是只把这个标志位置为 1,但没有真正加密,解压工具不知道这点,就当成真加密处理了。

解决:不要用右键的“全部解压”,改用 7-Zip 打开。如果弹密码框,直接点确定跳过(伪加密会直接放行);如果提示数据错误,换 Bandizip 的“修复压缩包”功能,再解压一次。解压完成后立刻检查内部路径是否带中文目录名,Django 的include和template路径解析对中文目录支持不稳定,最好统一改名为纯英文路径。

5.2 Django 静态文件加载不出来:图片和 CSS 全部白屏

现象:登录页面能打开,但是没有样式,图片全部裂开;或者上传的检测结果图画框后前端显示不了。

原因:这类项目压缩包里的static目录路径跟当前 Django 版本不匹配,最常见的是STATIC_URL配了但STATICFILES_DIRS没配,Django 只在开发环境下才自动搜 static 目录,版本一变化环境判断就出问题。

解决:第一件事检查settings.py里有没有这两行配置:

STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / "static"] # 不同 Django 版本路径拼接写法不同

第二件事确认模板里用的是{% load static %}加{% static 'xxx' %}的模板标签,而不是硬编码src="/static/xxx"这种绝对路径。硬编码在 Django 里有时能工作,但一旦部署到子路径就全挂。第三件事,前端显示的检测结果图如果存的是相对路径,要注意 Django 的MEDIA_URL和MEDIA_ROOT配置。图片上传和展示属于运行时数据,不应该放 static,要配独立的 MEDIA 路径,并在主urls.py里加一行static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)才能访问到。

5.3 模型推理时报 “CUDA out of memory” 或设备不匹配

现象:跑runserver后一访问检测页面,终端直接报RuntimeError: CUDA out of memory,或者AssertionError: Torch not compiled with CUDA enabled。

原因:前者是显存不足,后者是你机器上的 PyTorch 是 CPU 版,但代码里写了.cuda()或device='cuda'。很多 zip 里的代码默认运行环境是别人的 GPU 服务器,拿到自己机器上环境不符合就翻车。

解决:在加载模型前加一行设备自动选择逻辑:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = torch.hub.load('ultralytics/yolov5', 'custom', path='models/best.pt', device=device)

把model.conf和model.iou这些参数也改成可以在配置里调整,不要写死在代码里。如果显存不够,把--img尺寸从 640 降到 416,或者换 YOLOv5s 换成更小的yolov5n权重。但要注意,改推理分辨率后需要重新导出 ONNX 或重新加载.pt,不要改完不重启,TensorRT 缓存经常因为这个出问题。

5.4 检测框在视频里疯狂闪烁:同一个学生一会儿举手一会儿放下

现象:视频回放时,同一个学生画面上被标成不同类别,或者同一个人的框一会儿在、一会儿消失,统计结果忽高忽低。

原因:单帧检测是独立的,没有做时序平滑。模型偶尔把“举手”误判成“玩手机”(手的位置恰好和手机重叠),或者某帧置信度掉到阈值以下框就消失了。这是单帧检测方案的天然缺陷,不是模型崩了。

解决:引入一个简单的滑动窗口投票机制。维护每个检测目标最近 10 帧的类别历史,最终展示时取众数,只有连续 3 帧以上都属于新类别,才更新显示类别。在 Django 视图层实现的话,就是维护一个dict,key 是目标 ID(可以用坐标中心点做粗关联),value 是类别历史列表。这个处理对统计报告的影响特别大:不处理的话“玩手机”的比例可能虚高 30%,处理之后数据才真正能看。

5.5 训练和推理的类别顺序不一致,结果全乱了

现象:模型在训练时类别是[正常, 睡觉, 举手],部署时labels.txt写的是[睡觉, 正常, 举手],结果模型明明检出“睡觉”,界面上显示的是“正常”。

原因:类别顺序没有对齐。YOLO 的模型输出是类别索引,索引对应顺序完全由训练时的data.yaml决定。部署时直接读model.names或者硬编码类名,顺序对不上就错位。

解决:训练结束后把data.yaml里 classes 的顺序固定下来,同步生成的 labels.txt 永远从这个文件导出,不要手动改。换权重文件时必须重新检查 labels.txt,不要沿用旧文件。一个最笨但有效的方法是拿一张只有单个目标的测试图片,运行推理打印model.names,再对着原始标注看一次,确认映射关系没问题后再接入 Django 视图。

6. 让系统从“能跑”到“能用”:三个进阶技巧

6.1 端到端验证:用一段三分钟教室视频做回归测试

很多人在单张图片上检测效果满意,就以为系统没问题了。我习惯的做法是准备一段三分钟左右的教室视频,里面包含上课开始、低头写作业、有人举手、有人趴桌子这些典型片段,然后让系统跑完整条链路,检查三件事。第一,每类行为的检测结果是否跟肉眼观察一致,重点看睡觉和低头有没有大面积混同;第二,数据库里按时间聚合的行为分布是否符合常识——正常课堂“正常听讲”应该占比最高,如果“玩手机”超过“正常听讲”,不是模型坏了就是数据集偏了;第三,Web 页面的统计图表跟数据库查询结果是否完全一致,这里经常出现时区问题:auto_now_add存的 UTC 时间和本地时间差八小时,统计图按小时聚合时错位。

验证通过后再考虑部署方式。演示用runserver就够了,但要长期运行就换成 gunicorn 或 uWSGI,同时把模型推理从请求线程里拆出去。我自己更喜欢用 Django 的manage.py runserver做代码调试,用 gunicorn 做正式服务,两者行为差异在静态文件处理和并发性能上,不要用 runserver 顶着真实流量跑,这是最容易忽略的边界。

6.2 从行为记录到课堂专注度:一个可解释的聚合算法

单帧检测结果是一堆离散记录,老师真正想看的是一节课“多少人开小差”。聚合思路是:把整节课按分钟切片,每分钟内统计每个学生的行为众数,然后定义专注度 = 该分钟“正常听讲 + 举手”的学生数 / 总学生数。这样每一分钟都产生一个 0~1 的数值,画成折线图就是课堂专注度曲线。

这个算法有三个参数要调。切片窗口默认 1 分钟,太细曲线毛刺大,太粗看不出变化趋势;投票阈值默认连续 3 帧变更才切换类别,前面避坑章已经说过;最低置信度 0.4,低于这个值的框直接丢弃,统计人数时按座位或者检测框去重,避免同一个学生被数两次。这三个参数实际运行中要一起调:置信度调高了,漏检导致人数变少,专注度曲线整体下移,看着像全班都在开小差,其实是参数问题。

6.3 实时推送 vs 轮询:什么时候用 WebSocket 什么时候用 AJAX

前端页面要实时刷新检测结果,最粗暴的写法是setInterval每秒发一个 AJAX 请求查数据库。这对演示系统完全够用,但查询频率和数据库压力要平衡好——每秒查一次全表统计在几千条记录时没问题,几万条时 SQL 就开始拖慢页面了。优化思路有两个:把查询改成只统计最近一分钟的数据;或者给timestamp字段加索引,Django 迁移里写db_index=True即可。

如果追求实时性,用 Django Channels 做 WebSocket 推送,推理线程把检测结果实时推给前端,不用前端轮询。代价是整个部署复杂度上一个台阶——需要单独跑一个 ASGI 服务,反向代理要支持 WebSocket 升级协议。对这个项目场景,我更推荐一个折中方案:推理进程写库,前端每 3 秒轮询一次最近一分钟的聚合结果,APIs 返回的是聚合后的数字而不是原始记录,响应体小、压力小、代码简单。只有当你确实需要实时视频联动才值得上 WebSocket。

最后说一个我自己的习惯:每次跑完一段视频验证,我会把检测结果和原始视频片段一起存档,标注好当天的光线条件和摄像头角度。下次模型改参数或者换数据集后,用同一批视频重新验证,对比指标变化。这套存档方法帮我避免过很多次“看着效果好、换个教室就翻车”的尴尬。希望这个完整链路和踩坑经验能帮你把这个项目跑通,也祝你在教室场景里调出真正可用的课堂报告。

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

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

零基础学Substance Painter:PBR材质贴图入门与实操避坑指南

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

作者头像 李华
网站建设 2026/10/1 1:46:44

Substance 3D 2022安装激活全攻略:Painter/Designer/Sampler部署与授权详解

1. Substance 3D 2022 套装整体拆解与安装前的关键决策1.1 这套工具到底包含什么,各自解决什么问题Substance 3D 2022 是 Adobe 收购 Allegorithmic 之后推出的一整套面向 3D 内容创作的材质与纹理工具集。很多人第一次接触这套软件时,会被 Designer、Pa…

作者头像 李华
网站建设 2026/10/1 1:46:44

钢材缺陷分割实战:4100张像素级标注数据训练与避坑指南

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

作者头像 李华
网站建设 2026/10/1 1:46:36

Steam客户端降级指南:解决Win7下steamwebhelper无响应问题

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

作者头像 李华
网站建设 2026/10/1 1:46:29

ST与梯形图混编的三种写法:PLC程序架构实战解析

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

作者头像 李华
网站建设 2026/10/1 1:45:43

更好的优化:目标对齐、成本核算与验证闭环的工程实践

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

作者头像 李华