简介:这份数据集面向安防监控、行为识别方向的算法工程师与高校研究者,聚焦办公、值守等场景下的睡岗与玩手机两类异常行为检测,可用于训练与验证目标检测模型,帮助解决违规行为自动识别、智能巡检等实际问题。资源包共收录2000个文件,全部为VOC格式的XML标注文件,与4653张原始图像一一对应,压缩包整体约140.37MB,标注涵盖目标类别与边界框信息,可直接接入YOLO、Faster R-CNN等主流检测框架进行格式转换与训练。目前已有773人学习下载,具备一定的社区验证基础。数据采集覆盖多时段、多角度场景,图像命名包含时间戳信息,便于按时间维度划分训练集与测试集,也方便排查漏标与错标样本。对于需要快速搭建睡岗、玩手机检测基线或扩充现有数据集的读者,这份标注齐全、格式规范的资源能有效节省数据采集与标注成本,缩短从数据准备到模型迭代的周期。
1. 睡岗玩手机检测数据集:4653 张 VOC 标注图能直接喂给哪些模型
工厂夜班监控里最头疼的画面不是设备报警,而是工位上有人趴着睡、有人低头刷手机。这类行为检测靠传统运动侦测根本分不清"低头看仪表"和"低头玩手机",必须上目标检测模型。手头这份睡岗、玩手机数据集,4653 张原始图,全部配 VOC XML 格式标注,覆盖睡岗和玩手机两类行为,图片来自真实监控视角,文件名带时间戳和分辨率后缀(如72-94、72-76),说明是从连续视频流抽帧来的。它解决的是"从零标注成本太高"这个卡点——4653 张图如果自己标,按熟练工一天 800 张算也要近一周。适合做安防行为识别、工地/车间合规检测、边缘盒子部署的从业者,也适合想跑通 YOLO 全流程但缺真实场景数据的新手。VOC XML 是通用中间格式,转 YOLO、COCO、CSV 都有成熟脚本,不用被格式绑死。
2. VOC XML 标注结构拆解:先看懂再动手转
2.1 一份 XML 里到底存了什么
VOC 格式的标注文件本质是一个结构化描述:图片尺寸、通道数、每个目标的类别名和边界框坐标。拿项目正文里那份2021-08-25-07-59-06_72-94_4_png_jpg.rf.a14ae348d4d5d40bdb03368d0adad45b.xml来说,文件名里的72-94大概率对应图片宽高比或分辨率标识,rf.后面那串是抽帧/增强时的哈希,保证文件名唯一。打开后典型结构如下:
<annotation> <folder>images</folder> <filename>2021-08-25-07-59-06_72-94_4_png_jpg.rf.a14ae348d4d5d40bdb03368d0adad45b.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>sleep</name> <!-- 类别名,睡岗 --> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>412</xmin> <ymin>236</ymin> <xmax>688</xmax> <ymax>540</ymax> </bndbox> </object> <object> <name>phone</name> <!-- 类别名,玩手机 --> <bndbox> <xmin>900</xmin> <ymin>300</ymin> <xmax>1010</xmax> <ymax>470</ymax> </bndbox> </object> </annotation>size里的宽高是原图尺寸,bndbox是左上角和右下角绝对像素坐标。difficult标记难样本,训练时可以选择忽略。truncated表示目标是否被截断。这些字段在转 YOLO 时只有name和bndbox是必须的,其余可丢弃。理解这一点,后面写转换脚本就不会被多余字段干扰。
2.2 为什么选 VOC 而不是直接给 YOLO txt
VOC XML 可读性强,用文本编辑器就能改,标注工具 LabelImg 默认就存这个格式。YOLO 的 txt 是归一化后的class x_center y_center w h,人眼几乎没法校对。数据集给 XML 等于把"可编辑的源文件"交到你手上,转成什么格式取决于你的训练框架。常见做法是保留 XML 作为原始标注,训练前用脚本生成一份 YOLO 格式副本,原始文件不动。这样换模型、换框架时不用重新标注。另一个原因是 VOC 被 PASCAL 系列工具链支持得最久,很多老项目和标注平台导入导出都认它,兼容性比直接给 txt 稳。
2.3 目录组织与文件配对检查
拿到 4653 张图加对应 XML,第一步不是急着转格式,而是确认图片和标注一一对应。常见目录结构是JPEGImages/放图,Annotations/放 XML,文件名主干相同、扩展名不同。先跑一遍配对检查:
import os img_dir = "JPEGImages" xml_dir = "Annotations" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".png", ".jpeg"))} xmls = {os.path.splitext(f)[0] for f in os.listdir(xml_dir) if f.lower().endswith(".xml")} only_img = imgs - xmls # 有图没标注 only_xml = xmls - imgs # 有标注没图 print(f"图片总数: {len(imgs)}, 标注总数: {len(xmls)}") print(f"缺标注的图: {len(only_img)}, 缺图的标注: {len(only_xml)}") if only_img: print("示例:", list(only_img)[:5])这段脚本用集合差集找出不配对的样本。only_img非空说明有图没标,训练时要么删掉要么补标;only_xml非空说明标注文件是孤儿,直接删。4653 张的规模,配对检查几秒就跑完,但能避免训练时报"找不到标注"这种低级错误。我一般还会顺手统计每个类别的框数量,看两类是否严重失衡——如果睡岗 8000 个框、玩手机只有 900 个,后面训练就得考虑类别权重。
3. 转 YOLO 格式:脚本、参数与坐标归一化
3.1 坐标转换的数学逻辑
YOLO 要的是归一化中心点加宽高,公式不复杂但容易写错。设图宽W、高H,框xmin, ymin, xmax, ymax:
- 中心 x:
(xmin + xmax) / 2 / W - 中心 y:
(ymin + ymax) / 2 / H - 宽:
(xmax - xmin) / W - 高:
(ymax - ymin) / H
四个值都落在 0 到 1 之间。类别名要映射成从 0 开始的整数索引,比如sleep: 0、phone: 1。映射表必须固定,训练和推理用同一套,否则模型学出来的类别是乱的。下面脚本把 VOC 转成 YOLO txt,同时生成classes.txt和data.yaml。
import os import xml.etree.ElementTree as ET classes = ["sleep", "phone"] # 类别顺序固定,不可随意改 cls_map = {name: i for i, name in enumerate(classes)} xml_dir = "Annotations" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) def convert(xml_path, out_path): tree = ET.parse(xml_path) root = tree.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.strip() if name not in cls_map: continue # 跳过未定义类别,避免索引越界 bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 裁剪到图像边界,防止标注越界导致归一化后超出 0~1 xmin, xmax = max(0, xmin), min(W, xmax) ymin, ymax = max(0, ymin), min(H, ymax) cx = (xmin + xmax) / 2 / W cy = (ymin + ymax) / 2 / H bw = (xmax - xmin) / W bh = (ymax - ymin) / H lines.append(f"{cls_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(out_path, "w") as f: f.write("\n".join(lines)) for f in os.listdir(xml_dir): if f.endswith(".xml"): convert(os.path.join(xml_dir, f), os.path.join(out_dir, f.replace(".xml", ".txt"))) print("转换完成")关键参数说明:classes列表顺序就是最终类别索引,改顺序等于改标签含义;max(0, xmin)和min(W, xmax)是边界裁剪,真实标注里偶尔会出现xmax比图宽大几个像素的情况,不裁会导致归一化值大于 1,部分框架直接报错;:.6f保留六位小数,YOLO 官方推荐精度,太少会丢框。转换后随便抽一个 txt 和原 XML 对照,中心点应该在 0.5 附近(目标居中时),宽高小于 1。
3.2 生成 data.yaml 与训练集划分
YOLOv8 训练要一个 yaml 描述路径和类别。划分训练验证集按 8:2 或 9:1,4653 张建议留 400 到 500 张做验证。脚本按文件名随机划分,固定随机种子保证可复现:
import os, random, shutil random.seed(42) # 固定种子,划分可复现 img_dir, lbl_dir = "JPEGImages", "labels" names = [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".png"))] random.shuffle(names) split = int(len(names) * 0.9) sets = {"train": names[:split], "val": names[split:]} for s, items in sets.items(): os.makedirs(f"dataset/images/{s}", exist_ok=True) os.makedirs(f"dataset/labels/{s}", exist_ok=True) for n in items: for ext in (".jpg", ".png"): src = os.path.join(img_dir, n + ext) if os.path.exists(src): shutil.copy(src, f"dataset/images/{s}/{n}{ext}") break shutil.copy(os.path.join(lbl_dir, n + ".txt"), f"dataset/labels/{s}/{n}.txt") yaml_text = f"""path: {os.path.abspath('dataset')} train: images/train val: images/val nc: {len(classes)} names: {classes} """ with open("data.yaml", "w") as f: f.write(yaml_text) print("划分完成:", {k: len(v) for k, v in sets.items()})seed(42)保证每次划分结果一致,方便对比实验。nc是类别数,这里等于 2。names顺序必须和转换脚本里的classes完全一致。跑完检查dataset/images/train和labels/train文件数是否相等,不等说明有图缺标注,回到 2.3 的配对检查排查。
3.3 起训命令与关键超参
数据就绪后,YOLOv8 一条命令起训:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/sleep_phonemodel=yolov8n.pt是 nano 版,边缘盒子部署首选,精度不够再换yolov8s。imgsz=640是输入尺寸,监控图目标偏小的话可以提到 960,但显存和速度要权衡。batch=16按显存调,8G 显存跑 640 尺寸 16 一般够。patience=20表示 20 轮验证指标不升就早停,省时间。lr0=0.01是初始学习率,YOLOv8 默认值,数据量 4000 多张不用大改。训练时盯mAP50和mAP50-95两个指标,睡岗和玩手机两类如果某一类 AP 明显低,多半是样本少,回去补该类数据或加类别权重。
4. 避坑与排查:标注和转换里最容易翻车的五处
4.1 类别名大小写不一致导致漏标
现象:转换后某些图 txt 是空的,但 XML 里明明有框。原因:标注时同一类写成Sleep、sleep、SLEEP混用,脚本里cls_map只认小写,其余全被continue跳过。解决:转换前先统计所有 XML 里出现过的name值,统一转小写再映射,或在脚本里加name = name.strip().lower()。我一般会先跑一遍grep -h "<name>" *.xml | sort | uniq -c看类别名到底有几种写法。
4.2 坐标越界让归一化值大于 1
现象:训练启动报normalized coordinates out of range或 loss 直接 NaN。原因:标注框xmax超过图宽、ymax超过图高,归一化后大于 1。解决:转换脚本里做边界裁剪(3.1 已加),或者训练前用校验脚本扫一遍所有 txt,发现任一分量大于 1 就定位到对应 XML 修标注。裁剪是兜底,根治还得改标注源文件。
4.3 图片和标注文件名主干不匹配
现象:训练报No labels found,但 labels 目录里文件不少。原因:图片是.png,标注是.jpg.xml或主干多了后缀,配对不上。解决:跑 2.3 的配对检查,把不匹配的列出来。常见于从不同批次合并的数据集,文件名规则不统一。统一改成"主干相同、扩展名不同"再继续。
4.4 空标注文件被当成负样本
现象:模型把没目标的图也学出框,误检偏高。原因:有些图确实没有睡岗和玩手机,XML 里没有object,转出来是空 txt。YOLO 会把空 txt 当负样本,这是对的;但如果空 txt 是因为类别名没匹配上(4.1),那就成了假负样本,模型学到"有目标也不该报"。解决:区分真空标注和转换失败。真空标注保留,转换失败的要修类别名重转。统计空 txt 数量,和 XML 里无 object 的数量对比,两者应一致。
4.5 训练验证集划分泄漏
现象:验证集 mAP 高得离谱,实际部署一塌糊涂。原因:同一段视频抽的帧被分到训练和验证两边,画面几乎一样,等于变相泄漏。这批数据文件名带时间戳,同一时刻附近的帧高度相似。解决:按时间戳前缀分组划分,同一段连续抽帧整体进训练或整体进验证,不要随机打散。简单做法是取文件名前 16 位(到秒)做分组键,按组划分。
5. 进阶:小目标增强与部署前验证
玩手机这个类别有个天然难点——手机在监控画面里往往只占几十个像素,属于小目标。640 输入下,一个 60×40 像素的手机框缩到 640 后只剩 20 像素左右,特征很弱。我一般会做两件事:一是把imgsz提到 960 或 1280,代价是推理变慢,边缘盒子要实测帧率;二是开 Mosaic 和 Copy-Paste 增强,YOLOv8 默认开 Mosaic,Copy-Paste 需要自己加,把手机框抠出来贴到其他位置,增加小目标密度。另一个技巧是单独统计手机框的面积分布:
import os areas = [] for f in os.listdir("labels/train"): for line in open(os.path.join("labels/train", f)): p = line.split() if len(p) == 5 and p[0] == "1": # 类别 1 = phone areas.append(float(p[3]) * float(p[4])) # 归一化宽×高 areas.sort() n = len(areas) print(f"手机框数量: {n}") print(f"面积中位数: {areas[n//2]:.6f}") print(f"最小 10%: {areas[n//10]:.6f}")归一化面积乘上imgsz²就是实际像素面积。如果中位数对应的像素面积小于 32×32,就属于 COCO 定义的小目标,必须上高分辨率或专门的小目标检测头。部署前验证别只看 mAP,拿一段没参与训练的监控视频跑推理,人工数误报和漏报。我习惯用混淆矩阵看两类互相误判的比例——睡岗被误判成玩手机通常是因为人趴着时手部位置和手机框重叠,这种要靠补充难样本解决,不是调参能救的。
从那以后我每次拿到新数据集,都强制先跑配对检查、类别名统计、坐标越界扫描这三步,再动转换脚本。这三步加起来不到十分钟,能省掉后面几小时的训练排查。希望帮到你。
本文还有配套的精品资源,点击获取