☰
YOLO+大模型实战:课堂行为智能分析系统搭建指南
2026/10/19 3:54:31 网站建设 项目流程

去年帮某高校的信息中心搭了一套学生课堂行为智能检测分析系统,核心链路就两件事:YOLO目标检测负责识别学生课堂行为,qwen和deepseek系列大模型负责把检测结果沉淀成带教学价值的分析结论。说实话,这类项目市面上不是没有商用方案,但报价贵而且不透明,一套下来少说六位数,换来的往往就是个能看数字的看板,老师看完也不知道下一步该干嘛。所以我把整条链路都自己走了一遍,从行为类别定义、数据标注、模型训练,到大模型prompt设计、前后端联调,最后落成一套能实时输出课堂行为分析报告的完整系统。这篇文章就把整个项目的设计思路和实操细节完整写出来,给想做同类系统的朋友一个能直接参考的样本,不管你是刚入门还是已经跑过几个检测项目,应该都能从中找到有价值的东西。

1. 系统整体架构与设计思路

1.1 为什么选YOLO+大模型,而不是单模型硬扛

开始设计之前,我最先思考的问题不是"用哪个模型",而是"这套系统到底要回答什么问题"。课堂行为识别的需求看起来简单——检测学生行为——但往深了挖,教学管理者真正想要的不是"某个学生此刻在趴桌子"这一帧的事实,而是"这节课整体专注度怎么样""哪些环节学生容易走神""某个学生整节课的行为轨迹是什么"。这些结论性的问题,单靠目标检测模型是答不出来的。

目标检测模型解决的是空间感知问题:一帧画面里谁在哪、在做什么。而课堂分析需要的是时间维度的聚合和语义层面的解释,这正是大模型擅长的地方。YOLO负责把每帧画面变成结构化的行为标签,大模型负责把一段时间内的行为标签序列聚合成教学结论。这个分工明确之后,整个系统的骨架就清晰了:检测模块做数据生产,大模型做数据消费。

1.2 系统链路:从视频流到分析报告

整套系统的处理链路我拆成了五个环节,每一环都有独立的数据接口,方便单独替换和调试:

视频输入与抽帧:接教室的录播摄像头RTSP流,或者离线导入mp4文件。按5帧/秒的节奏抽帧,既能保证检测连续性,又不会让后续的计算压力爆炸。实际测试下来,25人左右的教室场景,这个帧率足够覆盖大部分行为变化。

目标检测与行为识别:抽出来的每一帧送入YOLO模型推理,模型输出每个学生的行为类别、置信度和边界框坐标。这一步是整个系统里计算量最大的环节,也是最容易出性能瓶颈的地方。

目标跟踪与去重:检测出"哪个学生是哪类行为"还不够,因为同一帧里可能有多个学生,而同一个学生在连续帧里会反复出现。我接入ByteTrack跟踪器,给每个学生分配一个稳定的ID,这样后面统计"某学生这节课被检测到玩手机多少次"才有意义,否则逐帧统计会把同一个行为重复计数。

行为统计与时间聚合:把带ID的行为标签按时间窗口聚合,生成行为分布矩阵。我的默认配置是每5分钟一个统计窗口,记录窗口内各类行为的占比和个体行为频次。这一步的数据结构设计很关键,后面大模型能不能输出高质量结论,很大程度取决于这里的数据喂得规不规范。

大模型语义分析与报告生成:把统计窗口的JSON数据传给qwen或deepseek模型,让模型基于真实检测数据生成课堂观察报告、专注度评估和教学改进建议。大模型输出的报告最终落库并渲染到Web看板上,教师端可以看到整节课的趋势曲线和问题学生列表。

这套链路的优势在于每层解耦。检测模型升级不影响大模型层,大模型换一个更强的版本也不影响检测模块。我后面对比过qwen和deepseek的不同版本,直接替换接口就行,改造成本很低。

1.3 行为类别定义的背后逻辑

行为类别怎么定,直接决定标注工作量、模型精度上限和报告价值的厚度。我第一版参考了一些论文里的做法,一口气定义了15类行为,结果标注到一半就发现扛不住了。

一是类别之间太容易混淆,比如"睡觉"和"趴桌子",从教室后排摄像头角度看边界非常模糊;二是类别太细,样本量被摊薄,模型精度上不去。所以我砍到了8个类别,原则很简单:每类行为必须能从单一视角可靠区分,且对应明确的课堂教学含义。

