疲劳驾驶检测数据集对齐与YOLOv5s训练实战指南
2026/9/11 1:45:12 网站建设 项目流程

简介:本资源是一个面向深度学习初学者的疲劳驾驶监测图像数据集,适用于人工智能、计算机视觉方向的入门级项目实践,特别适合以闭眼、打哈欠等典型疲劳姿态为识别目标的二分类或关键点检测任务。压缩包共含2000个文件,主体为2915张JPG格式人脸图像与2914份对应XML标注文件,涵盖多角度、多光照条件下的疲劳状态样本,总容量256.28MB;配套提供了数据清洗脚本,可自动剔除图片与标注不匹配的异常样本,显著降低新手预处理门槛。已有4733人学习下载,资源结构规范,标注格式兼容主流目标检测框架(如YOLO、Faster R-CNN),并附有典型XML文件预览,便于快速验证数据读取逻辑与标注解析流程。

1. 疲劳驾驶监测数据集不是“拿来即用”的图像包,而是需要先做数据对齐的训练起点

你下载的疲劳驾驶监测数据集.zip表面看是 10 个 XML 文件(如1429.xml894.xml),但实际它只提供了标注文件骨架,缺失配套的 JPEG 图像文件——这正是新手直接解压后跑不通训练脚本的根本原因。该数据集本质是一个「半结构化标注集」:XML 描述了每张图中人眼闭合、嘴巴张开等关键疲劳特征的位置与状态,但原始图像未随包分发,需自行采集或从其他合规渠道补充。它适合用作深度学习入门项目,核心价值在于标注规范清晰(Pascal VOC 格式)、疲劳定义明确(闭眼 + 张嘴双阈值),且样本量适中(约 100–200 张有效图经清洗后可得)。如果你正尝试复现驾驶员状态识别模型、调试 YOLOv5 或 Faster R-CNN 的小规模训练流程,这个数据集就是极佳的“最小可行验证集”——前提是先解决图片与 XML 的严格一一对应问题。跳过数据对齐直接训练,模型会因标签错位而输出完全不可信的 loss 曲线,这是 90% 初学者卡在第一步的真实陷阱。

2. 数据对齐:用 Python 扫描并剔除无图 XML 或无标 JPG,建立严格的一一映射关系

2.1 为什么必须做数据对齐?从 XML 结构反推图像缺失逻辑

Pascal VOC 格式的 XML 文件(如1429.xml)在<filename>标签内硬编码了对应图像的文件名,例如:

<filename>1429.jpg</filename> <size> <width>640</width> <height>480</height> </size> <object> <name>closed_eye</name> <bndbox> <xmin>120</xmin> <ymin>85</ymin> <xmax>180</xmax> <ymax>115</ymax> </bndbox> </object>

注意:<filename>值是1429.jpg,而非1429.xml。这意味着 XML 文件本身不包含图像,仅声明“我描述的是名为1429.jpg的图”。若你的JPEGImages/目录下没有1429.jpg,该 XML 就是悬空标注,强行参与训练会导致 DataLoader 加载时抛出FileNotFoundError,或更隐蔽地因cv2.imread()返回None导致 bbox 坐标计算崩溃。原始数据集提供的 10 个 XML 文件,恰恰是这种悬空标注的典型样本——它们是完整标注集的子集快照,而非独立可用包。

2.2 执行双向校验:先删无图 XML,再删无标 JPG

原始摘要中给出的代码仅单向清理(删除 JPEGImages 中无对应 XML 的图片),这在真实场景中是危险的。正确做法是执行双向强制对齐:确保每个 XML 有同名 JPG,每个 JPG 有同名 XML。以下是生产级校验脚本(含日志与备份):

