基于深度学习的学生课堂行为识别系统设计与Django部署实践 简介图像分类是计算机视觉的基础任务之一其核心在于利用卷积神经网络自动提取图像中的空间特征并映射到语义标签。随着深度学习技术的成熟模型不仅要具备高精度还需通过Web服务实现工程化落地。Django作为成熟的Python Web框架能够将训练好的模型封装为REST API支撑图片上传、推理调用与结果展示形成从数据标注到业务展示的完整链路。这一技术组合在智慧校园场景中具有典型应用价值例如课堂学生行为识别可自动分析学生听讲、玩手机、睡觉等状态辅助教学管理。本文围绕该场景系统讲解了CNN模型选型、训练调优、Django服务化部署、异步任务优化及生产环境搭建等关键环节为开发者提供一套可复用的深度学习工程实践方案。技术栈与架构设计这套系统到底是怎么跑起来的拿到这个题目的时候我第一反应是又有人要做课堂行为分析了。这个方向其实在计算机视觉领域火了好几年了从最早的传统图像处理到现在的深度学习方案技术迭代了好几轮但核心思路一直没变把摄像头采集到的课堂画面交给模型去推理自动判断画面里的学生是在认真听课、低头玩手机、趴在桌上睡觉还是举手发言、和同学交头接耳。这套系统的技术选型其实很有代表性。深度学习负责视觉识别这一块Django负责把模型能力封装成Web服务两者加起来就是一个完整的B/S架构应用。如果单独看深度学习部分本质就是一个图像分类任务输入是每一帧图像里的学生区域输出是该学生当前的行为类别而Django部分要解决的则是怎么把模型加载到后端、怎么接收前端上传的图片或视频流、怎么把识别结果持久化到数据库最后通过Web页面展示给老师或教务管理人员。从项目结构上讲这套系统大致分成了几个模块数据层标注好的学生行为图片数据集包含训练集和测试集。模型层基于CNN类的深度学习模型负责提取图像特征并完成分类。服务层Django编写的REST API处理图片上传、模型调用、结果返回。展示层管理后台和前端页面用于查看识别记录、统计结果、可视化展示。这个分工基本覆盖了“数据标注—模型训练—服务部署—业务展示”的完整链路也是目前做此类课题最标准的套路。如果你是自己做毕业设计或者课程项目直接照这个思路拆解文档整个项目的脉络一下就清楚了。深度学习模型的核心细节CNN和注意力机制怎么选既然系统名字叫“基于深度学习的上课学生行为识别”那模型部分自然是绝对的重头戏。这里我先说结论绝大多数学生行为识别系统走的路线都是CNN来做图像分类或者CNN序列模型来做视频行为识别。如果只是识别单张静态图片那本质上就是图像分类任务。比如判断一个学生是“认真听讲”“玩手机”“趴在桌子上”还是“举手”每一类都对应一个标签模型训练的过程就是让网络学会从像素到语义标签的映射。这种情况下常用的骨干网络有ResNet、MobileNet、EfficientNet这类经典分类网络它们的区别主要体现在精度和计算量的权衡上。如果是做视频流实时识别那就要用到时序信息了。因为单帧图像有时候会出歧义比如学生低着头可能是在看书也可能是在玩手机但如果看连续几帧的画面大概率就能判断出来。这种场景常用的方案是使用双流网络空间流时间流或使用SlowFast这类视频理解网络再或者用CNN提取特征后用LSTM来建模时序关系。不过考虑到训练成本和推理速度很多实际项目用的是折中方案每隔几帧取一帧关键帧用CNN识别帧中的学生行为再用平滑策略把相邻帧的结果做融合相当于用后处理逻辑模拟时序建模。再来说说池化这件事。CNN里的池化层虽然有多个变体核心作用就是下采样把特征图的尺寸缩小同时保留最重要的激活信息。最常用的是最大池化Max Pooling和平均池化Average Pooling。在行为识别任务里我个人倾向于使用全局平均池化Global Average Pooling接在全连接层之前它能直接把整个特征图压成一个特征向量降低过拟合风险也方便后面接分类器。很多初学者容易忽略一个细节池化不是越多越好一旦下采样过猛小目标比如画面里远处的学生的细节信息会直接丢失识别准确率会断崖式下降。我当时在做数据集测试的时候还发现一个问题同一个学生坐在第一排和坐在最后一排实际框出来的像素尺寸差异非常大。如果模型只针对近距离样本训练远距离基本全部误判。这个场景下的解决办法有两种数据增强阶段做多尺度训练Random Resized Crop让模型对不同尺寸的目标都具备一定的适应能力。检测阶段先做人脸或人体检测把目标区域裁剪出来再送入分类模型。因为裁剪后的目标大小基本一致分类器只需要关注目标区域内的特征。实际项目里第二种方法更稳。毕竟行为识别面向的是整个教室场景直接端到端分类容易受背景干扰。更合理的技术路线是“目标检测 目标分类 时序平滑”三层结构。目标检测负责框出每个学生分类网络负责判断框内行为最后用滑窗投票的方式稳定输出。环境准备模型训练和Django服务部署要准备哪些东西在真正动手之前环境这块得先理顺。这个项目牵扯两套环境一套是模型训练环境一套是Django运行环境。这两者不一定必须在同一台机器上但一定要提前规划好。模型训练一般要求有GPU因为CNN的训练若只在CPU上跑哪怕一个小的ResNet18跑几十个epoch也够你等到怀疑人生。我建议用一张NVIDIA显卡至少8GB显存起步。显存大小决定了batch size的上限batch size又直接影响收敛速度和最终精度这个关系很多新手第一次做项目时容易忽略。操作系统方面Windows和Linux都行。Ubuntu装深度学习环境确实坑比较多尤其是显卡驱动和CUDA版本匹配问题。我自己遇到最多的情况就是系统装好NVIDIA驱动之后执行nvidia-smi一点问题没有但一跑PyTorch就报错说CUDA不可用。排查到最后往往是PyTorch版本、CUDA toolkit版本和驱动版本三者之间不兼容。这里给你一个比较稳的组合如果不知道选什么版本就直接抄作业Python 3.8或3.10CUDA 11.8cuDNN 8.6PyTorch 1.13.1或2.0.1torchvision 0.14.1或0.15.1如果不想被本地环境折腾也可以直接用Autodl这类云GPU平台。它们的好处是环境镜像已经预先装好了主流深度学习框架租一台4090的机器半小时之内就能开始训练按小时计费对项目周期短、只需要验证实验结果的场景非常划算。Django环境就简单多了它本身就是纯Python的Web框架对硬件没有要求。安装方式直接pip install django就行需要确认的两件事是Python版本和Django版本。Django 2.2以上版本目前都支持Python 3.6以上的环境但为了省心建议直接用Python 3.10 Django 4.2组合这个组合在社区里踩坑的人少遇到的坑也大多有现成的解决方案。对了如果你之后要把项目部署到服务器上最好提前用虚拟环境把依赖锁一份完整的requirements.txt。训练环境和部署环境的依赖版本不一致非常容易导致模型推理结果和训练时的结果不一致而且这种偏差特别难排查。模型的训练流程与关键参数怎么把准确率调到能用的水平这套系统里深度学习模型是最核心的所以模型训练这部分要重点说。一张图像进入神经网络之后卷积层提取的空间特征会经过池化、归一化、激活函数等处理最终在全连接层被映射成每个行为类别的概率分布。损失函数最常用的是交叉熵损失CrossEntropyLoss优化器常用Adam或SGD带动量。Adam收敛快、对学习率不敏感初学用它比较容易出效果SGD训练出来的模型泛化性能通常更好但需要手动调整学习率策略训练周期也要更长。我的建议是先用Adam跑通流程确认数据没问题、loss能正常下降再用SGDCosineAnnealingLR做一次精细训练这样可以兼顾开发效率和最终精度。训练轮次epoch的话50到100轮是比较常见的设置。很多同学会踩一个误区喜欢把epoch设置到几百甚至上千轮然后发现一个典型问题训练集上的准确率持续上升验证集却一直在原地踏步。这就是过拟合的信号。遇到这种情况我的经验是优先检查数据集本身是否存在数据泄露比如同一个学生的多张图片被同时划入了训练集和验证集模型相当于直接记答案然后再看模型是否太大、数据增强是否太少最后再决定要不要加Dropout或权重衰减。数据增强这块我强烈建议要加到位。上课场景下学生的姿态变化、光线变化、遮挡情况都很复杂。像我之前做的一组实验仅仅加了随机水平翻转和随机亮度扰动准确率就有两三个点的提升。如果再加入RandomErasing、Cutout这类模拟遮挡的数据增强方式对遮挡情况的鲁棒性会有明显改善。行为类别定义也需要提前想清楚。有些项目把行为分得特别细比如“记笔记”、“翻书”、“看黑板”都算单独一类但这种细粒度分类对标注质量和数据量的要求极高单类别样本不够时模型很容易互混。更合理的做法是合并成大类别比如“认真听讲看黑板/看书/记笔记”“做小动作玩手机/东张西望/交头接耳”“睡觉趴在桌上/闭眼”这样组合实际效果会好很多。训练完成后一定要看混淆矩阵而不要只看总体准确率。我遇到过一种情况总体准确率到了90%以上看似不错打开混淆矩阵发现“玩手机”这个类别的召回率只有60%大量样本都被误判成了“听讲”。原因是玩手机时手部区域的视觉特征变化太大和低头看书的姿态又接近模型学到的最优策略就是直接把这个类别忽略掉。这种坑不看混淆矩阵完全发现不了。Django项目架构与核心代码实现模型怎么被部署成Web服务模型训练好了只是第一步真正让这个系统能被使用得靠Django把它封装成Web服务。Django本身是一个重量级框架自带ORM、Admin后台、模板引擎、认证系统等一整套组件。用它做算法服务的好处就是这些基础设施都给你准备好了你要做的只是把模型推理的逻辑融进去。我建议的Django项目结构大概是这样的student_behavior_system/ ├── manage.py ├── requirements.txt ├── behavior_app/ │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── services/ │ │ ├── recognition.py │ │ └── model_loader.py │ └── migrations/ ├── static/ │ ├── css/ │ ├── js/ │ └── uploads/ ├── media/ │ └── images/ └── templates/ ├── index.html └── result.html这里有个关键设计模型加载和业务逻辑分开。在Django菜鸟教程中第一个跑的Hello World基本都是在views.py里直接写业务逻辑但放到机器学习项目里如果每次请求都去加载模型系统会直接卡死。正确做法是写一个model_loader模块在Django启动时把模型加载进全局变量后续请求只需要复用这个已经加载好的模型实例即可。# behavior_app/services/model_loader.py import torch from torchvision import models, transforms _model None def get_model(): global _model if _model is None: _model models.resnet18(num_classes6) _model.load_state_dict(torch.load(models/resnet18_behavior.pth, map_locationcpu)) _model.eval() return _model def preprocess(image): transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) return transform(image).unsqueeze(0)views.py里调用这个模型时只需要调用get_model()拿模型实例然后把图片预处理之后丢进去推理就行。需要注意模型服务化的一个核心差异是推理模式必须设置为eval()因为训练模式下Dropout和BatchNorm的行为和推理模式完全不同直接用训练模式推理会导致结果随机波动这种问题在代码里特别隐蔽。# behavior_app/views.py import torch from django.shortcuts import render from django.http import JsonResponse from .services.model_loader import get_model, preprocess CLASS_NAMES [认真听讲, 举手发言, 睡觉, 玩手机, 低头写字, 站立] def recognize_view(request): if request.method POST: image_file request.FILES.get(image) if not image_file: return JsonResponse({error: 未上传图片}, status400) image Image.open(image_file).convert(RGB) tensor preprocess(image) with torch.no_grad(): logits get_model()(tensor) probs torch.softmax(logits, dim1) conf, idx torch.max(probs, dim1) label CLASS_NAMES[idx.item()] confidence conf.item() return JsonResponse({label: label, confidence: round(confidence, 4)}) return render(request, index.html)数据库模型部分可以设计一张BehaviorRecord表用来记录每一次识别的结果# behavior_app/models.py from django.db import models class BehaviorRecord(models.Model): image models.ImageField(upload_touploads/) label models.CharField(max_length50) confidence models.FloatField() created_at models.DateTimeField(auto_now_addTrue)数据库这里我建议先用Django默认的SQLite等系统规模上来了再切换到MySQL/PostgreSQL。SQLite对单机部署的毕设或小场景系统完全够用开箱即用、零配置而且Django对SQLite的支持一直很稳定。前端页面与可视化识别结果怎么展示才直观前端这块如果只是做后台管理Django自带的Admin就能应付。但如果你想做一个给老师直接用的界面那还是得自己写几个页面。这里我建议用一个相对简洁的方案原生HTML Bootstrap jQuery不需要引入Vue或React这种重型前端框架。因为行为识别系统的核心交互很简单——上传图片、查看结果、浏览历史记录用不上复杂的前端状态管理。首页可以做成上传交互form iduploadForm enctypemultipart/form-data {% csrf_token %} div classupload-area input typefile nameimage acceptimage/* required /div button typesubmit开始识别/button /form div idresult/div script $(#uploadForm).on(submit, function (e) { e.preventDefault(); var formData new FormData(this); $.ajax({ url: /recognize/, type: POST, data: formData, processData: false, contentType: false, success: function (res) { $(#result).html( h3识别结果: res.label /h3 p置信度: res.confidence /p ); } }); }); /script这个交互足够简单直接老师上传一张课堂照片系统返回识别结果和置信度。如果你希望系统看起来更高级可以把识别结果做成统计报表的形式。比如用ECharts画一个饼图展示当前照片里各种行为类别的占比或者用折线图展示一段时间内的课堂专注度变化趋势。ECharts的社区案例很多接入也简单引入js文件后直接按文档写配置项就行。前端展示这块有一个小建议置信度要展示但同时也要给一个可信任度分级标注。比如置信度在90%以上显示绿色“可靠”70%-90%显示橙色“参考”低于70%显示红色“需人工确认”。因为自动识别系统总有误判的可能让使用者知道哪些结果是可信的、哪些结果需要复核比单纯给一个冷冰冰的百分比要实用得多。还有一个点容易被忽略上传图片后最好直接把图片显示在页面上把检测框和标签画上去。如果你用了目标检测模型那输出就不只是类别标签而是一组框坐标。Django后端可以通过PIL的ImageDraw在图片上画框并把结果保存前端展示画好框的图片。这样老师一眼就能看出每个学生被识别成了什么行为而不是看一串文字结果。性能优化与并发策略多用户同时上传时怎么办校园环境里最可能的并发场景是多个老师同时登录系统上传课堂照片或者一个老师上传了一段视频系统需要逐帧处理。这种情况如果处理不好Django服务会直接被阻塞表现在用户体验上就是页面一直转圈、请求超时。模型推理本身是CPU密集如果GPU推理则是GPU密集操作处理一张图片大约需要几十到几百毫秒处理一段视频里的几百帧图片就不是几秒能解决的了。如果这些操作全部放在同步的views里Django的worker进程会被占满新的请求就排不上队。几种成熟的优化策略第一种是异步任务队列。把模型推理任务扔到Celery Redis队列里views里只负责接收任务、返回任务ID前端再通过轮询或WebSocket查询任务状态。这样用户上传视频后可以关闭页面等处理完成后再查看结果。这个方案功能最完整但增加了系统复杂度部署环境里要多维护Redis和Celery worker。第二种是进程内队列。用Python标准库的queue模块配合后台线程处理任务再搭配一个简单的任务状态表。实现起来比Celery简单很多适合中小型系统的并发需求。第三种是多进程部署。用Gunicorn或uWSGI启动多个Django worker每个worker内部加载一份模型副本。这样同一时刻可以并行处理多个推理请求但缺点是内存占用会随着worker数线性增长。一个ResNet18模型在内存中大约占几十MB启动4个worker就是几百MB一般服务器都能承受。我在实际项目中比较推荐第二种方案。简单稳定维护成本低。核心逻辑是把推理任务放入队列后台线程消费队列并更新任务状态。import queue import threading from .services.model_loader import model_inference task_queue queue.Queue() task_results {} def worker(): while True: task_id, image_path task_queue.get() try: result model_inference(image_path) task_results[task_id] {status: done, result: result} except Exception as e: task_results[task_id] {status: error, error: str(e)} finally: task_queue.task_done() threading.Thread(targetworker, daemonTrue).start()views里提交任务时直接把task_id返回给前端def async_recognize(request): task_id str(uuid.uuid4()) task_queue.put((task_id, image_path)) return JsonResponse({task_id: task_id}) def task_status(request, task_id): result task_results.get(task_id) if result: return JsonResponse(result) return JsonResponse({status: processing})前端只需要每两三秒轮询一次任务状态拿到结果后就展示。这个方案实现简单对Django的侵入性也小是整个项目里性价比最高的一块优化。模型训练与系统集成时的坑这些错误我当年都踩过做这个项目时我印象最深的一个问题是模型单独测试时准确率很高但集成到Django里之后识别结果出现了大量错误。当时我排查了半天最后发现原因让人哭笑不得——数据预处理对不上。训练时用的是PyTorch的transforms包含Resize到(224,224)、ToTensor、Normalize三步。但Django项目里为了图省事我直接用OpenCV读图然后调用训练好的模型完全跳过了Normalize这一步而且OpenCV读出来的是BGR通道顺序和PyTorch的RGB通道顺序不一致。两张图的输入分布完全不对模型预测出来的结果自然是一团糟。这个问题在各大数据科学社区里被讨论过很多次属于比较典型的封装阶段失误。解决方式很简单训练和推理过程必须共享同一套预处理流程。我的做法是把数据预处理逻辑整合成独立模块训练时和部署时都调用这一个模块不搞两套逻辑。另一个我踩过的坑是路径硬编码。训练脚本里写死了本地路径比如/home/ubuntu/data/classroom_images/train结果代码拿到新环境后直接报FileNotFoundError。后来我强制要求自己在项目里统一使用pathlib.Path来构建路径并且把数据目录、模型目录、日志目录等全部提取到配置文件里不做硬编码。还有一类问题跟模型文件大小有关。训练好的PyTorch模型权重文件如果是ResNet50这种中等规模的网络通常有100MB左右。Django部署时如果把这些权重文件直接塞进Git仓库文件会变得非常臃肿clone和部署都慢。更好的做法是权重文件单独存放或者用Git LFS管理大文件代码仓库里只保留下载脚本。模型量化这块也值得考虑一下。训练时用的是FP32精度但推理阶段可以转成int8量化模型体积能缩小到原来的四分之一左右推理速度也有显著提升。如果你的部署环境CPU性能一般量化是一个性价比很高的优化方向。不过量化后精度可能会有轻微损失建议量化前后都跑一遍同一批测试集对比一下准确率变化如果掉点超过1个百分点就建议保留FP32模型。数据库设计与查询技巧识别记录怎么管理才高效Django的ORM做得相当成熟这个项目的数据库设计也不需要太复杂核心就是一张行为记录表。但为了后续扩展方便我会在表结构设计上多做一点考虑。比如如果系统支撑的是一次性识别一张照片那行为记录表可以设计成上面提到的那样一条记录对应一次识别结果。但如果要做课堂整体统计分析就需要把“识别批次”这个概念加进来。可以设计两张表一张Session表保存一次识别会话的信息比如上传时间、图片数量、教师ID一张BehaviorRecord表每条记录关联一个Session。这样就能很方便地统计“某一次课堂里各类行为的占比”或者“某个班级连续几周的行为趋势”。class Session(models.Model): description models.CharField(max_length200, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class BehaviorRecord(models.Model): session models.ForeignKey(Session, on_deletemodels.CASCADE, related_namerecords) image models.ImageField(upload_touploads/) label models.CharField(max_length50) confidence models.FloatField() created_at models.DateTimeField(auto_now_addTrue)查询方面Django ORM提供了很多实用的方法。比如要统计某一次Session里每个行为类别的数量只需一行from django.db.models import Count record_counts BehaviorRecord.objects.filter(session_id1).values(label).annotate(countCount(id))如果要删除某个Session下的所有历史记录注意Django的CASCADE外键级联删除机制会自动把这个Session关联的所有BehaviorRecord一并清空不会留下孤儿数据。执行删除的时候有一个容易出错的地方如果记录条数很大直接调用delete()方法虽然简单但如果你希望保留操作日志或者需要先把数据备份到文件再删那就要在删除前先做好数据导出否则删错了数据是找不回来的。除了基础的增删改查Django还提供了一些更高级的查询功能比如按时间范围筛选或者按置信度阈值筛选。这些操作在后台管理里使用频率很高熟练运用Django的ORM真的能大幅提升开发效率。系统部署与上线从开发环境到生产环境的几道坎Django项目从开发环境跑到生产环境要做的迁移工作比很多人想象的多。开发时用的runserver服务器自带自动重载功能方便调试但它是一个单进程开发服务器性能和稳定性都无法支撑生产环境的并发要求。生产环境部署一般用Gunicorn或者uWSGI作为Django的WSGI服务器Nginx负责反向代理和静态文件服务。部署架构大致是这样浏览器 → Nginx → Gunicorn (Django) → MySQL/PostgreSQLNginx直接面对用户请求把动态请求转发给Gunicorn把静态文件直接返回这样能减轻Django应用服务器的压力。静态文件包括CSS、JS、图片等Django开发模式下能自动处理但生产环境必须用collectstatic命令收集到指定目录再由Nginx配置alias指向这个目录。我在Ubuntu上部署的时候整体流程大概是这样的安装Python虚拟环境和依赖python3 -m venv venv然后source venv/bin/activate再pip install -r requirements.txt安装Nginx和Gunicorn修改Django配置文件把DEBUG设为False配置ALLOWED_HOSTS为服务器IP或域名配置STATIC_ROOT和MEDIA_ROOT执行python manage.py collectstatic收集静态文件执行python manage.py migrate初始化数据库如果用的是SQLite这一步会创建数据库文件用Gunicorn启动Django服务gunicorn student_behavior_system.wsgi:application -b 127.0.0.1:8000配置Nginx把80端口请求转发给127.0.0.1:8000并配置静态文件的映射设置systemd服务让Gunicorn在系统重启后自动拉起还有一个很容易被忽略的配置是CSRF_TRUSTED_ORIGINS。如果Django部署在HTTPS域名下不配置这个参数所有POST请求都会因为CSRF校验失败而无法提交。这个报错在本地开发时不会出现只有部署到线上之后才会遇到而且报错信息比较隐蔽第一次遇到可能根本不会往这个方向排查。我的建议是部署前先在本地把SECRET_KEY改成环境变量读取把数据库连接、模型路径等全部通过环境变量或配置文件管理。这样在服务器上部署时只需要修改环境变量不需要改动任何代码。这个习惯看起来微不足道但在你真正需要把项目从一台机器迁移到另一台机器的时候能省下大量踩坑的时间。常见问题排查与调试技巧十个最典型的报错场景开发过程中会遇到很多让人头疼的报错我整理了一份排查清单这些都是在实际处理类似项目时反复出现的典型问题问题场景可能原因排查思路Django启动时报ModuleNotFoundError缺少依赖包检查requirements.txt是否完整用pip list核对执行迁移时报No migrations to apply迁移文件与数据库状态不一致删掉数据库和migrations记录重新执行migrate上传图片时提示文件过大请求体超过Django默认限制Django的DATA_UPLOAD_MAX_MEMORY_SIZE参数调大模型推理时报shape mismatch输入尺寸或通道数和模型预期不一致打印模型输入输出的shape检查预处理参数识别结果一直是一个类别模型训练过拟合或类别分布不均查看混淆矩阵检查测试集是否包含各类别的样本前端页面加载很慢静态文件没有被Nginx代理检查Nginx静态文件配置确认collectstatic执行过同步请求导致页面卡顿模型推理占满了worker改用异步队列方案限制推理并发数远程服务器访问不了网站安全组或防火墙没放行端口检查云控制台安全组规则确认端口已开放数据库迁移报字段冲突模型改动后未更新迁移文件执行makemigrations重新生成迁移文件视频上传后一直显示处理中后台任务线程被kill或崩溃查看Django日志确认异常信息这里我想特别提一下模型类别映射错乱这个坑。如果训练时类别的索引顺序是{听讲: 0, 玩手机: 1, 睡觉: 2}但Django代码里用的是[听讲, 睡觉, 玩手机]那么模型预测出索引1时系统会把它显示成“睡觉”但模型真正识别的是“玩手机”。这种问题在项目调试阶段尤其容易发生因为模型文件、代码、文档在多人协作或版本迭代过程中很容易出现不一致。我的解决方法是把类别映射表单独放在配置文件里模型训练时和Django加载时都读取同一份配置不复制两份映射表。同时在启动Django时打印一遍类别映射方便在日志里确认。效果评估与优化空间准确率还能不能往上提最后一个话题聊聊效果评估。行为识别系统的评估不能只靠准确率一个指标更合理的评估维度包括准确率所有预测中预测正确的比例。召回率某一类别的样本中被正确识别出来的比例。比如“睡觉”行为如果模型经常把睡觉误判成“低头”那“睡觉”这个类别的召回率就低。F1-Score准确率和召回率的调和平均类别不均衡时比准确率更可靠。单帧推理延迟模型对单张图片的处理时间。这个指标直接关系到系统能否支撑实时识别场景。如果发现模型在某个类别上表现特别差可以考虑从这几个方向去优化增加该类别的训练样本数量。现实中“睡觉”样本可能远远少于“听讲”样本需要在数据采集阶段做平衡。针对困难样本做数据增强特别是模拟教室环境下的光照变化、遮挡情况。尝试更强的骨干网络。ResNet18换成ResNet50或者EfficientNet-B3准确率通常会有提升但推理速度会下降需要结合部署环境做取舍。使用Focal Loss替代交叉熵损失。Focal Loss可以降低易分类样本的权重让模型更关注难分类的样本对类别不均衡问题有一定缓解效果。如果项目时间充裕还可以尝试引入时空注意力机制。之前有一段时间我在调研课堂行为识别方向时留意到几篇CVPR上关于视频动作识别的论文里提出了一个很有意思的方向检测学生的人体关键点来辅助行为判断。比如“举手”这个行为从姿态特征上识别比从图像特征上识别要容易得多因为它的姿态模式非常明确——手臂抬起超过肩膀。如果把姿态估计网络和分类网络的特征融合起来对一些姿态特征明显的行为准确率能有明显提升。不过引入姿态估计会增加系统的复杂度和推理时间是否值得去做取决于你的应用场景。如果识别目标只有听讲、玩手机、睡觉这三类用传统的CNN分类模型就能解决不需要太多的额外模块但如果你希望对“举手”“起立”这类动作类行为做细粒度识别姿态信息就很有价值了。我觉得从毕设或者课程设计的角度来说当前这套“目标检测CNN分类Django服务”的组合方案已经足够完整既能体现深度学习的技术深度又能借助Django展现实战项目的工程能力。整套系统从数据到训练到部署的链路全部打通是一个标准且实用的学习项目模板。如果你后续想在这个项目基础上继续迭代我建议把精力优先投向数据质量的提升和模型的轻量化部署这两个方向这两个方向带来的收益最直接。本文还有配套的精品资源点击获取