简介:这是一套面向高校计算机相关专业毕业设计场景的完整项目资料,围绕“上课学生行为识别”展开,采用Python语言与Django框架搭建后台管理系统,结合YOLOv5与OpenCV实现课堂行为检测,数据库使用MySQL,适合作为深度学习与Web开发结合的毕设选题参考。系统区分管理员与学生两类权限:管理员负责学生注册审核、课程信息维护、行为检测记录查询与通知发布;学生端可修改个人信息、查看通知,并通过摄像头选择当前课堂课程,实时识别玩手机、睡觉、举手等课堂行为。压缩包共705个文件,约299.74MB,包含224个png、207个jpg、96个gif等图像素材,44个py源码、50个js脚本、13个html页面及21个css样式文件,另有sql建库脚本与db数据文件,覆盖前端界面、后端逻辑与模型资源。资源还附带论文、开题报告与PPT,已有110人学习,便于读者快速理解系统架构、复现检测流程并整理毕设文档。
1. 从一张教室监控截图说起:这套 Django + 深度学习行为识别系统到底能跑出什么
去年帮一个做教务的朋友看他们学校的课堂录像,四十分钟一节课,人工回看统计学生抬头率、趴桌率、玩手机次数,一个老师一天最多处理两个班,眼睛都看花了。当时我就在想,这种重复劳动完全可以用视觉模型顶上去。后来拿到这套「上课学生行为识别系统」,它的定位很明确:用 Python 深度学习做课堂场景下的学生行为分类,再用 Django 把推理结果、统计报表、课程管理串成一个能登录、能上传、能看图的 Web 系统。技术栈是 Django + PyTorch/OpenCV 这一套,配套还带了论文、开题报告和答辩 PPT,属于典型的毕业设计完整交付物。适合谁?一是正在做计算机视觉方向毕设、需要一套能跑通闭环的同学;二是想拿一个真实场景练手 Django 工程化部署的开发者。它解决的不是「识别精度刷到 SOTA」的问题,而是「从数据集标注、模型训练、推理服务到 Web 展示」这条链路怎么打通。下面我按自己拆包复现的顺序,把能抄的步骤和踩过的坑都摊开讲。
2. 环境与依赖:把 Django 和深度学习框架装到同一台机器上
2.1 为什么这套系统对 Python 版本和 CUDA 特别敏感
这套项目本质上是两个技术栈的缝合:Django 负责 Web 层,PyTorch(或 TensorFlow,看具体实现)负责推理层。缝合点在于 Django 的视图函数里要 import torch 并加载模型权重。问题就出在这里——Django 4.x 要求 Python 3.8 以上,而很多深度学习轮子对 Python 小版本又很挑。我见过最常见的翻车是:系统里装了 Python 3.12,pip 装 torch 时找不到对应版本的 whl,于是 pip 自动去编译源码,编译到一半报错退出。所以第一步不是急着 pip install,而是先把版本对齐。
我一般会这样确认环境:
# 查看当前 Python 版本,建议 3.8 ~ 3.10 python --version # 查看 pip 对应的 Python pip --version # 如果有 NVIDIA 显卡,确认驱动和 CUDA 版本 nvidia-sminvidia-smi右上角显示的 CUDA Version 是驱动支持的最高版本,不是你必须装的版本。比如显示 12.1,你装 cu118 的 torch 也完全没问题,向下兼容。没有独显就用 CPU 版 torch,训练会慢,但推理一张图几百毫秒也能接受,毕设演示够用。
2.2 依赖安装的推荐顺序与 requirements 处理
拿到源码包后,先别急着pip install -r requirements.txt。很多毕设项目的 requirements 是作者本机pip freeze出来的,里面混了一堆无关包,甚至锁死了平台相关的版本。我的做法是先看文件内容,把核心依赖挑出来单独装。
# 建议先建虚拟环境,避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate # 先装深度学习框架,CPU 版示例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 再装 Web 与图像处理相关 pip install django opencv-python pillow numpy这里有个参数值得说:--index-url指向 PyTorch 官方轮子源,比默认源快很多,也能避免装到同名但不对的包。装完 torch 后,进 Python 敲import torch; print(torch.__version__)确认能正常导入,再继续装 Django。顺序反了的话,Django 装完发现 torch 导入失败,你还得回头排查,浪费时间。
2.3 数据库迁移与静态文件:Django 首次启动必做
Django 项目跑起来之前,数据库表结构得先建好。这套系统一般用 SQLite,开箱即用,不用额外装 MySQL。
# 生成迁移文件(如果作者没提供) python manage.py makemigrations # 执行迁移,建表 python manage.py migrate # 创建后台管理员账号 python manage.py createsuperuser # 启动开发服务器 python manage.py runservermigrate这一步如果报错,八成是 models.py 里定义的字段和已有迁移文件对不上,或者作者把 db.sqlite3 一起打包了但表结构是旧的。遇到这种情况,把 db.sqlite3 删掉重新 migrate 通常能解决,但要注意会丢失已有数据。createsuperuser创建的账号用来登录 Django admin 后台,方便你直接看数据库里的识别记录。启动后浏览器访问 127.0.0.1:8000,能看到登录页或首页,说明 Web 层通了。这时候还没到模型推理,别急着传图。
3. 模型推理链路:从上传一张课堂图到返回行为标签
3.1 数据预处理:为什么训练时的归一化参数必须和推理一致
深度学习模型对输入尺寸和数值范围极其敏感。训练时如果用的是 224x224 输入、ImageNet 均值方差归一化,推理时也必须一模一样。我见过有人推理时直接cv2.imread读原图塞进模型,结果输出全是乱码标签——因为尺寸和归一化都对不上。这套系统里通常会在推理脚本或 Django 视图里封装一个预处理函数,你要确认它和训练脚本用的是同一套参数。
import cv2 import numpy as np import torch def preprocess(image_path, input_size=224): # 读取图片,注意 OpenCV 默认 BGR img = cv2.imread(image_path) # 转 RGB,因为训练时多半用的是 RGB img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 缩放到模型输入尺寸 img = cv2.resize(img, (input_size, input_size)) # 转 float 并归一化到 [0,1] img = img.astype(np.float32) / 255.0 # 标准化,均值方差要和训练一致 mean = np.array([0.485, 0.456, 0.406]) std = np.array([0.229, 0.224, 0.225]) img = (img - mean) / std # HWC -> CHW,再加 batch 维度 img = img.transpose(2, 0, 1) img = np.expand_dims(img, axis=0) return torch.from_numpy(img).float()逻辑说明:cvtColor转 RGB 是因为 OpenCV 读进来是 BGR,而 PyTorch 预训练模型基本都按 RGB 训练。resize到 224 是 ResNet、MobileNet 这类骨干网的标准输入。归一化的 mean/std 是 ImageNet 统计值,如果你训练时用的是自己算的均值方差,这里必须换成你自己的。最后transpose把通道维换到前面,expand_dims加 batch 维,因为模型 forward 期望的是 NCHW 四维张量。参数input_size要和你训练时的配置对齐,改错一个数字,精度就掉一大截。
3.2 加载权重与推理:state_dict 不匹配怎么办
模型结构定义在某个 model.py 里,权重要么是 .pth 要么是 .pt。加载时最常见的报错是Missing key(s) in state_dict或Unexpected key(s)。前者说明模型定义少了层,后者说明多了层。还有一种情况是作者用torch.save(model)保存了整个模型对象,而不是state_dict,这种加载方式对目录结构有依赖,换台机器就可能失败。
import torch from model import BehaviorNet # 假设模型定义在这个模块 # 实例化模型结构 model = BehaviorNet(num_classes=5) # 加载权重,map_location 保证 CPU 也能加载 GPU 训练的权重 state_dict = torch.load('weights/best.pth', map_location='cpu') # 如果保存的是整个模型而非 state_dict,需要判断 if isinstance(state_dict, dict) and 'state_dict' in state_dict: state_dict = state_dict['state_dict'] model.load_state_dict(state_dict) model.eval() # 切换到推理模式,关闭 dropout 和 batchnorm 更新map_location='cpu'是个后悔药级别的参数,没有它,GPU 上训练的权重在纯 CPU 机器上加载会直接报错。model.eval()必须调用,否则 dropout 层会随机丢弃神经元,每次推理结果都不一样,你会以为是模型玄学,其实是模式没切对。如果load_state_dict报 key 不匹配,先用print(model.state_dict().keys())和print(state_dict.keys())对比,看差在哪一层,再决定是改模型定义还是改权重加载逻辑。
3.3 把推理接进 Django 视图:一次请求的完整生命周期
Web 层和模型层的交汇点在 views.py。用户上传图片,视图接收文件,调用预处理和推理,把结果写进数据库,再渲染到模板。这里要注意的是模型加载不能放在每次请求里,否则每传一张图就重新加载一遍权重,慢到无法忍受。正确做法是在模块级别或 AppConfig.ready() 里加载一次,全局复用。
from django.shortcuts import render from django.core.files.storage import FileSystemStorage from .models import RecognitionRecord from .inference import predict # 封装好的推理函数 def upload_and_predict(request): if request.method == 'POST' and request.FILES.get('image'): image = request.FILES['image'] # 保存上传文件 fs = FileSystemStorage() filename = fs.save(image.name, image) file_path = fs.path(filename) # 调用推理,返回标签和置信度 label, confidence = predict(file_path) # 写入数据库 RecognitionRecord.objects.create( image=filename, label=label, confidence=confidence ) return render(request, 'result.html', { 'label': label, 'confidence': round(confidence, 4), 'image_url': fs.url(filename) }) return render(request, 'upload.html')逻辑说明:request.FILES拿到上传的文件对象,FileSystemStorage负责落盘并返回可访问路径。predict函数内部做了预处理、模型 forward、softmax 取最大概率。RecognitionRecord.objects.create是 Django ORM 的写入操作,一行搞定建表插入。confidence用 round 保留四位小数,避免模板里显示一长串浮点数。这里有个隐藏坑:如果predict里加载模型用了全局变量,而 Django 开发服务器默认是多线程的,第一次请求时模型还没加载完,第二个请求进来可能拿到空模型。稳妥做法是在apps.py的ready()里预热加载。
4. 避坑与排查:复现这套系统时最容易翻车的五个地方
4.1 上传图片后页面报 500,日志显示 CUDA out of memory
现象:本地用 CPU 跑得好好的,换到有显卡的机器或者服务器上,传几张图之后突然 500。原因:模型默认加载到了 GPU,但显存被其他进程占着,或者 batch 维度没控制好,一次塞了太多图。解决:在加载权重时显式指定map_location='cpu',或者推理前torch.cuda.empty_cache()。如果确实要用 GPU,把 batch_size 设为 1,课堂场景单张推理足够。另外检查是不是在视图里循环调用了模型加载,导致显存里堆了一堆重复模型。
4.2 识别结果永远是同一个标签
现象:不管传什么图,输出都是「抬头」或都是「趴桌」。原因:最常见的是预处理归一化参数和训练不一致,模型收到的输入分布完全变了,相当于在噪声上推理。其次是model.eval()没调用,dropout 随机性导致输出不稳定但偏向某一类。解决:先确认训练脚本里的 mean/std 和 resize 尺寸,逐项对齐;再确认 eval 模式;最后用训练集里的一张图做推理,如果训练图都识别错,那就是权重加载有问题,检查 state_dict 的 key 是否完全匹配。
4.3 Django 静态文件 404,CSS 和 JS 加载不出来
现象:页面能打开,但样式全乱,控制台一堆 404。原因:Django 开发模式下静态文件需要配置STATIC_URL和STATICFILES_DIRS,生产模式下还需要collectstatic。很多毕设项目只在自己机器上跑过,settings.py 里的路径是绝对路径,换台机器就找不到。解决:检查 settings.py 里STATIC_URL = '/static/'和STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')],确保 static 目录真实存在。如果用了DEBUG = False,还要配STATIC_ROOT并执行python manage.py collectstatic。
4.4 数据库迁移报 no such table
现象:migrate显示成功,但一访问页面就报no such table: app_xxx。原因:Django 的 app 没有注册到INSTALLED_APPS里,迁移时跳过了这个 app 的模型。或者作者提供的 db.sqlite3 是旧版本,表结构对不上。解决:打开 settings.py,确认你的 app 名字在INSTALLED_APPS列表里。如果是旧数据库,删掉 db.sqlite3,重新makemigrations+migrate。注意makemigrations要指定 app 名,比如python manage.py makemigrations yourapp,否则可能漏掉。
4.5 上传大图导致请求超时或内存溢出
现象:手机拍的课堂照片三四千像素宽,上传后服务器卡死或报 MemoryError。原因:原图直接读进内存做 resize,一张 4000x3000 的图占几十 MB,并发几个请求就爆了。解决:在预处理前先做一次降采样,用cv2.resize把长边限制到 800 以内再走后续流程。或者在 Django 层面限制上传文件大小,settings.py 里设置DATA_UPLOAD_MAX_MEMORY_SIZE和FILE_UPLOAD_MAX_MEMORY_SIZE。课堂场景不需要原图精度,压缩后再推理对结果几乎没影响。
5. 进阶技巧:用 Django ORM 做行为统计与模型置信度过滤
5.1 按课程和时间段聚合识别记录
系统跑起来之后,真正有价值的是统计数据,而不是单张图的标签。Django ORM 的聚合查询能直接出报表,不用手写 SQL。比如统计某门课下每种行为出现的次数:
from django.db.models import Count from .models import RecognitionRecord # 按标签分组计数,过滤置信度低于 0.6 的记录 stats = (RecognitionRecord.objects .filter(course_id=1, confidence__gte=0.6) .values('label') .annotate(total=Count('id')) .order_by('-total')) for item in stats: print(item['label'], item['total'])filter里的confidence__gte=0.6是置信度阈值,低于这个值的记录不参与统计,避免模型瞎猜的标签污染报表。values('label')指定分组字段,annotate做计数,order_by('-total')按次数降序。这套查询在 Django 里是惰性的,只有遍历 stats 时才真正打数据库,所以可以放心在视图里用。如果你要按时间段统计,把filter里的条件换成create_time__range=(start, end)即可。
5.2 置信度阈值怎么定:别拍脑袋,看分布
置信度阈值不是拍脑袋定的 0.5 或 0.6。我的习惯是先把一批测试图的置信度打印出来看分布。如果模型对某类行为普遍给出 0.9 以上,那阈值可以设 0.7;如果普遍在 0.5 到 0.7 之间晃,说明模型本身没学好,这时候调阈值只是自欺欺人,得回去看训练。可以用下面这段代码快速看分布:
import numpy as np from .inference import predict confidences = [] for path in test_image_paths: _, conf = predict(path) confidences.append(conf) # 打印分位数,了解整体置信水平 print('25%:', np.percentile(confidences, 25)) print('50%:', np.percentile(confidences, 50)) print('75%:', np.percentile(confidences, 75))如果中位数在 0.8 以上,说明模型对大部分样本有把握,阈值设 0.6 到 0.7 比较稳。如果中位数只有 0.5 出头,那说明模型在瞎猜,这时候该做的是检查训练数据标注质量、类别是否均衡、学习率是否合理,而不是在推理端调参。我一般会把阈值设成中位数减 0.1,这样能过滤掉明显不可信的记录,又不至于把大部分结果都扔掉。
5.3 一个我踩过的坑:模型文件路径写死导致部署失败
最后说个血泪经验。很多毕设项目里加载权重的路径是写死的绝对路径,比如torch.load('D:/project/weights/best.pth')。你自己机器上跑没问题,一换机器或者部署到服务器就报 FileNotFoundError。正确做法是用os.path.join(BASE_DIR, 'weights', 'best.pth'),BASE_DIR 在 Django settings 里已经定义好了。从那以后我每次拿到新项目,第一件事就是全局搜torch.load和open(,看有没有硬编码路径。这个习惯帮我省了至少三次通宵排查。希望这套拆解能帮到你,少走点我走过的弯路。
本文还有配套的精品资源,点击获取