资讯动态

学生上课状态检测数据集:VOC/YOLO/JSON三格式完整解析与YOLO训练实战

发布时间:2026/10/2 3:52:14 来源:尧图企业网站定制
简介这是一份面向智慧课堂场景的学生上课状态检测数据集共包含1698张真实拍摄图片覆盖“认真听讲”“睡觉”“玩手机”三类典型状态适合用于课堂智能监控、智慧教室及学生学习行为分析等项目实践。数据集中图片背景丰富、类别分布均匀所有标注均经人工精校并同时提供VOC格式xml、YOLO格式txt及JSON格式标签可直接接入主流目标检测框架使用。整个压缩包共2000个文件包含1698张jpg原图及对应的xml/txt/json标注文件体积约973.64MB目录结构清晰便于训练与验证。目前已有约1400人学习浏览数据集源自博主实际比赛与项目经验标注质量较高配套标签格式齐全能有效降低算法适配成本适合目标检测方向的学生、开发者及竞赛团队下载使用。1. 学生上课状态检测数据集1698张三格式标签到底能做什么教室监控画面里最常被要求检测的三件事就是听讲、睡觉、玩手机。过去我要么自己对着视频抽帧标注要么到处找现成数据集结果常拿到一堆只有 VOC 或只有 YOLO 标签的包还得先花一晚上写格式转换脚本中间还容易出错。这份“学生上课状态检测数据集(听讲-睡觉-玩手机)1698张-含voc(xml)yolo(txt)json三种格式标签.zip”算是把前置工作做完了1698 张课堂图片同一批标注同时输出成 XML、TXT、JSON 三份解压就能用。它能解决什么简单说你想跑 YOLO 就用 YOLO 格式想跑 MMDetection 或 Detectron2 就用 JSON 或 VOC不需要自己造轮子。适合正在做课堂行为分析、学生专注度评估、智慧教室项目的人。新手拿它能完整走一遍数据到训练再到验证的流程熟手拿它能当种子集快速跑出 baseline再补自己的真实场景数据。2. 拆开压缩包VOC(xml)、YOLO(txt)、JSON 三种标签怎么对齐同一批图片2.1 目录结构与文件命名规则拿到手先看什么拿到 zip 后的第一件事不是解压就训练而是先把目录结构摸清楚。常见做法是解压后看到 images或 JPEGImages与几个标签目录文件名通常是同一个前缀比如 IMG_0001.jpg 对应 IMG_0001.xml、IMG_0001.txt。先跑一条命令看看扩展名分布确认标签是不是每张图片都齐全unzip 学生上课状态检测数据集*.zip -d classroom_dataset find classroom_dataset -type f | awk -F. {print $NF} | sort | uniq -c这条命令会列出压缩包内每种扩展名的文件数量。如果 jpg 是 1698 个而 xml、txt 明显小于这个数说明有图片没标注后面训练时要么删掉要么补标。这里有个小经验数据集的目录名往往和标注工具的导出设置有关有的把类别目录嵌套在路径里有的用中文目录解压后先看一遍顶层结构再写脚本别上来就假设。还需要顺手统计图片尺寸分布。课堂上不同教室的摄像头分辨率可能混着来有的 1280x720有的 1920x1080这会影响 YOLO 归一化坐标的换算也会影响训练时是否要开 letterbox。用 Python 批量读一下宽高from PIL import Image import glob sizes {} for p in glob.glob(classroom_dataset/images/*.jpg): w, h Image.open(p).size sizes.setdefault((w, h), 0) sizes[(w, h)] 1 print(sizes)这段代码把图片尺寸分布打印出来。如果尺寸种类超过三四种建议训练前统一做一次缩放或全部用 YOLO 的 letterbox让模型少学一种无关变量。另外注意有些图片本身带 EXIF 旋转信息PIL 默认读到的宽高可能和真实显示方向相反这种情况要先用 ImageOps.exif_transpose 处理再记录尺寸。提示课堂数据涉及学生隐私建议所有处理都在校园内网完成对外发布前对人脸做模糊或裁剪。2.2 VOC XML 解析从 到 的关键字段VOC 格式的标签是 XML 文件写起来杂但结构非常固定根节点是 annotation里面先有 size 子节点记录图片宽高然后是若干个 object 节点每个 object 里有 name类别名和 bndbox四个坐标。我用 xml.etree.ElementTree 解析脚本很短import xml.etree.ElementTree as ET def parse_voc(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) objects [] for obj in root.iter(object): name obj.find(name).text bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) objects.append({ name: name, bbox: [x1, y1, x2, y2] }) return img_w, img_h, objects这里返回的是绝对像素坐标x1、y1 是左上角x2、y2 是右下角。VOC 没有归一化所以解析出来可以直接用于画框或转其他格式。要注意的是有些标注工具会把 bndbox 的坐标写成整数有些写成带小数统一转 float 比较稳。还有的 XML 里嵌了 difficult、truncated 字段表示难例和被截断的物体。训练时这类框一般建议过滤掉否则模型会被拉着去学一些标注本身就模糊的样本。2.3 YOLO TXT 的归一化坐标与类别 ID 映射YOLO 的标签完全不同每张图片对应一个同名 txt 文件每行是“类别ID 中心点x 中心点y 宽度 高度”且 cx、cy、w、h 都是相对于图片宽高的归一化值范围在 0 到 1 之间。它不是绝对坐标所以读取时必须结合图片原始尺寸才能换算成像素框def parse_yolo(txt_path, img_w, img_h): with open(txt_path, r) as f: lines f.readlines() boxes [] for line in lines: line line.strip() if not line: continue cid, cx, cy, bw, bh map(float, line.split()) x1 (cx - bw / 2) * img_w y1 (cy - bh / 2) * img_h x2 (cx bw / 2) * img_w y2 (cy bh / 2) * img_h boxes.append((int(cid), x1, y1, x2, y2)) return boxes这里的 5 个浮点数决定了框的几何信息类别 ID 只是序号本身它具体对应“听讲、睡觉、玩手机”里的哪一个完全由训练时的 names 配置决定。所以拿到数据集后第一件事是确认 txt 里的 0、1、2 到底对应哪三个类别。常见做法是随机抽几张图把 txt 里的框画出来人工看一眼。不要只看类别名因为不同人导出数据集时 ID 顺序可能完全不一样这个映射错位是后面所有训练翻车的源头。2.4 JSON 标签的字典结构与 COCO 风格转换JSON 标签在不同数据集里的表现差异最大。有的包给的是 COCO 风格顶层有 images、annotations、categories 三个数组适合直接喂给 Detectron2、MMDetection有的包给的是单文件列表每条记录包含文件名和框数组。拿到 JSON 先别急着写转换先打印它的类型和顶层 keyimport json with open(classroom_dataset/labels/annotations.json, r) as f: data json.load(f) if isinstance(data, dict): print(keys:, data.keys()) if images in data: print(images:, len(data[images])) print(annotations:, len(data[annotations])) print(categories:, data[categories]) else: print(list length:, len(data)) print(first item:, data[0])输出结果基本就能判断格式。COCO 风格里 annotation 的 bbox 字段是 [x, y, width, height]x、y 是框左上角坐标不是 x1,y1,x2,y2 那套。categories 里通常有 category_id 对应类别名和 YOLO 的 names 需要一一对应。如果 JSON 是自定义列表bbox 可能是 [x1,y1,x2,y2]也可能是 [cx,cy,w,h]必须用一张图的 ground truth 画出来验证。这一步不能省我见过太多人栽在“看起来像 COCO其实是 xyxy”上。2.5 三格式一致性校验训练前必做的坐标对齐检查三种格式来自同一批标注理论上框数量和坐标完全一致但压缩包在生成、传输、转码过程中可能丢框、改字、截断。我见过不止一次 XML 里 10 个框、同图 TXT 里只有 8 个框的情况训练时虽然不报错但类别学习会受影响。所以训练前写个简单校验脚本import os, glob import xml.etree.ElementTree as ET xml_dir classroom_dataset/voc yolo_dir classroom_dataset/yolo def voc_box_count(xml_path): tree ET.parse(xml_path) return len(tree.getroot().findall(object)) def yolo_box_count(txt_path): with open(txt_path, r) as f: lines [l for l in f.read().splitlines() if l.strip()] return len(lines) mismatch [] for xml_path in sorted(glob.glob(os.path.join(xml_dir, *.xml))): stem os.path.splitext(os.path.basename(xml_path))[0] txt_path os.path.join(yolo_dir, stem .txt) if not os.path.exists(txt_path): mismatch.append((stem, missing txt)) continue vc voc_box_count(xml_path) yc yolo_box_count(txt_path) if vc ! yc: mismatch.append((stem, vc, yc)) print(mismatch files:, len(mismatch)) for m in mismatch[:10]: print(m)这段脚本先数 XML 里的 object 数量再数 txt 非空行数不一致的记下来。坐标层面的比对更严格需要把 txt 还原成像素坐标再与 XML 逐框比对允许 1 像素误差。凡是校验不过的图片建议直接从训练集剔除而不是强行修复因为三份标签没有对齐意味着原始标注本身可能就有问题修好一个坑会踩出下一个坑。3. 用 YOLO 训练上课状态检测模型从数据集到第一个 mAP3.1 把 VOC/JSON 转成 YOLO 训练格式的标准脚本前面讲了读法这一步是批量转格式。既然数据包自带 YOLO 的 txt理论上可以省掉这步但实际中很多人拿到的 JSON 才是原始标注VOC 或 YOLO 反而是转换产物。这里给一个从 VOC 到 YOLO 的批量脚本逻辑是把 bndbox 的绝对坐标换算成归一化中心点与宽高import os, glob import xml.etree.ElementTree as ET class_names [听讲, 睡觉, 玩手机] class2id {name: idx for idx, name in enumerate(class_names)} def convert_voc_to_yolo(xml_path, output_dir, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class2id: print(fskip unknown class {name} in {xml_path}) continue bb obj.find(bndbox) x1 float(bb.find(xmin).text) y1 float(bb.find(ymin).text) x2 float(bb.find(xmax).text) y2 float(bb.find(ymax).text) cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{class2id[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: stem os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(output_dir, stem .txt), w) as f: f.write(\n.join(lines))这里的关键是 class2id 映射表必须和最终训练用的 data.yaml 完全一致。脚本里遇到不在映射里的类别选择跳过并打印比直接报错中断更适合批量处理但前提是打印信息要认真看别让不认识的类别悄悄消失。如果原始标注是 JSON转 YOLO 的逻辑完全一样只是读取坐标的部分换成从 JSON 取 [x, y, w, h]先换算成左上角和右下角x2xw、y2yh再走上面的归一化公式。特别注意JSON 的 bbox 是 xywh直接套 VOC 公式会得到错得离谱的框转换完务必抽三张图把结果画出来对比原图。3.2 data.yaml 与模型选型yolov8n 还是 yolov8sYOLO 训练的第一步是写好 data.yaml告诉框架图片路径、验证集路径和类别表。对这个数据集我一般这样配置path: /data/classroom_dataset train: images/train val: images/val names: 0: 听讲 1: 睡觉 2: 玩手机names 的顺序不是随便排的它必须和 txt 文件里行首的数字一一对应。比如 txt 里某行是“2 0.5 0.6 0.1 0.2”那这个框就代表 names 里索引 2 的“玩手机”。如果顺序错位模型训练时会把听讲的框当玩手机学loss 照降mAP 照出但实际推理结果完全不可用这是数据格式里最安静的黑匣子。模型选型上YOLO 系列对比下来对这个 1698 张、3 类的小规模任务yolov8n 和 yolov8s 是首选。n 参数量小、训练快适合先跑通全流程验证标签没问题s 精度更高适合最终部署。不要一上来就用 x课堂场景的目标不算大模型容量带来的收益远小于数据质量的影响。预训练权重从官方仓库下载即可注意下载的权重版本要和训练脚本匹配不同版本的 ultralytics 参数略有差异跑之前先yolo checks确认环境。注意names 的顺序一旦写入 data.yaml后续所有转换脚本都必须沿用同一张映射表改一个数字就要重转一遍标签。3.3 训练参数与数据划分1698 张图怎么分才不翻车数据划分是课堂检测任务里最容易翻车的环节。如果单纯随机划分同教室、同时段的图片会同时出现在训练集和验证集模型看着 mAP 很高一换教室就现原形。正确做法是按时间段或教室维度划分比如前 1360 张做训练、后 338 张做验证或者按摄像头位置分开。比例上 train:val 用 8:2 比较稳数据量小就别再抠出测试集用验证集兼任即可。训练命令我用 ultralytics 的标准写法yolo detect train \ modelyolov8s.pt \ dataclassroom.yaml \ epochs100 \ imgsz640 \ batch16 \ patience20 \ device0几个参数值得说。epochs100 给足迭代空间配合 patience20 做早停在验证集不再提升时提前结束省时间也防止过拟合。imgsz640 是大多数课堂场景的合理值但如果摄像头画面里学生离得远、手机目标只有 20 像素建议试试 imgsz960小目标 mAP 会明显改善。batch16 在 16GB 显存下跑 s 模型没问题显存小就降到 8。device0 指定 GPU没有 GPU 就别硬跑 s换 n 或直接用 CPU 慢跑一遍流程验证脚本没写错。这里有个容易被忽略的参数 cache。小数据集完全可以加 cacheTrue把图片缓存到内存里训练速度能快一倍前提是内存足够。1698 张的 640 分辨率图片缓存后大约占 2-3GB 内存别在 8GB 的小机器上开。3.4 训练中看什么指标loss、mAP、混淆矩阵怎么看训练开始后终端会实时打印 box_loss、cls_loss、dfl_loss 三项损失。box_loss 衡量框的位置误差cls_loss 衡量分类误差dfl_loss 是分布焦点损失负责框边界质量。它们的绝对数值没有统一标准没必要纠结具体大小重点看曲线是否平稳下降、验证集 loss 有没有在某个点反弹。验证集 loss 反弹就是过拟合信号早停这时候就会生效。epoch 结束后框架会输出 mAP50 和 mAP50-95。mAP50 是 IoU 阈值 0.5 下的平均精度课堂检测场景看重它mAP50-95 更严苛从 0.5 到 0.95 每隔 0.05 算一次再平均目标边框要求更准。对三类检测mAP50 跑到 0.85 以上算是可用低于 0.7 就要回看标签或数据处理。训练完跑验证集的命令是yolo detect val modelruns/detect/train/weights/best.pt dataclassroom.yaml imgsz640混淆矩阵在验证结束后自动生成横轴是真实类别纵轴是预测类别。很多教程说混淆矩阵总和应该唯一实际并不一定因为漏检的框和背景类没有计入统计出现过拟合时对角线数字也不整齐。矩阵里最值得看的是“睡觉被预测成听讲”这类跨类混淆它直接告诉你该补哪类样本。4. 标签格式转换与训练避坑5 个我踩过的真实问题4.1 类别顺序不一致导致训练标签全错现象训练 loss 降得挺好看但验证集 mAP 趋近 0画出来的框类别全乱。原因是 txt 里行首的类别 ID 顺序和 data.yaml 里的 names 顺序对不上常见于从 JSON 转 YOLO 时用了另一套排序。解决训练前写个统计脚本把所有 txt 的第一列数字做个分布统计from collections import Counter import glob cnt Counter() for txt in glob.glob(classroom_dataset/yolo/*.txt): with open(txt) as f: for line in f: if line.strip(): cnt[int(line.split()[0])] 1 print(cnt)打印结果如果是 Counter({0: 800, 1: 700, 2: 600}) 这种形状就逐个数字确认它对应 names 里的哪个类别。这一步是纯人工确认不依赖任何工具但它是整个训练流程里最值得花五分钟做的事。4.2 XML 里被截断的 bndbox坐标越界与归一化后负数现象训练到一半 loss 突然震荡或者推理时框跑到图像外。原因是标注过程中 bndbox 坐标被截断或粘贴复制出错出现 xmin xmax 或坐标超出图片宽高换算成归一化坐标后就变成负数或大于 1。解决在解析 XML 时对坐标做 clamp 并过滤非法框x1 min(max(float(bb.find(xmin).text), 0), img_w - 1) y1 min(max(float(bb.find(ymin).text), 0), img_h - 1) x2 min(max(float(bb.find(xmax).text), 0), img_w - 1) y2 min(max(float(bb.find(ymax).text), 0), img_h - 1) if x2 x1 or y2 y1: continue参数上注意用 img_w - 1 而不是 img_w否则归一化时可能出现等于 1 的边界点。这段代码放在解析函数里逐文件处理同时打印出被过滤的文件名事后回去看原始标注再决定是修还是删。4.3 JSON 与 VOC 的坐标表示差异左上右下 vs 左上宽高现象用 JSON 转换出来的框整体偏移尤其在目标边缘框的右下角位置明显不对。原因是 JSON 标的 bbox 几乎都是 xywh左上角 x、左上角 y、宽度、高度而 VOC 是 xyxy。两套表示法的转换公式是 x2 x1 wy2 y1 h很多人只改了取字段名忘了加宽高。解决写一个探测函数先从 JSON 里抽一张图把框画在图上人工确认再写批量转换。这个过程不要省我在这上面反复横跳过两次都是因为太自信。4.4 图片尺寸不一致时 YOLO TXT 归一化失效现象某些图片的框位置完全错误但另一些图是好的。原因是 YOLO 的归一化坐标依赖于图片原始宽高如果数据集混入了缩放过的图或 txt 是从一个统一尺寸版本转换而来而 images 目录里的实际尺寸变了坐标就会错位。解决解析 txt 时不要信任目录名或 XML size要现场用 PIL 读图片宽高。同时对尺寸差异过大的图片做预统一。更隐蔽的是 EXIF 旋转问题手机拍的图在标注工具里显示是正的但训练框架读到的是带旋转的原始数据宽高直接对调这种情况建议解压后先统一清洗一遍。4.5 背景类干扰把“听课”误检成“玩手机”现象验证集 mAP 不低但放到真实教室过视频时记笔记、翻书、托腮这些动作频繁被检成玩手机。原因是上课场景里手部动作高度相似玩手机和翻书在 640 分辨率下特征差异很小模型学到的是“手在桌面下方有动作”这个粗糙模式。解决思路有两层。数据层把这类误检帧抽出来做难负样本标成 background 或直接作为无目标图片加入训练。逻辑层部署时加时序约束单帧检测结果只有连续 N 帧同时命中才输出报警能压掉一大半抖动误报。这算是做课堂检测的血泪经验前期标数据时没人提醒后期全靠补样本。5. 验证与进阶把 1698 张数据集做成能上线的课堂检测方案5.1 用混淆矩阵和分时统计验证模型真实可用性训练完成后先别急着部署。把验证集的预测结果落盘成 CSV统计每张图的检测结果与真实标签的匹配情况同时带上图片在课程中的时间位置按“上课前 10 分钟、中段、后 10 分钟”分桶。课堂场景有个明显规律睡觉和玩手机集中在后段听讲集中在前段分时准确率能直接暴露模型在哪个时段不可用。下面用 sklearn 的 confusion_matrix 做一个快速验证from sklearn.metrics import confusion_matrix import pandas as pd df pd.read_csv(val_results.csv) # 包含 image, true_label, pred_label labels [听讲, 睡觉, 玩手机] cm confusion_matrix(df[true_label], df[pred_label], labelslabels) print(pd.DataFrame(cm, indexlabels, columnslabels))注意这里输入的 true_label 和 pred_label 是图片级标签不是框级标签需要先按 IoU 把检测框匹配到目标。验证的意义在于把模型从黑匣子变成能用数字判断的东西之后决定要不要给项目组交付。5.2 从三类扩展到更多状态的迭代思路1698 张数据能跑通但上线前最好扩到五类加“举手”和“趴桌”。做法不是重新标一遍而是把现有检测结果当预标注人工快速修正。先训练三类的 s 模型用它对新增教室图片预测抽帧后人工只改框和类别改完回流给模型做下一轮训练。这个闭环里数据增强别加太猛课堂场景的平移、翻转够用颜色抖动和 mosaic 增强对这种小目标任务反而容易过拟合。5.3 伪标签与数据回流小数据集的后悔药我的习惯是第一轮只训练三个原始类别然后让模型去标注同教室不同时段的未标注图片把置信度高于 0.9 的框直接作为伪标签0.5 到 0.9 的框人工复查低于 0.5 的丢弃。这样 1698 张可以扩到 3000-4000 张有效标注成本几乎为零。伪标签回流时注意只保留同一个模型能稳定复检的框不要混入多条来源的标注标准。说到底这类数据集的价值不在数字本身而在于它给了你一个干净、对齐、多格式的起点。拿到先解压、统计、校验再谈训练训练时先定类别映射再调参数部署前先看分时混淆矩阵。这套流程我踩过不少坑才固定下来希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