这8类分别是:听讲、举手、阅读、书写、低头看手机、趴桌子、东张西望、站立。听讲是默认行为,也就是不属于其他任何行为时的兜底归类;站立单独列出来,因为教室场景下学生起立可能是回答问题,也可能是随意走动,需要结合时间窗口判断;举手和东张西望虽然都是手部或头部的变化,但前者是积极参与信号,后者是注意力分散信号,教学价值完全不同。

这个类别设计还有一个好处:可以直接映射到教学评估指标上。听讲占比对应课堂专注度,举手和站立频次对应互动活跃度,低头看手机和趴桌子对应课堂纪律问题。数据出来之后不需要再做一层人为解读,老师看一眼就知道问题在哪。

2. 数据准备与标注实操

2.1 行为类别清单与标注规范

数据是这套系统里最花时间、也最决定成败的部分。我整理了一张标注规范表,直接发给标注的同学照着做,避免标注口径不统一导致模型学偏。

类别ID行为名称标注要求易混淆项
0听讲面向讲台方向,目视前方或低头做笔记均可与趴桌子难区分,注意头部高度
1举手单臂或双臂抬起超过肩部与伸懒腰区分,看手臂停留时间
2阅读手持书本或目光落在书页上与玩手机区分,看手持物体形状
3书写手部在桌面有连续书写动作静态画面难判定,可参考前后帧
4低头玩手机双手持手机在桌面或桌下与阅读区分,看屏幕亮光和手部姿势
5趴桌子头部贴桌面或手臂上与睡觉高度重合,合并处理
6东张西望头部频繁转向两侧与听讲区分,看头部朝向变化频率
7站立身体明显高于座位与举手区分,看下半身是否离开座椅

有几个细节必须强调。第一,标注时尽量保证框住整个身体,不要把头部单独框出来,因为课堂场景摄像头视角多半是从教室后方斜上方打的,身体框更稳定;第二,每张图里可以有多个人,每个有效目标都要标注,不要漏人;第三,"趴桌子"和"睡觉"我最终合并成一类,标注时统一归为趴桌子,模型对这两者的区分能力有限,硬拆只会拉低整体精度。

2.2 数据采集的视角与场景覆盖

数据采集的坑主要在视角上。刚开始我拿了几段网上公开的课堂视频做测试,效果看起来还行,一部署到真实教室就露馅了——视角完全不一样。

公网视频大多是侧面或者正前方视角,而实际教室摄像头基本挂在黑板正上方往后拍,看到的多是学生的后脑勺和背影。同样的"低头看手机",正面视角下特征非常明显,背面视角下就和一个正常低头看笔记的动作几乎没法区分。所以后期我干脆放弃了网络公开数据,全部用目标教室的实拍画面重新采集和标注。

采集时注意覆盖多个时间段。上午第一节课学生普遍精神状态好,下午第一节课趴桌子比例明显上升,不同光照条件、不同座位密度、不同课型下的行为分布差异很大。我总共采集了大约40节课的视频,覆盖了三个年级、六个班型,最终筛选出有效标注图片8000多张,每张平均标注3到5个学生目标,算下来大概三万多个标注框。

2.3 数据增强与样本平衡的取舍

标注完看一眼分布就知道问题来了:听讲和书写占了将近60%,低头玩手机、举手、站立加一起不到15%。这种严重的类别不平衡会让模型对少数类别几乎不敏感。

我做了三层处理。第一层是离线数据增强,对少数类别的样本做随机旋转、水平翻转、亮度抖动和轻微透视变换,把每类的样本量尽量拉到接近;第二层是在训练时的loss计算里提高少数类别的权重,YOLO的自定义训练脚本里可以直接调整每个类别的loss系数,这个参数我调了几轮才找到一个比较合适的平衡点;第三层是训练后检查每类的precision和recall,如果少数类还是明显偏低,回头补充针对性数据,而不是盲目继续增强。

这里要给个忠告:数据增强不是万能的。旋转角度太大会生成明显失真的样本,反而干扰模型学习。我实测旋转角度控制在正负10度以内、亮度抖动幅度不超过正负25%的时候效果最稳定,再大胆反而掉点。

3. YOLO模型选型与训练调优

3.1 YOLOv5、v8还是v10

这个话题每次聊都有人问。我的结论:课堂行为检测这种任务,YOLOv8是性价比最高的选择,没有之一。

对比项YOLOv5YOLOv8YOLOv10
部署生态成熟但较老完善,文档多较新,社区积累少
推理速度快快更快,但提升不明显
NMS后处理需要内置无NMS设计
自定义数据集支持好最好一般
与跟踪模块兼容性需手动适配官方配套ByteTrack兼容性有待验证

