
简介疲劳驾驶监测数据集是一份面向深度学习与图像识别初学者的入门练习数据以人在疲劳状态下的真实照片为素材重点刻画打哈欠张嘴状态和闭眼状态可用作图像分类、目标检测等小型项目的训练样本。压缩包内包含2915张JPG格式图像与2914个XML格式标注文件合计约5800个文件包体大小256MBXML标注遵循VOC格式可直接对接主流检测框架省去数据格式转换的麻烦。目前已有4742人浏览学习资源热度较高。针对原始数据中图片与标注不匹配的常见问题包内附有Python清理脚本可自动移除无对应标注的图片减少人工清洗工作量。同时目录按JPEGImages与Annotations分目录存放结构清晰适合刚接触视觉任务的新手走通数据整理、样本划分、模型训练全流程也可作为课堂练习或课程设计的基线数据集。1. 疲劳驾驶监测数据集一个让新手少走弯路的练手起点疲劳驾驶监测是人工智能图像识别里一个很适合新手落地的方向这份疲劳驾驶监测数据集收录了人打哈欠、闭眼状态的照片并配套了 VOC 格式的 XML 标注文件。我刚拿到时第一反应是直接丢给训练脚本结果跑了一轮精度惨不忍睹后来才发现原数据里图片与标注存在不匹配的问题不清理就直接训练等于把脏数据全喂给了模型。把不匹配的样本剔掉之后以张嘴打哈欠和闭眼作为两类疲劳标准整套流程就是一次完整的目标检测入门实战。2. 数据集结构拆解图片与XML标注配对的完整流程2.1 两个目录的对应关系解压数据集之后第一眼看到的目录层级是Dataset/dataset/底下并排放着JPEGImages和Annotations两个文件夹。JPEGImages 里全部是 .jpg 图片就是模型真正要读的视觉输入Annotations 里放 VOC 格式的 .xml 标注文件负责告诉模型每张图里哪个位置是什么状态。疲劳驾驶采集的照片经过处理后通常被统一改成数字编号做归一化这份数据里能看到 1429、894、1062、2311、2582、2081、1529、1250、1992、1500 这类编号说明原始收集来自不同批次。一个合格的目标检测数据集图片和标注必须一一对应。1429.xml 对应 1429.jpg894.xml 对应 894.jpg这种命名规范在开源数据集里已经是事实标准。我接手项目时有个习惯先对两个目录做一次配对检查。ls JPEGImages | sed s/\.jpg$/.xml/ | sort /tmp/jpeg_list.txt ls Annotations | sort /tmp/anno_list.txt diff /tmp/jpeg_list.txt /tmp/anno_list.txt这段命令把 JPEGImages 里的 .jpg 文件名全部替换成同名 .xml再和 Annotations 目录列表做排序对比。diff 输出左边是图片目录有而标注目录没有的文件右边正好相反。如果两侧完全一致说明数据集干净一旦有输出就已经能闻到翻车的气息了。2.2 XML标注内容与疲劳标准的对应随便挑一个 1529.xml 打开看标准 VOC 结构大致是这样annotation filename1529.jpg/filename size width640/width height480/height depth3/depth /size object nameclosed_eye/name bndbox xmin210/xmin ymin155/ymin xmax285/xmax ymax192/ymax /bndbox /object /annotationfilename 字段与图片名对应size 记录原始图片宽高object 里的 name 是目标框的类别bndbox 里四个值分别是框的左上角和右下角坐标。摘要里建议把打哈欠张嘴状态和闭眼状态作为疲劳标准来训练说明这份数据集的标注设计本身支持两类目标。打开之前我不确定 name 字段具体叫 yawn 还是 closed_eye但至少要能区分出张嘴和闭眼两种状态如果全部混成一个标签训练前就需要先处理标签语义。很多人在这一步会忽略一个细节bndbox 里的坐标是绝对像素值还是归一化小数决定了后续训练脚本怎么写。这份数据集看着是 VOC 格式VOC 规范里就是绝对像素坐标范围受 width 和 height 约束。如果你发现某个 XML 里 xmax 比 width 还要大几倍那基本可以断定这是坏标注留着它训练损失函数会在这一项上反复震荡。2.3 不匹配问题出在哪个环节摘要已经明确提示原数据集存在图片和标注不匹配的问题。根据我拆解这类数据集的经验产生这种状况的原因大致有三个一是标注过程中某张源图被误删但标注工具不会自动清掉对应的 XML于是 Annotations 里多出孤儿标注二是导出时文件名编码出问题编号被改写过导致配对关系断掉三是多个批次合并数据时拷贝中断只复制了图片没复制 XML或者反过来。不管原因是什么表现都一样JPEGImages 里某些 .jpg 在 Annotations 里找不到同名 XML又或者 Annotations 里存在找不到源图的 XML。模型训练时数据加载器靠文件名做匹配匹配失败的处理方式各框架还不一样有的直接抛异常卡住训练有的静默跳过该样本。静默跳过最坑你看着每个 epoch 的 loss 在下降实际上模型少看了一批数据而不自知。所以动手训练之前先把这个环节堵住。3. 核心模块实战清洗脚本逐行拆解与参数说明3.1 清洗前的样本统计写删除脚本之前先统计两遍。这一步看起来很基础但能直接算出需要清理的文件数量跑完之后还可以对照验证清洗是否彻底。import os jpeg_dir Dataset/dataset/JPEGImages anno_dir Dataset/dataset/Annotations jpeg_list os.listdir(jpeg_dir) anno_list os.listdir(anno_dir) print(f图片数量: {len(jpeg_list)}) print(f标注数量: {len(anno_list)}) jpeg_names {os.path.splitext(f)[0] for f in jpeg_list} anno_names {os.path.splitext(f)[0] for f in anno_list} only_pic jpeg_names - anno_names only_xml anno_names - jpeg_names print(f缺少标注的图片: {len(only_pic)} 张) print(f缺少图片的标注: {len(only_xml)} 份)用集合做差集比逐个 list 遍历快一个量级几万张图也扛得住。这里有个细节os.path.splitext(f)[0]只去掉最后一个扩展名比手动split(.)[0]更安全文件名带点号时不会拆错。统计完你会看到 only_pic 和 only_xml 两边的规模这个数字决定删除策略——如果只有图片缺标注删图片如果两边都有多余文件两个目录都删如果数量一样但编号对不上那是错位问题不能盲删得先做名称对齐。3.2 删除无标注图片的脚本摘要里给出的清洗代码就是核心动作我基于它补充了路径拼接和删除计数下面这段可以直接复用import os jpeg Dataset/dataset/JPEGImages jpeg_list os.listdir(jpeg) anno Dataset/dataset/Annotations anno_list os.listdir(anno) removed 0 for pic in jpeg_list: name pic.split(.)[0] anno_name name .xml if anno_name not in anno_list: pic_path os.path.join(jpeg, pic) os.remove(pic_path) removed 1 print(f已删除: {pic_path}) print(f共删除 {removed} 张无标注图片)逐行看逻辑pic.split(.)[0]按点分割取第一段把 1429.jpg 变成 1429再拼接成 1429.xml 去 anno_list 里做成员判断。找不到对应标注就执行os.remove把源图删掉。os.path.join在这里值得注意它负责把目录和文件名拼成完整路径Windows 和 Linux 下分隔符不同手动拼字符串容易在跨平台时踩坑。直接删文件是不可逆操作我一般会在第一遍清洗时把删除改成移动到 deleted 目录确认无误再物理清空。训练之前再用 len(jpeg_list) 和 len(anno_list) 对数两者一致了才敢进下一步。3.3 清理多余 XML 与校验闭环只删图片不够如果 Annotations 里还剩孤儿 XML后续划分训练集和验证集时样本统计会不准。补一段对称逻辑for xml_name in anno_list: name os.path.splitext(xml_name)[0] pic_name name .jpg if pic_name not in jpeg_list: xml_path os.path.join(anno, xml_name) os.remove(xml_path) print(f已删除孤儿标注: {xml_path})这段语义和删图片完全对称XML 在而图片不在说明是残留标注删掉。跑完后建议抽三个 XML 打开看 filename 字段是否与磁盘上图片名一致防止出现「文件名能对上但 XML 内部记录的 filename 还是旧名字」的假配对。import xml.etree.ElementTree as ET sample Dataset/dataset/Annotations/1529.xml tree ET.parse(sample) root tree.getroot() filename root.find(filename).text print(fXML 内 filename: {filename}) import os pic_path os.path.join(Dataset/dataset/JPEGImages, filename) print(f图片是否存在: {os.path.exists(pic_path)})这里用的是 Python 标准库xml.etree.ElementTree不用额外装依赖。走完这一段清洗闭环才真正拿到一份干净可训练的数据集。我见过不少人清洗后训练还在报错最后排查发现是训练代码里的路径少了一层 dataset 目录和清洗脚本用的不是同一个基准路径这种问题后面还会细说。4. 训练避坑指南标注质量检查与常见问题排查4.1 现象训练正常结束但 mAP 很低如果是没做清洗直接进训练或者清洗时只删了图片没删 XML数据加载器在处理每个 epoch 时都会反复尝试打开不存在的标注文件。某些框架会选择静默跳过这部分样本结果就是你以为用了两千多张图训练实际参与计算的只剩下一小半mAP 能高才怪。解决方法是训练前强制跑一遍配对校验确认图片和标注一一对应后再划分训练集和验证集。这个顺序不能反先删干净再训练数据集质量决定模型上限。4.2 现象dataloader 报 FileNotFoundError 导致训练中断我自己踩过的一个坑是清洗脚本没有任何问题文件也全对上了但训练代码里 Annotations 路径写的是Dataset/Annotations实际应该是Dataset/dataset/Annotations少了一层目录。有些框架不会直接报路径不存在而是吞掉异常返回空列表然后 dataloader 读到 0 个标注训练直接卡住。解决这类问题最有效的办法是把清洗脚本和训练脚本共用一个配置文件里的路径常量不要在训练代码里重新手写一遍目录。路径写死是脏数据之外最容易翻车的地方而且翻得悄无声息。4.3 现象闭眼和打哈欠两类样本比例失衡这个数据集的疲劳标准是张嘴打哈欠和闭眼两类状态。清洗之后经常出现一类多一类少的情况模型会偏向样本量大的那类直观表现就是某一个类别的 AP 明显高于另一个。处理方式有三种一是对少样本类别做在线增强比如随机旋转、亮度扰动、轻微裁剪这些在 transform 里加几行就能生效二是给损失函数按类别加权重少样本类的 loss 乘一个大于 1 的系数这个在新手阶段容易调过头不推荐优先尝试三是如果两类数量差得实在太远先用单个类别人脸做二分类把流程完整跑通目标检测留到后面数据扩充了再上。数据量不够的时候降级任务比硬训更实际。4.4 现象增强后的图片出现黑边或标注框错位数据增强和目标框坐标联动是新手最容易翻车的地方。如果只对图片做了随机裁剪、缩放或翻转却忘了同步更新 XML 里的 bndbox那模型训练时看到的框位置就全错了。用增强库的时候尤其要注意 bbox 参数是否开启增强库会在返回结果时同时给出变换后的框坐标前提是你显式传入原始框。另外增强之后一定要写个可视化脚本抽查把框画回图上人工确认import cv2 image cv2.imread(Dataset/dataset/JPEGImages/1429.jpg) box [210, 155, 285, 192] # xmin, ymin, xmax, ymax cv2.rectangle(image, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2) cv2.imwrite(check.jpg, image)注意cv2.rectangle的坐标参数是左上角和右下角两对点也就是 (x1, y1, x2, y2)不是有些人写代码时习惯的 (x, y, w, h) 中心点加宽高格式。这个顺序一旦写错画出来的框会从目标点斜向偏移典型特征就是框的对角线是对的但位置整体偏了。画出来瞄一眼框是否紧贴嘴巴或眼睛区域肉眼一秒就能判断增强是否有效。5. 从分类到检测在这个数据集上做二次开发的三条路线路线一先做图像分类。不碰目标检测的框直接把图片缩放到统一尺寸按闭眼和非闭眼、打哈欠和非打哈欠做二分类。自己动手搭一个三层卷积加全连接的简单 CNN或者直接套现成分类网络训练目标是图片级标签。这个方案对标注噪声容忍度高、跑得最快适合验证清洗后的数据到底能不能学出疲劳特征。路线二主流目标检测微调。清洗完数据之后上检测框架需要先把 XML 转成对应训练格式的 txt 标注。转换核心逻辑就是读 bndbox 的四个值分别除以图片宽高得到归一化坐标再按类目编号写进 txt写个十几行脚本就能搞定。注意转换时顺便检查坐标是否越界越界的框直接过滤掉。路线三先分类后检测的组合管线。先用图像分类模型判断当前帧是否存在疲劳状态存在时再调用目标检测模型定位眼睛和嘴的具体位置。这种两阶段结构在真实监控场景里比单模型更常见因为不是每帧图像都值得跑一次检测分类器先过滤掉大量正常状态帧计算成本会低很多。我通常选择路线一作为第一版验证它能用最短时间确认数据本身能不能用。等分类精度稳定了再切到路线二去迭代框的精度这时候如果发现清洗后的数据量撑不起目标检测模型收敛优先做数据增强而不是急着找更多数据增强的顺序放在框坐标同步更新之后别让脏框污染了增强结果。从那以后我每次拆数据集都强制走一遍统计、清洗、抽检、增强四步流程顺手把 check_annotation 脚本固化成模板放进项目根目录省下来的调试时间远比写脚本那半小时多。希望帮到你。本文还有配套的精品资源点击获取