☰
基于YOLOv11+Django+DeepSeek的课堂行为检测系统设计与部署
2026/10/19 3:05:19 网站建设 项目流程

近几年智慧课堂、教学督导这些词在教育信息化里热度一直很高,但真正落到地上、能部署到中职和高职机房里跑起来的项目其实不多。我之前带团队做过不少类似的监控分析类Demo,也踩过不少坑。最近看到一个“基于yolov11+django+deepseek的学生课堂行为检测系统带登录界面python源码+onnx模型+精美web界面”的项目标题,正好和我手上在弄的一个模拟项目X高度重合,就想把这个系统的拆解思路、代码组织和部署经验完整写出来。

先给不了解的朋友说下这是个什么东西:它本质上是一个“摄像头画面进、结构化行为数据出”的课堂分析系统,摄像头抓拍课堂场景,yolov11负责把画面里的学生头和身体框出来,并按行为类别分类(比如举手、玩手机、睡觉、趴桌子、站立),django负责把检测结果存库、渲染Web页面、提供账号登录和权限控制,DeepSeek大模型接口则负责把一段时间的检测记录转成自然语言的教学建议。最终交付物是一个带登录页、实时检测画面、行为曲线、报告导出功能的完整Web应用,模型部分已经转成onnx格式,所以不依赖GPU也能在普通教室主机上跑推理。

这个项目最值得讲的地方在于:它不是一个纯算法实验,而是一条完整的工程链路——从模型训练、模型转换、后端服务、前端可视化再到接口对接,全都串起来了。如果你正在做毕业设计、实训项目,或者想给学校做一套课堂行为分析Demo,这套组合是很有参考价值的。下面我按实际开发顺序,把这套系统从设计到部署的关键环节全拆开讲。

1. 系统整体设计:为什么是yolov11+django+deepseek这个组合

1.1 技术选型背后的核心逻辑

先说模型端。yolov11是2024年发布的新一代YOLO系列模型,相比上一代yolov8,它在Backbone里用C2PSA模块替换了原来的C2f模块,引入注意力机制后对小目标的特征提取能力明显增强。课堂场景里有大量远距离、小尺寸目标——最后一排的学生头可能只有几十个像素大,用yolov8经常漏检,yolov11的mAP50在小目标上能提升3到5个点,这个差距在真实教室环境里非常直观。

选择onnx格式做推理,是为了解决部署环境不一致的问题。Python的原生PyTorch模型要求服务器上装好对应版本的torch、CUDA等一大堆依赖,换一台机器就容易版本冲突。而onnx是一种中间表示格式,只要有onnxruntime这个轻量级推理库就能跑,CPU也能流畅推理,Windows和Linux都能用。我们把训练好的yolov11.pt导出成yolov11.onnx后,部署端就只需要装onnxruntime和opencv,依赖关系干净利落。

再说服务端。django是Python生态里最成熟的Web框架,自带ORM、Admin后台、用户认证体系,这对一个需要登录界面和权限控制的系统来说太关键了。自己用Flask写登录和权限管理也能做,但django内置的django.contrib.auth模块开箱即用,Session管理、密码哈希、登录状态判断全都有现成的实现,开发效率完全不同。

最后是DeepSeek大模型接口。这个环节解决的是“检测出来之后怎么办”的问题——如果系统只输出一堆行为标签和时间戳,老师根本看不懂。DeepSeek的作用是把这些数据做语义化总结:比如读取过去45分钟的行为记录,自动生成“本堂课学生专注度整体中等偏上,走神行为集中在第20到30分钟,建议在此时段插入互动环节”这类教学建议。这种“视觉模型+大模型”的组合思路,是目前课堂分析类项目的主流演进方向。

1.2 系统功能模块划分

整套系统按功能可以拆成四个模块:

用户认证模块:包含登录页、注册页、Session管理。用户角色分管理员和普通教师,管理员能看到全部班级和所有检测记录,普通教师只能看自己名下班级的数据。登录密码使用django内置的PBKDF2算法哈希存储,不存明文。

实时检测模块:读取本地摄像头或者视频文件,帧送入onnxruntime推理,做NMS后处理得到每个目标的类别和坐标框,在画面右上角叠加实时FPS,并将每帧的检测结果存入SQLite或者MySQL。

行为统计模块:按时间维度聚合检测数据,生成每类行为的统计直方图、按分钟折线的注意力曲线、课堂参与度指标等。这里需要做一些数据清洗,因为单帧误检很容易产生毛刺数据,要加滑窗平滑逻辑。

