
简介面向需要构建课堂行为识别模型的算法工程师、科研人员与教育信息化开发者该数据集提供5622张课堂场景图片同时给出Pascal VOC格式xml与YOLO格式txt标注覆盖dk、dx、js、tt、xt、zl、zt七个类别可直接用于目标检测模型训练、算法验证与课堂分析系统开发。压缩包共2000个文件以xml标注文件为主便于标注解析与跨框架使用另有1个txt说明文件整体大小约268.46MB目录结构清晰下载后无需复杂转换即可按需取用。目前已有1531人学习下载数据组织规范可支撑从标注格式理解、标签校验、模型迭代到课堂行为统计的完整实践流程。无论是毕设实验、科研对比还是落地教学督导工具都能省去人工标注与格式对齐的重复工作快速获得可用于训练与评估的数据基础。1. 学生课堂行为检测数据集5622 张图、7 类行为、VOC 与 YOLO 双格式直接开训做课堂行为检测卡住大家的第一道坎往往不是模型而是数据。通用数据集场景不对口自己标又费劲。这套学生课堂行为检测数据集一共 5622 张 jpg 图片每张图配套一个 Pascal VOC 格式的 xml 和一个 YOLO 格式的 txt三件套数量完全对齐解压出来就能直接开训不需要先写格式转换脚本。标注类别 7 个覆盖课堂里最高频的行为举手、站立、转头、抬头、低头、写字、打瞌睡。适合做智慧教室、教学督导、课堂考勤的从业者也适合刚入门目标检测、想拿一份真实场景数据跑通「数据准备 → 训练 → 评估」全流程的开发者。如果你手里只有 COCO/VOC 这类通用数据这份数据能直接补上场景缺口省掉最耗时的标注环节。2. 读懂标注体系VOC xml 与 YOLO txt 的对应关系、7 个类别代码的语义拿到数据集先别急着开训花十分钟把标注体系看清楚。这套数据的核心是「一个样本三件套」jpg、xml、txt 三个文件同名同数量xml 是原始标注源头txt 是从 xml 转好的 YOLO 格式。7 个类别代码都是拼音缩写不提前搞清楚语义训练完才发现类别指错了等于白跑一轮。2.1 一个样本三件套jpg、xml、txt 怎么对上每个样本由三个同名文件组成比如firc_class_707.jpg、firc_class_707.xml、firc_class_707.txt。展开目录后三种文件各 5622 个一一对应不存在某个样本缺标注的情况。这在下载数据集里算相当干净的至少省去了「图片有但标签没有」的补对工作。文件类型数量作用jpg5622原始图像帧xml5622Pascal VOC 标注含类别名、目标框、图像尺寸txt5622YOLO 格式标注归一化坐标直接供 YOLO 系框架读取这里要注意一个点txt 不包含分割路径信息也不包含类别名只存类别id x_center y_center width height五列数字。所以训练前必须靠我们自己组织目录结构把图片和 txt 按 train/val/test 分好这就是第 3 章要做的事。xml 里存的是人类可读的类别名如namedk/nametxt 里存的是数字 id两者靠「类别列表顺序」对应起来改列表顺序等于重排 id这是后面最容易翻车的点之一。2.2 七个类别代码先搞清楚每个行为指的是什么7 个类别代码是[dk, dx, js, tt, xt, zl, zt]按这个顺序从 0 开始编号。按这类数据集最常见的约定语义对应如下代码常见语义判定要点dk打瞌睡趴在桌上或低头闭眼头部姿态低且长时间静止dx低头低头看桌面/手机但还没到趴下的程度js举手手臂抬起通常伴随身体挺直tt抬头面部朝前朝上面向黑板或老师xt写字低头且手部在桌面区域有书写动作zl站立整体站起目标框高度明显大于坐姿zt转头头部转向侧面或后方通常伴随身体转动具体语义以包内「使用前必读.txt」为准我一般会在开工前打开这个文件确认一遍因为不同数据集对 dx 和 xt 的边界定义可能有差异。比如有的数据集把「低头玩手机」单独列一类有的并进低头里。对这份数据先按上面的默认约定跑基线训练完看混淆矩阵再决定要不要合并易混类别比一开始纠结定义更实际。2.3 VOC 坐标与 YOLO 归一化坐标换算公式与两种笔误xml 里一个目标的长相是这样的object namedk/name bndbox xmin280/xmin ymin146/ymin xmax512/xmax ymax388/ymax /bndbox /object对应到 txt 里的那一行是0 0.330 0.247 0.242 0.224。VOC 存的是绝对像素左上右下坐标YOLO 存的是归一化中心点加宽高。换算关系x_center 取 xmin 和 xmax 的中点再除以图像宽度y_center 同理除以高度宽高分别用 xmax-xmin、ymax-ymin 除以图像宽高。写成函数就是import xml.etree.ElementTree as ET CLASSES [dk, dx, js, tt, xt, zl, zt] def voc_xml_to_yolo_line(xml_path): root ET.parse(xml_path).getroot() w float(root.find(size/width).text) h float(root.find(size/height).text) lines [] for obj in root.iter(object): cid CLASSES.index(obj.find(name).text) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_c round(((xmin xmax) / 2) / w, 6) y_c round(((ymin ymax) / 2) / h, 6) bw round((xmax - xmin) / w, 6) bh round((ymax - ymin) / h, 6) lines.append(f{cid} {x_c} {y_c} {bw} {bh}) return lines这段代码的核心逻辑是先读 xml 里size存的实际图像宽高再遍历所有object用CLASSES.index()把类别名转成 id最后按公式算归一化坐标。两个常见笔误提醒一下一是计算时没除以size里存的宽高而是除以了自己记的尺寸图片一旦被压缩过整套框就整体偏移二是CLASSES列表顺序和 txt 里的 id 约定不一致导致类别张冠李戴。这两个错在训练时都不报错只在结果上体现特别坑。3. 用 YOLOv8 训练这套课堂行为数据目录划分、data.yaml 与参数选择标注看懂了就开始动手。下面按 YOLOv8 的习惯组织目录给出可直接复制的划分脚本、配置文件和训练命令并把每个关键参数为什么这么设说清楚。整套流程走下来大约半小时能出第一个基线模型。3.1 先划分数据8:1:1 切分脚本与文件同步这份数据没有预切分目录需要自己划分。训练集、验证集、测试集按 8:1:1 切约 4498 张训练、562 张验证、562 张测试。划分时有个细节文件名是firc_class_707这类连续编号如果这些图是从同一段监控视频抽帧得到的相邻帧内容高度相似随机洗牌后可能把相似帧同时分进 train 和 val造成验证集虚高。我一般会固定随机种子并把 val 比例保持在 10% 以上尽量摊平这种相关性。import os, random, shutil random.seed(2024) # 固定种子保证划分结果可复现 src_img, src_txt images, labels dst classroom_behavior for split in (train, val, test): os.makedirs(f{dst}/images/{split}, exist_okTrue) os.makedirs(f{dst}/labels/{split}, exist_okTrue) imgs [f for f in os.listdir(src_img) if f.lower().endswith(.jpg)] random.shuffle(imgs) n len(imgs) # 5622 cut1, cut2 int(n * 0.8), int(n * 0.9) for i, img in enumerate(imgs): stem os.path.splitext(img)[0] split train if i cut1 else val if i cut2 else test shutil.copy(f{src_img}/{img}, f{dst}/images/{split}/{img}) shutil.copy(f{src_txt}/{stem}.txt, f{dst}/labels/{split}/{stem}.txt) print(ftrain {cut1} val {cut2 - cut1} test {n - cut2})这段脚本先创建 train/val/test 的 images 和 labels 目录再用固定种子洗牌后按比例切分最后通过复制而不是移动文件来保留原始完整副本。stem是去掉扩展名的文件名确保 jpg 和同名 txt 进入同一个 split。跑完建议抽 20 张验证集图片人工看一眼确认标签和画面内容对得上——这一步能提前发现文件名对不齐的问题。3.2 写 data.yamlnc、names、路径三处易错点YOLOv8 靠一个 yaml 文件描述数据集路径和类别定义。很多人在这里翻车三个位置最容易错path 写了相对路径导致找不到图片、names 顺序和 txt 里的 id 不一致、nc 数错了类别。按这份数据的类别列表配置文件如下# classroom_behavior.yaml path: /home/user/classroom_behavior # 改成你解压后的绝对路径 train: images/train val: images/val test: images/test nc: 7 names: 0: dk 1: dx 2: js 3: tt 4: xt 5: zl 6: ztpath建议写绝对路径因为 YOLOv8 对 train/val/test 的解析是相对于 path 的相对路径在不同工作目录下行为不一致。names的键就是类别 id必须和 txt 第一列的数字一一对应。这份数据本身对齐得干净只要别手滑改动 names 顺序就行。3.3 启动训练模型、imgsz、batch 怎么选配置文件就绪后直接跑训练命令yolo detect train \ dataclassroom_behavior.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16 \ projectruns/classroom \ nameyolov8n_baseline第一次跑建议用yolov8n当基线速度快、显存占用小先把整个流程跑通。单卡显存 8G 以上可以换yolov8s或yolov8m精度会有明显提升。参数选择我一般这样定参数我一般怎么设说明modelyolov8n.pt 起步先拿可用基线再换大模型对比imgsz640 起步验证用 960教室多人小目标960 通常更稳batch16 或 8以不爆显存为准爆了就减半epochs100五千张图 100 epoch 足够收敛patience默认 50验证集 50 轮不涨会自动早停训练结束后看runs/classroom/yolov8n_baseline/weights/best.pt这就是验证集上最优的权重。首次训练到收敛大约需要一到两小时视显卡而定中途打开results.png看 loss 曲线如果验证集 loss 在 60 轮后还明显下降可以顺手加 50 epoch 继续跑。4. 常见问题与避坑五条实测踩坑记录这套数据本身对齐得比较干净但从拿到手到训出能用的模型中间至少有五个坑我踩过或看过别人踩过。每条按「现象 → 原因 → 解决」写都是能直接对着排查的。4.1 训练时类别越界提示 nc 不匹配现象yolo detect train跑起来后立刻报 index 越界或者提示nc与names数量不匹配。原因某个 txt 里出现了 0~6 之外的 class id最常见的是标注工具从 1 开始编号或者有人改过类别顺序后没同步 txt。我见过有人把 names 按中文语义重新排序直接把 id 打乱了。解决先扫描全部 txt 的 class id把越界的找出来import os bad [] for f in os.listdir(labels): if not f.endswith(.txt): continue for line in open(flabels/{f}): cid int(line.split()[0]) if cid 0 or cid 6: bad.append((f, cid)) print(越界数量:, len(bad), bad[:10])这段脚本遍历所有 txt取每行第一列做范围检查正常应该输出 0 条。如果报出大量越界大概率是标注工具约定不同用 2.3 的转换函数重新生成 txt 即可。4.2 验证集 mAP 不低后排小目标几乎全漏现象整体 mAP50 到了 0.85但把置信度阈值一提高后排学生全部消失只剩前排大目标。原因教室是典型的多人小目标场景后排人物在原始画面里可能只有 20×40 像素缩到 640 输入后只剩几个像素信息量不够模型判别。解决把imgsz提到 960 或 1280batch 相应减半还不够就在推理侧做滑窗把原图按 2×2 切片分别过模型再合并结果。对于课堂考勤这类场景imgsz960 yolov8s是个性价比很高的组合。4.3 dx低头和 xt写字互相误检现象混淆矩阵里这两个类串得厉害一个低头写字的样本有时判成 dx 有时判成 xt。原因两个类姿态本身接近xml 框的都是整个人头部朝向和手部动作在低分辨率下根本分不开标注边界天然模糊。解决两条路一是先用合并类训把 dx/xt 合并成「低头/书写」一类7 类变 6 类推理时再按规则细分二是单独收集这两类的贴近特写数据做二次分类。对大多数课堂考勤场景合并后整体准确率反而更高这个取舍值得做。4.4 视频实测时检测结果一帧有一帧无抖得没法用现象单帧检测正确率不低但同一个学生连续 30 帧里被检出来 15 次、消失 15 次统计行为占比时数据完全不能用。原因单帧检测的置信度在姿态变化时会抖动比如转头过程中目标框恰好处于半遮挡状态置信度在阈值附近来回横跳。解决加一个滑窗投票机制连续 N 帧里出现频次超过 60% 的行为才判定为有效。这个在 5.3 节会给一个最小实现直接抄即可。4.5 自己补标新数据后旧类别 AP 反而下降现象把自己拍的课堂视频截图加入数据集重新训练训练集变大了但旧类别的 AP 掉了好几个点。原因常见做法是用在线标注工具或 labelImg 补标导出的 txt 有的没按图像实际宽高归一化或者图片经过聊天工具转发被压缩过尺寸XML 里记录的尺寸和实际 jpg 不一致。解决每次合并新数据前强制跑一遍 4.1 的脚本再随机抽 20 张图人工比对框和画面。从固定摄像头录的原始视频直接抽帧补标不要用转发压缩过的图片这个习惯能省掉大量返工。5. 进阶玩法课堂场景的数据增强与视频流检测接入基线跑通之后想让模型在实际教室里更稳主要看两件事增强参数怎么围绕课堂场景调、以及怎么把单帧检测变成稳定的行为判定。这两件事都做好了才谈得上落地。5.1 数据增强围绕教室场景调别用通用默认值YOLOv8 自带的增强参数针对的是通用场景直接套到教室上不一定合适。课堂视频的特点是相机固定安装顶装或侧装、人物始终处于直立姿态、光照基本恒定。基于这个先验我一般这样调yolo detect train \ dataclassroom_behavior.yaml \ modelyolov8s.pt \ imgsz960 \ epochs120 \ batch8 \ hsv_h0.01 hsv_s0.6 hsv_v0.5 \ degrees0.0 scale0.5 fliplr0.5 \ mosaic1.0 mixup0.2参数逻辑degrees0关掉旋转增强因为教室不存在横着的人开了只会让模型学出奇怪的特征fliplr0.5保留左右镜像举手行为不分左手右手镜像不会改变类别语义hsv三项用接近默认的值教室灯光固定过度色偏增强会让模型去拟合颜色噪声而不是形体特征scale0.5配合mosaic模拟不同距离下学生尺度的变化这对小目标很有帮助。5.2 视频流推理conf 和 iou 参数设多少训练完用best.pt对视频做推理命令如下yolo detect predict \ modelruns/classroom/yolov8n_baseline/weights/best.pt \ sourceclassroom.mp4 \ conf0.35 \ iou0.45 \ saveTrue \ showTruesource可以是视频文件路径、摄像头设备号或图片文件夹。conf0.35是教室场景的合理起点调太低会出一堆误检调太高后排小目标会被过滤掉。iou0.45是 NMS 用的阈值多人密集场景可以降到 0.4减少互相重叠的框被压掉的情况。这里要强调一个认知yolo detect predict是逐帧独立推理帧与帧之间没有状态。实际课堂中「打瞌睡」是一个持续过程单帧判定天然不稳定所以需要下面的时序平滑。5.3 行为时序平滑15 帧滑窗投票的最小实现我一般会在推理结果后面接一个滑窗投票器让连续出现的检测结果互相投票滤掉单帧抖动from collections import deque, Counter class BehaviorSmoother: def __init__(self, window15, min_ratio0.6): self.window_buffer deque(maxlenwindow) self.min_ratio min_ratio def push(self, cls_ids, confs, threshold0.35): hit {cid for cid, conf in zip(cls_ids, confs) if conf threshold} self.window_buffer.append(hit) def stable_behaviors(self): votes Counter() for frame in self.window_buffer: votes.update(frame) return [c for c, cnt in votes.items() if cnt len(self.window_buffer) * self.min_ratio]用法是每帧检测完调用一次push把置信度高于阈值的类别 id 集合送进去stable_behaviors()返回窗口内稳定出现的行为。参数含义window15对应约 0.5 秒30fps 视频越大越稳定、对急性动作响应越慢min_ratio0.6表示 60% 以上的帧都出现才算数做课堂考勤这个值合理如果要做举手这类瞬时动作降到 0.4 更合适。6. 把检测结果落成行为统计导出标签、算占比、定位问题片段模型训好了最后一步是让它产出业务上能用的东西。对课堂行为检测来说最实用的输出不是画框视频而是「一段时间内各类行为出现多少次、占比多少」。用 YOLOv8 自带的导出功能加一个小脚本就能实现。先对测试帧批量推理并导出每帧的检测结果yolo detect predict \ modelruns/classroom/yolov8n_baseline/weights/best.pt \ sourcetest_frames/ \ conf0.35 \ save_txtTrue \ save_confTrue \ projectruns/export \ namebehavior_stats加上save_txtTrue后每一帧的检测结果会写成一个同名 txt每行格式是类别id 置信度 x_center y_center w h。接下来统计所有帧里各类行为出现的总频次import glob from collections import Counter CLASSES [dk, dx, js, tt, xt, zl, zt] counter Counter() for txt in glob.glob(runs/export/behavior_stats/labels/*.txt): with open(txt) as f: for line in f: cid int(line.split()[0]) counter[CLASSES[cid]] 1 total sum(counter.values()) for name, cnt in counter.most_common(): print(f{name}: {cnt} ({cnt / total:.1%}))这段脚本把每个 txt 的第一列类别 id 拿出来计数再除以总数得到占比。跑完你会得到类似「tt 抬头 38.2%、dx 低头 22.5%、dk 打瞌睡 11.3%」的结果这就是教学督导最关心的课堂行为分布。配合时间维度按分钟把视频切成片段分别统计还能画出「后半节课低头、打瞌睡占比上升」的曲线。最后验证统计可信度的一个技巧把conf从 0.35 换成 0.5 重跑一遍对比两份占比。如果某个类别的占比差超过 10%说明模型在这个片段上不够稳优先去翻置信度在 0.35~0.5 之间的检测结果确认是误检还是漏检再决定调阈值还是补数据。从那以后我每拿到一份新数据集都强制走一遍「先校验三件套、再固定种子划分、训完看混淆矩阵、导出标签做统计」这条流程一步都不跳。很多看起来玄学的模型效果问题最后都出在数据没对齐而不是网络结构上。希望帮到你。本文还有配套的精品资源点击获取