☰
睡岗玩手机检测数据集:VOC转YOLO与YOLOv8训练全流程
2026/9/28 16:48:50 网站建设 项目流程

简介:面向安防监控、行为识别方向的算法工程师与高校研究者,这份睡岗与玩手机检测数据集可直接用于目标检测模型的训练与验证。资源包含4653张原始图像,并配套VOC XML格式标注文件,覆盖睡岗、玩手机两类典型违规行为,适合课堂考勤、工位监管、值班室智能巡检等场景的算法开发与效果评估。压缩包内共2000个文件,以XML标注文件为主,与原始图片一一对应,整体约140.37MB,体积适中便于快速下载与本地解压使用。目前已有773人学习下载,具备一定的社区验证基础。借助该数据集,读者可省去繁琐的标注环节,直接投入模型训练、数据增强与迁移学习实验,也可用于对比不同检测网络在真实监控画面下的精度表现,为后续部署与优化提供可靠的数据支撑。

1. 睡岗玩手机数据集:4653 张原始图怎么变成能训的目标检测集

工地值班室、工厂中控室、矿区调度台,这类场景的监控画面里,最常出现的两类违规行为就是睡岗和玩手机。想用视觉模型自动识别,第一步不是选模型,而是先有一批标注好的图。这个标题讲的是一份 4653 张原始图、带 VOC XML 标注的数据集,目标场景就是睡岗与玩手机检测。它解决的是「从零标注成本太高」的问题,适合两类人:一类是想快速验证睡岗/玩手机检测可行性的算法工程师,另一类是手里有监控视频、想自己攒数据集但不知道标注格式怎么定的开发者。VOC XML 是这套数据最常见的载体,理解它、清洗它、转成 YOLO 能吃的格式,是落地绕不开的三步。

2. VOC XML 标注到底存了什么:字段拆解与选型理由

2.1 一张 XML 里哪些字段真正影响训练

VOC 格式的标注文件本质是一个 XML,每张图对应一个同名.xml。很多人拿到数据集直接转格式,转完发现框全错位,问题往往出在没看懂字段含义。下面是一份典型的睡岗标注文件结构,我按实际会读到的字段拆开讲。

<annotation> <folder>images</folder> <filename>sleep_0001.jpg</filename> <!-- 图片文件名,必须和 jpg 同名 --> <size> <width>1920</width> <!-- 原图宽,坐标换算的基准 --> <height>1080</height> <!-- 原图高 --> <depth>3</depth> </size> <object> <name>sleep</name> <!-- 类别名,大小写敏感 --> <pose>Unspecified</pose> <truncated>0</truncated> <!-- 是否被截断,0/1 --> <difficult>0</difficult> <!-- 是否难样本,训练时可过滤 --> <bndbox> <xmin>412</xmin> <!-- 左上角 x,绝对像素 --> <ymin>236</ymin> <xmax>890</xmax> <!-- 右下角 x --> <ymax>701</ymax> </bndbox> </object> </annotation>

逻辑说明:size里的宽高是坐标换算的唯一基准,如果标注时用的是缩放后的图,而size写的是原图尺寸,框就会整体偏移。bndbox四个值是绝对像素坐标,不是归一化值,这点和 YOLO 的.txt完全不同。name是类别字符串,睡岗场景常见写法有sleep、sleeping、shuigang混用,转换前必须先统一。

参数说明:truncated和difficult这两个字段在睡岗/玩手机场景里很关键。监控画面里人经常被桌子、设备挡住一半,truncated=1的样本如果直接丢掉,模型对遮挡场景的鲁棒性会明显下降。我的做法是保留truncated=1但把difficult=1的样本在训练时降权,而不是一刀切删除。

2.2 为什么这类场景优先选 VOC 而不是直接上 YOLO txt

常见做法是标注阶段用 labelImg,它默认导出 VOC XML。原因很实际:XML 可读性强,出错了肉眼能查,改一个框不用重跑转换脚本;而 YOLO txt 是纯数字,五列数据错一位很难定位。4653 张这个量级,标注周期通常要一到两周,中途换人、换工具的概率很高,XML 的容错性更好。

另一个理由是复用。VOC 是通用格式,转 COCO、转 YOLO、转 CSV 都有成熟脚本,而 YOLO txt 想转回带类别名的可读格式反而麻烦。所以我的习惯是:原始标注永远存 VOC,训练前再转,转换脚本固定下来,不手工改 txt。

提示:如果数据集里同时有sleep和phone两类,务必确认每张图的 XML 里 object 数量是否和实际一致。漏标比错标危害更大,模型会把漏标的人当成背景学进去。

3. 从 4653 张原始图到可训练集:清洗、划分与转换

3.1 先做一致性体检,再谈转换

拿到数据集别急着转格式,先跑一遍体检脚本,把命名不匹配、坐标越界、类别混乱三类问题揪出来。这三类问题在真实数据集里出现频率极高,尤其是多人协作标注的。

