ARTICLE DETAIL

资讯详情

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

YOLOv8训练嗜睡行为检测数据集:7类标签处理与避坑指南

YOLOv8训练嗜睡行为检测数据集:7类标签处理与避坑指南 简介面向疲劳驾驶与分心行为检测的嗜睡数据集涵盖头部下垂、唤醒、昏昏欲睡等典型状态适合目标检测算法学习者与开发者快速上手。压缩包共2000个文件以XML标注文件为主并配套YOLO格式TXT标签整体大小172.36MB数据集已预先划分好训练、验证和测试集内置data.yaml配置文件可无缝适配YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10及YOLO11等主流算法。标签覆盖头部下垂、唤醒、昏昏欲睡、分心、吸烟、打哈欠、电话共七类行为同时提供yolotxt与vocxml两种标注格式便于不同训练框架直接调用。目前已有91人学习下载数据可直接复用无需二次整理。该资源省去手动标注环节拿到后即可开展训练与验证初学者还能对照两种标签格式理解yolo归一化坐标与voc绝对坐标的差异非常适合作为驾驶员状态监测、安全驾驶预警等场景的算法验证数据集。1. 嗜睡行为数据集不是解压即用8979 张图背后的 7 类标签关系解压这个 zip 之后先别急着跑yolo train。这 8979 张图像带标签的嗜睡数据集标注里包含头部下垂、唤醒、昏昏欲睡、分心、吸烟、打哈欠、电话 7 个行为类别正好覆盖 DMS 驾驶员监控系统里最难处理的疲劳行为和分心行为组合。它的定位很明确让你不用从零收集数据、不用请人标框就能把一个多类别行为检测的底座搭起来。适合两类人——一类是要做车载 DMS、疲劳驾驶预警产品验证的工程师另一类是拿 YOLO 做毕业设计或竞赛、需要真实带标签数据的学生。但这不是一个解压就能直接训出好模型的包标签之间的关系、类别数量差异、连续帧图像的分组方式都会在训练中变成实实在在的坑。下面按我实际处理这类行为数据的流程一步步拆开。2. 把 7 类嗜睡标签转成 YOLO 格式类别编号、坐标归一化与目录规划2.1 先定类别表唤醒类是负样本锚点不能删拿到数据集第一件事不是看图像而是把标注里的类别名称列出来写成一份固定的类别映射表。这个数据集里的 7 个标签我一般会按下表编号编号类别名对应标签训练中的作用0head_down头部下垂疲劳正样本与 drowsy 常共现1awake唤醒正常驾驶基线负样本锚点2drowsy昏昏欲睡核心疲劳状态3distracted分心目光偏离前方、操作中控4smoking吸烟分心子类单独检出5yawning打哈欠疲劳先兆6phone电话分心子类单独检出注意「唤醒」这一类很多人会把它当成废话类删掉这是第一个要避开的坑。DMS 是一个开机就持续运行的检测任务摄像头里大部分时间是正常驾驶状态。如果训练集里没有「唤醒」这个类模型就缺少背景锚点会把正常驾驶时的低头看仪表、倾斜身体、自然转头全部误判成疲劳或分心。这个类的存在不是冗余而是给其他 6 个正类划了一条决策边界。到第 5 章的避坑部分我会专门讲删掉它之后会发生什么。还有一个语义重叠问题需要提前处理分心、吸烟、电话三个标签在场景上有交叉。一个人打电话时视线往往也偏离前方但数据集里把它标成了 phone 而不是 distracted。我的约定是只要画面里有手机就归 phone有烟支就归 smoking二者都没有但视线离开前方归 distracted。转换脚本里按这个优先级判断避免同一帧在多个类里重复造框。2.2 转换脚本从源标注到每图一个同名 txtYOLO 系列训练的标签格式不是 json 也不是 csv而是每张图像对应一个同名 txt每行写一条记录类别编号 中心点x 中心点y 框宽 框高坐标全部用图像宽高归一化到 01 之间。绝大多数压缩包里的原始标注都不是这种格式所以第一步永远是写转换脚本。下面是我处理行为类数据集的常用模板其中load_source_annotations按你手里实际标注格式json、csv、xml 都行替换读取逻辑import os from PIL import Image # 源标注演示结构anno[文件名] [ (类别名, x_min, y_min, x_max, y_max), ... ] # 实际项目中常见的是 json / csv / xml读取部分按你的源标注格式替换 anno load_source_annotations(/path/to/raw_annotations.json) # 7 个类别按固定顺序编号顺序一旦定下来就别改训练和推理共用这一份 CLS_MAP { head_down: 0, # 头部下垂 awake: 1, # 唤醒 / 正常驾驶保留作负样本锚点 drowsy: 2, # 昏昏欲睡 distracted: 3, # 分心目光离开前方或操作中控 smoking: 4, # 吸烟 yawning: 5, # 打哈欠 phone: 6 # 使用电话 } def convert_to_yolo(image_dir, output_dir): for img_name, boxes in anno.items(): img_path os.path.join(image_dir, img_name) w, h Image.open(img_path).size txt_path os.path.join(output_dir, os.path.splitext(img_name)[0] .txt) lines [] for cls_name, x1, y1, x2, y2 in boxes: if cls_name not in CLS_MAP: continue # 未映射的标签直接跳过避免污染 # 防止出现负坐标或越界坐标先夹紧 x1 max(0, min(x1, w - 1)) y1 max(0, min(y1, h - 1)) x2 max(0, min(x2, w)) y2 max(0, min(y2, h)) if x2 x1 or y2 y1: continue # 宽高非正的框是无效框丢弃 cx ((x1 x2) / 2) / w cy ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{CLS_MAP[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) print(f转换完成共生成 {len(anno)} 个 txt)这段脚本里有三个细节决定了后面训练能不能顺利跑起来。第一坐标归一化用的是中心点/宽高不是左上右下YOLO 的标签格式要求的就是中心点加宽高四个值必须在 01 之间。第二夹紧和丢弃逻辑不是多余的——行为数据集中经常出现标注框略微越界、或者框退化成一个点的情况这些框如果不清理训练时 loss 会先爆掉。第三类别编号顺序一旦定下来训练和推理必须共用同一个CLS_MAP不要中途调整顺序否则你后面做模型部署时预测结果和类别对不上这种问题排查起来最费时间。2.3 目录结构与第一轮校验先跑一遍文件完整性转换完标签后目录结构按 YOLO 惯例来组织一个data根目录下分images和labels各自再分train、val。这种结构是之后 data.yaml 能直接引用的标准布局也是主流开源训练平台默认识别的格式。data/ ├── images/ │ ├── train/ # ~8000 张 │ └── val/ # ~900 张 ├── labels/ │ ├── train/ # 与 images/train 同名同前缀的 txt │ └── val/ └── data.yaml # 训练配置划分比例上8979 张图的规模我一般按 90/10 划分val 里保证每个类别至少有几十个框否则验证集的 mAP 波动会大到没法看。划分之后必须做一轮完整性校验遍历 labels 目录下每个 txt检查里面每一行的类别编号是否都在 06 之间、四个坐标值是否都在 01 之间、每张图是否都能找到对应 txt。这一步可以用一个十几行的 Python 脚本完成但很多初次接触行为数据集的人会跳过它结果训练到一半才报错浪费几个小时。校验脚本我一般会在转换脚本里顺手加上跑完直接打印异常样本列表。到这一步数据格式就齐了接着进入训练前的数据体检。3. 训练前给 8979 张图体检类别不均衡、空图和连续帧泄漏3.1 统计各类别框数吸烟、电话这类少数类要心里有数行为检测数据集和通用物体检测数据集最大的不同是类别分布极其不均衡。通用数据集里的猫、狗、车数量比例通常在一个数量级内但驾驶行为数据里「唤醒」这种正常状态的框可能占一半以上而「吸烟」「电话」这类短时行为的框可能只有前者的十分之一。直接拿原始分布去训练模型会把多数类学得很好少数类几乎全漏。所以在划分数据集之前必须先跑一遍统计脚本做到心里有数。import glob from collections import Counter LABEL_DIR labels/train # 先只看训练集val 单独看 cls_counter Counter() box_per_img [] for txt in glob.glob(f{LABEL_DIR}/*.txt): rows open(txt).read().strip().splitlines() if not rows or rows []: box_per_img.append(0) continue box_per_img.append(len(rows)) for row in rows: cls_counter[int(row.split()[0])] 1 print(各类别框数:, dict(cls_counter)) print(空标注图数量:, box_per_img.count(0)) print(总图数:, len(box_per_img)) print(单图平均框数:, round(sum(box_per_img) / len(box_per_img), 2))这段统计能一次性暴露三个问题哪些类是少数类、有多少图是空的、每张图平均有几个框。空标注图不是脏数据它们对应的就是「唤醒」状态下的正常驾驶画面是负样本绝对不能删除。但要注意的是它们会让模型更偏向预测「无目标」所以在训练配置里要么提高正类权重要么在采样时控制空图占比。单图平均框数也能侧面反映该数据集的难度——平均框数超过 2说明一张图里经常同时出现头和手等多个目标这个信息后面排查混淆矩阵时会用到。3.2 按视频片段分组划分 train/val别用随机划分行为数据集大多是从连续视频里抽帧标注的同一段视频相邻帧的背景、角度、光照几乎完全一样。如果直接用random_split按图随机划分同一个视频片段的帧会同时出现在训练集和验证集里。这种情况下验证集的 mAP 会虚高因为模型相当于做过原题——它记住了背景而不是学到了行为特征。等你部署到车上面对新的摄像头视角精度会断崖式下跌。正确处理方式是按视频片段或受试者编号分组。文件名通常带片段前缀比如cam01_000123.jpg、subject02_000456.jpg取前缀作为分组键import os, random from collections import defaultdict group2imgs defaultdict(list) for img in os.listdir(images): group img.split(_)[0] # 按视频片段/受试者分组 group2imgs[group].append(img) random.seed(2024) train_imgs, val_imgs [], [] for group, imgs in group2imgs.items(): random.shuffle(imgs) split int(len(imgs) * 0.9) # 组内 90/10 train_imgs.extend(imgs[:split]) val_imgs.extend(imgs[split:])组内划分保证了同一个片段最多只有 10% 的帧进验证集且其他 90% 的训练帧虽然背景相似但不会和验证帧逐帧重复。对于 8979 张图的规模这样划分后 val 里每个类别依然能保留足够的框数。切分完成后把train_imgs、val_imgs分别硬链接到images/train、images/val再同步把对应 txt 复制到 labels 目录。这一步是防止数据泄漏最关键的一环也是很多训练平台默认随机划分做不到的。3.3 一张图多个行为框的处理不拆类但要看共生关系行为检测还有一个通用检测里不常见的问题同一个人的同一时刻可能出现多个行为标签。典型场景是驾驶员打哈欠的同时头部下垂、眼皮半闭这时候同一张图里会出现 yawning 框和 head_down 框甚至 drowsy 框。目标检测本身就是多框多类的这些框不用拆模型会同时学习「打哈欠」和「头部下垂」两个独立的视觉特征。但你要注意它们之间的共生关系——如果 yawning 和 drowsy 在训练集里高度共现模型可能学到的是「有哈欠就有嗜睡」的关联而非真正区分两个类别的边界。后面观察混淆矩阵时这两类之间的错误匹配也会明显偏高。这不是数据问题而是行为本身的强相关性标注工具无法给一个行为框同时打两个标签只能按主标签打。我的做法是在训练时接受这种共生但在评估指标上不看单一类的 AP而是看疲劳类head_down、drowsy、yawning的合并召回率只要三个类里有任一检出就算疲劳命中。这样更贴近真实 DMS 的使用逻辑。4. 用 YOLOv8 跑训练预训练权重、损失函数与 120 轮迭代策略4.1 data.yaml 与预训练模型9000 张图规模别从零训练类别和目录都整理好后训练配置就相对省事了。先写 data.yaml让训练工具能定位到图像和标签# data.yaml path: /data/drowsiness_detection train: images/train val: images/val nc: 7 names: [head_down, awake, drowsy, distracted, smoking, yawning, phone]8979 张图带标签的规模说大不大、说小不小但从零初始化训练一个检测头基本是浪费算力。行为数据集的共性特点是背景相对单一都是驾驶舱视角但目标的姿态变化大所以前几层特征用预训练模型是绝对划算的。我一般会下载 YOLOv8m 的预训练权重作为起点而不是用yolov8n——行为框通常偏大、偏清晰模型的容量主要花在区分不同行为类别上n 的骨干网络在少量数据下很难把吸烟和正常喝水这类细粒度差异分开。如果你手里显存紧张把 imgsz 降到 480 比换小模型更有效。预训练模型这块选择上v8 的官方权重在 COCO 上学到的通用目标表征对头、手、手机、烟支这些目标都有不错的迁移效果。4.2 训练命令与 6 个必调超参数训练命令本身不长关键是每个参数的行为数据含义。下面是针对 8979 张图、7 类行为数据的一套完整命令yolo detect train \ modelyolov8m.pt \ datadata.yaml \ imgsz640 \ batch16 \ epochs120 \ patience20 \ workers8 \ device0 \ projectruns/dms \ namedrowsy_v8m几个参数按我的习惯逐个说。imgsz640是性价比最高的分辨率。DMS 摄像头画面里驾驶员头部区域通常占整幅图像的 15% 以上目标大、细节不算苛刻640 足够捕捉头部姿态和嘴部状态。如果你发现打哈欠和小幅头部下垂漏检多再把 imgsz 提到 960 或 1280但训练时间会接近翻倍建议先用 640 跑通流程。batch16在 12GB 显存上配合 yolov8m 刚好合适24GB 显存可以上 32。batch 太小小于 8会让 BN 层的统计量不稳定行为数据里的空图和稠密图差异大这个问题更容易被放大。epochs120配patience20是行为检测的稳妥组合。数据集只有 9000 张图不算大120 轮足够模型收敛patience 20 意味着 20 轮内验证集没有提升就提前停避免后期过拟合到训练集的背景细节上。另外两个值得关注的参数是optimizer和mosaic。迁移学习场景下我用 AdamW 起步前 50 轮保持冻结 backbone后 70 轮解冻全部层微调。mosaic 增强在通用检测里效果很好但行为检测里它会拼出半个人脸或断裂的手部我的经验是把 mosaic 概率从默认的 1.0 降到 0.5让模型不至于过度依赖拼出来的假样本。4.3 类别不平衡怎么传给训练cls_pw 与损失函数选择YOLOv8 默认的损失函数是三个分支的组合分类用 BCEWithLogitsLoss回归用 CIoU外加 DFL 分布损失。这套组合在大多数检测任务里都不用动行为数据集的麻烦不在损失函数形式而在类别权重。上一章统计里大概率会发现吸烟、电话、头部下垂这几个类别的框数明显少于唤醒和昏昏欲睡。如果直接训模型会对少数类的损失贡献不够敏感。YOLOv8 训练参数里可以通过cls_pw传入每个类的正样本权重我一般按频率倒数平方根计算import numpy as np freq np.array([ 420, # head_down 框数示例值以你的统计为准 3100, # awake 框数示例值 2100, # drowsy 框数示例值 560, # distracted 框数示例值 180, # smoking 框数示例值 780, # yawning 框数示例值 220 # phone 框数示例值 ], dtypefloat) weights 1.0 / np.sqrt(freq) weights weights / weights.mean() # 归一到均值 1 print(weights.round(3))输出结果像[0.62, 0.22, 0.27, 0.53, 0.96, 0.46, 0.87]这样的数组直接拼在训练命令的cls_pw参数后面。用平方根而不是直接用倒数是为了避免把权重拉得过大——像 smoking 这种样本特别少的类权重一旦超过 3模型会开始把喝水、吃零食的普通抬手动作全部误判成吸烟因为漏报的惩罚太重了。权重的作用是保底让少数类在损失里有一定的存在感而不是压过所有其他类别。训练过程中我建议每 20 轮存一次 checkpoint而不是只存最后的权重。后面要做难例挖掘第 6 章内容需要拿不同训练阶段的模型对同一批数据进行预测对比哪些误检是模型中期有、后期消失的——这能帮你判断是数据量不够还是类别边界真的模糊。5. 训练嗜睡数据集最容易翻车的 5 个坑从 BN 崩溃到混淆矩阵失真5.1 BN 崩溃、loss 变成 nanval 精度全程为 0现象训练刚开始几轮 loss 还能正常下降到第 5 轮左右突然出现 inf之后 mAP 一直是 0日志里 batch 统计的输入图像也都正常。原因绝大多数情况出在标签上。嗜睡行为数据集的标注框里存在两种脏数据一是坐标越界——标注框的右下角超出了图像宽高或者在归一化后出现了负值二是「退化框」——比如某一帧驾驶员低头幅度极大标注框压成了一个薄片宽或高接近于 0导致回归分支的 loss 爆炸。这两种脏框在训练初期会被模型当成真实目标去拟合梯度瞬间失控。解决在转换脚本阶段就做夹紧和过滤这是我第 2 章脚本里x2 x1 or y2 y1那段逻辑存在的意义。如果你已经训练到一半才发现别想着靠调低学习率补救直接回退到转换脚本把异常框清理后再重新训练。这个坑是所有行为数据集的第一个拦路虎训练前跑一遍完整性校验的成本远低于白跑几十轮。5.2 把「唤醒」类删了换来一整屏疲劳误报现象只保留头部下垂、昏昏欲睡、分心等 6 个行为类训练验证集 mAP 看着不错但拿到一段正常驾驶的视频里跑每几秒就报一次疲劳驾驶员不过是低头看了一眼仪表盘或者调了一下后视镜。原因漏了「唤醒」这个负样本锚点类。没有唤醒类的数据模型内部就没有「以上都不算」的空间它只能在 6 个行为类里硬选一个最接近的——而头部下垂类在姿态上跟低头看仪表盘几乎无法区分。你缺少的是一种用来压制误报的负样本而不是模型能力问题。解决训练集里保留全部 7 个类尤其是唤醒类不能删。更进一步的建议是给唤醒类单独设置一个过滤逻辑推理时把唤醒类的置信度阈值提高到 0.7其余行为类用默认的 0.25——这样既能保证正常状态不被误报又不会因为唤醒类抢占了行为类的概率空间而影响真正的疲劳检出。5.3 混淆矩阵总和永远对不上看起来像数据错乱现象训练结束后打开 confusion_matrix 图矩阵里每个格子都有数字但把所有数字加起来发现远远大于训练样本总数甚至大好几倍于是怀疑数据集或训练工具出了问题。原因目标检测的混淆矩阵本来就不是按「样本数」算的。YOLO 是先把每个 GT 框和预测框做 IoU 匹配然后每个预测框都可能进入矩阵的一个格子同一个 GT 可能被多个预测框命中。再加上行为数据里一张图平均有 1.52 个框头和手同时出现预测框的数量天然比 GT 多。矩阵总和虚高是正常现象不是 bug。解决别用混淆矩阵核对数据量。你想知道训练集规模回到第 3 章的统计脚本里去数框。想评估模型到底漏了多少去看每类的 recall 和针对性的 PR 曲线。混淆矩阵只用来观察类别之间的混淆模式——尤其是 smoking 和 phone 之间、drowsy 和 yawning 之间的错位这是行为类别的语义相似性造成的属于预期内现象不符合预期才需要排查数据。5.4 连续帧泄漏val mAP 0.9 以上上路测试一塌糊涂现象训练结果极好val mAP 超过 0.9每个类的 AP 都稳定。但把模型部署到一套全新的摄像头画面上检测率跌了 30 个点以上头部下垂经常漏检。原因这是最典型的随机划分翻车现场。8979 张图如果按文件名随机打乱划分同一段视频的连续帧会出现在训练集和验证集里。模型训练时见过这些帧的背景纹理和驾驶员衣着验证时等于做原题mAP 虚高。但真实场景里的汽车内饰、座椅角度、光线条件和数据集完全不同模型立刻露馅。解决回到第 3.2 节的按片段分组划分逻辑。划分完成后我习惯做一个抽查——随机从 val 里取 30 张图去看它们的文件名前缀确认每个片段在 val 里只占很小比例。这个检查便宜且有效比任何训练指标都更能说明数据划分是否干净。5.5 吸烟类被喂成「乱枪打鸟」recall 高但 precision 惨不忍睹现象训练结束后打印分类报告smoking 类 recall 有 0.7但 precision 只有 0.2预测出的吸烟框一大半是驾驶员在喝水、吃东西、甚至手靠近嘴边的普通动作。原因smoking 类样本太少模型无法学到一个紧致的「烟支」特征表示只能把凡是「手在嘴边附近」的框都判成吸烟。YOLOv8 的检测头在类别不平衡时会倾向于扩大正例范围来降低损失于是 precision 被牺牲掉了。解决在两个层面下手。数据层面用难例挖掘补充训练——拿当前模型去预测行车视频把置信度在 0.30.6 之间的边缘框全部导出成小图人工筛选出真正的吸烟动作回填到训练集。参数层面把 smoking 类的cls_pw权重从 0.96 调到 1.2 左右再用一个轻微的下置信度阈值过滤比如推理时 smoking 类阈值从 0.25 提到 0.35。这一条治标难例挖掘才是治本的路子具体操作在第 6 章展开。6. 把边缘样本捞回来再训一轮置信度筛图与难例补充训练完第一轮别急着下车验证。针对吸烟、电话这类样本少的类别我有一个固定动作跑一遍置信度筛查把模型犹豫过的样本捞回来重新标注。具体做法是拿训练好的 best.pt 对一组新的行车视频片段做预测输出每帧的检测置信度然后按下表分档处理置信度区间模型状态处理动作0.6 以上高置信分类基本可靠不处理直接作为有效检测0.30.6边缘预测模型在两类之间摇摆导出小图人工复核后补标回填训练集0.25 以下模型认为无目标人工抽查这些帧里是否存在漏检的短时行为回填流程很简单用预测脚本先把边缘帧输出成带名字的图片列表yolo predict modelruns/dms/drowsy_v8m/weights/best.pt \ sourceraw_clips/ \ save_txtTrue \ conf0.25 \ iou0.5把输出的 txt 和图片交给标注工具重点检查边缘区间里那些被模型标成 smoking 但实际是喝水、被标成 drowsy 但实际是头部微小晃动的帧。人工确认后把修正结果合并回训练集再微调一轮。我一般会重复这个流程两轮第一轮解决少数类的 precision 问题第二轮解决疲劳类的漏检问题。这种做法不是玄学它的原理是让模型在它最模糊的决策边界附近获得更多真实样本——比无脑加相同数量的随机帧有效得多。回填完成后再跑一次同样的预测你会发现边缘置信度区间的帧数明显减少这说明类别边界被压实了而不是模型整体的置信度虚高。到了这一步这个 8979 张图的数据集才算真正被榨干价值。拿到这类行为数据集我的第一习惯永远是先统计、再划分、最后才开训——顺序反了后面的每一轮迭代都在还数据债。希望帮到你。本文还有配套的精品资源点击获取
返回列表