简介这份疲劳驾驶行为数据集面向计算机视觉与深度学习开发者可服务于驾驶员状态监测、疲劳预警、辅助驾驶等场景帮助研究者训练模型识别专注、昏昏欲睡、闭眼、张嘴、打哈欠、睡着等多种驾驶行为。数据包共两千个文件包含一千九百九十七张JPG图片与三个COCO格式JSON标注文件图片与标注一一对应压缩包整体约七百八十九MB。目前已有七百零六人学习使用。该数据集规模可观总计两万一千六百六十八张图片覆盖多种姿态、角度与光照条件标注内容细致既可用于目标检测、实例分割也便于调整成分类或关键点任务COCO JSON格式与主流框架无缝衔接免去手动标注的耗时环节对于开展疲劳驾驶识别算法验证、模型调优的开发者来说是一份可直接投入训练的优质资源。1. 疲劳驾驶行为数据集21668 张 COCO JSON 图片能解决什么做 DMS驾驶员监控系统的同行都清楚疲劳驾驶行为的数据比模型本身更难搞。市面上公开数据集要么是实验室摆拍、动作夸张要么类别只有「睁眼/闭眼」两极真正落地时你需要的是「专注」「昏昏欲睡」「打哈欠」「睡着」「不打哈欠」这种贴近实车状态的行为标签还要能区分微表情差异。这份 21668 张图片、COCO JSON 格式标注的疲劳驾驶行为数据集正好把目标检测任务里最缺的一环补上了它不只是告诉你「人睡着了」而是把闭眼、张嘴、打哈欠这类局部行为分别框出来方便后续做 PERCLOS、哈欠频次等时序判断。适合正在训 YOLO 系检测模型、又不想从零标注的算法工程师和车载视觉团队。2. 拆解 COCO JSON 标注结构类别、bbox 与统计脚本2.1 COCO JSON 的四个核心数组疲劳驾驶类别如何落进去COCO 格式的 JSON 本质上是一个大字典四个数组各司其职images记录每张图的路径和尺寸annotations记录每个标注框的位置和类别categories定义类别 ID 到名称的映射licenses和info属于元信息训练时通常用不到。拿到手先别急着转格式把categories打开看一遍确认类别 ID 顺序和你预期一致尤其注意「闭眼」和「不打哈欠」这类容易混淆的标签。{ images: [ { id: 1, file_name: frame_000001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 2, bbox: [610, 312, 420, 530], area: 222600, iscrowd: 0 } ], categories: [ {id: 0, name: focused, supercategory: driver}, {id: 1, name: drowsy, supercategory: driver}, {id: 2, name: eye_closed, supercategory: driver}, {id: 3, name: mouth_open, supercategory: driver}, {id: 4, name: yawn, supercategory: driver}, {id: 5, name: asleep, supercategory: driver}, {id: 6, name: no_yawn, supercategory: driver} ] }注意bbox四项依次是左上角 x、左上角 y、框宽 w、框高 h坐标以原图像素为单位。很多人在后面转 YOLO 格式时翻车就是因为把bbox当成了中心点坐标。area可以直接用宽高相乘算出也可以沿用标注工具导出的值前提是工具没犯懒填 0。iscrowd统一给 0表示每个框都是独立实例不参与群体标注。这个 JSON 片段里我把类别顺序按实际行为语义排了focused专注和drowsy昏昏欲睡是全局状态eye_closed、mouth_open、yawn是局部动作asleep是重度状态no_yawn则是「嘴巴闭着、没有哈欠」的负样本。实际数据集的类别 ID 可能不同写代码前先打印出来核对。2.2 统计脚本标注质量如何跑一次就知道拿到数据集先做三件事看每类有多少框、看图片和标注是否一一对应、看有没有坐标超出图像边界的脏框。这几步用 Python 的json模块就能完成直接把 JSON 当普通字典遍历。import json from collections import Counter with open(annotations.json, r, encodingutf-8) as f: coco json.load(f) # 建立 image_id - 图像尺寸 的映射方便查越界框 img_info {img[id]: (img[width], img[height]) for img in coco[images]} cat_info {cat[id]: cat[name] for cat in coco[categories]} cat_counter Counter() invalid_boxes 0 missing_img 0 for ann in coco[annotations]: cat_counter[cat_info[ann[category_id]]] 1 img_id ann[image_id] if img_id not in img_info: missing_img 1 continue w, h img_info[img_id] x, y, bw, bh ann[bbox] # 框超出右边界或下边界超过 5 像素就标记为非法 if x bw w 5 or y bh h 5 or bw 0 or bh 0: invalid_boxes 1 print(各类别框数) for name, cnt in cat_counter.most_common(): print(f {name}: {cnt}) print(f缺失对应图片的标注: {missing_img}) print(f越界/非法框: {invalid_boxes})这个脚本的价值在于把「标注质量」这个黑匣子打开。我第一次拿到类似数据集时只看了images数组长度是 21668 就觉得没问题结果annotations里有 300 多个框的image_id指向了不存在的图片训练时这些样本被 YOLO 静默跳过类别分布瞬间缺了一块。cat_counter输出的每类数量也要留意如果某个类别只有几十个框说明标注策略严重失衡后面需要针对性地做数据增强或补采。2.3 图像与标注的对应关系用集合差集快速定位images是 21668 张图但不代表每条记录都有效。用集合运算查差集是最快的定位手段也能顺手确认是否有重复的image_id。image_ids {img[id] for img in coco[images]} annotated_ids {ann[image_id] for ann in coco[annotations]} unlabeled image_ids - annotated_ids orphan_anns annotated_ids - image_ids print(f有图无标注: {len(unlabeled)} 张) print(f有标注无图: {len(orphan_anns)} 条)有图无标注的情况在行为数据里很常见标注员对「专注」和「不打哈欠」拿不准干脆漏标了整张图。如果这类图片比例超过 3%建议退回重标如果只是零星几张训练时把unlabeled从数据清单里剔除即可。orphan_anns若不为空说明 JSON 是从多个标注任务拼接的需要检查拼接脚本里的image_id偏移量。3. 行为数据集的标注流程用 CVAT 建项目、定规则、导出 JSON3.1 为什么选 CVAT 而不是 LabelImg 或 LabelMe做目标检测标注工具首选不是 LabelImg。LabelImg 单机单文件操作适合小样本快速标注LabelMe 导出的是自家 JSON 结构转 COCO 还要写脚本。而 CVAT 是网页版标注工具支持视频抽帧、多人协作导出选项里直接就有 COCO JSON省去中间转换环节。对于 21668 张图的规模单机工具标到后期会陷入「文件同步」和「标签不一致」的泥潭CVAT 的服务端存储能少踩很多坑。CVAT 的导出格式默认是 COCO 1.0categories数组会自动生成supercategory统一给一个占位名。导出后先跑一遍上一章的统计脚本确认类别 ID 没有因为标注期间调整标签而错位。3.2 标注规则的制定头部框还是局部框闭眼怎么算疲劳驾驶行为标注最大的争议是「框哪里」。我的做法是统一框头部区域包含完整面部轮廓肩部以下不框。理由有二一是头部框比眼睛、嘴巴的局部框更容易标多人协作时一致性高二是后续若要做头部姿态估计或瞳孔定位头部框能直接作为 ROI局部框还得重新聚合。接下来是行为定义逐条写进 CVAT 的标注规范文档里否则同一个画面两个标注员会给出不同标签专注focused双眼睁开目视前方嘴巴自然闭合或正常说话昏昏欲睡drowsy眼皮明显下垂眼睛半闭但尚未完全闭合闭眼eye_closed上下眼皮完全接触睫毛可见张嘴mouth_open嘴巴张开但非打哈欠状态比如说话或惊讶打哈欠yawn嘴巴张大且持续时间超过 2 秒伴随深吸气动作面部肌肉拉伸睡着asleep双眼闭合且头部低垂或倾斜身体姿态放松不打哈欠no_yawn嘴巴闭合、无哈欠动作的常态帧。一个容易漏掉的关键点drowsy和asleep的区别在于「头部姿态」,昏昏欲睡时头部基本直立但眼皮下垂睡着时头部明显失力倾斜。标注规则里写清楚这一点模型训练时这两个类别的特征才能真正区分开。3.3 CVAT 建项目与导出 COCO JSON 的完整步骤在 CVAT 里创建一个新项目标签列表按上面的七类录入注意标签名必须和最终训练类别的英文 ID 一致避免导出后还要做名称映射。视频数据可以直接拖入 CVAT 做抽帧设置每 5 帧抽一帧再让标注员逐帧标注。21668 张图的规模建议 3 人协作每人负责不同视频片段对同一画面不重复标。导出时选择「Export dataset - COCO JSON 1.0」。导出的文件里licenses数组可能是空的某些下游工具读取时会报错自己补上一段即可。我在导出后必做一次「JSON 语法体检」python -m json.tool annotations.json /dev/null echo JSON 语法 OKjson.tool可以解析并重新格式化 JSON如果文件损坏会在这里直接报错比等你写训练脚本时再去 debug 要早发现问题。这一步相当于给标注结果上一道保险真等训练跑到一半发现json parse error再从 21668 张图里定位坏文件心态会崩。3.4 标注一致性检查同一个动作在不同人手里别标成两类多人标注最容易翻车的地方是「闭眼」和「昏昏欲睡」的边界。我的做法是从已标注图片里随机抽 5%让第二个标注员重新标一遍再用脚本对比两轮标注的category_id一致性。抽样检查脚本可以直接复用 2.2 节的统计逻辑只不过把coco[annotations]换成两份 JSON 的交集对比。如果两个标注员在「闭眼」和「昏昏欲睡」上的一致性低于 80%说明规则定义还不够明确。我会选择退回规则而非修改模型——模型无法学习两个标注员的主观差异。常见做法是在规则里补充参考截图把半闭眼、全闭眼、低头的边界帧各放一张示例图标注员对标示例图而不是凭感觉判断。4. 从 COCO JSON 到 YOLO 训练转换脚本与数据划分4.1 为什么要转换格式YOLO 训练不吃 COCO JSONYOLO 系训练框架YOLOv5/v8 等使用的是每张图一个同名 txt 文件的标注方式内容为类别ID x_center y_center width height坐标归一化到 0~1。COCO JSON 是全局集中式结构两者不互通。所以拿到coco json格式数据集后的第一件事就是写转换脚本把集中在 JSON 里的标注拆成 21668 个独立 txt 文件。这个转换脚本自己写并不复杂核心就是两步按image_id映射文件路径把像素坐标归一化。用 YOLO 官方仓库自带的转换工具也行但自带的工具默认按 8:1:1 划分数据行为数据集通常需要按视频片段划分避免同一视频的帧同时出现在训练集和验证集导致数据泄漏。4.2 转换脚本实现注意归一化边界和空标注文件import json import os from collections import defaultdict def coco_to_yolo(coco_path, output_dir): with open(coco_path, r, encodingutf-8) as f: coco json.load(f) img_map {img[id]: img for img in coco[images]} cat_map {cat[id]: idx for idx, cat in enumerate(coco[categories])} os.makedirs(output_dir, exist_okTrue) # 按 image_id 聚合所有标注框 anns_by_img defaultdict(list) for ann in coco[annotations]: anns_by_img[ann[image_id]].append(ann) for img_id, anns in anns_by_img.items(): img img_map[img_id] img_w, img_h img[width], img[height] txt_path os.path.join(output_dir, img[file_name].replace(.jpg, .txt)) lines [] for ann in anns: cat_id cat_map[ann[category_id]] x, y, w, h ann[bbox] # COCO 的 bbox 是左上角YOLO 需要中心点 x_center (x w / 2) / img_w y_center (y h / 2) / img_h w_norm w / img_w h_norm h / img_h # 归一化后超出 [0,1] 的框直接夹回边界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) w_norm min(w_norm, 1) h_norm min(h_norm, 1) lines.append(f{cat_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: coco_to_yolo(annotations.json, labels)这个脚本有几个关键参数需要说明。cat_map的重映射我按categories数组的枚举顺序重新编号而不是直接沿用category_id因为 COCO 的类别 ID 是任意整数YOLO 要求从 0 开始连续编号。归一化边界处理坐标理论上不会越界但标注手抖时会出现x w img_w的情况直接裁剪到 1 比报错退出更稳妥。file_name里的路径有的 COCO JSON 里file_name带子目录前缀需要os.path.join时先os.path.dirname提取。4.3 按视频片段划分数据集避免数据泄漏疲劳驾驶数据通常是从视频里按帧抽取的同一条视频里相邻帧高度相似。如果随机划分 train/val验证集里会出现和训练集几乎一样的画面mAP 虚高部署到实车上立刻穿帮。正确做法是先从file_name里抽出视频片段 ID比如文件名clip_07_frame_0012.jpg里的clip_07再用这个 ID 做分组划分。import random from collections import defaultdict clip_to_imgs defaultdict(list) for img in coco[images]: clip_id img[file_name].split(_)[0] # 例如 clip07 clip_to_imgs[clip_id].append(img[id]) clips list(clip_to_imgs.keys()) random.seed(42) random.shuffle(clips) val_ratio 0.15 val_clips set(clips[:int(len(clips) * val_ratio)]) test_clips set(clips[int(len(clips) * val_ratio):int(len(clips) * (val_ratio 0.1))]) val_ids [img_id for clip_id in val_clips for img_id in clip_to_imgs[clip_id]] test_ids [img_id for clip_id in test_clips for img_id in clip_to_imgs[clip_id]]划分完之后把val_ids和test_ids分别存成 txt 文件一行一个图片路径供 YOLO 训练时指定验证集和测试集。这里seed(42)固定随机种子是为了结果可复现——如果你跑完一轮实验想调整超参数训练集划分不变对比才有意义。不要小看这一步我见过有人忘设随机种子重新训练后指标涨了 2 个点实际是数据划分变了不是模型变强了。4.4 生成 data.yaml 并用 YOLOv8 拉起训练YOLOv8 需要一份数据集配置文件指定训练集、验证集路径和类别列表。注意类别顺序必须和转换脚本里的编号一致否则模型输出的类别索引会和实际标签错位。path: /data/fatigue_detection train: images/train val: images/val test: images/test names: 0: focused 1: drowsy 2: eye_closed 3: mouth_open 4: yawn 5: asleep 6: no_yawn然后用 YOLOv8 直接训练。针对疲劳驾驶这种小目标密集、局部特征明显的场景输入分辨率我一般设为 640 起步如果显卡显存足够调到 960 能把眼睛、嘴巴这些细节特征保留得更好。batch size 根据显存来8~32 之间调整学习率按默认值 0.01 跑一轮看 loss 曲线再决定。yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16这里选用yolov8n是轻量版适合先跑通流程。如果你最终要部署到车内算力平台n 系列或更小的 nano 版是合理选择如果是在服务器上刷指标可以换yolov8s或yolov8m但推理速度和 mAP 的权衡要你自己拍板。训练过程中重点盯两个指标各类别的per-class mAP和混淆矩阵。focused这种多数类通常一开始就涨得很快no_yawn这种负样本类别容易一直上不去后面排查看是标注问题还是样本量问题。5. 疲劳驾驶行为标注与训练的常见问题排查5 个踩坑记录5.1 JSON 解析报错日期字段反序列化失败现象用 Java 或某些标注平台读取 COCO JSON 时报json parse error: cannot deserialize value of type \java.util.Date from str脚本直接中断。原因COCO JSON 的info字段里date_created是字符串格式部分标注平台导出时写成了带时区偏移的长字符串而 Java 的ObjectMapper默认配置无法反序列化。严格说这是工具链问题不是数据集本身损坏。解决不跟它硬刚直接用 Python 的json.load读取并重新导出把日期字段统一成YYYY-MM-DD HH:MM:SS字符串格式如果你必须用 Java 读取给ObjectMapper配置JavaTimeModule并指定日期格式即可。这个坑让我养成了一个习惯任何 COCO JSON 到手先跑一遍python -m json.tool语法层面的问题在第一关就挡掉。5.2 转换 YOLO 格式后全部框跑到图片外现象用转换脚本生成 txt 后随便挑几张图用yolo自带的可视化脚本画框发现框全部贴在上边缘或左边缘面积异常。原因把 COCO 的[x, y, w, h]直接当成了 YOLO 格式里的[x_center, y_center, w, h]。两个格式的坐标基准完全不同不转换直接套用相当于每个框的左上角被当成中心点真实中心点全部偏移到画外。解决转换公式没有讨价还价的余地——x_center x w / 2y_center y h / 2再做归一化。写完脚本后抽 3 张图把原始像素坐标和 YOLO txt 坐标手动算一遍验证别依赖程序员的直觉。5.3 类别严重失衡闭眼、打哈欠样本远少于专注现象统计各类别框数时发现focused有 18000 多框yawn只有 600 框asleep不到 200 框模型训练后几乎只会输出focused。原因视频抽帧时按固定间隔抽取哈欠、闭眼是瞬时动作自然占比极低。标注团队如果按画面的时间顺序工作也会对高频出现的正常帧产生标注疲劳漏掉关键行为帧。解决抽帧策略从「均匀抽帧」改为「运动检测优先」。常见做法是先用一个预训练人脸关键点模型跑一遍视频检测到眼睛纵横比EAR或嘴巴纵横比MAR突变时密集抽帧正常时段稀疏抽帧这样能把哈欠和闭眼的候选帧数量提升一个数量级。训练侧给yawn、asleep适当提权重或做 mosaic 增强时每个 batch 强制包含一定比例稀有类别样本。5.4 昏昏欲睡和睡着、闭眼三个类别互相打架现象查看混淆矩阵时drowsy大量被预测为asleepasleep又和eye_closed纠缠不清三个类别的 mAP 都停留在 0.4 左右。原因这三个类别在视觉特征上有天然重叠——睡着必然闭眼昏昏欲睡也是半闭眼单帧静态图很难区分。标注规则里如果只写了「眼睛状态」而不写「头部姿态、身体姿态」两个标注员会按各自理解标注数据里就混入了噪声标签。解决重新梳理类别定义把「睡着」的判定条件加上「头部低垂 30 度以上或后仰、身体姿态放松」「昏昏欲睡」强调「头部基本直立但眼皮下垂」「闭眼」则剥离状态语义只描述眼睛闭合的几何事实。训练时也可以把三个类别合并成层级结构先训一个「眼睛闭合状态」检测器再做状态分类。5.5 CVAT 导出的 JSON 缺 licenses 或 info下游工具狂报错现象用 OpenMMLab 或 Detectron2 读取 COCO JSON 时提示KeyError: licenses或info字段缺失导致数据集初始化失败。原因CVAT 导出 COCO 1.0 格式时如果项目里没有配置 license导出的 JSON 里licenses数组为空列表部分旧版本工具会直接省略info字段。下游框架默认 COCO 完整结构读取时对缺失字段不做容错。解决导出后用 30 行小脚本补全标准licenses和info字段不要手工改 JSON——21668 张图对应十几万行标注手工改一个引号都能毁掉整份文件。补全后重新跑json.tool校验一次再进训练流程。6. 进阶用检测结果做疲劳状态时序判定与完整验证单帧检测只是第一步真正的疲劳驾驶判断依赖时间维度。我习惯把模型输出接一个滑动窗口统计器窗口长度取 30 秒步长 5 秒统计窗口内eye_closed和yawn两类检测框的时间占比。PERCLOS 标准的经验值是一分钟内眼睛闭合时间超过 40%或者连续 3 秒闭眼算一次微睡眠哈欠则按每分钟超过 2 次作为疲劳阈值。这套逻辑在车载 DMS 上比单纯看单帧检测结果可靠得多能过滤掉眨眼、说话等高频假阳性。验证时要关注的不只是整体 mAP。行为数据集里focused类别的 mAP 再高也说明不了问题关键是看eye_closed和yawn的recall。部署后的误报直接来自这两个类别的precision漏报则来自recall。我一般会在测试集上按视频片段逐帧跑一遍检测再统计报警事件级别的准确率——检测指标达标不代表时序判定达标因为时序窗口会放大单帧检测的错误。最后补一个验收技巧把测试视频的检测结果和标注框同时画到画布上按 10 倍速播放。肉眼扫一遍能立刻发现「打哈欠被分成两次检测」「闭眼瞬间漏检」这类指标看不出来的问题。我的习惯是每次训练完必做这步可视化巡检比看十张曲线图都好使。这套从数据检查、标注规则、格式转换到时序判定的流程我反复用了多个项目希望帮到你——尤其是那些标完才发现类别定义有歧义的同行规则先立住模型才不会给你表演翻车。本文还有配套的精品资源点击获取