智能报告模块:前端把一段时间内的检测数据格式化成JSON,通过HTTP请求发送给DeepSeek的API,由大模型生成课堂行为分析报告,包含问题诊断、教学建议、重点关注学生列表。

这四个模块在django里对应四个app——accounts、detector、analytics、report,各app职责单一,方便后续维护和扩展。我画过一张架构图,检测推理放独立线程,Web请求处理走django主线程,两者通过队列通信,避免推理阻塞页面响应。

2. 核心细节解析:模型训练、onnx导出与推理封装

2.1 课堂行为数据集的准备与标注

模型训练的第一步是拿到高质量行为数据集。公开数据集里用得多的是COCO的person类,但COCO只有“人”这个类别,没有“举手”“睡觉”“玩手机”这些行为标签。常见的做法是找开源的课堂行为数据集(比如SchoolClassBehavior之类的公开集),加上自己采集的数据做补充。

我项目里用的是8个行为类别,这里强烈建议类别别定义太多,课堂场景下真正稳定可检的就是这几个:

类别编号行为标签可检测性说明
0举手高姿态特征明显,小目标也容易识别
1读书高头部下沉,配合书本目标
2写字中手部动作细节,靠中近距离镜头
3睡觉高伏案+头部贴近桌面,特征稳定
4趴桌子中容易和睡觉混淆,需统一标注规则
5玩手机低目标小,手机亮度变化是关键特征
6站立高和坐着差异明显
7正常听课高背景类,数量最多

标注规则里面有一个非常容易踩的坑:“睡觉”和“趴桌子”这两个类,很多人标注的时候主观性太强——同一个画面,A标注成睡觉,B标注成趴桌子,模型训练出来这两个类就一直互相打架。建议标注前定明规则:闭眼或头完全埋进手臂定义为睡觉,只是头低但眼睛可见或者坐姿歪斜定义为趴桌子,并且让同一个人统一标注这两个类。

数据量方面,每类至少准备800到1500张,用labelimg或者anylabeling做YOLO格式标注。另外提醒一句,公开数据集里的课堂照片很多是固定机位的,和实际部署机位高度相似,模型在相同视角下表现很好,换个俯视角度效果就掉得厉害。条件允许的话,自己架两台摄像头在不同高度采集半小时视频,截帧后补充进去,泛化能力会好很多。

2.2 训练配置和关键参数

这里直接给一份我实验下来比较稳的训练配置,基于yolov11n.pt预训练权重起步:

yolo train model=yolov11n.pt data=classroom.yaml epochs=120 imgsz=640 batch=16 device=0 patience=20 optimizer=AdamW lr0=0.001

几个参数需要说明一下。imgsz我用的是640而不是1280,因为课堂行为检测对细节的要求不像工业质检那么高,640推理速度比1280快一倍以上,在CPU部署场景下这是决定能不能实时跑的关键。batch要看显存,16G显存跑yolov11n的640分辨率,batch调到16是没问题的。

训练过程里重点观察两类指标:一类是每个类别的AP值,另一类是PR曲线。如果发现“玩手机”的AP明显低于其他类,不要盲目加数据,先检查是不是标注框太小导致正样本占比低,可以尝试在训练时开启mosaic=1.0增强,把小目标拼到大幅画面里,改善小目标的学习效果。

训练完成后得到best.pt,就可以进入导出环节。导出onnx这一步很关键,很多新手在这里栽跟头,原因是不理解opset版本和动态轴的含义。

yolo export model=best.pt format=onnx opset=12 dynamic=False simplify=True

这里dynamic=False意味着输入shape被固定为[1,3,640,640],好处是推理时不需要动态分配内存,CPU上执行效率更高;坏处是如果将来要跑不同分辨率输入,需要重新导出。对于固定教室摄像头场景,固定shape是合理选择。

simplify=True会调用onnx-simplifier做计算图优化,去掉一些冗余算子,实测能让推理速度提升10%到15%。我对比过simplify前后的onnx文件,大小从88MB降到78MB,单帧CPU推理时间从96ms降到84ms左右,这个优化值得做。

导出后用onnxruntime验证一下模型的输入输出:

import onnxruntime as ort session = ort.InferenceSession("yolov11n.onnx") input_name = session.get_inputs()[0].name outputs = session.run(None, {input_name: np.zeros((1,3,640,640), dtype=np.float32)}) print([o.shape for o in outputs])