我一开始用的是YOLOv5,主要看重它社区大、踩坑经验多。但后来切到v8,原因是Ultralytics把训练、验证、导出、跟踪的API做了统一,工程集成省了很多事。YOLOv10在精度上和v8互有胜负,优势在去掉了NMS带来的推理延迟下降,但课堂场景本身对帧率要求不高,5帧/秒的处理节奏下那几十毫秒的差距感知不出来,所以没必要追新。

3.2 训练配置与关键参数

数据集准备好之后,训练本身反而没什么玄学。我用的是YOLOv8n的预训练权重作为起点,得益于迁移学习,不需要从零训练。

先看数据集配置文件,存成classroom.yaml:

path: /data/classroom train: images/train val: images/val names: 0: listening 1: hand_raising 2: reading 3: writing 4: using_phone 5: lying_on_desk 6: looking_around 7: standing

然后直接跑训练命令:

yolo detect train data=classroom.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=16 device=0 patience=20

几个参数的取舍说明一下。imgsz我试过640和960,640在常规教室场景下足够覆盖学生目标,960对小目标(后排学生)会更友好,但显存占用和推理耗时都上去一大截。训练阶段用960、推理阶段用640这种粗粒度方案我不是很推荐,因为训练和推理的输入分辨率不一致会带来一定的精度损失。要么统一640,要么统一960。

batchsize在单卡RTX 3090上跑到16没问题,显存大概占用16GB左右。patience设为20,意思是连续20个epoch验证集指标没有提升就提前停止,我实测跑到第110轮左右就收敛了,150的设定主要是兜底。

训练完之后看两个核心指标:mAP50在0.89左右,mAP50-95在0.72左右。对于课堂行为这种类别间有天然相似性的任务,这个精度已经够用了。

3.3 训练中的几个典型坑

第一坑是标签框过小。后排学生目标在640分辨率下可能只有二三十个像素,如果标注框抠得太紧,模型很难学到稳定的特征。我的做法是标注时统一向外扩10%到15%的边距,把身体周围的一小圈环境也包进来,模型反而学得更稳。

第二坑是验证集和训练集来自同一批课的视频。因为同一节课的帧之间背景高度相似,模型很容易记住背景而不是学生特征,导致验证集mAP虚高,换到新教室立刻掉点。解决办法是划分数据时保证同一节课的视频帧只出现在训练集或验证集一边,不能两边都占。

第三坑是类别间的语义混淆。趴桌子和低头看手机这两个类别在背部视角下经常出现在同一帧甚至同一个学生身上,模型容易同时输出两个高置信度框。我的处理是后处理时加一个简单的互斥规则:同一个目标框上,趴桌子置信度高时强制抑制低头玩手机的输出。这种规则虽然粗暴,但实测确实能把误检率降下来一截。

4. 大模型接入:从检测数据到教学结论

4.1 大模型在系统里的角色定位

很多人会问:YOLO已经能识别行为了,为什么还非要接个大模型?答案在于检测模型输出的是"是什么",而教学人员需要的是"意味着什么、该怎么办"。

同样是"一节课里趴桌子占比25%",这个数据对不同年级、不同课型、不同时间段含义完全不同。上午第一节课趴桌子25%可能是因为学生睡眠不足,下午最后一节课趴桌子25%可能就是课堂教学节奏出了问题。这种上下文相关的解读能力,传统规则引擎写不出来,而qwen和deepseek这类大模型天然具备。

选型上,我同时跑了qwen和deepseek的API对比效果。qwen系列在中文教育场景的语义理解上表现更细腻,给出的教学建议措辞更贴近一线教师的口吻;deepseek在数据推理和结构化输出上更稳定,生成的JSON格式几乎没有解析失败的情况。我的最终方案是双模型冗余:默认走qwen生成报告,若接口超时或解析异常则自动切换到deepseek兜底。

4.2 Prompt设计:让输出可落库、可展示

大模型接入最核心的工程问题不是API调用,而是prompt设计。第一版我直接甩了一段统计数字给模型让它"分析一下",结果输出洋洋洒洒几百字,有夸有批但结构完全不可控,没法直接渲染到看板。

后来我把prompt改成了强约束结构,强制模型按固定字段输出。核心思路是:给模型明确的角色定义、只允许引用输入数据中的事实、限定输出格式为JSON、提供few-shot示例。

