ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

VOC标注数据集训练YOLOv8睡岗检测模型全流程解析

VOC标注数据集训练YOLOv8睡岗检测模型全流程解析 简介面向智能安防、机房值班室及远程监控场景的睡岗检测数据集采用VOC格式标注覆盖趴桌睡、埋头睡、靠椅睡、平躺睡等多种典型睡姿适用于目标检测、姿态识别和异常行为分析等任务。包内共2000个XML标注文件对应6549张图片压缩后约422.3MB标注文件按原始图片名一一命名包含类别与边界框信息可直接接入YOLO、Faster R-CNN等常见检测框架进行训练和评估。目前已有936人学习下载主要面向计算机视觉算法工程师、安防系统开发者及高校相关专业学生。借助这批规范数据可快速搭建睡岗检测模型区分不同睡眠姿态并通过数据增强、难例挖掘等手段提升模型在夜间、强光或遮挡条件下的鲁棒性同时清晰的VOC标注结构也便于二次改造为COCO或YOLO格式为后续告警联动、考勤统计等上层应用提供基础数据集支持。1. 睡岗检测数据集6549 张 VOC 标注图能帮你把值班室监控从「人盯屏」变成「算法盯屏」值班室监控有个老问题再敬业的保安也没法全天盯住七八块屏幕。睡岗检测要做的事就是把「画面里有没有人在睡觉」交给算法判断——有人趴在桌上、埋头打盹、靠着椅背睡或者直接躺平时自动报警。标题里这个数据集正好覆盖这四种睡姿6549 张 VOC 格式标注图属于yolov8训练自己的数据集场景里少见的对口数据比从 COCO 里挑人形再标注要省得多。它适合做智慧工厂、变电站、机房、保安岗值班监控的工程师。通用目标检测数据集里的样本是车、人、猫狗而这里每一张图的标注目标都是真正要报警的睡姿场景和业务完全一致拿来可以直接进训练链路。下面按一套完整落地流程讲先看懂 VOC 标注本身再把 XML 转成 YOLO 能吃的 txt然后是训练参数与指标验证最后是五条会让你翻车的坑和上线前的验证方法。目标只有一个让你拿到这个数据集就能复现出一条可用的睡岗检测模型。2. 拆解 VOC 标注与四种睡姿先搞清楚数据集里有什么再谈训练2.1 VOC 标注格式与目录结构XML、JPG 和标签映射VOCPASCAL VOC是最老牌的目标检测标注格式它的规则是「一张图配一个同名 XML」。图片统一放JPEGImages标注放Annotations这是几乎所有 VOC 数据集的标准目录组织方式标题里这个 6549 张的数据集也沿用这套结构。不管哪个版本XML 里真正要关心的核心字段就下面这些annotation filename0345.jpg/filename size width1920/width height1080/height depth3/depth /size object namedesk_sleep/name truncated0/truncated difficult0/difficult bndbox xmin520/xmin ymin310/ymin xmax890/xmax ymax640/ymax /bndbox /object /annotation逻辑说明size里的宽高是坐标归一化的分母训练时靠它把绝对像素坐标换算成 0~1 的相对值object可以出现多次一张图里有两个人睡就有两个objectbndbox的四个值分别是左上角 x、左上角 y、右下角 x、右下角 y。参数说明difficult这个字段经常被忽略它的含义是「这个框遮挡严重、标注者自己都没把握」训练时建议过滤验证时保留能更真实地反映模型在困难样本上的表现。另外XML 里偶尔还有pose、segmented这些字段睡岗检测是纯框任务用不上解析时直接跳过即可。还有一个容易看漏的点有些 VOC 数据集会自带ImageSets/Main下的 train.txt、val.txt如果你拿到的版本里有划分数据集时优先用官方划分别自己重抽。2.2 四种睡姿的类别定义与样本分布标题里面提到的四种睡姿放在监控画面里对应的视觉特征差别很大这也决定了模型能不能把它们分开睡姿画面里的视觉特征常见误判桌子上趴着睡 desk_sleep头脸完全趴在桌面上手臂前伸或交叉面部不可见和埋头睡混淆埋头睡觉 head_down坐姿头低垂到胸前面朝下肩背呈弓形被当成正常低头看文件座椅上靠着睡 lean_sleep头后仰或侧靠椅背面部基本可见眼睛闭合被当成正常靠坐休息平躺着睡 lying_sleep在沙发、行军床上平躺或侧躺全身可见被当成空床或蹲着我见过有人直接拿「人」检测模型来检测睡岗结果惨不忍睹——正常坐着的人全被框出来报警完全没法用。原因就是普通行人检测关注的是「人形」而不是「姿态」。而这四类睡姿里lean_sleep是最难的一类它的姿态和正常靠坐高度相似只靠目标框根本分不开需要模型学到头部后仰角度和躯干放松程度这些细粒度特征。样本分布上这类值班室数据集通常趴桌睡和埋头睡数量多躺睡因为现场床位机位少框数明显偏少。6549 张图并不等于 6549 个目标一张宽景画面里同时出现两个睡岗人员很常见所以统计必须以「目标数」为单位下面这个脚本就是干这个的。2.3 用脚本统计标签分布先看类别均衡再谈训练import os import xml.etree.ElementTree as ET from collections import Counter def analyze_voc(xml_dir): cls_count Counter() # 每个类别的目标总数 diff_count Counter() # 每个类别的 difficult 数量 aspect_ratios [] # 所有框的宽高比 for name in os.listdir(xml_dir): if not name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, name)) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in root.findall(object): label obj.find(name).text.strip() diff obj.find(difficult) if diff is not None and int(diff.text) 1: diff_count[label] 1 continue cls_count[label] 1 box obj.find(bndbox) xmin float(box.find(xmin).text) xmax float(box.find(xmax).text) ymin float(box.find(ymin).text) ymax float(box.find(ymax).text) aspect_ratios.append(((xmax - xmin) / w) / ((ymax - ymin) / h)) return cls_count, diff_count, aspect_ratios counts, diffs, ars analyze_voc(Annotations) for label, n in counts.most_common(): print(f{label}: {n} 个目标 / difficult {diffs.get(label, 0)} 个) print(框宽高比中位数: %.2f % sorted(ars)[len(ars) // 2])逻辑说明脚本先把每张图的size读出来作为归一化基准再逐个object统计标签名和 bndboxdifficult1的框单独计数、不进训练统计。参数说明xml_dir要指向 Annotations 目录如果 XML 里的标签是中文label打印出来就是中文不影响统计但后面转格式时必须先做映射。这个脚本的输出直接决定三件事类别严重不均衡时给少数类加权或复制增强宽高比分布极端说明画面里躺睡占比高输入分辨率策略要调整difficult 占比高则说明标注噪声大训练时要考虑加 label smoothing。我一般拿到任何数据集第一件事就是跑它跑完才开始调参数。3. 把 VOC 转成 YOLO 格式转换脚本与四个边界坑3.1 坐标归一化与类别映射XML 转 TXT 的标准做法YOLOv5 和 YOLOv8 训练时要求标签是「每张图一个同名 txt」每一行对应一个目标格式固定为class_id x_center y_center width height后四个值都是相对图片宽高归一化的 0~1 小数。VOC 的 bndbox 是绝对像素坐标所以转换本质上只有两件事坐标归一化、类别字符串转数字 ID。类别映射先定死后面所有环节都用这张表数据集标签以英文为准对应睡姿YOLO class_iddesk_sleep桌子上趴着睡0head_down埋头睡觉1lean_sleep座椅上靠着睡2lying_sleep平躺着睡3实际拿到手的 XML 里标签名可能是中文或别的写法所以脚本里维护一个NAME_TO_ID字典不要硬编码顺序转换前先打开一个 XML 确认 name 字段的真实值。import os import xml.etree.ElementTree as ET NAME_TO_ID { desk_sleep: 0, head_down: 1, lean_sleep: 2, lying_sleep: 3, # 如果标签是中文在这里补映射 趴桌睡: 0, 埋头睡: 1 ... } def voc_to_yolo(xml_path, out_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) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in NAME_TO_ID: continue difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue 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) # 边界保护标注偶尔会超出图片范围先 clamp 再判断有效性 xmin max(0.0, min(xmin, img_w)) xmax max(0.0, min(xmax, img_w)) ymin max(0.0, min(ymin, img_h)) ymax max(0.0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{NAME_TO_ID[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) xml_dir Annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for fn in os.listdir(xml_dir): if fn.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, fn), os.path.join(out_dir, fn.replace(.xml, .txt)))逻辑说明脚本先读size作为归一化分母再遍历所有objectdifficult1的框直接跳过不写进训练标签所有坐标先 clamp 到图片范围内再判断宽高是否有效无效框丢弃。参数说明NAME_TO_ID的映射必须和后面 data.yaml 的names顺序完全一致顺序错一位训练出来的所有类别预测就整体错位这是最隐蔽的坑之一。txt 里保留 6 位小数足够不需要更高精度。3.2 训练/验证集划分与数据组织YOLOv8 默认的目录结构是datasets/场景名/images/train、images/val、labels/train、labels/val。转换完的标签要和图片按文件名一一对应然后做随机划分。import os import random import shutil random.seed(42) # 固定种子保证每次划分结果可复现 img_root JPEGImages label_root labels train_img datasets/sleep/images/train val_img datasets/sleep/images/val train_lab datasets/sleep/labels/train val_lab datasets/sleep/labels/val for d in [train_img, val_img, train_lab, val_lab]: os.makedirs(d, exist_okTrue) imgs [f for f in os.listdir(img_root) if f.lower().endswith((.jpg, .png, .jpeg))] random.shuffle(imgs) val_count int(len(imgs) * 0.15) val_files set(imgs[:val_count]) for img in imgs: label img.rsplit(., 1)[0] .txt if not os.path.exists(os.path.join(label_root, label)): print(无标签文件:, img) continue if img in val_files: shutil.copy(os.path.join(img_root, img), os.path.join(val_img, img)) shutil.copy(os.path.join(label_root, label), os.path.join(val_lab, label)) else: shutil.copy(os.path.join(img_root, img), os.path.join(train_img, img)) shutil.copy(os.path.join(label_root, label), os.path.join(train_lab, label))逻辑说明按图片划分而不是按目标划分避免同一张图里的多个目标同时出现在训练和验证集里造成数据泄漏。参数说明val_ratio取 0.156549 张图大约分 980 张做验证对检测任务足够random.seed(42)必须放在 shuffle 前面否则每次运行结果不同后续复现对比会很痛苦。提示如果数据集自带ImageSets/Main/train.txt和val.txt直接读取文件列表做划分比随机抽更接近发布方的原始设定。验证集原则上不动训练集后续增删样本都不影响对比基线。3.3 转格式时最容易翻车的四个边界坑先说最容易遇到的坐标越界。有些标注框是标注人员在图上手工拖出来的放大缩小时偶尔会拖出图片边缘导致xmax比图片宽度还大。转换时不 clamp归一化后框宽超过 1训练 loss 会异常甚至会出 NaN。解决方式见上面的脚本注释clamp 后再判断xmax xmin直接丢弃。第二个是空 txt。部分 XML 里没有任何object转换后生成 0 字节 txt。YOLOv8 训练时能容忍这种背景图但老版本 YOLOv5 偶尔会报 No labels found in... 的警告。稳妥做法是把空 txt 挪到独立目录训练时只放有标注的部分避免干扰。第三个坑是同名图片覆盖。如果原始数据不是单一 JPEGImages 目录而是按场景分了子目录不同子目录里可能有同名 JPG直接复制到统一目录后标签和图片会配错。解决方法是复制时用xml 文件名 序号重新命名保证全局唯一我当时就是没检查重名训练完发现验证集 mAP 诡异查了半天才发现是一张图片被另一张覆盖了。第四个是中文标签与编码问题。Windows 下读中文 label 的 XML 容易遇到编码不一致name字段读出来乱码映射字典匹配不上目标被全部跳过训练集显示 0 个标签。解决统一在NAME_TO_ID里做中文到英文的映射脚本用ET.parse默认的编码读取文件保持 UTF-8不要在转换脚本里混用 GBK。4. 用 YOLOv8 训练睡岗检测模型参数设置与效果验证4.1 数据配置 YAML 与训练命令处理完标签下一步就是配置数据描述文件。YOLOv8 用 YAML 告诉训练器「数据在哪、类别叫什么」注意names的顺序必须和第三章的NAME_TO_ID完全一致# sleep.yaml path: /data/sleep_detection # 数据根目录建议绝对路径 train: images/train val: images/val names: 0: desk_sleep 1: head_down 2: lean_sleep 3: lying_sleep逻辑说明path写绝对路径最省事相对路径容易和 Ultralytics 的工作目录搞混train和val是相对path的目录。参数说明names不能写成列表之外的格式这个字段在 Ultralytics 8.x 里被解析成类别名索引如果类别数对不上会直接报错。训练命令yolo detect train \ datasleep.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience15 \ projectrun_sleep \ nameexp1参数说明modelyolov8n.pt会加载 COCO 预训练权重做迁移学习不是从头训练这能显著缩短收敛时间也决定了你对检测任务的理解能复用多少patience15表示连续 15 个 epoch 验证集指标不提升就早停对 6549 张图的数据集来说非常实用device0指定第一块 GPU没有 GPU 时改成devicecpu但 100 轮会非常慢建议先用小数据集试跑。4.2 关键参数怎么调imgsz、batch、epochs、模型尺度imgsz是睡岗检测里最值得花时间调的参数。默认 640 是速度和精度的平衡点但这个数据集的监控画面里远端机位的人体可能只有几十像素高640 输入下细节丢失严重。如果发现小目标漏检把imgsz提到 960 或 1280mAP50 通常能涨 2~5 个点但推理时间约按分辨率平方增长。要部署在 CPU 盒子上就先 640别贪。batch受显存限制。8G 显存跑yolov8n加imgsz640可以用 16换yolov8s或imgsz1280就要降到 4~8。batch 太小2~4 时BN 统计不稳定loss 曲线会明显抖动这时候先降分辨率或换小模型而不是硬扛大 batch。epochs和patience是一对搭档。6549 张图属于小数据集100 轮足够关键是有 patience 帮你自动停。如果 60 轮就因为早停结束去看run_sleep/exp1/results.csv里 val 指标是不是已经平台期是的话直接采用这版就行不用非跑满 100 轮。模型尺度选择上有个血泪经验别一上来就上yolov8m或yolov8l。睡岗检测场景单一、目标是大尺度的人体yolov8n和yolov8s的效果差距没有想象中大但推理速度差距很大。服务器部署选 s边缘盒子选 n先跑通再升级。4.3 验证指标怎么读mAP50 与召回优先的评估口径训练结束后不要只盯着 mAP 一个数字。睡岗检测的业务口径是「别漏」漏一次就是一次真实睡岗事故所以评估时要优先看 recall。正常情况下这套数据训出来的模型应该达到mAP50在 0.85 以上、recall0.5在 0.9 以上才值得上线。如果 recall 上不去问题基本出在类别混淆或小目标而不是模型容量。指标文件都在run_sleep/exp1/目录下。results.png看整体趋势confusion_matrix.png看具体哪些类别互相错检F1_curve.png会告诉你哪个置信度阈值下 F1 最高。我一般先看混淆矩阵再决定要不要合并类别最后才调阈值顺序反了容易被整体指标带偏。还有一个容易被忽略的检查点val_batch*_pred.jpg的可视化图。打开它看预测框是不是贴住人的躯干和头部而不是套住一整张桌面或椅子。框太松说明定位没学好框太紧可能漏了垂下的手臂这些是 mAP 数字体现不出来的质量问题。5. 睡岗检测模型训练避坑指南五个常见问题与排查记录5.1 现象训练到 40 轮左右 loss 突然变成 NaN原因数据里存在极窄或极宽的异常标注框或者某张图片实际尺寸和 XML 里记录的size不一致。比如原始图被旋转后重新保存宽高对调归一化分母用错坐标就会飞到离谱范围梯度爆炸直接把 loss 打成 NaN。解决转换前全量校验一次图片和标注。用cv2.imread读每张图拿到真实宽高和 XML 里的width/height对比不一致的直接剔除同时遍历 bndbox宽或高小于 1 像素的框删掉。这一步听着繁琐但能省掉后面一整天的排查时间。5.2 现象趴桌睡和埋头睡互相错检严重原因这两类的语义边界本来就模糊。头低垂到胸口算埋头头完全趴到桌面算趴桌不同标注人员对「趴」的理解不一致导致同类样本的视觉分布很散。训练出来模型在两个类之间反复横跳。解决先看混淆矩阵里这两个格子的具体数值。如果错检率超过 20%直接把desk_sleep和head_down合并成一个大类「坐姿瞌睡」重新训练。睡岗检测的目的是报警不是学术上的细粒度分类少一个类往往能换回 2~3 个点的 mAP 和明显更低的误报率。这个决定越早做越好。5.3 现象监控远端的小目标漏检特别是靠椅睡原因摄像头对着整个值班室坐在远处的人可能只有 30~40 像素高。imgsz640输入下缩得更小模型学不到足够的人体细节。这种情况在验证集里表现为「近处全检出来远处全漏」。解决imgsz提到 960训练时保持默认 mosaic 增强能显著改善小目标推理时也不要先把视频流压到 640 再送模型按原分辨率缩放。如果边缘设备撑不住高分辨率另一个思路是在人员常驻区域加一路近景摄像头用小分辨率的近距离画面做二次确认比硬怼分辨率划算。5.4 现象夜间和背光场景误报率明显升高原因数据集样本以白天为主夜里显示器屏光、走廊背光会把画面亮度压得很低模型把椅子靠背纹理、桌面反光当成目标。整体 mAP 看着不错但夜间的 precision 惨不忍睹。解决做离线增强补夜间分布。随机 gamma 从 0.5 到 1.5、亮度抖动 0.2、加轻度高斯噪声都是模拟监控夜视的常用手段。增强后单独统计夜间子集的召回率和误报率不要只看整体指标。如果现场能抽到夜间真实录像直接抽帧回填训练集是最靠谱的方案。5.5 现象训练指标不错部署到盒子上只有 3~5 帧原因imgsz用了 1280或者模型选了 m/l边缘 CPU 或 NPU 根本吃不消。这是最常见的「实验室满意、现场翻车」案例。解决部署侧先用yolo export modelbest.pt formatonnx导出再转 OpenVINO 或 TensorRTimgsz降到 640能接受再降就做 int8 量化。睡岗报警本身允许 2~3 秒延迟帧率掉到 8~10 也能用关键是别在最大分辨率上死磕。导出前用一个真实视频片段测端到端延迟别只看单张推理时间框后处理和数据前后编解码都会吃掉时间。6. 让睡岗检测模型更抗造数据增强、阈值选择与上线验证6.1 用 Ultralytics 内置增强参数模拟监控场景训练命令里可以直接叠加增强参数不需要改代码yolo detect train \ datasleep.yaml \ modelyolov8n.pt \ epochs100 imgsz640 batch16 device0 \ hsv_h0.01 hsv_s0.3 hsv_v0.4 \ degrees0 translate0.1 scale0.2 mosaic1.0参数说明hsv_v0.4是亮度抖动专门对付监控画面的光照波动degrees0必须设 0睡姿是有方向语义的旋转 90 度后「躺睡」和「靠椅睡」会彻底混淆mosaic1.0保持开启对增强小目标鲁棒性帮助最大。6.2 用一段真实值班视频做端到端验证指标再好也要拿真实录像说话。方法录 5~10 分钟值班室画面包含至少 2 次正常活动和 2 次打盹场景抽帧后跑模型ffmpeg -i monitor_room.mp4 -vf fps1 frames/%05d.jpg yolo detect predict modelbest.pt sourceframes/ conf0.2 save_txtTrue逻辑说明fps1每秒抽一帧5 分钟视频就是 300 帧人工逐帧标记「睡/正常」的成本可控conf0.2是比默认 0.25 更低的阈值睡岗场景下宁可多报警也不漏报。参数说明抽帧密度根据现场动作速度调整人员移动快就fps2基本静止的值班室fps1足够。报警逻辑上加一个冷却时间连续 3 帧检出同一个位置目标才触警能压掉大量单帧抖动误报触发后 30 秒内不重复报警避免同一事件刷屏。6.3 我的习惯与这个方案的上限说实话睡岗检测的技术难度不在模型而在数据一致性。换一个值班室机位高度、灯光角度、桌椅颜色全变了模型效果就可能打折。所以我的固定习惯是拿到任何新数据集先跑统计脚本看分布再转格式训一版基线然后看混淆矩阵决定要不要合并类别最后才碰增强参数。这个顺序能帮你把「数据集的问题」和「模型的问题」分开不至于在错误的维度上瞎调。还有一点提醒检测到睡姿只是第一步真正要落地还要接报警推送、复核界面和值班记录这些在工程里比模型本身更花时间。6549 张图训出来的模型做单一场景的睡岗报警是完全够用的但别指望它是万能黑匣子边界在于场景变化后需要持续补充新样本。我的教训是上线后前两周持续收集新画面、每周回填一次训练集比任何调参都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表