yolov11的onnx输出是一个[1, 84, 8400]的张量——84对应4个边界框坐标加80个COCO类别概率。如果的行为类别是8个,那输出shape应该是[1, 12, 8400],这个数字对不上基本就是训练数据类别数和后处理代码没对齐。

2.3 推理后处理的两种写法

yolov11的原始输出是一个大张量,必须经过后处理才能拿到可视化的框。后处理包括置信度过滤、类别筛选、NMS去重。这里有两种实现思路:

第一种是传统的循环式NMS,用numpy或者纯Python遍历,代码写起来直观,但8400个候选框逐一遍历在CPU上耗时明显,整体推理时间被拉到150ms每帧。

第二种是我在项目里最终采用的向量化写法,用cv2.dnn.NMSBoxes做NMS,配合numpy的数组索引一次性过滤置信度低于阈值的框:

import cv2 import numpy as np def postprocess(outputs, conf_threshold=0.35, iou_threshold=0.45, num_classes=8): # outputs shape: [1, 4 + num_classes, 8400] preds = outputs[0].transpose((0, 2, 1)) # [1, 8400, 4 + num_classes] boxes_xywh = preds[..., :4] class_scores = preds[..., 4:] class_ids = np.argmax(class_scores, axis=-1) confidences = np.max(class_scores, axis=-1) mask = confidences >= conf_threshold boxes_xywh, class_ids, confidences = boxes_xywh[mask], class_ids[mask], confidences[mask] # 将xywh转为xyxy格式 boxes_xyxy = np.concatenate([ boxes_xywh[:, :2] - boxes_xywh[:, 2:] / 2, boxes_xywh[:, :2] + boxes_xywh[:, 2:] / 2 ], axis=-1) # 类别过滤 + NMS keep = cv2.dnn.NMSBoxes(boxes_xyxy.tolist(), confidences.tolist(), conf_threshold, iou_threshold) if len(keep) > 0: keep = np.array(keep).flatten() return boxes_xyxy[keep], class_ids[keep], confidences[keep]

这段代码看起来简单,但有一个非常隐蔽的问题——如果mask过滤后没有任何框满足阈值,cv2.dnn.NMSBoxes在某些版本的OpenCV里会因为传入空列表报错,所以一定要加len(keep)判断。这是我实际部署时才发现的坑,文档里不会写。

3. 实操过程:django后端集成、登录鉴权与Web界面

3.1 django项目结构和登录模块落地

django这块我直接用django-admin startproject config .初始化项目,然后依次创建三个app。目录结构是这样的:

config/ ├── settings.py ├── urls.py ├── wsgi.py accounts/ # 登录注册、用户管理 ├── models.py # 自定义UserProfile,关联班级信息 ├── views.py # 登录、注册、登出 ├── forms.py # 注册表单,含密码校验 detector/ # 检测引擎 ├── detector.py # onnxruntime推理封装 ├── models.py # 检测记录表DetectRecord、帧结果FrameResult ├── tasks.py # 后台推理线程 ├── views.py # 实时检测页、历史记录页 analytics/ # 数据统计 ├── views.py # 图表数据接口 ├── utils.py # 数据聚合算法 report/ # DeepSeek报告 ├── deepseek_client.py ├── views.py templates/ ├── login.html ├── register.html ├── dashboard.html ├── live_detect.html ├── analytics.html └── report.html static/ ├── css/ ├── js/ └── models/ └── yolov11n.onnx

登录模块直接用django内置的LoginView就能省掉大半代码,但我建议不要偷这个懒,因为课堂系统需要“登录后跳转不同角色首页”的逻辑。自己写登录视图也就二十行:

from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def login_view(request): if request.method == "POST": username = request.POST["username"] password = request.POST["password"] user = authenticate(request, username=username, password=password) if user is not None: login(request, user) if user.is_staff: return redirect("admin_dashboard") return redirect("teacher_dashboard") return render(request, "login.html", {"error": "用户名或密码错误"}) return render(request, "login.html")

这里要特别注意一个安全问题:authenticate函数内部已经处理了密码哈希比对,千万不要自己从数据库里取密码字段做比较。django的密码字段是哈希值,明文比对永远返回False。

注册页面我加了一个“教师邀请码”的字段,普通注册需要管理员提供邀请码才能完成注册,避免系统对外开放后被无关账号注册。这个功能用forms.py里的clean_invite_code方法校验,非常简单但很实用。