system_prompt = """ 你是课堂行为分析助手。你只能基于用户提供的行为统计数据进行分析, 不得编造任何统计数据中没有的信息。输出必须是一个JSON对象,包含以下字段: { "overall_focus": "0-100之间的整数", "focus_level": "high/moderate/low", "main_issues": ["问题1", "问题2"], "student_alerts": [{"student_id": "编号", "behavior": "行为", "suggestion": "建议"}], "teaching_suggestions": ["建议1", "建议2"] } """

user消息里则传入结构化统计数据。这里有个经验:传给大模型的数据越规范,输出越稳定。我在统计层就把5分钟窗口的数据整理成下面这种紧凑格式,模型几乎不需要做复杂推理就能直接解读:

课时: 第2节 班级人数: 25 统计窗口: 0-5分钟 行为分布: {"listening": 0.62, "writing": 0.21, "using_phone": 0.08, "lying_on_desk": 0.05, "other": 0.04} 异常个体: ["S03: using_phone * 5", "S12: lying_on_desk * 4"]

温度参数我设到0.3,太低会显得机械,太高容易跑题。模型上下文窗口只保留最近两到三个统计窗口的数据,避免整节课的原始数据一次性塞进去,那样既浪费token又容易让模型抓不住重点。

4.3 接口对接与容错处理

调用部分我封装了一个统一的接入层,方便切换不同模型供应商:

from openai import OpenAI client = OpenAI( base_url="your_llm_api_endpoint", api_key="your_api_key" ) def generate_classroom_report(stat_data: dict) -> dict: response = client.chat.completions.create( model="your_model_name", temperature=0.3, response_format={"type": "json_object"}, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": json.dumps(stat_data, ensure_ascii=False)} ] ) result = json.loads(response.choices[0].message.content) return result

容错方面做了三层:第一层是接口超时重试,最多重试两次,每次间隔3秒;第二层是返回内容解析失败时的降级处理,直接把统计数字渲染成基础表格,不让教师端出现空白页;第三层是敏感信息过滤,输出内容在落库前会过一遍关键词检查,确保报告里的表述中性客观,不给任何学生贴标签定性。

实践下来,这一层容错的价值比想象中大。真实环境里API偶发超时太常见了,有一次下午连续三节课的分析报告生成失败,如果不是降级方案兜底,教师看板就是一片空白,整套系统的口碑直接崩掉。

5. 系统集成与部署实录

5.1 实时检测与目标跟踪实现

系统核心模块用Python写,检测部分通过FastAPI提供异步接口。视频流处理线程从摄像头拉流,按固定间隔抽帧交给YOLO推理,检测结果送入ByteTrack分配学生ID。这一步不做好的话,后面所有个体统计都是错的。

from ultralytics import YOLO from ultralytics.utils.tracker import BTrack model = YOLO("best.pt") tracker = BTrack() def process_frame(frame): results = model.predict(frame, conf=0.35, iou=0.45, verbose=False) if len(results[0].boxes) == 0: return [] # 提取检测框和类别,送入跟踪器 boxes = results[0].boxes.xyxy.cpu().numpy() scores = results[0].boxes.conf.cpu().numpy() classes = results[0].boxes.cls.cpu().numpy() tracked = tracker.update(boxes, scores, classes) return tracked

conf阈值我调到了0.35,这个值比默认的0.25高但比0.5低。低于0.35会出现大量误检,把书包当成趴桌子的学生;高于0.5会漏掉后排的小目标。iou阈值0.45是常规值,主要用来抑制重叠框。

5.2 行为统计窗口与报告生成

统计模块维护一个按学生ID聚合的计数器,每5分钟输出一个统计快照。快照数据结构设计成下面这样,直接兼容大模型的输入格式:

{ "class_id": "C302", "time_range": "09:00-09:05", "total_students": 25, "behavior_distribution": {"listening": 15, "writing": 6, "using_phone": 2, "lying_on_desk": 1, "standing": 1}, "individual_behaviors": [ {"student_id": "S03", "behavior": "using_phone", "count": 5}, {"student_id": "S12", "behavior": "lying_on_desk", "count": 4} ] }

整节课结束之后,系统把每5分钟的统计快照合并,加上课程元信息(学科、课型、时间段),一次性提交给大模型生成完整课堂报告。报告里除了定性分析,还包括每个学生的行为频次排行,这部分数据来自统计模块而非大模型生成,确保精确性。

5.3 部署环境与性能调优

最终的部署配置供参考:检测推理用一张RTX 3090,CPU用32核的服务器,内存64GB。YOLOv8n模型在TensorRT FP16加速下,640分辨率单帧推理耗时约8到12毫秒,预留出足够的余量给跟踪和业务逻辑。整套系统单卡同时处理4路教室视频流毫无压力,大部分场景下CPU反而是瓶颈,因为视频解码和抽帧很吃CPU。

Web端用前后端分离架构,后端提供一个精简的Dashboard,展示实时班级专注度曲线、每节课的行为分布饼图和异常学生列表。前端每30秒向后端拉一次最新统计数据,不需要WebSocket那么复杂的实时通道。

一个部署经验:模型推理和业务接口最好分进程部署。推理进程只做推理,通过消息队列把检测结果交给Web服务进程。否则推理的GPU占用波动会直接影响接口响应延迟,教师端打开看板等好几秒,体验很差。

6. 常见问题与排查速查表

现象可能原因排查与解决
检测框频繁抖动,同一学生ID被反复切换检测置信度接近阈值,跟踪器关联失败降低conf到0.3;调整ByteTrack的匹配阈值;检查抽帧间隔是否过长
后排学生完全检测不到目标像素过小;imgsz设置太低训练和推理统一用960分辨率;或对后排区域做局部放大推理
趴桌子和玩手机频繁互相误判类别语义相似,背部视角特征不足后处理加互斥规则;补充更多视角的标注数据
大模型报告里出现统计中不存在的数据模型幻觉加强prompt约束,明确禁止编造;传数据时附带统计时间范围
大模型输出JSON解析失败输出被截断或包含多余文本使用response_format强制JSON;做重试和降级处理
新教室部署后精度明显下降训练数据与部署视角差异大按目标教室的真实视角补充采集数据;微调而非全量重训
多路视频流同时跑的时候GPU显存溢出推理进程没有做显存复用复用同一种模型实例,不要每路视频加载一个模型副本
专注度曲线突然断崖式下跌摄像头被遮挡或画面切换检查视频源是否断开;在抽帧层做画面连续性校验

这里再补充一个排查思路:遇到精度问题,先区分是检测层的问题还是统计层的问题。把检测结果可视化保存下来,直接看标注框画出来的效果。如果框的位置和类别都正确,问题出在后面的统计逻辑;如果框本身就不对,才需要回头调模型和数据。可视化是排查这类系统最有效的第一步,不要一上来就动训练参数。

7. 实操心得与后续扩展方向

整个项目做下来,我最深的体会是:技术选型不是这套系统的难点,难的永远是数据质量和需求理解的准确性。YOLO和大模型都有成熟的开源方案和大量文档,但"这节40分钟的课到底要分析出什么"这个问题没想清楚的话,模型再强也输出不了老师真正要的东西。

几个具体的经验沉淀:

第一,需求调研阶段多和一线教师聊。我之前只顾着和技术人员对需求,结果做出来的报告里全是"注意力分散比例""行为检出频次"这类术语,老师根本不爱用。后来改成"这节课有几个学生频繁走神""建议在25分钟后安排一次互动环节"这种表达,老师和教学管理者的接受度完全不一样。感谢大模型在这层帮了大忙,它天然擅长把数据翻译成人话。

第二,别在检测模型上过度投入。课堂行为检测的精度做到一定程度之后,再往上抠的边际收益很低。后期我反而把精力更多放在了统计口径设计和大模型prompt迭代上,报告质量的提升明显快于模型精度的提升。

第三,系统上线之后要做长周期验证。我刚上线时觉得mAP挺高,系统挺稳,结果跑了一个月发现不同班级、不同课型的检测偏差比想象中大。后来增加了定期抽检机制,每周人工复核一小部分检测结果,发现问题及时微调。

后续扩展方向我目前在看三个。一是接入姿态估计模型,用骨骼关键点来辅助区分趴桌子和低头看书这类难例,理论上能把最头疼的混淆项掰开;二是加入学生个体行为时间线,按学生维度统计整节课的行为变化曲线,为个别学生的长期关注提供数据支撑;三是把多节课的数据横纵向对比,比如同一老师不同班级的课堂参与度差异,这块数据对教研组的价值比单节课报告还要高。

这套系统目前的运行状态在一个可控的迭代节奏里,每次改完prompt或者模型都会先跑一周的灰度对比,确认没有引入新的问题再全量更新。如果你也正准备做类似的课堂分析系统,建议先别急着堆功能,把行为定义的边界和报告输出的口径想清楚,后面所有环节都会顺很多。

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

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

立即咨询