import os import xml.etree.ElementTree as ET IMG_DIR = "images" XML_DIR = "annotations" VALID_CLASSES = {"sleep", "phone"} # 统一后的类别白名单 def check_dataset(): imgs = {os.path.splitext(f)[0] for f in os.listdir(IMG_DIR)} xmls = {os.path.splitext(f)[0] for f in os.listdir(XML_DIR)} # 1. 图片和标注是否一一对应 print("缺标注的图:", imgs - xmls) print("缺图的标注:", xmls - imgs) for name in imgs & xmls: tree = ET.parse(os.path.join(XML_DIR, name + ".xml")) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) for obj in root.findall("object"): cls = obj.find("name").text.strip() if cls not in VALID_CLASSES: print(f"{name} 非法类别: {cls}") xmin = int(obj.find("bndbox/xmin").text) xmax = int(obj.find("bndbox/xmax").text) ymin = int(obj.find("bndbox/ymin").text) ymax = int(obj.find("bndbox/ymax").text) # 2. 坐标越界检查 if xmin < 0 or ymin < 0 or xmax > w or ymax > h or xmax <= xmin or ymax <= ymin: print(f"{name} 坐标异常: {xmin},{ymin},{xmax},{ymax}") check_dataset()

逻辑说明:脚本先比对图片和 XML 的文件名集合,差集就是缺失项。然后逐文件解析,检查类别是否在白名单内、坐标是否越界或宽高为负。坐标越界通常来自标注时手滑或图片被裁剪过。

参数说明:VALID_CLASSES要根据你的实际类别改。如果数据集里出现sleep、Sleep、sleeping三种写法,先在这里列全,再统一映射成一个,不要指望模型自己区分大小写。

3.2 训练/验证集划分:别用随机划分坑自己

睡岗和玩手机的数据集有个特点:同一段监控视频截出来的帧高度相似。如果按帧随机划分,训练集和验证集里会出现几乎一样的图,验证指标虚高,上线就翻车。正确做法是按视频源或时间段划分。

import random from collections import defaultdict # 假设文件名前缀能标识视频源,如 cam01_0001.jpg groups = defaultdict(list) for f in os.listdir(IMG_DIR): src = f.split("_")[0] # 按摄像头/视频源分组 groups[src].append(f) sources = list(groups.keys()) random.seed(42) random.shuffle(sources) val_ratio = 0.2 n_val = max(1, int(len(sources) * val_ratio)) val_sources = set(sources[:n_val]) with open("val.txt", "w") as fv, open("train.txt", "w") as ft: for src, files in groups.items(): target = fv if src in val_sources else ft for f in files: target.write(os.path.join(IMG_DIR, f) + "\n")

逻辑说明:先按视频源分组,再对源做随机划分,保证同一来源的帧只出现在训练或验证一侧。这样验证指标才接近真实泛化能力。

参数说明:val_ratio设 0.2 是常规值,但如果视频源总数少于 10 个,建议提到 0.3,否则验证集覆盖的场景太少。random.seed固定住,保证每次划分一致,方便复现。

3.3 VOC 转 YOLO:归一化坐标的四个边界坑

转换的核心是把绝对像素坐标变成归一化的中心点加宽高。公式不复杂,但边界处理容易出错。