3.2 实时检测的后端实现思路

实时检测是整个系统里最容易出现性能问题的模块。最直接的方案是前端通过HTTP请求每一帧图片,后端检测完返回结果——但这样有两层开销,一是HTTP请求的延迟,二是图片base64编码传输的流量,实测每帧延迟能到200ms以上,画面像幻灯片。

我最终采用的是MJPEG流方案。Django端开一个后台线程,持续从视频源读帧、推理、画框、编码成JPEG,然后把最新一帧存到内存缓冲区。前端页面用<img>标签的src指向一个专门的流视图,浏览器会持续接收后端推过来的JPEG数据流,画面就能保持25帧左右的流畅度。核心实现逻辑是:

# detector/detector.py class FrameBuffer: def __init__(self): self.current_frame = None self.lock = threading.Lock() def update(self, frame): with self.lock: self.current_frame = frame def get(self): with self.lock: return self.current_frame # detector/views.py 里的MJPEG流视图 def mjpeg_stream(request): def generate(): while True: frame = frame_buffer.get() if frame is not None: _, jpeg = cv2.imencode(".jpg", frame) yield (b"--frame\r\n" b"Content-Type: image/jpeg\r\n" b"Content-Length: " + str(len(jpeg)).encode() + b"\r\n\r\n" + jpeg.tobytes() + b"\r\n") time.sleep(0.03) return StreamingHttpResponse(generate(), content_type="multipart/x-mixed-replace; boundary=frame")

这里有个django容易踩的坑:StreamingHttpResponse在django开发服务器和uWSGI部署环境下表现完全不同。开发服务器下一切正常,但如果用gunicorn部署,gunicorn默认的worker是一次性读取全部响应的,MJPEG流会被直接卡死。解决方式是gunicorn启动时加--worker-class gevent或者改用uWSGI并把http-timeout设置到0。我实测在这上面耗了整整一天,希望你不要重蹈覆辙。

检测线程的生命周期管理也要注意。不要在线程里直接操作django的ORM模型对象,因为django的数据库连接是线程不安全的,不同线程共用连接会导致数据库报错“lost connection”。正确做法是检测线程只负责把检测结果放到内存队列,由Web请求线程定时从队列里批量取数据并写入数据库。

3.3 Web界面:图表展示与数据刷新

前端我用的是BootStrap 5 + ECharts 5的组合。ECharts的大盘图表组件适合做行为分布饼图和注意力折线图,不需要自己造轮子。

页面布局分左右结构:左侧是实时视频流区域,右侧是统计面板。统计面板里的数据通过/api/analytics/recent/接口获取,前端每秒轮询一次:

async function refreshStats() { const response = await fetch("/api/analytics/recent/?seconds=60"); const data = await response.json(); attentionChart.setOption({ series: [{ data: data.attention_curve }] }); behaviorPie.setOption({ series: [{ data: data.behavior_distribution }] }); } setInterval(refreshStats, 1000);

轮询间隔不要设置得太小,1秒已经能保证图表的实时感,太密的请求对服务器压力大,而且检测数据本身就做了5秒级别的时间聚合,毫秒级的轮询没有任何意义。

“精美”这个词在Web界面里主要体现在视觉设计上面,我做了三个细节:深色主题背景(#0f172a)、卡片带圆角和阴影、行为类别用不同的颜色映射。这些看起来是UI细节,但观感上能明显拉开和默认模板的差距,对演示场景影响很大。

3.4 DeepSeek接口对接:提示词工程才是关键

DeepSeek接入在代码层面很简单,它就是标准的OpenAI兼容接口,用requests发POST请求就行:

import requests def generate_report(stats_json, api_key): prompt = f""" 你是课堂行为分析助手。以下是一堂课的行为统计数据,请用中文给出分析报告。 要求: 1. 先总结整体课堂活跃度 2. 指出最突出的行为问题 3. 给出3条具体可行的教学改进建议 4. 不要超过300字 统计数据: {stats_json} """ response = requests.post( "https://api.deepseek.com/v1/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json={ "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3, "max_tokens": 500 } ) return response.json()["choices"][0]["message"]["content"]

