疲劳驾驶行为数据集处理与YOLOv8训练实战:从zip解压到闭眼检测
简介面向计算机视觉和智能交通研究者的疲劳驾驶行为数据集已整理为VOC格式适合用于目标检测、驾驶员状态识别等相关课题的模型训练与算法验证。压缩包共2000个文件其中1997个xml文件提供标注框与类别信息3个txt文件分别为训练集、验证集、测试集划分列表整体大小255.77MB数据集按7:2:1比例切分免去自行划分的琐碎工作可直接输入YOLO、Faster R-CNN等常见检测框架也便于复现疲劳驾驶检测实验。资源内容围绕驾驶舱内疲劳特征如打哈欠、闭眼、视线偏移等展开既可作为疲劳驾驶检测系统的训练基准也适合研究者结合CNN、RNN等方法对比不同模型在真实驾驶场景下的识别效果。目前已有243人学习下载特别适合有一定深度学习基础、需要标准标注数据开展疲劳检测实验或毕业设计的学生与工程师。1. 疲劳驾驶行为数据集一份zip里装的不只是图片先给一句反直觉的话判断一份疲劳驾驶数据集好不好看的不是“图多不多”而是“闭眼瞬间和打哈欠顶点”有没有被单独拎出来标。很多从公开渠道下载的疲劳驾驶行为数据集-zip解压出来几万张图真正能用来训练闭眼检测的干净样本可能只有三分之一还有一批zip会在解压到一半时报“Central Directory not found”逼着人手动修。这个方向要解决的是让一个目标检测或关键点模型能从视频帧里稳定认出闭眼、打哈欠、低头这些驾驶状态而不是让模型把“眨眼”也当成疲劳。适合正在做驾驶员监控系统DMS、车队安全提醒或者想用yolov8训练自己的数据集的同学。后面几章只围绕一件事把zip变成能直接喂给模型的训练集并让Loss曲线真的降下去。2. 从zip压缩包到可用训练集解压、校验与目录清理2.1 为什么疲劳驾驶数据集普遍用zip分发常见做法是公开数据集和商业数据包都优先打成zip而不是rar或7z。原因很朴素zip在Windows、macOS、Linux三种系统上都是默认支持双击能解压、命令行也有unzip接收方不需要额外装Zip压缩大师之类的工具另一个原因是zip保留了目录结构数据集内部通常是images/和labels/两棵目录树打包成zip后目录关系不会乱。和coco2017数据集结构里那种“annotations里塞一份json”的做法不同疲劳驾驶行为数据集-zip里最常见的组织方式是图片文件夹加上一一对应的txt标注文件这种结构用zip分发最省事。下载这份zip时还要多看一层文件旁有没有附带md5或sha256校验值。公开渠道下载的数据集尤其是从网盘或huggingface下载数据集时文件在传输中可能损坏缺标注、坏图片都是传输问题留下的坑。看到zip先别急着双击把校验文件也一起下下来这一步能省掉后面“训练到一半Loss变成nan”的排查时间。2.2 解压后第一件事md5校验和zip完整性检查我一般会先跑一遍哈希校验确认不是“下载了个残缺包”再去碰解压命令。拿到的目录里如果有MD5.txt直接执行# 先校验压缩包完整性再决定是否解压 md5sum -c MD5.txt # 如果没有校验文件至少跑一次zip自带的测试模式 unzip -t fatigue_driving_dataset.zipmd5sum -c会读取MD5.txt里的文件名和哈希值逐项比对输出OK才算过unzip -t是zip自检接口不解压内容只检查压缩包的中央目录和每个分块的CRC校验值。这两种方式都要在训练前做因为zip文件在网盘被“秒传”后偶尔会出现结构完整但内部数据错位的情况直接解压会得到一批能打开但内容花屏的jpg。如果unzip -t报Central Directory not found别急着判死刑。这个报错有一种常见场景是zip伪加密文件看起来有密码锁但实际只有目录区被标记了加密位数据区没加密。这时候用7-Zip打开直接右键“提取到当前目录”很多伪加密包能绕过弹窗正常解出文件真遇到zip密码忘了的情况先确认来源方是不是给过解压密码没有就从数据包版本信息里找线索不要浪费时间试暴力破解。解压命令上Linux和macOS直接用unzipWindows命令行用tar -xf也能解zipWin10 1803以上自带的tar支持zip# 所有平台通用的做法解压到独立目录避免污染上层路径 # Linux / macOS mkdir -p ./fatigue_dataset unzip fatigue_driving_dataset.zip -d ./fatigue_dataset # Windows PowerShell Expand-Archive -Path .\fatigue_driving_dataset.zip -DestinationPath .\fatigue_dataset-d参数把文件释放到指定目录而不是当前位置这一步能防止数据集里的顶层目录名和别人冲突。解压完成后立刻du -sh看一眼目录体积和压缩包大小对比如果体积异常小说明有文件没解出来。2.3 清理隐藏文件和macOS冗余常见误伤源解压完的目录里经常混入一堆训练时完全用不到的东西__MACOSX/macOS压缩时自动生成的资源索引目录、.DS_Store、Thumbs.db。问题不在于它们占空间而在于做数据划分时这些垃圾文件会混进文件列表导致train/val划分时把非图片路径写进训练配置训练启动后报“image not found”或者“label not found”。清理命令# 删除macOS自带冗余目录和系统垃圾文件 find . -name __MACOSX -type d -exec rm -rf {} \; find . -name .DS_Store -delete find . -name Thumbs.db -deletefind . -name ... -exec的写法在Linux和macOS都通用Windows上如果装了Git Bash也能跑。这一步做完之后再检查一下压缩包内部的路径是不是带了中文字符。疲劳驾驶数据集大多来自研究机构或众包标注平台中文文件名在服务器之间转手时编码容易坏解压后出现乱码文件名会让yolov8的数据加载器直接跳过对应图片还不报错——只在日志里静默显示0 images。遇到这种文件批量重命名成纯英文和下划线# 把文件名中的空格和中文替换成下划线再做后续处理 find . -type f | while read f; do newname$(echo $f | tr _ | iconv -f UTF-8 -t ASCII//TRANSLIT 2/dev/null) [ $f ! $newname ] mv $f $newname done这段命令逐行读文件路径把空格换成下划线再用iconv把非ASCII字符替换成近似拉丁字母2/dev/null把转码失败的杂音压掉。处理完再看一眼目录结构至少要做到所有jpg、png、txt的父路径里不含空格和中文后面做数据集划分才不会被路径解析坑到。3. 读懂疲劳驾驶数据集的目录结构与标注格式3.1 典型目录结构image、label与annotations的组织方式解压并清理完先把目录树打出来看全貌tree -L 2 ./fatigue_dataset一般会看到两种布局。第一种是YOLO原生布局fatigue_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classes.txt这种布局最省事图片和txt按train/val/test分好放yolov8里直接配置路径就能训练。第二种是COCO风格布局fatigue_dataset/ ├── train/ │ ├── images/ │ └── annotations/ ├── val/ │ ├── images/ │ └── annotations/COCO风格下的标注集中在annotations里的json文件记录格式是“图像ID 标注框 类别ID”不细看json结构的人很容易在后续转YOLO格式时把框坐标算错。疲劳驾驶行为数据集-zip里两种布局都常见顺手说一句COCO2017数据集结构就是这个样子习惯之后换其他目标检测数据集会少踩一遍坑。3.2 标注坐标系从像素框到yolo归一化框YOLO格式的txt每一行是class x_center y_center width height全部数值除以图片宽高做归一化落在0到1之间。疲劳驾驶数据集的原始标注如果是COCO json或VOC xml得先转成这个格式。转换的核心是把绝对坐标换成相对坐标import json # 读取COCO格式标注转换成YOLO txt with open(annotations.json, r, encodingutf-8) as f: coco json.load(f) # 建立图片id到文件名的映射 img_map {img[id]: img[file_name] for img in coco[images]} # 建立类别id到类名的映射并给每个类一个从0开始的索引 cat_map {cat[id]: idx for idx, cat in enumerate(coco[categories])} for ann in coco[annotations]: img_id ann[image_id] cat_id cat_map[ann[category_id]] # COCO框格式是[x, y, width, height]左上角为原点 x, y, w, h ann[bbox] # 同步读取图片尺寸这里假设从图片元数据取 image_w coco[images][0][width] image_h coco[images][0][height] # 转成中心点归一化坐标 cx (x w / 2) / image_w cy (y h / 2) / image_h nw w / image_w nh h / image_h out_line f{cat_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n # 按图片名写入对应的txt with open(img_map[img_id].replace(.jpg, .txt), a) as f: f.write(out_line)这段脚本里最容易被忽略的是image_w和image_h不能从单张图片统一取因为一个数据集里所有图片未必同尺寸。实际使用时先构建一张“文件名→宽高”的映射表再去遍历标注才不会被过小的图搞出width越界。转完之后抽样检查随意挑几张图把txt里的坐标乘以图像宽高看还原出的框是否贴合眼睛或嘴巴区域这一步是检验转换脚本正确性的最低成本办法。3.3 数据质量初检类别分布、空标注与坏图排查花五分钟对数据做一次体检能避免训练到一半才发现标注大面积失效。import os from collections import Counter from PIL import Image image_dir fatigue_dataset/images/train label_dir fatigue_dataset/labels/train label_count Counter() empty_label 0 bad_image 0 for img_name in os.listdir(image_dir): # 检查图片是否损坏Image.open后调用load才会真正解码 try: img Image.open(os.path.join(image_dir, img_name)) img.load() except Exception: bad_image 1 continue # 一一对应的txt路径 label_path os.path.join(label_dir, img_name.replace(.jpg, .txt)) if not os.path.exists(label_path): empty_label 1 continue with open(label_path, r) as f: lines [line.strip() for line in f if line.strip()] if len(lines) 0: empty_label 1 for line in lines: label_count[line.split()[0]] 1 print(类别分布:, dict(label_count)) print(空标注数量:, empty_label) print(损坏图片数量:, bad_image)img.load()是真正的解码动作文件缺失或截断在这里会抛异常只open不load是检测不出坏图的。label_count统计每个类别的目标数量最关心的两类是closed eye和open eye。如果发现闭眼样本只有睁眼样本的十分之一这不是bug而是疲劳驾驶数据集的通病戴眼镜的驾驶员闭眼标注更难做样本量天然偏少这个信息要留给训练阶段去决定要不要做类别加权。空标注文件也要单独看一张人脸特写里只有睁眼状态模型要拿它学“不疲劳”的负样本这类空txt其实不是垃圾反而是平衡类别的重要素材别顺手删光。4. 用yolov8把疲劳驾驶行为数据集跑起来最小训练流程4.1 制作dataset.yaml并重新划分train/val拿到手的zip里train/val划分未必合理常见问题是同一段视频里的连续帧被同时分进train和val导致验证指标虚高。稳妥做法是重新划分并且按“视频片段”而不是“单帧”来分否则模型在val上看到的画面几乎和train里长得一样跑出来的mAP没有任何参考价值。import os import random import shutil random.seed(42) # 假设所有图片在一个大目录里按前缀文件名区分视频片段 all_images [f for f in os.listdir(images_all) if f.endswith(.jpg)] # 按视频片段id聚合文件名前8位通常是视频片段编号 grouped {} for img in all_images: video_id img.split(_)[0] grouped.setdefault(video_id, []).append(img) video_ids list(grouped.keys()) random.shuffle(video_ids) # 80%的视频片段做训练20%做验证 split_idx int(len(video_ids) * 0.8) train_videos set(video_ids[:split_idx]) val_videos set(video_ids[split_idx:]) for img in all_images: dst_dir images/train if img.split(_)[0] in train_videos else images/val shutil.copy(os.path.join(images_all, img), os.path.join(dst_dir, img))划分完图片后把同名txt也按同样规则拷到labels目录这个对应关系不能丢。有了目录结构写dataset.yaml# dataset.yaml path: ./fatigue_dataset train: images/train val: images/val nc: 3 names: 0: open_eye 1: closed_eye 2: yawnnc是类别数量names的排列顺序必须和txt第一列的数字对应上。很多人在这一步把names列表写错位置比如把closed_eye写成1、open_eye写成2训练不会报错但最终输出结果完全错位模型学了半天学的是“睁眼闭眼”的标签。这层对应关系在生成txt时就要定死后面不要改。4.2 训练命令与四个必调参数用yolov8训练自己的数据集命令不复杂复杂的是参数选择yolo detect train \ datafatigue_dataset.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ patience10 \ cos_lrTruemodelyolov8s.pt是从官方预训练权重起步比随机初始化收敛快得多疲劳驾驶数据集再大也就几万张图用m或l体量反而容易过拟合。imgsz640是默认值但闭眼这个目标其实很小如果算力允许imgsz960能让小目标特征更明显显存不够就保持640并配合下面第5章的切片技巧。batch16在8G显存左右能跑batch太小会让BN层的统计量不稳定Loss曲线会出现锯齿状patience10表示10个epoch验证指标不上升就早停这不是偷懒是防模型在疲劳驾驶数据集上死磕到最后把噪声都背下来。cos_lrTrue配合余弦退火学习率前半段快学、后半段细调对闭眼这种精细特征比固定学习率好收敛。训练日志要盯两个点第一是box_loss和cls_loss是不是整体下降偶尔反弹正常连续20个epoch不降就要怀疑学习率太大第二是val的mAP50-95有没有在最后20个epoch还在缓步上升如果还在升说明epochs加到150可能还能涨一点。4.3 训练完成后如何验证混淆矩阵、PR曲线和误检样例训练结束在runs/detect/train目录下看这几个文件confusion_matrix_normalized.png里看closed_eye被识别成open_eye的比例这是疲劳驾驶检测里最不能容忍的错——把闭眼判成睁眼等于漏报疲劳。如果这个比值超过10%回去检查标注很多数据集里戴眼镜的人闭眼框和睁眼框本身边界模糊标注员凭感觉给的框模型学到的是“眼镜反光睁眼”这种假规则。再打开results.png看F1曲线和PR曲线重点看closed_eye类别的PR曲线下面积。如果PR曲线在召回率0.8附近骤降说明模型对闭眼的检出能力集中在高置信度区间需要把conf_thres调低到0.15再跑一批验证yolo detect val \ modelruns/detect/train/weights/best.pt \ datafatigue_dataset.yaml \ conf0.15 \ save_jsonTruesave_jsonTrue会在验证时输出每张图的预测框方便下一步做误检抽帧。直接把预测结果可视化出来按置信度排序看top-20的误检长什么样——这一步能发现很多指标上看不出的问题比如车载遮阳板被当成闭眼、下颌阴影被当成打哈欠。这些误检细节直接决定模型能不能真上车而不只是“论文指标好看”。5. 疲劳驾驶数据集训练避坑zip、标注与Loss曲线上的血泪经验5.1 zip伪加密导致解压报“Central Directory not found”现象解压到一半提示Central Directory not found压缩包看起来有密码锁试了网上说的常见密码全部无效重新下载两遍还是一样。原因数据包在打包时对zip的目录区做了加密标记但文件数据区并没有真正加密这种技术叫zip伪加密。很多网盘分享的哈希校验文件在转存后没有被篡改于是hash对得上、内容解不开非常迷惑人。解决换7-Zip打开压缩包选中文件后直接点“提取”不去管密码弹窗如果7-Zip也拦用Python的zipfile库手动绕过import zipfile # 尝试忽略伪加密标志直接读取内部文件 zf zipfile.ZipFile(fatigue_driving_dataset.zip) for info in zf.infolist(): # 把标志位末位清零去掉加密flag再读取 info.flag_bits 0xFFF7 with zf.open(info) as f: data f.read()0xFFF7按位与操作把第3位加密位清零zf.open就能直接读取原本被加密标记挡住的文件。跑完之后把data二进制内容写到本地文件整个包就救回来了。这个办法只对伪加密有效真加密的包这样读会得到乱码数据不要当成万能密码钥匙。5.2 中文路径与文件名编码导致标签读取失败现象训练启动后日志显示train: 0 images或者训练能跑但val的mAP全是0打开数据加载器日志发现大量“Invalid label”静默跳过。原因数据集里图片叫“驾驶员_闭眼_001.jpg”这类中文名txt里叫“驾驶员_闭眼_001.txt”。Windows上没问题Linux服务器上locale不是UTF-8时Python读文件名拿到的字节序列和硬盘上的实际字节序列不一致标签文件和图片文件就关联不上Loader把所有未匹配的txt当成不存在。yolov8的数据加载器只在debug日志里打印这种跳过很多人看到mAP为0才回头看数据。解决先用第2章的批量重命名把所有路径改成纯ASCII再在数据加载前进一次文件名严格校验# 检查所有jpg是否有同名txt列出不成对的 find images/train -name *.jpg | while read img; do labellabels/train/$(basename ${img%.jpg}.txt) [ ! -f $label ] echo missing: $img done输出结果里凡是路径含中文或空格的jpg基本都会报missing。处理完这些之后记得删除yolov8缓存再训练rm -rf runs/detect/train/否则旧的未匹配缓存还会被加载。5.3 类别不平衡疲劳样本占比过低时mAP虚高但实际不可用现象训练完看results.pngmAP50高达0.93但真把best.pt放到一段夜间行车视频里测闭眼检出率惨不忍睹反而是睁眼状态被反复误报成闭眼。原因疲劳驾驶数据集的多数帧里驾驶员是正常睁眼状态closed_eye和yawn目标数可能只有open_eye的5%。模型只要学会“全都判成睁眼”准确率就能到95%以上mAP指标被带跑偏实际场景自然翻车。用imgsz640时这个问题更严重小目标闭眼在特征图上只剩几个像素模型根本没有动力去学它的细节。解决先从检视类别分布开始print(label_count)里如果最少类和最多类差距超过10倍先做类别人工扩样把闭眼帧和打哈欠帧在训练集里重复拷贝2-3次再配合yolov8的class_weights或写一个自定义Loss加权。模型结构上把输入分辨率提到imgsz960并开启augmentTrue让mosaic增强增加小目标的多样性。跑完之后不要只看val的mAP单独抽200帧闭眼连续帧做一次实际推理统计“连续闭眼帧里检出闭眼的比例”这个数字才是真实可用性指标。5.4 打哈欠嘴部小目标anchor尺寸不合适导致小目标漏检现象闭眼检测尚可但打哈欠的嘴部张口状态经常漏检尤其当人脸在画面里占比偏小时yawn类别的召回率直接掉到0.3以下。原因yolov8预设的anchor尺寸是针对coco这种通用目标设计的最小的先验框对“半张脸里的嘴”来说依然过大在原始图像里嘴部区域往往不到40×40像素经过8倍下采样后只剩5×5像素左右特征几乎被卷积层抹掉。解决把训练分辨率提高到imgsz1280是最直接的手段但显存开销翻倍。内存卡得紧时就做切片推理先把原图按人脸检测框裁出来只把人脸区域送入检测器。常见做法是在疲劳驾驶模型前挂一个人脸检测模型把检测到的人脸crop后resize到448×448再喂给闭眼/打哈欠检测器。这个方案对闭眼小目标的提升比任何参数调优都明显缺点是端到端延迟增加十几毫秒但车载场景每秒处理10帧是足够的。5.5 关键点方案 vs 检测方案闭眼标注不稳定时该选哪个现象用闭眼检测框方案做精度调优怎么调都到不了生产要求换一个数据包重新训练指标立刻上涨一大截怀疑是标注质量不稳定。原因疲劳驾驶行为数据集的“疲劳”天然是模糊概念有的标注员把“轻微闭眼”标成闭眼有的标成睁眼检测框方案要求每张图给出精确的左右眼两个框标注员主观偏差被直接放大成模型噪声。而关键点方案只需要标眼睛的6个关键点上下眼睑、内外眼角模型学的是“眼睑间距的几何比例”对框的边界误差不敏感。PERCLOS指标本质也是算眼睑开合曲线和关键点方案是同构的。解决如果手里数据集的txt是框格式但明显感觉闭眼/睁眼边界标得飘换成关键点检测是合理路径。从检测框反推眼睑关键点不可行因为框里不含上下眼睑的具体位置更实际的做法是直接用现成的人脸关键点模型如mediapipe的Face Mesh对原图重跑一遍取左眼、右眼各6个关键点做新数据集再训练一个轻量的关键点回归或分类模型。代价是多一轮数据处理换来的是对标注噪声的高容忍度。6. 让疲劳驾驶检测真正可用的几个进阶技巧单帧检测做得再准也不等于疲劳驾驶系统能落地原因是“疲劳”是一个时间维度的概念。眨眼闭眼不到200毫秒模型误报一次闭眼完全正常真正的疲劳判定要结合连续多帧统计。我最后的项目里用的是PERCLOS接近算法在10秒时间窗内闭眼帧占比超过40%才判定疲劳。用模型的输出置信度直接做平滑比加规则更能抗抖动。# 连续帧闭眼判定滑动窗口统计闭眼比例 closing_frames [] WINDOW 150 # 10秒15fps THRESHOLD 0.4 for frame_result in video_stream: is_closed frame_result[closed_eye_conf] 0.5 closing_frames.append(is_closed) if len(closing_frames) WINDOW: closing_frames.pop(0) close_ratio sum(closing_frames) / len(closing_frames) if close_ratio THRESHOLD: alarm_triggered TrueTHRESHOLD0.4对应的事故防控经验值短时快速眨眼会被窗口平滑掉不会误报。部署前还要做一件事采集一段真实行车视频包含不同角度光照和戴眼镜/不戴眼镜的驾驶员把best.pt跑一遍人工记录漏报帧的时间点回头对照标注看这些帧是不是都集中在“眼镜反光”或“逆光”场景。如果是就针对这些场景补数据不要盲目增加横向数据量。这套流程走下来真正让我长记性的教训是疲劳驾驶数据集最贵的不是标注框而是“闭眼时刻”和“打哈欠顶点”的精准捕捉模型结构再花哨数据集的类别分布和路径卫生不解决训练只是在浪费时间。希望这些实际操作能帮你在下一份zip上少走一段弯路。本文还有配套的精品资源点击获取