
简介目标检测是计算机视觉中的核心任务其本质是在图像中定位并分类多个物体。在工程实践中基于YOLO系列模型的目标检测方案因端到端推理和实时性优势被广泛应用于智能座舱、安防监控等领域。对于驾驶员监控系统DMS而言通过检测眼睛与嘴巴的局部状态——睁眼、闭眼、张嘴、闭嘴可有效判断疲劳驾驶行为。这一任务对数据标注质量与格式兼容性要求极高。VOC格式的XML标注因携带丰富的附加信息成为数据管理与工具链适配的优选。本文围绕一套专为YOLO设计的驾驶人睁闭眼张合嘴检测数据集解析其标注规范、字段含义及转换脚本并分享基于YOLOv8训练与调优的完整流程。从数据集质量检查到模型推理部署为开发者提供一套可复用的工程实践路径帮助快速构建疲劳驾驶检测应用。 最近在做一个疲劳驾驶检测的项目核心任务就是通过摄像头实时判断驾驶员是否闭眼、是否打哈欠。这个场景看着简单真落地的时候才发现最难的其实不是模型而是数据。今天分享一个我整理并实战过的数据集——驾驶人睁闭眼张合嘴检测数据集图片xml格式标签主要针对YOLO系列目标检测模型包含清晰的睁眼、闭眼、张嘴、闭嘴四类目标标注。无论你是刚入门目标检测还是在做DMS驾驶员监控系统相关产品这套数据都能帮你省掉大量标注时间直接跑到可用的精度。先说结论这个数据集以VOC格式xml标签发布兼容性极好既能喂给YOLOv5、YOLOv8也能转成COCO或YOLO txt格式内容聚焦于驾驶舱场景下的人脸局部区域标注边界框针对眼睛和嘴巴类别划分干净几乎没有无效冗余标签。接下来我会把这套数据集的字段含义、标注规范、转格式脚本、训练配置、踩坑记录全部分享出来方便你拿回去直接用。1. 项目概述与数据集设计思路1.1 数据集要解决的核心问题驾驶员疲劳驾驶检测是智能座舱和车路协同里的重要一环但这里说的“疲劳”不是靠心率或者方向盘转角去间接推断而是直接盯住面部状态眼睛闭合时间过长说明驾驶员可能在打瞌睡嘴巴连续张大并保持说明在打哈欠这两种状态一叠加疲劳概率就非常高。所以这个数据集本质上是在做一个“局部状态分类定位”的任务在摄像头画面里把眼睛区域和嘴巴区域用矩形框框出来并标出它们当前的状态。这里有个容易混淆的点它不是在整张人脸图像上做表情分类而是对眼睛和嘴巴两个局部目标做检测和状态判断。用YOLO这类目标检测模型来做好处是端到端一次推理就能同时输出位置和类别不需要先做人脸关键点再裁剪分类省掉一整套pipeline。1.2 为什么选择YOLO模型和xml格式YOLO之所以适合这个场景主要是速度和精度的平衡。在车机或者嵌入式设备上算力有限但要求实时YOLOv5s/v8s这类轻量级模型在GPU上能达到几百FPS在边缘设备上也能跑出30FPS以上足够满足驾驶监控的实时性。而且YOLO的anchor机制对眼睛、嘴巴这种小目标相对友好只要数据标注到位mAP能做到很高。至于xml格式其实是沿用了Pascal VOC的标准标注格式。很多人一看“xml”就头疼觉得不如yolo的txt简洁。但从数据管理和工具链角度看xml有它不可替代的优势首先xml除了框坐标还能携带大量附加信息比如图片尺寸、通道数、标注来源、是否被截断、是否难以辨认等这些信息在做数据清洗和难例分析时非常有用其次LabelImg、CVAT这些主流标注工具默认支持xml格式后续你想补充数据、修正标注不用换工具链。如果你只想训练YOLO那最终肯定要转成txt格式但保留一份xml作为原始数据资产绝对是好习惯。后面我会给出转换脚本几分钟就能完成。1.3 数据集构成与类别分布这个数据集的图片内容都是真实或仿真驾驶场景下的人脸近景以驾驶员面部为主有时包含部分身体和车内背景。标注类别总共四类类别名称含义典型场景eye_open眼睛睁开正常驾驶、扫视后视镜eye_close眼睛闭合打瞌睡、眨眼瞬间mouth_open嘴巴张开打哈欠、说话、喊叫mouth_close嘴巴闭合正常表情、抿嘴从目标尺寸来看眼睛区域通常只有几十乘几十像素嘴巴稍大一些整体都属于小目标范畴。所以如果直接用原始图片训练YOLO的小目标检测层至关重要如果效果不佳可以尝试把脸区域先裁剪出来放大再喂给模型检测眼睛和嘴巴也就是常见的“两阶段脸部局部检测”思路。2. 核心细节解析与标注规范2.1 四类目标的定义与边界判断很多新手在拿到类似数据集时只关心“能跑通”却不关心标注的边界条件。实际上这类数据的标注质量直接决定模型上限。先看眼睛眼睛睁开瞳孔和眼白清晰可见眼睑没有覆盖瞳孔大部分区域标注框需完整包裹上下眼睑和眼角。眼睛闭合上下眼睑基本贴合瞳孔不可见或仅露出极小部分。注意眨眼的瞬间如果瞳孔还能看见一半应该标成eye_open因为眨眼太快模型不该对这种瞬间过度敏感。嘴巴的标注相对简单嘴巴张开嘴唇分离且能看到牙齿或口腔内部空间通常伴随下颌下移打哈欠时嘴巴会持续张大数秒。嘴巴闭合上下嘴唇贴合或仅有轻微缝隙但仍能看到唇线这种情况统一标mouth_close。实际操作中容易出问题的是“说话中间张嘴”和“打哈欠”的区别。很多标注员会把所有张嘴都标成mouth_open导致模型把说话也当作打哈欠误报率飙升。我建议张开的判定标准加上“张大口”即嘴唇张开的角度和面积显著大于正常说话大家拿到数据集后建议先抽样看一眼分布心里有数。2.2 xml标注文件字段逐项拆解xml格式的标注文件每个图片对应一个同名xml文件内部结构如下annotation folderJPEGImages/folder filenameimg_000123.jpg/filename path/data/JPEGImages/img_000123.jpg/path source databaseDriverMonitorDataset/database /source size width1280/width height720/height depth3/depth /size segmented0/segmented object nameeye_open/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin540/xmin ymin320/ymin xmax610/xmax ymax365/ymax /bndbox /object object namemouth_open/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin580/xmin ymin430/ymin xmax700/xmax ymax500/ymax /bndbox /object /annotation这里我解释几个关键字段的坑如果你用Python或者labelimg打开过xml应该会有体会size里的width/height是原图的宽高这个必须在转换时保证正确否则归一化坐标全错。万一你用的图片被人为resize过而xml没同步更新就会出现框偏移问题。object里的name是类别名训练前最好统一为英文字符不要出现中文或空格否则容易在加载类别文件时报错。truncated表示目标是否不完整比如被图片边缘截断difficult表示是否难识别。这两个字段在YOLO训练中通常直接用不上但如果你后续要做hard example mining可以留着用。2.3 标注质量检查的三板斧拿到数据集后不要直接开训。先花10分钟做一次质量抽检能避免很多返工。第一板斧看同名文件是否一一对应。很多数据集在打包过程中容易丢文件图片数量与xml数量不一致会造成训练时寻找标签失败。第二板斧看坐标是否越界。把xml里的xmax、ymax和图片宽高做个对比如果出现 xmax width 或者 ymin 0 这种异常解析时就会出问题尤其是用OpenCV读取坐标画框时直接越界报错。第三板斧看类别名是否统一。由于标注是多人协作完成的可能有人标了 eye_open有人标了 EyeOpen还有人标了 eye-open如果你不检查训练时类数量会莫名其妙变多而且模型根本学不到正确的语义。我写过一个快速检查脚本遍历所有xml统计类别集合和坐标最大值有需要可以自己写一个很简单。3. 实操过程基于该数据集训练YOLOv8模型3.1 准备训练目录结构最省事的方式是先把数据集按VOC风格组织好再一键转成YOLO格式。目录结构如下datasets/ driver_monitor/ images/ train/ img_000123.jpg val/ img_000456.jpg labels/ train/ img_000123.txt val/ img_000456.txt如果你的原始数据和xml混在同一个文件夹就先按8:2比例随机分为train和val图片和xml成对移动。这里有个小提醒划分的时候一定要固定随机种子保证复现不然每次跑分出来的训练集不一样结果没法对比。3.2 xml转YOLO txt格式脚本这是整个数据准备里最关键的一步。YOLO的txt格式每一行代表一个目标格式为class_id x_center y_center width height其中x_center等值全部是归一化后的相对坐标。下面是完整的转换脚本我实测可以跑通直接改路径就行import os import xml.etree.ElementTree as ET from pathlib import Path # 配置路径 xml_dir Path(path/to/xml) # 存放xml的原始目录 img_dir Path(path/to/images) # 对应图片目录 out_label_dir Path(path/to/labels) # 输出txt目录 out_label_dir.mkdir(parentsTrue, exist_okTrue) # 类别映射按数据集实际类别顺序排列 classes [eye_open, eye_close, mouth_open, mouth_close] def convert_xml_to_yolo(xml_file, img_file, out_txt): tree ET.parse(xml_file) root tree.getroot() # 读取图片尺寸 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) with open(out_txt, w) as f: for obj in root.iter(object): name obj.find(name).text if name not in classes: print(fWarning: {name} not in classes) continue cls_id classes.index(name) 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) # 坐标裁剪到图片范围内 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue # 转为YOLO格式 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) # 遍历xml文件 for xml_file in xml_dir.glob(*.xml): img_file img_dir / (xml_file.stem .jpg) if not img_file.exists(): print(fmissing image: {img_file}) continue out_txt out_label_dir / (xml_file.stem .txt) convert_xml_to_yolo(xml_file, img_file, out_txt) print(转换完成)这段代码里我加了两个保险一是对坐标做了裁剪防止越界二是过滤掉宽高为0的无效框。实际转换时你会发现总有几百个框因为标注员手抖导致xmax xmin这种情况直接跳过就行硬留反而干扰训练。3.3 编写YOLOv8数据集配置YOLOv8使用的是yaml配置文件指向训练验证图片路径以及类别名。文件内容如下# driver_monitor.yaml train: datasets/driver_monitor/images/train val: datasets/driver_monitor/images/val nc: 4 names: [eye_open, eye_close, mouth_open, mouth_close]这里注意train和val路径我写的是图片目录YOLO会按照txt格式自动在对应labels目录下找标签文件。所以标签目录结构要和images目录严格一致否则会报“label not found”的错误。3.4 开始训练与常用参数如果你用的是ultralytics库命令行直接运行yolo detect train datadriver_monitor.yaml modelyolov8s.pt epochs100 imgsz640 batch16 device0我推荐使用yolov8s作为起点m参数可能有点过重n参数精度略低。训练过程中几个关键参数的经验值imgsz不要盲目用640。眼睛和嘴巴是小目标图像分辨率如果不高640的输入反而让目标更小。如果原始图片是1280x720可以试试imgsz800或960能显著提升小目标召回。代价是训练变慢显存占用变大。batch根据显存调一般10GB显存用默认16没问题如果显存不够减小batch的同时可以适当降低epoch不用担心过拟合这类数据量大的任务early stop会帮你判断。patience训练时默认的早停参数是50意思是如果50个epoch没有提升就自动停。我建议设置成30节约时间。训练完成后模型会输出在验证集上的mAP50、mAP50-95等指标。对于这个数据集mAP50应该能达到95%以上如果是通过人脸裁剪后只检测局部mAP50甚至会超过98%。3.5 推理测试与模型导出训练完直接用测试图片看效果yolo predict modelruns/detect/train/weights/best.pt sourcetest_images/ saveTrue推理时建议把置信度阈值调高一些比如conf0.6因为闭眼和睁眼之间有时会误判。我自己的项目里把conf设为0.65iou设为0.5误检明显减少。如果要做边缘部署直接导出为onnx或者tensorrt引擎yolo export modelbest.pt formatonnx dynamicTrue导出onnx时要注意YOLOv8的detect层输出是一个1x84xN的tensorN是候选框数解析时需要用python后处理或者opencv dnn做NMS。如果不想自己写可以用onnxruntime的官方demo模板改一下类别数量。4. 常见问题与排查技巧实录4.1 xml无法解析提示编码错误这类数据集经常来自不同标注工具有的xml开头带BOM或者包含特殊字符用xml.etree.ElementTree解析时会报parse error。解决方法是读取文件时用utf-8-sig编码或者用bytes模式读取后先解码import xml.etree.ElementTree as ET with open(xml_file, rb) as f: content f.read() # 去掉BOM content content.lstrip(b\xef\xbb\xbf) root ET.fromstring(content)这也是为什么我之前在转换脚本里直接用了ET.parse(Path)但如果遇到报错第一时间切到字节流模式。4.2 类别名不匹配训练时显存爆掉或标签越界如果你刚拿到数据集第一件事就是检查类别列表。有次我用脚本统计类别时发现除了四个预期类别外还有个eye-open中划线因为有人用不同的标注工具自动生成了带中划线的类别。YOLO训练时类别数量是按yaml里的nc生成的如果txt里的类别id已经超过nc就会在加载时直接报IndexError或者静默丢弃导致模型学不到这个类别。解决办法是写个脚本把所有类别名映射到标准四类或者重新生成txt。4.3 图片有旋转信息导致检测框偏移手机或者某些行车记录仪拍摄的照片会带有EXIF旋转信息用opencv直接读图时能正常显示但如果标注工具读取时没有正确处理旋转xml坐标就是按原图方向标的训练时模型看到的是旋转后的图片两者对不上loss居高不下。检测方法很简单用PIL读取图片的exif信息如果 orientation 字段不为1说明有旋转。处理方式有两种一是先用工具把所有图片统一转为无旋转的jpg并同步更新xml坐标二是训练前在代码中强制按exif旋转图片后再训练但这会拖慢图片加载速度。我建议直接用批处理工具把所有图片转正顺便生成新xml。4.4 模型对“眼睛闭合”不敏感召回率低如果你的验证集显示 eye_close 的recall只有80%左右而其他三类都能到95%以上大概率是闭眼样本数不足或场景单一。这个数据集的闭眼样本可能占总数比较低尤其是多种光照条件下的闭眼样本少。解决方法是做针对性数据增强对闭眼区域进行 over-sample重复采样或者对眼睛区域做亮度、对比度扰动。还有一个很实用的技巧是“上下文裁剪”将眼睛和嘴巴周围扩展1.2到1.5倍再裁剪作为输入给YOLO训练。这等于让模型看到更多上下文信息眉毛、额头、脸颊对于区分“闭眼”和“小眼睛”非常有效。当然推理时也要同样先检测人脸区域再裁剪眼睛区域保证训练和推理的数据分布一致。4.5 在嵌入式设备上推理速度不达标如果你在Jetson Nano上跑yolov8s可能帧率不到20FPS这时可以考虑用yolov8n加TensorRT加速或者改用yolov5n6检测头大一些但参数少。另外把推理分辨率降为416x416同时降低NMS的iou阈值到0.45速度能提升不少精确度下降可以接受。我个人实际测试过同样用这个数据集训练yolov8n在Jetson Nano上fp16推理可以达到25FPS眼睛和嘴巴的mAP50在93%左右而yolov8s是18FPSmAP50在96%。具体取舍看你项目对精度的硬性要求。5. 继续扩充数据集与优化方向这个数据集虽然是直接可用的但它显然不是完美的。如果你真的要做产品级DMS我建议准备一套自己的数据采集车采集不同年龄、不同性别、不同光照和戴眼镜/不戴眼镜的驾驶员数据然后针对闭眼和打哈欠两种状态做专项补充。一个比较有效的做法是使用“伪标签”迭代优化先用现有数据集训练一个模型放到难例场景里推理把置信度低于0.5的检测结果全部保存下来人工挑出真正难识别的样本补充标注后加入训练集这样能显著提升模型在边缘场景下的鲁棒性。对于眼睛区域检测还有一个方向是加入关键点信息作为辅助监督比如同时预测眼角和瞳孔位置让模型更好地捕捉眼部闭合状态。这在YOLOv8-pose里有支持不过工程复杂度会高一个量级。如果你只是想快速落地纯目标检测已经够用了。最后分享一个小技巧在训练时把闭眼、张嘴这两类在loss中的权重调高一点比如用YOLOv8的class weights参数设置class_weights[1.0, 1.5, 1.5, 1.0]让模型更关注少数类。对于疲劳驾驶预警来说漏报一个闭眼远比误报一次说话严重所以哪怕牺牲一些整体mAP也要保证闭眼类别的召回率。我最终调下来的结果eye_close的recall从88%提到了95.2%误报还在可接受范围内。这就是疲劳检测数据集的实战价值所在不是跑通就算完而是要在真实指标上达到可落地的标准。本文还有配套的精品资源点击获取