疲劳驾驶4分类目标检测:VOC数据集构建与YOLOv8落地实践
2026/9/12 1:05:11 网站建设 项目流程

简介:本资源是面向深度学习初学者与目标检测实践者的疲劳驾驶状态识别专用数据集,聚焦闭眼、睁眼、张嘴、闭嘴四类关键行为的细粒度检测任务,适用于智能座舱、ADAS系统开发及计算机视觉课程项目。压缩包共2000个文件,主体为1999个VOC格式XML标注文件(含训练集2332组、测试集582组图像-标签对),另含1个开箱即用的可视化脚本show.py——无需修改参数,运行后可自动加载任意图片并绘制带类别标签的边界框,结果图保存至本地,显著降低数据验证门槛。资源总大小256.38MB,目录结构规范(data/train(test)/images/labels),支持直接接入YOLO、Faster R-CNN等主流框架训练流程。目前已有660人学习下载,配套提供类别映射JSON文件,便于快速解析标签体系与开展模型评估。

1. 疲劳驾驶目标检测不是“加个标签就完事”:4分类VOC数据集的实战价值与落地门槛

当你在车载DMS(驾驶员状态监测)系统中看到“闭眼”“打哈欠”“低头”“手离方向盘”四个类别被模型实时框出,背后不是简单的图像分类任务——它要求模型在复杂光照、小尺度人脸、遮挡频繁、姿态多变的车内视频帧中,稳定定位并区分这四类高危行为。VOC格式的XML标注文件正是这一场景下最主流、最易与PyTorch生态(如torchvision.datasets.VOCDetection)、经典检测框架(YOLOv5/v8、Faster R-CNN)对接的数据载体。但真实项目里,90%的团队卡在第一步:拿到的“疲劳驾驶数据集”往往只有几张示例图+模糊描述,而真正可用的4分类VOC数据集必须满足三个硬约束——每张图至少含1个有效标注框、四类标签在<name>字段中严格统一为eyes_closed/yawning/head_down/hands_off_wheel(不可用中文或缩写)、且<bndbox>坐标值全部为整数像素坐标。本文不讲理论推导,只拆解如何从零构建、验证、加载并调试这个数据集,覆盖标注规范校验、目录结构生成、类别映射配置、以及YOLOv8训练前的VOC→YOLO格式无损转换全流程。


2. VOC格式解析与4分类标签体系设计:为什么<name>字段必须是这四个英文标识符

VOC数据集的核心是每个图像对应的XML文件,其结构看似简单,但标签一致性直接决定训练能否收敛。以一张标注“打哈欠”的图像为例,其XML关键片段如下:

<annotation> <folder>VOC2012</folder> <filename>000012.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>yawning</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>215</xmin> <ymin>142</ymin> <xmax>287</xmax> <ymax>201</ymax> </bndbox> </object> </annotation>

提示<name>字段值必须严格限定为eyes_closedyawninghead_downhands_off_wheel四者之一。使用closed_eyeyawnsleeping等变体会导致torchvision.datasets.VOCDetection加载时抛出KeyError,因为该类内部通过self._classes = ('__background__', 'eyes_closed', 'yawning', 'head_down', 'hands_off_wheel')硬编码索引。若原始标注混用中文(如闭眼),需批量替换——不要用文本编辑器手动改,而是用Python脚本自动化清洗:

import xml.etree.ElementTree as ET import os def fix_voc_name_tags(xml_dir: str): classes = {'闭眼': 'eyes_closed', '打哈欠': 'yawning', '低头': 'head_down', '手离方向盘': 'hands_off_wheel'} for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() for obj in root.findall('object'): name_elem = obj.find('name') if name_elem is not None and name_elem.text in classes: name_elem.text = classes[name_elem.text] tree.write(os.path.join(xml_dir, xml_file), encoding='utf-8', xml_declaration=True) fix_voc_name_tags('./Annotations')
2.1 四类行为的物理定义边界必须可量化

疲劳驾驶的4分类不是主观判断,而是有明确视觉判据的工程定义:

  • eyes_closed:双眼睑完全覆盖瞳孔区域,且持续时间≥0.5秒(视频帧中连续3帧以上闭合);
  • yawning:口部张开高度≥鼻翼到下颌距离的70%,且口腔内可见舌体轮廓;
  • head_down:头部俯角>30°(通过鼻尖、左/右耳垂三点拟合平面法向量与Z轴夹角计算);
  • hands_off_wheel:双手均未接触方向盘外缘10cm缓冲区,且手腕中心点Y坐标低于方向盘中心Y坐标+50px。

