驾驶人睁闭眼张合嘴检测数据集:YOLO训练与DMS疲劳驾驶实战
2026/9/20 7:06:36 网站建设 项目流程

简介:目标检测是计算机视觉中的核心任务,其本质是在图像中定位并分类多个物体。在工程实践中,基于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> <folder>JPEGImages</folder> <filename>img_000123.jpg</filename> <path>/data/JPEGImages/img_000123.jpg</path> <source> <database>DriverMonitorDataset</database> </source> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>eye_open</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>540</xmin> <ymin>320</ymin> <xmax>610</xmax> <ymax>365</ymax> </bndbox> </object> <object> <name>mouth_open</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>580</xmin> <ymin>430</ymin> <xmax>700</xmax> <ymax>500</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(parents=True, exist_ok=True) # 类别映射,按数据集实际类别顺序排列 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(f"Warning: {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(f"missing 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 data=driver_monitor.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0

我推荐使用yolov8s作为起点,m参数可能有点过重,n参数精度略低。训练过程中几个关键参数的经验值:

  • imgsz:不要盲目用640。眼睛和嘴巴是小目标,图像分辨率如果不高,640的输入反而让目标更小。如果原始图片是1280x720,可以试试imgsz=800或960,能显著提升小目标召回。代价是训练变慢,显存占用变大。
  • batch:根据显存调,一般10GB显存用默认16没问题;如果显存不够,减小batch的同时可以适当降低epoch,不用担心过拟合,这类数据量大的任务,early stop会帮你判断。
  • patience:训练时默认的早停参数是50,意思是如果50个epoch没有提升就自动停。我建议设置成30,节约时间。

训练完成后,模型会输出在验证集上的mAP50、mAP50-95等指标。对于这个数据集,mAP50应该能达到95%以上,如果是通过人脸裁剪后只检测局部,mAP50甚至会超过98%。

3.5 推理测试与模型导出

训练完直接用测试图片看效果:

yolo predict model=runs/detect/train/weights/best.pt source=test_images/ save=True

推理时建议把置信度阈值调高一些,比如conf=0.6,因为闭眼和睁眼之间有时会误判。我自己的项目里,把conf设为0.65,iou设为0.5,误检明显减少。

如果要做边缘部署,直接导出为onnx或者tensorrt引擎:

yolo export model=best.pt format=onnx dynamic=True

导出onnx时要注意,YOLOv8的detect层输出是一个1x84xN的tensor,N是候选框数,解析时需要用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是18FPS,mAP50在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%,误报还在可接受范围内。这就是疲劳检测数据集的实战价值所在:不是跑通就算完,而是要在真实指标上达到可落地的标准。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询