import os import shutil from pathlib import Path # 配置路径(按实际解压结构调整) JPEG_DIR = Path("Dataset/dataset/JPEGImages") ANNOTATION_DIR = Path("Dataset/dataset/Annotations") BACKUP_DIR = Path("Dataset/dataset/backup_mismatched") # 创建备份目录 BACKUP_DIR.mkdir(exist_ok=True) # 获取所有 JPG 文件名(不含扩展名) jpg_names = {f.stem for f in JPEG_DIR.glob("*.jpg") if f.is_file()} # 获取所有 XML 文件名(不含扩展名) xml_names = {f.stem for f in ANNOTATION_DIR.glob("*.xml") if f.is_file()} # 计算差集 jpg_only = jpg_names - xml_names # 有图无标 xml_only = xml_names - jpg_names # 有标无图 print(f"发现 {len(jpg_only)} 张无标注图片: {sorted(jpg_only)[:5]}...") print(f"发现 {len(xml_only)} 个无图片标注: {sorted(xml_only)[:5]}...") # 备份并删除无标注 JPG for stem in jpg_only: jpg_path = JPEG_DIR / f"{stem}.jpg" backup_path = BACKUP_DIR / f"{stem}.jpg" shutil.move(jpg_path, backup_path) print(f" 已备份无标图: {jpg_path.name} → {backup_path.name}") # 备份并删除无图片 XML for stem in xml_only: xml_path = ANNOTATION_DIR / f"{stem}.xml" backup_path = BACKUP_DIR / f"{stem}.xml" shutil.move(xml_path, backup_path) print(f" 已备份无图标: {xml_path.name} → {backup_path.name}") # 输出最终有效样本数 valid_count = len(jpg_names & xml_names) print(f"\n✅ 对齐完成:剩余 {valid_count} 组有效 (JPG + XML) 对")

提示:此脚本使用shutil.move()而非os.remove(),强制保留所有被剔除文件至backup_mismatched/目录。这是工程实践底线——任何数据清洗操作都必须可逆。若后续发现误删,可直接从备份目录恢复。

2.3 验证对齐结果:生成清单文件并人工抽检

对齐后需生成trainval.txt(VOC 标准划分文件)并抽检。运行以下命令生成清单:

# 在终端中执行(Linux/macOS)或 PowerShell(Windows) cd Dataset/dataset ls JPEGImages/*.jpg | xargs -I {} basename {} .jpg > ImageSets/Main/trainval.txt

然后手动打开trainval.txt,随机选取前 3 行(如1429,894,1062),检查:

  • JPEGImages/1429.jpg是否存在且能正常打开(用file JPEGImages/1429.jpg查看 MIME 类型);
  • Annotations/1429.xml是否存在且<filename>值为1429.jpg
  • 用浏览器打开1429.jpg,肉眼确认是否包含闭眼或张嘴动作(疲劳核心特征)。

若任一检查失败,说明对齐不彻底,需重新运行脚本并检查路径拼写(常见错误:JPEGImages写成JpegImages或大小写不一致)。

3. 模型训练:基于 YOLOv5s 的轻量级疲劳检测实现与关键参数调优

3.1 为什么选 YOLOv5s 而非 ResNet 或 Faster R-CNN?

疲劳驾驶检测是典型的单阶段目标检测任务:需同时定位眼部(闭合状态)和口腔(张开状态)两个关键区域,并判断其空间关系。YOLOv5s 在以下维度优于其他架构:

  • 推理速度:在 Jetson Nano 等嵌入式设备上可达 25 FPS,满足车载实时性要求;
  • 小目标敏感度s版本的 Neck 层(PANet)对 40×40 像素级的眼部 bbox 有更强响应;
  • 标注容错性:相比 Faster R-CNN 的两阶段流程,YOLO 对 bbox 标注轻微偏移(±5px)鲁棒性更高,适配手工标注数据集。

ResNet 仅适合分类任务(如“疲劳/清醒”二分类),无法输出位置信息;Faster R-CNN 虽精度高,但训练耗时长(同等配置下比 YOLOv5s 慢 3.2 倍),且对小样本收敛困难——本数据集经对齐后仅约 150 张图,YOLOv5s 是唯一能在 2 小时内完成端到端训练的合理选择。

3.2 构建 VOC-to-YOLO 格式转换器:将 XML 转为.txt标签

YOLO 要求标签为class_id center_x center_y width height(归一化坐标)。编写转换脚本xml_to_yolo.py