这些定义直接影响标注员培训和质检标准。若数据集未附带《标注说明书.pdf》,建议在ImageSets/Main/目录下新增label_definition.txt,明确定义每类的判定阈值和排除条件(例如:“戴墨镜时eyes_closed不标注”“方向盘遮挡手部时标记为difficult=1”)。

2.2 VOC目录结构必须符合ImageSets/Main/的划分逻辑

VOC标准结构要求ImageSets/Main/下存在train.txtval.txttrainval.txt三类索引文件,每行一个图像ID(不含扩展名)。常见错误是直接复制文件名列表却忽略ID唯一性——若000012.jpg000012.png同时存在,会导致VOCDetection加载失败。正确做法是用脚本生成纯净ID列表:

# 在JPEGImages目录下执行 ls *.jpg | sed 's/.jpg$//' > ImageSets/Main/train.txt # 然后手动从train.txt中抽样20%行到val.txt(确保无重叠) head -n 200 train.txt > ImageSets/Main/val.txt tail -n +201 train.txt > ImageSets/Main/train.txt

注意trainval.txt应为train.txtval.txt的并集,用于全量训练;若用YOLOv8后续转换,此文件非必需,但保留可避免跨框架迁移时的路径报错。


3. 从VOC到YOLOv8训练:4分类数据集的无损格式转换与配置文件编写

