
简介这份睡岗与玩手机行为检测数据集面向安防监控、工位值守、智慧园区等场景的算法开发与研究人员可用于训练人员违规行为识别模型解决长时间值班场景下的脱岗、注意力分散等安全隐患。该数据集面向4653张原始图像构建压缩包内含2000个VOC XML格式的标注文件总大小约140.37MB标注覆盖目标框与类别信息可直接与YOLO、Faster R-CNN、SSD等主流检测框架无缝衔接。目前已有773人浏览学习适合需要高质量监督信号的计算机视觉学习者、项目团队及企业算法部门。通过该资源使用者能省去手工标注时间快速获得覆盖白天、黑夜及不同室内外环境的训练样本标注框对睡姿、头部低垂、手持手机等典型姿态均有细致框选便于直接训练或微调模型也可结合少量自定义数据扩充场景提升实际布控中的鲁棒性。1. 睡岗与玩手机数据集4653张原始图能解决什么问题如果你接过工厂或工地安防监控的项目一定知道这类需求有多具体员工在岗打瞌睡、低头玩手机摄像头拍到了但值班员不可能一直盯着屏幕。睡岗和玩手机检测就是要在视频画面里自动把这些行为框出来。这个标题给出的数据集正好补上最缺的一环——4653张原始图像全部用VOC XML格式做了标注每张图对应一个XML文件里面用边界框标出两类目标睡岗、玩手机。拿到它就能直接训练行为检测模型不需要自己找图画框。它可以用来训练YOLO、Faster R-CNN、RT-DETR这类目标检测模型部署到工厂、加油站、数据中心、工地等监控后端对值班员离岗、疲劳、违规行为做自动告警。适合正在做智慧安防、人员行为分析或算法落地的团队以及准备在项目里快速验证方案的个人开发者。下面按拿到数据集后的实际操作顺序从解析标注、转换格式、训练模型到部署排错完整讲一遍。2. 解析VOC XML标注睡岗和玩手机的数据长什么样2.1 目录与字段一张原始图对应一个XML文件拿到数据集后我一般不会立刻去训练而是先花半小时看清标注格式。VOC XML格式虽然老但在目标检测里依然是通用交换标准很多标注工具如LabelImg默认导出的就是VOC格式Pascal VOC也是计算机视觉里绕不开的标准数据集之一。这个数据集以VOC XML作为标注组织方式意味着你有两条路要么直接用支持VOC的工具训练要么转成自己熟悉的格式。转格式前先搞清楚它长什么样。解压后大概率看到两个目录JPEGImages放原始jpg图片Annotations放同名XML文件。4653张图对应4653个XML文件命名完全一样比如img_0001.jpg对应img_0001.xml。整个目录结构里XML是核心它描述一张图的基本信息和所有目标框。拿一个典型文件来说annotation folderJPEGImages/folder filenameimg_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namesleeping/name difficult0/difficult bndbox xmin482/xmin ymin350/ymin xmax720/xmax ymax620/ymax /bndbox /object /annotation根节点annotation下面folder和filename告诉你图片来自哪里size里的width、height、depth是图像宽高和通道数这些信息在做坐标换算时非常关键。一个object代表一个目标实例name是类别名这个数据集里通常写作sleeping和playing_phone实际以你解压后的XML为准difficult标记这个目标是否难识别约定为1的样本可以不参与评估bndbox四个顶点xmin、ymin、xmax、ymax是目标外接矩形的像素坐标。这里最容易踩坑的是坐标格式。VOC用左上和右下两个点不是中心点加宽高宽度和高度不会直接写出来需要你自己算。另外XML中所有坐标都是整数像素分辨率越大同一目标在坐标绝对值上的差异越大转换时要小心归一化精度。有的标注文件会把object写成objects或者在bndbox里加extra节点用标准解析库时要注意这些变体。2.2 用Python把标注画回原图先确认数据没标错解析XML的标准做法是Python标准库xml.etree.ElementTree不需要额外装依赖。下面这段代码读取一个XML打印出每张图的尺寸和全部目标并把边界框画在原图上用于肉眼抽查。import xml.etree.ElementTree as ET import cv2 def parse_voc(xml_path): root ET.parse(xml_path).getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) boxes [] for obj in root.findall(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) boxes.append((name, (xmin, ymin, xmax, ymax))) return width, height, boxes # 使用示例 xml_file Annotations/img_0001.xml img_file JPEGImages/img_0001.jpg w, h, boxes parse_voc(xml_file) print(f图片尺寸: {w}x{h}, 目标数: {len(boxes)}) for name, bbox in boxes: print(f{name}: {bbox})这段代码的逻辑很直白先取size节点得到宽高再遍历所有object对每个object取name和bndbox子节点的四个坐标组成一个元组。后面要解析成其他格式时这套读取逻辑可以复用到不同项目。参数上只需要注意路径分隔符Windows下用绝对路径时建议写成pathlib.Path避免反斜杠转义问题。然后是可视化脚本。直接把标注框叠加到原图上存成check.jpg用几分钟把几十张图肉眼过一遍能提前发现很多标错的框。import os import xml.etree.ElementTree as ET import cv2 xml_dir Annotations img_dir JPEGImages out_dir check os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue base xml_name[:-4] xml_path os.path.join(xml_dir, xml_name) img_path os.path.join(img_dir, base .jpg) if not os.path.exists(img_path): print(f缺少图片: {img_path}) continue w, h, boxes parse_voc(xml_path) img cv2.imread(img_path) for name, (xmin, ymin, xmax, ymax) in boxes: cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2) cv2.putText(img, name, (xmin, max(0, ymin - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(os.path.join(out_dir, base _check.jpg), img) print(f可视化完成结果保存在 {out_dir}/)这段脚本有几个地方需要按数据集实际情况调整目录名Annotations和JPEGImages要对应解压路径图片扩展名可能是.png或.bmp记得改如果图片文件名不完全是XML去掉后缀程序会跳过并打印缺图提示。跑一遍就知道有没有图片和标注对不上的情况这其实是很好的数据完整性检查。跑完可视化后重点看三类问题一是框是否把人物完整包含二是该类别行为是否被明显误标三是是否存在大框套小框、坐标越过图像边缘。所有标成difficult1的目标虽然不参与模型评估但建议在转换时保留因为它们代表了真实难例训练时对提升鲁棒性有帮助。3. 把VOC XML转成YOLO格式数据清洗与转换脚本3.1 为什么必须转换YOLO训练不认XML虽然不少检测框架支持VOC XML但大家用得最多的YOLOv8、YOLOv5在训练时用的是TXT标注。每张图片对应一个同名txt文件里面每行格式是类别编号 中心点x 中心点y 宽度 高度五个值都用归一化到0-1之间的浮点数。如果你把VOC数据直接丢给YOLO它会因为找不到标签而报错或者训练出随机结果。所以拿到数据集的第一件事不是急着开训而是做一次格式转换和数据清洗。做转换前要定三件事第一类别到编号的映射比如sleeping0playing_phone1这个顺序会直接影响模型输出层第二坐标归一化公式先从左下/左上角式转换成中心式再除以图像宽高第三如何处理difficult1和越界框我一般建议保留difficult目标但把越界坐标clip回图像边界内。别小看这三件事后面好几个训练翻车现场都由它们引起。3.2 转换脚本一张图对应一个txt下面这段脚本是完整的VOC转TXT逻辑包含坐标裁剪和空标注统计。第一段是单个XML的转换函数第二段批量遍历数据集目录。import os import xml.etree.ElementTree as ET CLASSES [sleeping, playing_phone] # 类别顺序决定编号 def voc_to_yolo(xml_path, out_txt, clampTrue): root ET.parse(xml_path).getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue # 未知类别跳过 cls_id CLASSES.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) if clamp: xmin max(0, min(xmin, w)) ymin max(0, min(ymin, h)) xmax max(0, min(xmax, w)) ymax max(0, min(ymax, h)) if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if lines: with open(out_txt, w) as f: f.write(\n.join(lines)) return len(lines) else: open(out_txt, w).close() return 0再跑个循环处理整个数据集xml_dir Annotations txt_dir labels os.makedirs(txt_dir, exist_okTrue) empty_count 0 total_boxes 0 for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue base xml_name[:-4] n voc_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(txt_dir, base .txt)) total_boxes n if n 0: empty_count 1 print(f转换完成: {len(os.listdir(xml_dir))} 个XML, 有效框 {total_boxes}, 空标注 {empty_count})逻辑说明第一个函数读XML并计算中心点坐标、宽高比例保留6位小数足够YOLO使用。clamp参数用来裁掉越界坐标如果发现xmaxxmin则丢弃该框防止无效尺寸干扰训练。第二个函数遍历目录统计空标注数量转换完成后你可以直观看到两类目标的框总数。这里有两个参数建议重点检查。第一是CLASSES顺序必须和后续训练时data.yaml里的names顺序完全一致否则训练出的模型类别标签是错乱的。第二是空标注的处理方式脚本会生成一个空txtYOLO训练时一张图片没有标签相当于只有背景理论上可以参加训练但ultralytics在加载数据时偶尔会跳过这类样本导致数据量对不上。我一般把空标注对应的图片单独移到一个negatives目录留作测试集或误检集不参与训练。3.3 按场景拆分训练集与验证集防止同类图片扎堆拆分数据这件事比转换格式更容易被忽略。如果你只是随机把4653张图按8:2分很可能同一监控点位的连续帧同时出现在训练集和验证集里因为是同一个场景模型记住了背景就能在验证集上得到很高mAP一到新点位立刻掉下来。这是数据集中很常见的陷阱。常见做法是按采样来源或时间戳组织拆分。但这个数据集的原始标注里一般没有时间字段退而求其次是按文件名前缀或目录分组。如果文件名是按摄像头编号和帧号命名的比如cam01_00001.jpg那就按cam01、cam02来分保证同一摄像头只出现在一边。下面这段代码按图片文件名中的摄像头前缀做分层拆分并把训练图片和标签分别拷贝到images/train和labels/trainimport os import random import shutil random.seed(42) def split_by_prefix(img_dir, label_dir, out_root, val_ratio0.2): groups {} for img_name in os.listdir(img_dir): if not img_name.endswith(.jpg): continue prefix img_name.split(_)[0] # 比如 cam01 groups.setdefault(prefix, []).append(img_name) train_imgs, val_imgs [], [] for prefix, imgs in groups.items(): random.shuffle(imgs) cut int(len(imgs) * val_ratio) val_imgs imgs[:cut] train_imgs imgs[cut:] for split, imgs in [(train, train_imgs), (val, val_imgs)]: os.makedirs(f{out_root}/images/{split}, exist_okTrue) os.makedirs(f{out_root}/labels/{split}, exist_okTrue) for name in imgs: base name[:-4] shutil.copy(f{img_dir}/{name}, f{out_root}/images/{split}/{name}) label_src f{label_dir}/{base}.txt if os.path.exists(label_src): shutil.copy(label_src, f{out_root}/labels/{split}/{base}.txt) print(f训练集 {len(train_imgs)} 张, 验证集 {len(val_imgs)} 张) split_by_prefix(JPEGImages, labels, dataset)这个脚本的关键参数是拆分粒度。如果摄像头前缀不多val_ratio可以调到0.25如果前缀多且每个前缀有足够样本0.2是合理默认值。随机种子固定为42保证每次复现结果一致。除了按前缀你还可以按时间分组文件名带时间戳的话把前一半时间段作为训练后一半作为验证更贴近线上使用场景。转换和拆分之后建议检查一下训练集和验证集的类别分布。把两个目录下的txt统计一遍看看sleeping和playing_phone各自有多少框如果某个类别只占5%后续训练时就要考虑类权重增强或者在评估阶段单独看它的小类目指标。4. 用YOLOv8训练睡岗/玩手机检测模型配置与训练命令4.1 准备data.yaml类别、路径和模型配置YOLOv8是目前跑自己的数据集最快的方式一条命令就能从官方预训练权重开始微调。先建立data.yaml放在项目根目录train: dataset/images/train val: dataset/images/val nc: 2 names: 0: sleeping 1: playing_phone每个字段含义train和val指向上一节拆分出的图片目录nc是类别数这里两类names是类别名字典。YOLOv8会通过图片所在目录去查找同层次的labels目录所以images/train对应labels/train路径结构必须一致。如果你转换txt时把类别顺序写反了在这里也要同步改过来。训练前还要决定用哪个模型规格。4653张图在目标检测里算小规模常见的YOLOv8n或YOLOv8s即可。如果有GPU显存大于6G用s只有4G显存用n。不要一上来就上yolov8l小数据集上大模型容易过拟合训练速度也慢。下面命令默认用yolov8s。4.2 训练命令与关键参数在小数据集上把模型调稳模型准备就绪后用下面的命令启动训练yolo detect train \ datayaml/data.yaml \ modelyolov8s.pt \ epochs300 \ imgsz640 \ batch16 \ device0 \ patience50 \ lr00.005 \ augmentTrue参数说明如下epochs300看起来多但配合patience50意思是连续50次epoch验证集mAP没有增长就提前停止实际通常跑100-150轮就停了imgsz640是YOLO默认输入尺寸如果原始监控是1920x1080大图且目标较小可以提高到960代价是显存和训练时间翻一倍batch16在16G显存下没问题显存小就减到8device0指定GPUCPU训练的话把batch设为4并把imgsz调小但时间会非常久。lr00.005比默认0.01低一半这是我在小数据上常用的做法能减少过拟合和震荡augmentTrue开多尺度、翻转、色彩增强相当于把4653张图在每轮扩展出更多变体。命令跑起来后正常会输出每轮训练损失、box_loss、cls_loss和验证集指标。前三轮损失下降幅度可能不大因为模型还在暖启动之后才进入正常下降。如果第20轮后mAP50仍在0.1以下优先检查前面转换出来的txt和类别顺序十有八九是类别映射错位把sleeping标成了playing_phone。4.3 看训练日志mAP、PR曲线和混淆矩阵读法训练结束后runs/detect/train/目录下会有weights/best.pt和weights/last.ptbest是验证集上mAP最高的权重。results.png里有mAP50和mAP50-95曲线看训练结束时曲线是否还在上升如果训练末段mAP50高但mAP50-95低往往说明框的位置偏差较大可以增加epochs或用更高分辨率训练。confusion_matrix.png要重点看sleeping和playing_phone之间有没有大量互相误检。行为检测中这两个类别天然接近——低头玩手机和趴在桌上睡觉形态相似。如果混淆比例超过20%回到数据层面去补充更像的负样本或者将这两类合并成一类“违规行为”检测只输出一个框让告警逻辑再按连续帧维持时间来区分。还有一个隐藏指标F1曲线。它告诉你置信度阈值定多少合适。这个数据集原始标注会有一些模糊目标比如远处看不清的手机导致低置信度预测被判定为误检。用F1曲线找到平衡点后续部署时把置信度设在这个值附近而不是用默认的0.25。5. 数据集使用避坑指南5个高频翻车点与对应排查5.1 转换后大量txt为空文件YOLO训练时报错找不到标签现象训练开始时提示找不到标签或者明明有标注图却一个目标也加载不到。原因原始XML里可能有些图没有任何object是负样本也可能坐标转换时被脚本误删比如坐标异常导致xmax xmin。解决转换脚本要对每个XML返回目标数量把目标数为0的图移到negatives目录不放进训练集。同时检查XML的object节点是否规范用ElementTree.findall(object)能拿到所有object但节点名拼写错误或大小写不一致也会被忽略。建议先跑一遍可视化脚本把解析失败的图打印出来再决定是重标还是丢弃。5.2 模型loss不下降训练曲线震荡成锯齿现象loss前10轮下降后就开始震荡验证指标忽高忽低。原因最常见的是坐标异常。VOC XML中有几个bbox的ymax超过图像高度YOLO归一化后变成大于1的坐标模型训练时会产生不合理的损失项。解决转换时用clip把坐标限制在图像边界内同时记录clip前后的差值。我一般在转换脚本里加一个计数器发现坐标越界超过5%时就回到标注工具重新检查。另外把imgsz从默认640调整到与原始标注分辨率匹配比如原图是1920x1080imgsz960可能更合适但会带来显存压力需要再降batch。5.3 睡岗和玩手机互相误检混淆矩阵一片糊现象验证集mAP不错但在现场视频中把低头看书、操作电脑误检成玩手机或者把趴在桌上休息误判成睡岗。原因训练数据中两个类别的样本数量可能不平衡玩手机目标少另一个原因是两个行为的视觉形态确实相似模型没有足够的区分性特征。解决先统计类别框数如果比例大于3:1考虑给少样本类别做复制粘贴增强或者重复过采样。YOLOv8没有直接的类别权重参数实用做法是合并类别。在行为检测项目里把睡岗和玩手机合并成一个“岗位行为异常”类别现场告警逻辑根据连续帧维持时间来区分往往会比硬拆两个类别更可靠。我做过的几个项目中这种方案把误报率压低了一半。5.4 小目标漏检玩的是远处的手机框却画不出来现象摄像头视角大人物在画面中占比较小手机在手中只有几十像素模型完全漏检。原因训练时imgsz默认为640而原图是高清大图目标相对尺寸被缩小小目标特征在深层特征图中消失。解决把训练imgsz提到960或1280并开启多尺度训练。如果显存不够推理时用SAHI这类切片工具把大图切块每个块独立检测后再拼接结果。这个方案在小目标场景里几乎必用代价是推理耗时增加但对监控后台任务可以接受。5.5 夜间或暗光场景漏检严重白天指标虚高现象数据集中大多是白天清晰样本夜间图像一旦偏暗或噪点多检测率会掉到白天的一半以下。原因数据集采集时段单一模型学到了明亮的颜色分布对光照变化不鲁棒。解决训练时显式开启YOLO的随机光度增强在命令中加上hsv_h0.015 hsv_s0.7 hsv_v0.4。这组增强幅度能模拟不同曝光度。更彻底的办法是采集夜间真实帧用训练好的白天模型做伪标注人工修正后再加入训练集这是典型的半自动标注回流。我做过一次把夜间样本加到10%后夜间mAP提升了15个点。6. 从数据集到落地用测试视频验证并导出模型6.1 用测试视频验收模型别只看静态图指标训练时的mAP是静态图片上的指标到了实际监控视频中有连续帧、有抖动、有移动你需要用一段现场视频做冒烟测试。下面命令读取视频用训练好的权重预测并打印每秒平均帧数yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetest.mp4 \ imgsz640 \ conf0.35 \ device0这个命令会输出逐帧预测到runs/detect/predict下同时打印推理耗时。默认conf0.25我建议先用0.35试太低会有一堆误检框太高则漏检。预测结果如果出现大量抖动比如同一个目标框忽大忽小就要回到训练阶段做多尺度训练或者在后处理上做时间维度的过滤。对监控场景常见的做法是连续5帧命中同一目标才触发告警能有效过滤掉闪烁和抖动。6.2 导出ONNX并部署到边缘设备验收通过后把best.pt导出为ONNX方便在NVIDIA设备上转成TensorRT或在CPU主机上用onnxruntime加载yolo export modelruns/detect/train/weights/best.pt formatonnx opset12导出后可以用onnxruntime加载模型推理部署层面建议把输入尺寸固定为640x640避免动态尺寸带来的额外延时。如果现场需要多路视频并发优先考虑TensorRT的批处理优化把多路帧打包成一个batch送入GPU吞吐量可以成倍提升。最后说一个我自己摔过的跟头一开始我直接用这个数据集训练完就上现场结果白天一切正常下午两三点阳光照进室内模型突然把墙上的插座误检成玩手机。原因是置信度阈值设成0.2太低了。后来把阈值提到0.45并在告警逻辑中增加连续5帧命中才告警的消抖动条件误报才算压住。从那以后我每做完一版模型都会先在真实视频上跑半小时统计误报率和漏报率再决定要不要回到训练环节调数据。这个数据集适合用来验证行为检测流程但真正落地时还需要补充自己现场的帧希望帮到你。本文还有配套的精品资源点击获取