import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(xml_path, img_width, img_height, class_map): tree = ET.parse(xml_path) root = tree.getroot() yolo_lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text.strip() if cls_name not in class_map: continue # 跳过非目标类别(如 'face') 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) # 归一化:center_x, center_y, w, h x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height yolo_line = f"{class_map[cls_name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}" yolo_lines.append(yolo_line) return yolo_lines # 配置 CLASS_MAP = {"closed_eye": 0, "open_mouth": 1} # 疲劳双特征:0=闭眼, 1=张嘴 VOC_ANN_DIR = Path("Dataset/dataset/Annotations") YOLO_LABEL_DIR = Path("Dataset/yolo/labels") YOLO_LABEL_DIR.mkdir(exist_ok=True) # 遍历所有 XML(假设图像尺寸统一为 640x480) for xml_path in VOC_ANN_DIR.glob("*.xml"): stem = xml_path.stem yolo_path = YOLO_LABEL_DIR / f"{stem}.txt" # 从 XML 中读取 size(更健壮) tree = ET.parse(xml_path) size = tree.find('size') w = int(size.find('width').text) h = int(size.find('height').text) yolo_lines = convert_voc_to_yolo(xml_path, w, h, CLASS_MAP) yolo_path.write_text("\n".join(yolo_lines)) print(f"已生成: {yolo_path.name} ({len(yolo_lines)} 个目标)")

注意CLASS_MAP显式定义了两类疲劳特征。实际训练中,模型会学习closed_eyeopen_mouth的联合出现模式——单类检测准确率可能达 92%,但双类共现才判定为疲劳,这是业务逻辑层的关键设计。

3.3 YOLOv5s 训练命令详解与超参调优表

yolov5仓库根目录下执行训练(假设已克隆官方 repo):

python train.py \ --img 416 \ # 输入尺寸:416x416(平衡速度与小目标检测) --batch 8 \ # batch_size=8(显存占用约 3.2GB,GTX 1080 可行) --epochs 100 \ # 小数据集需足够 epoch,避免欠拟合 --data dataset.yaml \ # 数据配置文件(见下文) --weights yolov5s.pt \ # 使用 COCO 预训练权重(迁移学习关键) --name fatigue_yolov5s \ # 实验名称,输出至 runs/train/fatigue_yolov5s/ --exist-ok # 允许覆盖同名实验目录

dataset.yaml内容必须严格匹配你的目录结构:

train: ../Dataset/yolo/images/train # 训练图路径(需提前复制对齐后的 JPG) val: ../Dataset/yolo/images/val # 验证图路径 nc: 2 # 类别数 names: ['closed_eye', 'open_mouth'] # 类别名,顺序与 CLASS_MAP 一致
参数推荐值调优逻辑
--img416小于 640 可提升小目标 recall,大于 416 显存溢出风险陡增
--batch8batch=16在 1080Ti 上 OOM;batch=4收敛慢 40%
--epochs100学习率衰减策略(cosine)需足够轮次,50 epoch 时 mAP@0.5 通常仅 0.32
--weightsyolov5s.pt必须启用迁移学习,随机初始化在 150 图上无法收敛

训练完成后,runs/train/fatigue_yolov5s/weights/best.pt即为最优模型。用val.py验证:

python val.py --weights runs/train/fatigue_yolov5s/weights/best.pt --data dataset.yaml --task test

重点关注metrics/mAP_0.5(IoU=0.5 时的平均精度),新手项目达到0.72+即为合格。

4. 疲劳判定逻辑落地:从模型输出到驾驶状态决策的端到端链路

4.1 解析模型输出:提取闭眼与张嘴的置信度及空间关系

YOLOv5 输出为(x, y, w, h, conf, cls)六元组。关键不是单独看conf,而是构建双特征联合判定规则