真正需要花心思的是提示词的设计。直接丢一串JSON给大模型,它只能说出“该课堂存在多种行为类型”这种正确的废话。我测试了几版提示词之后发现,必须要做两件事:一是析数据的口径讲清楚,比如“班级总人数45人,平均每帧检测到40人,检出率88%”,大模型才能判断检出率是否正常;二是要求它忽略短时的个别误检,聚焦持续时间超过3分钟的行为模式。加了这两条之后,报告质量提升非常明显。

API的key不要写死在代码里,用django的settings.py配合环境变量管理,部署时从.env文件读取。另外deepseek接口是收费的,在demo里可以把报告生成设计成手动点击触发的模式,而不是每节课自动生成,省下不少费用。

4. 常见问题与排查技巧实录

4.1 推理帧率上不去,CPU占用拉满

这是最多人问的问题。拿到onnx模型后第一次跑实时检测,发现CPU风扇狂转,FPS只有七八帧。排查步骤按照这个顺序:

先看是不是OpenCV编译时没启用优化。cv2.getBuildInformation()输出里能看到CPU Dispatch部分是否支持AVX2,如果你的Python环境是用pip安装的opencv-python,默认是支持AVX2的,这步一般没问题。再看是不是onnxruntime的线程数设置问题。onnxruntime默认会吃满所有CPU核心,但在教室主机这种可能要同时跑Web服务的机器上,得手动限制线程数量:

import onnxruntime as ort session = ort.InferenceSession( "yolov11n.onnx", providers=["CPUExecutionProvider"], sess_options=ort.SessionOptions() ) session.set_providers(["CPUExecutionProvider"], [{"enable_cpu_mem_arena": False}])

还有一个技巧是把onnxruntime的intra_op_num_threads设置为物理核心数的一半,比如4核机器设2,这样Web服务能同时处理请求,UI不卡。

最后看预处理是不是反复执行了缩放。很多人的代码里对每一帧cv2.resize到640×640,但imgsz=640训练出来的模型对768、800这种分辨率输入也有一定的容忍度,如果摄像头本身是1280×720,可以直接resize到640×384,减少计算量,检测精度损失很小。

4.2 漏检和重复框问题

漏检的第一个原因是置信度阈值调太高。在我自己的数据集上,conf_threshold=0.45时“玩手机”根本检不出来,因为手机目标小、遮挡多、置信度普遍在0.2到0.4之间。我的建议是课堂场景阈值不要超过0.3,宁可多一些误检,靠后面的时间平滑来过滤。

重复框的出现通常是NMS失效的典型表现。如果同一帧里同一个学生出现了两三个框,问题基本都在后处理代码——cv2.dnn.NMSBoxes的输入要求是[x, y, w, h]格式,如果你传入的是[x1, y1, x2, y2],IOU计算会完全错乱,NMS等于没跑。检查一下后处理里坐标转换是否正确,这个我每次写都要反复确认。

4.3 DeepSeek接口超时

DeepSeek的接口响应时间波动比较大,高峰期可能要等10秒以上。如果客户端用默认的requests超时设置(无限等待),页面会一直转圈,体验很差。我的做法是给请求加30秒超时,并配合一个“生成中”的loading动画,同时在失败时允许用户点“重试”。

另外一个很容易忽略的点是请求体大小。如果你把一节课45分钟、每秒6帧的检测记录全塞给DeepSeek,JSON可能达到几百KB,不仅浪费token,还可能触发接口的长度限制。解决方案是在前端或者后端先做聚合:把行为占比、时间曲线采样点、高频行为时段这些压缩成只有几十行的摘要JSON,再发给大模型,这样既省token,生成质量也更稳定。

4.4 登录页面样式加载不出来

django的静态文件在生产环境是个经典坑。开发时DEBUG=True,静态文件自动由django处理;部署时DEBUG=False,静态文件就需要nginx来托管,否则CSS、JS全部404,登录页变成裸HTML。

解决方案是在settings.py里设置:

STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles"

线上执行python manage.py collectstatic把所有静态文件复制到staticfiles目录,然后nginx配置里加一条location /static/ { alias /path/to/staticfiles/; }。django本身不擅长处理静态文件,这不是bug,是框架设计决定的,生产环境就该交给专门的静态文件服务器。

5. 测试与部署:从开发机到教室主机

5.1 本机测试的几个关键指标

开发完成后先在本地把各环节跑通,我建议关注这几个硬指标:

推理速度:onnx模型在CPU单线程下,640分辨率推理时间要控制在100ms以内。用timeit循环跑100次取平均,如果超过150ms,考虑换yolov11n或者降低输入分辨率。