import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): cls = obj.find("name").text.strip() if cls not in class_map: continue xmin = float(obj.find("bndbox/xmin").text) ymin = float(obj.find("bndbox/ymin").text) xmax = float(obj.find("bndbox/xmax").text) ymax = float(obj.find("bndbox/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"{class_map[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return lines class_map = {"sleep": 0, "phone": 1}

逻辑说明:先裁剪坐标到图像范围内,再算中心点和宽高。class_map把类别名映射成从 0 开始的整数,顺序一旦定下就不能改,否则训练和推理的类别对不上。

参数说明:保留 6 位小数足够,YOLO 读取时精度损失可忽略。如果某张图转换后某行宽或高为 0,说明原始框退化成一个点,这种样本要单独挑出来删掉,否则训练时会报 NaN。

注意:转换后务必抽查几张,用可视化脚本把框画回原图看一眼。我见过太多「转换成功但框全偏」的情况,肉眼验证五分钟,能省掉后面几小时的排查。

4. 用这份数据集训 YOLOv8:配置、参数与首轮验证

4.1 目录结构和 data.yaml 怎么写

YOLOv8 对目录结构有约定,转换完的 txt 要和图片分开放。推荐结构如下:

dataset/ images/ train/ val/ labels/ train/ val/ data.yaml

data.yaml是训练的入口配置,字段不多但每个都关键:

path: /data/dataset train: images/train val: images/val nc: 2 names: ["sleep", "phone"]

逻辑说明:path是数据集根目录,train和val是相对路径。nc是类别数,必须和names长度一致。names的顺序必须和转换时的class_map完全对应,sleep是 0,phone是 1,写反了模型学出来的类别就是错的。

参数说明:如果数据集放在移动硬盘或网络盘,path用绝对路径更稳。names里不要用中文,YOLOv8 读取时可能编码出错,用英文或拼音。

4.2 首轮训练命令与必调参数

首轮训练不要一上来就调大 epoch,先用小轮次验证流程通不通。

yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=50 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=15 \ project=runs/sleep_phone \ name=baseline

逻辑说明:model=yolov8n.pt用 nano 版先跑通,速度快,能快速暴露数据问题。imgsz=640是默认输入尺寸,睡岗和玩手机的目标通常占画面比例不小,640 够用。patience=15表示验证指标 15 轮不提升就早停,避免无效训练。

参数说明:batch=16要按显存调,8G 显存跑 640 尺寸大概能到 16,爆显存就降到 8。lr0=0.01是初始学习率,如果 loss 一开始就震荡,降到 0.001。epochs=50只是首轮,确认流程没问题后再加到 200 以上。

4.3 看什么指标判断数据集质量

训练跑起来后,重点看三样:mAP50、每一类的precision/recall、以及混淆矩阵。如果sleep的 recall 明显低于phone,通常是睡岗样本里遮挡多、或者标注时把趴桌子的人漏标了。混淆矩阵里如果sleep和phone互相误判,说明两类在画面里姿态接近,需要补充区分度高的样本,而不是继续加轮次。

提示:验证集指标好但实际监控画面漏检,八成是划分时没按视频源隔离。回头检查第 3.2 节的划分逻辑。

5. 避坑与排查:睡岗玩手机数据集最常见的五个翻车点

5.1 现象:训练 loss 正常下降,但验证 mAP 一直是 0

原因:data.yaml里的names顺序和转换脚本的class_map不一致,或者nc写成了 1 但实际有两类。模型学到的类别索引和验证时对不上,指标自然为 0。

解决:打印一张转换后的 txt,确认第一列类别索引;再对照data.yaml的names顺序。两者必须严格一致,改完重新训练。

5.2 现象:框的位置整体偏移,且偏移量随图片尺寸变化

原因:XML 里的size宽高和实际图片尺寸不符。常见于标注时用了压缩图,但size填的是原图尺寸,或者图片被批量裁剪过但 XML 没更新。

解决:跑第 3.1 节的体检脚本,用 PIL 读实际图片尺寸和 XML 里的size比对,不一致的直接修正 XML 或重新标注。

5.3 现象:模型把坐着低头的人也判成睡岗

原因:睡岗和「低头看手机」「趴着休息」在单帧画面里姿态接近,标注时边界没划清。数据集里如果sleep包含了大量低头样本,模型学到的就是「低头=睡岗」。

解决:明确标注规范,睡岗限定为闭眼且头部低垂或趴伏,玩手机限定为手持设备且视线朝下。对边界样本单独建一个uncertain类,训练时排除,而不是硬塞进两类之一。

5.4 现象:小目标玩手机检测 recall 很低

原因:监控画面里手机目标本身很小,640 输入尺寸下可能只剩十几个像素。加上玩手机的人通常坐着,手机被手和身体遮挡。

解决:把imgsz提到 960 或 1280 再训一轮对比;或者在数据增强里开启mosaic和scale,增加小目标出现频率。如果还是不行,考虑对画面做区域裁剪,只对工位区域做检测。

5.5 现象:同一段视频的相邻帧分别进了训练和验证集

原因:划分时按文件名随机,没按视频源分组。相邻帧几乎一样,验证集等于变相泄漏。

解决:回到第 3.2 节,按视频源或时间戳分组划分。如果文件名里没有来源信息,用感知哈希对图片去重后再划分。

6. 让这份数据集更值钱:增量标注与难例回流

4653 张能跑通 baseline,但真正上线后你会发现漏检集中在几类场景:夜间红外画面、人员密集遮挡、手机屏幕反光。这些靠原始数据集覆盖不到,得靠难例回流补。我的习惯是部署一个轻量推理服务,把置信度在 0.3 到 0.5 之间的检测结果自动存图,每周人工过一遍,确认是漏检还是误报,然后按 VOC 格式补标,再合并进训练集重训。这样数据集是活的,模型迭代才有源头。

具体操作上,回流样本的命名要带日期和来源,比如cam03_20240612_001.jpg,方便追溯。补标时沿用同一套class_map,不要中途改类别顺序。重训时新旧数据按 1:1 混合,避免模型只学新场景而遗忘旧场景。验证方法很简单:固定一个包含新旧场景的测试集,每次重训后对比各类别的 recall,只要没有明显下降就说明增量是正向的。

我踩过最深的坑是早期图省事,回流样本直接覆盖进原训练集,结果类别分布被新场景带偏,老场景的睡岗漏检率反而上升。后来改成独立存放、按比例混合,才稳定下来。数据集不是标完就完事,它是个需要持续喂养的东西。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询