import cv2 import torch from models.experimental import attempt_load from utils.general import non_max_suppression model = attempt_load("runs/train/fatigue_yolov5s/weights/best.pt", map_location="cpu") model.eval() def detect_fatigue(frame): # 预处理:BGR→RGB→归一化→添加 batch 维度 img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = torch.from_numpy(img).float().div(255.0).permute(2,0,1).unsqueeze(0) # 推理 pred = model(img)[0] pred = non_max_suppression(pred, conf_thres=0.4, iou_thres=0.4)[0] # NMS 后处理 # 分离两类检测框 eyes = pred[pred[:, -1] == 0] # cls_id=0: closed_eye mouths = pred[pred[:, -1] == 1] # cls_id=1: open_mouth # 计算联合置信度(几何平均) if len(eyes) > 0 and len(mouths) > 0: eye_conf = eyes[:, 4].max().item() mouth_conf = mouths[:, 4].max().item() joint_conf = (eye_conf * mouth_conf) ** 0.5 return joint_conf > 0.65 # 双特征置信度乘积开方 > 0.65 判定疲劳 return False # 使用示例 cap = cv2.VideoCapture(0) while cap.isOpened(): ret, frame = cap.read() if detect_fatigue(frame): cv2.putText(frame, "FATIGUE DETECTED!", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2) cv2.imshow("Fatigue Monitor", frame) if cv2.waitKey(1) == ord('q'): break cap.release()

提示joint_conf使用几何平均而非算术平均,是因为闭眼和张嘴是必要非充分条件——任一特征缺失(如只闭眼不张嘴)不能判定疲劳,几何平均天然惩罚单边高置信。

4.2 抗干扰优化:时间窗口平滑与瞳孔面积动态阈值

真实车载场景中,单帧误检率高(强光反射导致假闭眼、说话导致假张嘴)。引入时间维度过滤:

# 维护最近 5 帧的 joint_conf 序列 conf_history = [] def robust_fatigue_decision(current_conf): conf_history.append(current_conf) if len(conf_history) > 5: conf_history.pop(0) # 连续 3 帧 > 0.65 才触发报警 return sum(c > 0.65 for c in conf_history) >= 3 # 瞳孔面积动态阈值(防强光误判) def get_pupil_area(frame, eye_bbox): x1, y1, x2, y2 = [int(v) for v in eye_bbox[:4]] eye_roi = frame[y1:y2, x1:x2] gray = cv2.cvtColor(eye_roi, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY_INV) # 强光下阈值需下调 area = cv2.countNonZero(thresh) return area / ((x2-x1) * (y2-y1)) # 归一化面积比 # 在 detect_fatigue 中调用: # if get_pupil_area(frame, eyes[0]) < 0.12: # 瞳孔面积占比 <12% 才认为真闭眼

4.3 部署验证:用 OpenCV-DNN 加载 ONNX 模型实现零依赖推理

为脱离 PyTorch 环境部署,导出 ONNX 并用 OpenCV 加载:

# 导出 ONNX(在 yolov5 目录) python export.py --weights runs/train/fatigue_yolov5s/weights/best.pt --include onnx

OpenCV 加载代码(无需安装 PyTorch):

import cv2 import numpy as np net = cv2.dnn.readNetFromONNX("yolov5s_fatigue.onnx") net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) def cv2_dnn_inference(frame): blob = cv2.dnn.blobFromImage(frame, 1/255.0, (416,416), swapRB=True, crop=False) net.setInput(blob) outputs = net.forward(net.getUnconnectedOutLayersNames()) # 解析 outputs(YOLOv5 ONNX 输出为 [1, 25200, 6]) detections = outputs[0].squeeze() # shape: (25200, 6) boxes, confs, classes = [], [], [] for det in detections: conf = det[4] if conf > 0.4: x, y, w, h = det[:4] cls = int(det[5]) boxes.append([int(x-w/2), int(y-h/2), int(w), int(h)]) confs.append(float(conf)) classes.append(cls) # NMS indices = cv2.dnn.NMSBoxes(boxes, confs, 0.4, 0.4) return [(boxes[i], confs[i], classes[i]) for i in indices.flatten()]

至此,你已构建出一条从原始数据清洗、模型训练、多特征融合判定到轻量部署的完整技术链路。下一步可接入 CAN 总线触发车辆警报,或叠加头部姿态估计增强鲁棒性——但那已是另一个项目的边界了。

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

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

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

立即咨询