尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

真实课堂行为检测数据集:YOLO训练与部署全攻略

真实课堂行为检测数据集:YOLO训练与部署全攻略 简介面向课堂场景的YOLO目标检测数据集覆盖学生回答问题、板书等典型教学行为共4类已按标准格式完成标注并划分训练集与验证集适合高校、教育科技公司及个人开发者用于课堂行为分析、教学评估系统的模型训练与验证。资源包共2000个文件核心为约11800张图片对应的txt标注文件以及便于快速预览标注效果的show.py可视化脚本整体包体约787.64MB配合YOLOv5等框架可直接开展训练或改进实验。当前已有76人学习浏览该资源。使用者可借助完整标注文件与可视化脚本快速检查标签质量缩短数据准备周期结合作者博客持续更新的YOLO实战与改进文章适合作为行为检测方向的基础数据支撑与实验参考。1. 真实课堂数据为什么是行为检测落地的第一个坎教育信息化的采购清单里课堂行为检测这两年出现得越来越频繁需求方开口就是“学生专注度分析”。可真把模型落到教室里的人都知道第一个卡住你的不是算法精度而是数据网上公开的目标检测数据集要么是行人、车辆这类通用场景要么是实验室里摆拍的“模拟课堂”一上真实教室的监控画面就水土不服。这份约 11,800 张、已标注、YOLO 格式的真实课堂教室行为检测数据补的正是这个断层——画面来自实际教学现场光照、遮挡、后排小目标这些脏问题都在里面。适合正在接教育信息化项目的算法工程师、做课堂智慧评估的小团队以及想拿真实数据练手 YOLO 的入门者。接下来我把数据格式、训练、避坑、落地推理这条线一次讲透。2. 打开这份课堂行为数据YOLO 标注格式的目录结构与标签解析2.1 目录结构images 与 labels 的两种组织方式拿到这份数据集第一眼先看目录。绝大多数 YOLO 训练集会按 images 和 labels 两个大目录组织图片与同名 txt 标签文件一一对应。实际交付时常见的组织方式有两种一种是扁平式所有图片在一个 images 目录里靠一个 train.txt / val.txt 文件列出图片路径另一种是更推荐的方式——按 train / val 子目录分好训练脚本直接读取目录不容易出错。你可能还会看到 classes.txt 或 data.yaml 放在根目录这两个文件决定了类别 ID 的顺序。classroom_11800/ ├── images/ │ ├── train/ │ │ ├── img_00001.jpg │ │ └── ... │ └── val/ │ ├── img_10001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_00001.txt │ │ └── ... │ └── val/ │ ├── img_10001.txt │ └── ... ├── classes.txt └── data.yaml拿到数据第一件事不是开训而是跑一遍目录检查确认每个 .jpg 都有同名 .txt没有悬空的 txt 也没有缺失的标签。图片和 txt 同名是 YOLO 匹配标签的唯一依据不要依赖 Excel 或 JSON 里的映射表。另外注意检查文件名有没有空格或中文Ultralytics YOLO 对路径里的中文支持不太稳定我习惯先统一转成 img_00001 这种纯 ASCII 格式省得后面一堆莫名其妙的报错。2.2 标注行解析class_id、归一化坐标与像素换算YOLO 标注格式每行对应一个目标框一共五个数字空格分隔类别 ID、归一化中心 x、归一化中心 y、归一化宽 w、归一化高 h。归一化的意思是除以图片本身的长宽所以所有值都在 0 到 1 之间。比如一个“举手”目标框在 1920x1080 图上中心点位于 (960, 540)框宽 300、高 500那这一行就是0 0.5 0.5 0.15625 0.46296常用类别 ID 从 0 开始计数具体哪个数字代表“举手”还是“趴桌”完全由 classes.txt 的书写顺序决定。同一个数据集里class 0 在各种 txt 里必须始终是同一个含义否则模型会学到错乱的特征。拿到标签后我一般会写个脚本把每个 txt 的坐标反算回像素查有没有越界或者宽高为 0 的脏框。这一步看起来多余但对 11,800 张的量级来说人工标注很容易在边缘帧上留下几个畸形框直接进训练会让损失函数振荡后面排查起来很抓狂。import os label_dir labels/train for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname), r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f{fname}: 列数不对 - {line.strip()}) continue cid, cx, cy, w, h map(float, parts) if not (0 cx 1 and 0 cy 1): print(f{fname}: 中心点越界 - {line.strip()}) if w 0 or h 0: print(f{fname}: 宽高异常 - {line.strip()})这段脚本的逻辑并不复杂逐行解析每个 txt校验列数、坐标范围和宽高。列数不对最常见的原因是标注工具导出时行尾多了逗号或者标签与图片不是同一次处理周期生成的中心点越界多半是标注时目标只露出一部分标注员把框中心拖到了图片外。发现这些问题优先回到标注工具里修框而不是硬着头皮训练。修改之后重新校验一遍直到没有任何报错再进入下一步。2.3 类别清单检查这份数据里你该关注哪几类行为一份课堂行为检测数据集类别设计通常围绕教学管理关注的行为展开。常见的类别包括举手、站立、阅读、书写、趴桌、玩手机、讨论这几类有的数据集会把“趴桌”和“睡觉”合并处理或者把“讨论”和“转头说话”合并。合并与否没有绝对的对错它决定的是模型输出的粒度客户想拿“专注度”指标那你至少要有阅读、书写这类正向行为也要有趴桌、玩手机这类负向行为才能算出一个有说服力的比例。类别常见检测难度备注举手中低手部区域小容易漏检站立低目标明显但易与走动混淆阅读中低头动作侧脸难辨书写中手部遮挡笔尖依赖上下文趴桌低大面积遮挡特征强玩手机高手机目标小亮度变化大讨论高需多人框配合判断我会把类别清单打印出来逐项核对确认它与我的业务目标一致。中途修改类别名或者重组类别意味着之前训练的权重全部作废这个代价在项目排期里通常承受不起。另一个容易被忽略的点是类别顺序classes.txt 里第 0 行对应模型输出通道 0如果你用迁移学习预训练权重自带的类别顺序必须与这份数据一致否则要用 transfer 参数把最后一层重新初始化。数据集的标注质量直接决定模型天花板这一步花一小时检查能省后面三天调参。3. 用 Ultralytics YOLO 训练 11,800 张课堂数据命令与参数3.1 环境准备ultralytics 安装与依赖对齐拿到标注数据之后的固定动作是装训练环境。Ultralytics YOLO 是目前最顺手的训练框架一条 pip 命令就能装完。比较稳妥的做法是新建一个独立的 Python 环境避免和项目里的其他库互相污染。装完验证一下版本号YOLOv8 和 YOLO11 的训练接口基本一致但模型定义文件各有不同别拿老版本的权重硬套新版本框架。conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics yolo version这三行命令里的版本对齐细节值得留意Python 3.10 是当前 ultralytics 兼容性较好的版本安装时建议直接装最新版而不是锁一个旧版本因为新版本对 PyTorch 2.x 的适配更完整。装完后先跑yolo version确认命令行工具能正常调用如果这步报错多半是环境变量或 Python 路径的问题不用急着重装先检查当前激活的 conda 环境是不是你刚建的那个。GPU 训练还要确认 PyTorch 的 CUDA 版本可用跑python -c import torch; print(torch.cuda.is_available())看到 True 再继续。CPU 也能训但 11,800 张数据一次 epoch 的时间会让人怀疑人生。3.2 编写 data.yaml路径、类别 ID 与划分对齐ultralytics 训练时依赖一个 YAML 配置文件它把数据路径、训练集、验证集和类别名绑定在一起。这个文件写错是新人最常见的翻车原因尤其是路径写绝对路径时换台机器就崩。常见的稳妥做法是把 data.yaml 放在数据集根目录下路径用相对当前工作目录的写法这样整体迁移项目时不用改配置文件。path: ./classroom_11800 train: images/train val: images/val names: 0: hand_raise 1: standing 2: reading 3: writing 4: sleeping 5: phone 6: discussion这里的path是相对于你执行训练命令的目录的所以我通常建议直接在数据集根目录的上一级跑训练命令。train和val指向的是图片目录框架会自动去同名 labels 目录里找对应的 txt。names这个字典的顺序必须和 classes.txt 完全一致否则模型输出的通道和语义就对不上。如果你拿到的是扁平目录加 train.txt / val.txt 的形式把 train 和 val 改成对应的 txt 文件路径也行但目录结构更直观、更不容易错。3.3 训练启动命令一次全量训练的参数组合环境装好、配置写对训练命令本身其实很短。我一般会先用一个较小的 epoch 数验证数据流能跑通确认没有路径或格式错误再启动完整训练。直接跑 100 个 epoch 结果 loss 一直不变才回头排查既浪费时间又容易把问题归因到模型上。yolo detect train \ dataclassroom_11800/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ cacheTrue \ projectruns/classroom \ nametrial1几个关键参数的实际影响按排坑优先级排序imgsz640是速度和精度的平衡点课堂场景后排学生目标小如果你显存够用我建议直接上 640 或 768不要用 416batch16取决于显存大小8GB 显存跑 yolov8s 用 batch 16 是安全的再大容易 OOMpatience20是早停参数验证集指标连续 20 个 epoch 不提升就自动停止避免无效计算。cacheTrue把图片提前缓存到内存第一次 epoch 慢后面明显加速。如果机器显存小于 8GB换modelyolov8n.pt精度略降但显存占用小很多。训练启动后不要盯着终端发呆。重点看 loss 曲线的收敛速度box_loss 和 cls_loss 如果在前十个 epoch 内没有明显下降大概率是数据链路有问题最常见的是标签错位或者类别不平衡太严重。3.4 训练日志怎么读loss、mAP 与早停Ultralytics 训练时会实时打印每个 epoch 的指标包括 box_loss、cls_loss、dfl_loss 以及验证集的 precision、recall、mAP50、mAP50-95。很多人只看 mAP50 过拟合与否这是不够的。我会同时盯 train loss 和 val mAP 两组数字train loss 持续下降而 val mAP 停滞说明模型在拟合训练集的噪声这时候先降学习率或加数据增强train loss 和 val loss 都在高位横盘则要检查标签质量或模型容量。训练结束后的产物在runs/classroom/trial1/weights/下best.pt是验证集 mAP 最高的权重last.pt是最后一次 epoch 的权重。永远拿 best.pt 做后续推理不要拿 last.pt哪怕两者只差 0.01 的 mAP在课堂这种类别不平衡的数据上best.pt 的泛化通常更好。我的习惯是把每次训练的 data.yaml、训练命令和 results.csv 一起存档下次调参可以直接对比。训练这件事一半是工程一半是玄学但可复现的工程习惯能让你更快摸到那个玄学的边界。4. 教室场景训练避坑5 个翻车现场与排查方法4.1 标签错位loss 炸了先检查同名文件现象训练一开始 loss 就异常高或者前几个 epoch loss 大幅震荡完全收敛不下去模型预测结果驴唇不对马嘴。原因图片和同名 txt 不匹配。这类数据在分发、压缩、重命名过程中偶尔会出现图片是一批、标签是另一批的情况文件名相同但内容对不上。YOLO 训练框架按文件名配对不会报错只会静默地用错误的标签训练。解决写脚本对每一张图做基础校验比如随机抽 20 张图把标注框画到图上肉眼看一遍。这一步比任何自动化检查都直接5 分钟就能发现大头问题。另外确认 labels 目录下没有多余的历史文件比如标注工具自动生成的 classes.txt 或 _backup 文件框架有时会把它们误读为标签。4.2 类别不均衡趴桌样本多举手样本拉不住现象训练完 val mAP 看着不错但逐类看指标趴桌和阅读的 AP 很高手举和玩手机几乎不可用。教室场景天然不均衡趴桌可能占了三成样本玩手机可能只有 3%。原因模型在多数类上见过足够多的正样本少数类的梯度被淹没。YOLO 的损失函数是逐目标累加的多数类的框数量多反向传播时主导了梯度方向。解决先统计数据里每个类别的框数量按比值定过采样策略对样本少的类别把对应图片复制几份放进训练集或者用 ultralytics 自带的augmentTrue让裁剪、翻转增强在训练时多做几次。更不动数据的方法是提高少数类的损失权重在 data.yaml 里加loss_weights: {4: 2.0, 5: 3.0}但要注意别调过头否则模型会牺牲多数类精度来迁就少数类。我的经验是先过采样到最少类占比不低于 10%再谈权重。4.3 后排小目标imgsz 不够头部目标变成几个像素现象验证集上总体 mAP50 还行但按距离分桶看后排学生的漏检率特别高。1080p 画面里后排人脸区域只有 30x30 像素左右手的区域甚至不到 20 像素。原因YOLO 检测小目标本来就吃力640 的输入尺寸会把后排目标缩得更小特征图上的响应被周围背景淹没。解决最直接的是把imgsz调到 768 或 832输入尺寸大了小目标在特征图上占据的格子就多。代价是训练和推理变慢显存需求变大。另外检查标注框是否框得过大有些标注员习惯把“举手”连同整个上身框进去框里背景太多模型学到的特征被稀释。重新框成紧贴目标的最小矩形比调模型参数收益大得多。4.4 数据泄漏同一教室连续帧被随机分到 train 和 val现象训练时验证集指标好得离谱final mAP50 到 0.95 以上但是拿到真实新教室现场一测直接掉回 0.6。原因数据集按单张图片随机划分 train/val同一教室同一节课的连续视频帧被同时分进了两边。训练集里见过这个教室的光线、桌椅布局和某个学生的半张脸验证的时候等于开卷考试。解决按教室或按视频片段划分数据而不是按单帧随机划分。拿到数据先看文件名是不是带教室编号如果带按编号分组一个编号的所有帧进同一个集合如果不带文件名信息至少做到连续帧按时间窗口切分比如每段视频前 80% 进 train、后 20% 进 val。这个教训很血泪我早期一个交付项目就是在这个坑上被客户现场质疑的重划数据后再训指标掉了 0.1 但真实场景表现反而正常了。4.5 注意力漂移投影仪和黑板内容被当成“阅读”现象推理时发现模型对教室投影幕布上的文字频繁报警把幕布上的 PPT 内容框出来标记为“阅读”。原因课堂画面里投影幕布区域光线亮、文字密集局部特征与书本页面的纹理高度相似。模型没有足够的上下文区分“人在读书”和“屏幕上有字”特别是训练数据里如果包含了大量带投影的教室全景幕布区域会被当成目标物体。解决后处理上做一个感兴趣区域过滤用教室的固定机位标定一个 ROI把投影仪区域和讲台区域排除出检测范围。也可以在标注阶段就把投影幕布框加为背景类或“屏幕”类让模型学会区分。最省事的路线是推理时把整帧按 ROI 掩码处理只检测座位区域既减少了误报又降低了计算量课堂行为检测本身关心的是学生老师区域本来就不该进统计。5. 把模型接进教室摄像头RTSP 拉流推理与行为统计5.1 推理脚本骨架VideoCapture 与模型加载训练完的模型要接进真实课堂最常见的部署方式是直接拉教室的 RTSP 视频流。海康、大华这类摄像头基本都支持 RTSP 协议通过 cv2.VideoCapture 就能读帧。推理代码本身不复杂但有几个坑值得提前说摄像头断流重连、每帧检测耗时、以及检测结果的时序统计。import cv2 from ultralytics import YOLO model YOLO(runs/classroom/trial1/weights/best.pt) rtsp_url rtsp://192.168.1.100:554/Streaming/Channels/101 cap cv2.VideoCapture(rtsp_url) frame_id 0 while True: ret, frame cap.read() if not ret: print(断流等待重连...) cap.release() cv2.VideoCapture(rtsp_url) continue frame_id 1 if frame_id % 5 ! 0: continue results model(frame, conf0.45, imgsz640)[0] for box in results.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f帧 {frame_id}: 类别 {cls_id}, 置信度 {conf:.2f}, 坐标 {xyxy})这个脚本里有一个关键处理是跳帧策略frame_id % 5 ! 0直接跳过当前帧。YOLOv8s 在 1080p 输入下GPU 推理单帧大约 20 到 30 毫秒但教室摄像头通常 25 帧每秒如果每一帧都做检测CPU 解码加上 GPU 推理会来不及导致视频越来越卡。跳帧后检测频率大约 5 帧每秒对行为统计来说已经足够因为“举手”“趴桌”这类状态的持续时间都是以秒为单位的。conf0.45是置信度阈值课堂场景宁可漏检也不希望误报我会保守地设到 0.5 左右。5.2 帧率与延时跳帧、队列与断流重连部署到真实教室后最影响体验的不是检测精度而是画面的实时性。一条完整的链路是摄像头 RTSP → FFmpeg 解码 → 模型推理 → 结果叠加显示或写入数据库每一环都可能成为瓶颈。ffmpeg 拉流解码的延迟通常比 cv2 原生 VideoCapture 更低因为 OpenCV 的内部缓冲默认会堆积若干帧导致画面越来越滞后。常见的做法是禁用 OpenCV 的缓冲或者直接用 ffmpeg 子进程读流再转成 numpy 数组。断流也是必须处理的教室的摄像头偶尔会重启路由抖动会让 RTSP 连接断开脚本里需要捕获ret False并自动重连否则整个推理进程就挂在那里什么也检测不到。我对跳帧策略的建议是先量一下你机器单帧端到端耗时设定目标检测间隔为 300 到 500 毫秒一次。比如单帧 50 毫秒那每 8 帧处理一次约 400 毫秒间隔既保留了响应速度又不会让 GPU 满载。检测间隔再短也没有实际意义因为课堂行为统计的粒度是一节课 45 分钟不是 45 毫秒。5.3 从单帧目标框到课堂行为统计拿到每帧的检测框后下一个问题是怎样把这些框整成客户要的“专注度”。Raw 输出只是一堆坐标和类别客户看到的是“本节课举手的次数”“趴桌人数占比”这些数字。这一步不能简单地把每帧的目标数加起来因为同一个学生连续 10 帧被检测到“举手”你算 10 次还是一次我的做法是引入一个简易目标跟踪用 IoU 把相邻帧的检测框关联起来同一个位置、同一个类别的框视为同一个目标记录它首次出现和持续存在的帧数。from collections import defaultdict tracked {} next_id 0 def update_tracks(detections, iou_thresh0.4): global next_id new_tracks {} for det in detections: matched_id None for tid, prev_box in tracked.items(): iou compute_iou(det[box], prev_box[box]) if iou iou_thresh: matched_id tid break if matched_id is None: matched_id next_id next_id 1 new_tracks[matched_id] det return new_tracks这段逻辑是简易的 IoU 跟踪每当一帧检测出目标就和上一帧的所有已跟踪目标计算交并比超过阈值就沿用原有 ID否则新建 ID。这样同一个学生引起的多次检测被合并成一次行为事件统计按 track 维度计数而不是按 frame 维度计数。如果想更稳可以用 ByteTrack 或 DeepSORT但对于课堂这种固定机位、目标运动缓慢的场景IoU 跟踪已经够用还能省去额外的依赖。这里的compute_iou需要自己实现就是个标准坐标重叠率公式。行为统计的最终输出我通常做成一个 JSON 写入数据库每一行包含时间戳、教室编号、行为类别、数量。客户前端拉这个接口就能画出整节课的行为曲线。到这一步一个从数据到部署的完整闭环就走通了。6. 进阶课程表先验与数据闭环比换模型更见效把模型从“能检测”推进到“真能用”我摸索出的最有效技巧是给后处理加上课程表先验。同样的“趴桌”在数学课和体育课上的含义完全不同体育课学生本来就在活动趴桌可能是休息自习课全员低头书写阅读的行为密度天然就高。常见做法是把课表解析成一个时段映射表给不同课程类型设定不同的置信度阈值和类别权重。比如自习课把书写和阅读的阈值降到 0.35把玩手机的阈值提到 0.6体育课则反过来。这个逻辑不需要动模型只在推理结果上做一层加权换来的是客户能直观感受到的准确率提升。另一个值得投入的方向是主动学习闭环。11,800 张数据训出的模型在生产环境会碰到没见过的场景新的教室布局、新的摄像头角度、阴天光线不足。我的习惯是每次部署后把置信度低于 0.4 的检测帧自动抽帧保存每周人工过一遍挑出错检漏检的图片补标扩进训练集重新迭代。这个流程跑三到四次之后模型的真实场景表现能上一个台阶比盲目换模型骨架或者调数据增强参数稳健得多。推理加速方面把 best.pt 导出成 ONNX 再用 TensorRT 跑1080p 下能把单帧延迟从 30 毫秒压到 10 毫秒左右但这是优化收益递减明显的一块放在数据闭环稳定之后再做。我自己在这个项目上最大的教训是早期过于执着于调模型参数把 mAP 从 0.82 抠到 0.85结果客户现场反馈的准确率提升远不如后来加入课程表先验和 ROI 过滤那一步。希望这个顺序上的取舍能帮到你少走一段弯路。本文还有配套的精品资源点击获取
返回列表