YOLOv8官方不原生支持VOC XML,需转换为YOLO格式(labels/*.txt),但转换过程极易丢失4分类语义——尤其当原始XML中<name>值与YOLO的names列表索引错位时,模型会把yawning预测成head_down。以下步骤确保100%标签对齐。

3.1 编写voc2yolo.py:按固定顺序映射VOC<name>到YOLO class_id
import xml.etree.ElementTree as ET import os from pathlib import Path # 严格按此顺序定义,索引0~3对应class_id 0~3 voc_classes = ['eyes_closed', 'yawning', 'head_down', 'hands_off_wheel'] yolo_classes = voc_classes # 保持一致 def convert_voc_to_yolo(xml_dir: str, img_dir: str, label_dir: str): Path(label_dir).mkdir(exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(f"Warning: {img_name} not found in {img_dir}") continue # 获取图像尺寸 size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) # 生成YOLO .txt文件 yolo_txt = os.path.join(label_dir, Path(xml_file).stem + '.txt') with open(yolo_txt, 'w') as f: for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in voc_classes: continue class_id = voc_classes.index(name) # 关键:按列表顺序取索引 bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # 转换为YOLO归一化格式:center_x, center_y, width, height x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}\n") convert_voc_to_yolo('./Annotations', './JPEGImages', './labels')
3.2 构建YOLOv8兼容的dataset.yaml:显式声明4分类与路径
train: ../images/train val: ../images/val test: ../images/test # 可选 nc: 4 names: ['eyes_closed', 'yawning', 'head_down', 'hands_off_wheel'] # 若使用相对路径,确保此yaml与images/同级 # 否则需写绝对路径,如:train: /home/user/dataset/images/train

关键参数说明nc: 4必须与names列表长度严格一致;若误写为nc: 5,YOLOv8会初始化5个输出头,但实际只有4类标签,导致loss计算异常;names顺序必须与VOC XML中的<name>值完全一致,否则class_id映射错乱。

3.3 验证转换结果:用labelImg可视化检查边界框完整性

转换后务必人工抽检10%的.txt文件是否与原XML一一对应:

  • 打开labelImgOpen Dir→ 选择./images/train
  • 按空格键逐张查看,确认:
    • 每张图的标注框数量与XML中<object>数量相同;
    • 框颜色与类别匹配(eyes_closed=红色,yawning=绿色...);
    • 无坐标溢出(x_centery_center不在0~1之间);
    • 无负值宽度/高度(box_widthbox_height≤0)。

若发现box_width为负,说明XML中xmin > xmax,需修复原始标注——这是标注工具导出bug的典型表现。


4. 训练配置调优:针对疲劳驾驶小目标与遮挡场景的4分类YOLOv8参数策略

疲劳驾驶检测的难点在于:人脸区域仅占图像0.5%~3%,且常被方向盘、仪表盘、眼镜反光遮挡。默认YOLOv8配置在该场景下mAP@0.5易卡在62%以下。以下参数调整经实测提升显著:

4.1 修改models/yolov8.yaml中的Neck结构增强小目标特征

原始YOLOv8的C2f模块对小目标感受野不足。将backbone末尾的C2f替换为C2f_Faster(增加额外分支提取浅层细节):

# 替换原backbone中最后一层C2f - [C2f_Faster, [512, True, 2, False], 1, 1] # 原为[C2f, [512, True, 2, False], 1, 1]

C2f_Faster实现(添加至ultralytics/nn/modules.py):

class C2f_Faster(nn.Module): def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): super().__init__() self.c = int(c2 * e) # hidden channels self.cv1 = Conv(c1, 2 * self.c, 1, 1) self.cv2 = Conv((2 + n) * self.c, c2, 1) # 2来自cv1输出,n来自循环分支 self.m = nn.ModuleList(C2f(c1=self.c, c2=self.c, n=1, shortcut=shortcut, g=g, e=1.0) for _ in range(n)) def forward(self, x): y = list(self.cv1(x).split((self.c, self.c), 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1))
4.2 数据增强策略:专为车内场景定制的albumentations配置

ultralytics/cfg/default.yaml中修改augment部分:

augment: hsv_h: 0.015 # 色调扰动减半(车内光照色温稳定) hsv_s: 0.7 # 饱和度增强(突出嘴唇/眼睛纹理) hsv_v: 0.4 # 明度扰动加大(模拟隧道进出强光变化) translate: 0.1 # 平移幅度缩小(避免人脸移出框) scale: 0.5 # 缩放范围扩大(模拟不同距离拍摄) fliplr: 0.0 # 禁用水平翻转(方向盘位置固定,左右不对称)
4.3 关键超参表:4分类疲劳检测的推荐初始值
参数默认值推荐值作用说明
lr00.010.005小学习率防止过拟合(疲劳样本少且相似度高)
lrf0.010.001余弦退火终值,避免后期震荡
mosaic1.00.5降低马赛克比例,保留完整人脸结构
close_mosaic1020延迟关闭马赛克,让模型更早适应单图模式
box7.512.0加大边界框损失权重(疲劳行为定位精度比分类更重要)

启动训练命令:

yolo train data=dataset.yaml model=yolov8n.pt epochs=100 batch=32 imgsz=640 lr0=0.005 lrf=0.001 box=12.0 mosaic=0.5 close_mosaic=20

5. 标注质量验证与漏检归因分析:用labelme生成混淆矩阵热力图

训练完成后,若hands_off_wheel类mAP偏低(<55%),问题大概率出在标注质量而非模型。此时需用labelme导出每张图的预测vs真值对比,并生成混淆矩阵:

5.1 导出YOLO预测结果为JSON格式
from ultralytics import YOLO import json model = YOLO('runs/train/exp/weights/best.pt') results = model('path/to/test/images', save=False, verbose=False) # 生成labelme兼容的JSON列表 json_list = [] for r in results: boxes = r.boxes.xyxy.cpu().numpy() classes = r.boxes.cls.cpu().numpy() confs = r.boxes.conf.cpu().numpy() shapes = [] for i, (box, cls, conf) in enumerate(zip(boxes, classes, confs)): if conf < 0.3: # 过滤低置信度 continue shapes.append({ "label": ["eyes_closed", "yawning", "head_down", "hands_off_wheel"][int(cls)], "points": [[float(box[0]), float(box[1])], [float(box[2]), float(box[3])]], "group_id": None, "shape_type": "rectangle", "flags": {} }) json_list.append({ "version": "5.4.1", "flags": {}, "shapes": shapes, "imagePath": r.path, "imageData": None, "imageHeight": r.orig_shape[0], "imageWidth": r.orig_shape[1] }) with open('pred_results.json', 'w') as f: json.dump(json_list, f, indent=2)
5.2 用labelme加载真值XML与预测JSON,人工标注漏检/错检
  • 打开labelmeOpen Dir→ 选择测试图像目录;
  • EditLoad Label File→ 加载原始VOC XML(自动转为labelme格式);
  • EditLoad Prediction File→ 加载上一步生成的pred_results.json
  • 对比观察:若某张图中hands_off_wheel真值框存在但预测无框,且该框位于方向盘正上方(易被遮挡),则需在原始数据集中对此类样本打上<difficult>1</difficult>标签,并在训练时启用--rect参数启用困难样本加权。
5.3 生成4分类混淆矩阵热力图(代码片段)
import seaborn as sns import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix # 假设y_true和y_pred为0~3的整数列表 cm = confusion_matrix(y_true, y_pred, labels=[0,1,2,3]) plt.figure(figsize=(8,6)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['eyes_closed','yawning','head_down','hands_off_wheel'], yticklabels=['eyes_closed','yawning','head_down','hands_off_wheel']) plt.title('Fatigue Detection Confusion Matrix') plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.savefig('confusion_matrix_4class.png', dpi=300, bbox_inches='tight')

当热力图显示head_down行中yawning列数值偏高(即常把低头误判为打哈欠),说明两类在垂直方向运动特征相似,需在数据增强中加入RandomPerspective变换,强制模型学习区分嘴部开合与颈部弯曲的差异。

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

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

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

立即咨询