端到端延迟:从摄像头画面出现到检测框叠加完毕显示在Web页面上,这个时间包含采集、传输、推理、编码、前端渲染,总延迟目标控制在300ms以内,否则现场演示时手挥一下,框要过半天才跟上,观感很差。

并发稳定性:用多浏览器标签页同时打开实时检测页,确认MJPEG流不会因为第二个客户端连接而崩溃。django的StreamingHttpResponse默认是每请求独立生成器,但要确保frame_buffer.get()的线程安全,不然高并发下读取到半个帧。

5.2 用systemd把服务跑成守护进程

开发阶段用python manage.py runserver没问题,但部署到教室主机上不可能开着一个终端不放。写一个systemd服务文件放到/etc/systemd/system/classroom-detector.service:

[Unit] Description=Classroom Behavior Detection System After=network.target [Service] Type=simple User=classroom WorkingDirectory=/opt/classroom-detector ExecStart=/opt/classroom-detector/venv/bin/python manage.py runserver 0.0.0.0:8000 --noreload Restart=always RestartSec=5 EnvironmentFile=/opt/classroom-detector/.env [Install] WantedBy=multi-user.target

提两个注意点。--noreload必须加,django开发服务器默认会监听文件变化自动重启,这在服务器环境下是灾难行为,会导致检测线程被频繁杀死重启。另外EnvironmentFile用来注入Django的SECRET_KEY、DeepSeek的API_KEY这些敏感变量,避免密钥直接出现在代码库里。

5.3 摄像头参数的选择经验

实际部署时摄像头的选择比模型本身影响还大。我之前试过用普通的USB摄像头,720p 30fps规格的,画质在教室这种大场景里很糊,远排学生的人脸区域全是噪点,检测效果大打折扣。后来换成海康或者大华的网络摄像头,用RTSP流接入,情况好了很多。

不是所有人都方便接触RTSP设备,如果只有USB摄像头,至少要保证两点:镜头视角能覆盖整个教室,焦距不要调太近;安装高度在教室中后方,朝斜前方俯拍,避免逆光。摄像头宁可在2.5米以上,不要平视,平视会被前排同学挡住后排人脸,检测率直线下跌。

6. 运行效果与系统性能参考

这套系统在模拟教室环境中实测,一台i5-12400处理器、16GB内存的普通台式机,不装独显,CPU推理模式下性能表现如下:

指标数值备注
模型yolov11n.onnx类别数8,imgsz=640
CPU推理单帧耗时55ms ~ 80ms依CPU负载浮动
实时画面帧率20~25 FPSMJPEG流输出
检测准确率(正常课堂)91%左右平均置信度0.46
同时在线用户5~8人页面操作流畅
GPU推理(可选)5~8ms/帧需CUDA环境

这个数据说明,即使没有GPU,一个普通的教室电脑也足以支撑系统流畅运行。如果把yolov11换成s或者m版本,精度会更高,但CPU推理时间会涨到200~300ms,那就只能降到10帧以下,实时体验变得卡顿。对于课堂行为分析这种场景,n版本的精度已经够用了,不必盲目追求模型尺寸。

补充一个实测里的经验

课堂真实环境比数据集复杂得多,最大的干扰源是极端光照和姿态遮挡。下午三点左右阳光斜射进教室,桌面反光会直接把“玩手机”这类小目标的特征抹掉;两个学生凑在一起交头接耳,检测框大概率会合并成一个,行为分类直接被带偏。

我的处理经验是在后处理加一道时间平滑逻辑——单帧结果只做参考,取过去3秒内每个学生的多数投票作为最终行为标签。这个改动简单但极其有效,把单帧误检导致的曲线毛刺全部磨平了,行为统计报表的表观置信度高了很多。代码层面其实就维护一个deque(maxlen=15),每帧把框的位置ID关联到已有轨迹,更新对应的行为计数。这一步不需要复杂算法,纯工程直觉就能解决问题。

这个系统的后续扩展方向其实还有不少空间。比如把检测结果按段落切分,结合课程时间表做专注度时间段分析;比如往yolov11里加入头部姿态估计分支,判断学生是看黑板还是看窗外;再比如对接学校统一身份认证实现免登录。但就当前这个项目标题覆盖的范围而言,yolov11做检测、django搭服务、deepseek生成报告、onnx保证部署可用性,这四条线已经形成了一条完整且可交付的闭环,足够支撑一个高完成度的Demo项目甚至初级商用原型。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询