尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

疲劳驾驶检测数据集VOC转YOLO格式与YOLOv8训练全流程避坑指南

疲劳驾驶检测数据集VOC转YOLO格式与YOLOv8训练全流程避坑指南 简介这是一份面向计算机视觉与智能驾驶研究者的Pascal VOC格式疲劳驾驶数据集包含4362张真实驾驶场景图片及对应的4362份xml标注文件共4个类别closed_eye闭眼、closed_mouth闭嘴、open_eye睁眼、open_mouth张嘴可用于训练疲劳驾驶检测模型也可用于教学实践中的目标检测项目。全部标注通过labelImg工具以矩形框形式完成并附有各类别框数统计闭眼2485个、闭嘴3343个、睁眼4903个、张嘴936个方便读者快速评估数据分布为后续数据增强或类别均衡提供依据。图片与xml一一对应目录结构清晰可接入YOLO、SSD等主流检测框架。资源包为zip格式约368.57MB含8725个文件jpg图像、xml标注和1份使用说明txt目前已有2803人学习下载。需注意数据集只保证标注准确合理不对任何训练模型精度作承诺适合作为算法验证与基础训练数据使用。1. 疲劳驾驶检测数据集先别急着训练花十分钟把文件数清楚拿到任何数据集的第一件事不是急着跑训练脚本而是先验证它的完整性和一致性。这份疲劳驾驶数据集是典型的Pascal VOC格式包含4362张jpg图片和对应的4362个xml标注文件覆盖closed_eye闭眼、closed_mouth闭嘴、open_eye睁眼、open_mouth张嘴四个类别一共标注了248533434903936约11667个目标框。数据分布上睁眼样本最多、张嘴样本最少这种不平衡会直接影响后续训练时的类别权重设置。适合用它来做疲劳驾驶检测的基线实验、YOLOv8/v5的微调测试或者验证自己写的VOC转YOLO格式脚本是否可靠。但我要先泼一盆冷水这份数据没有提供train/val/test划分文件连官方说明文档都只有一份使用说明.txt图片和xml是否成对、有没有损坏文件都得自己动手验证。2. 数据集全貌文件清单、标注格式与四类目分布2.1 下载后先做文件一致性校验解开压缩包后你会发现里面是一堆jpg和xml文件混在一起没有子目录。网上很多教程会直接告诉你“把文件扔进images和labels文件夹就能训练”但这里有个隐藏问题——如果某个xml对应的jpg缺失或者某张jpg没有xml训练时就会报错或漏样本。我一般会先跑一遍下面的脚本把配对情况摸清楚。#!/bin/bash # 校验图片与xml是否一一对应 cd /path/to/dataset ls *.jpg | sed s/\.jpg$// jpg_names.txt ls *.xml | sed s/\.xml$// xml_names.txt # 找出存在于jpg但不存在于xml的文件名缺标注 while read name; do if [ ! -f ${name}.xml ]; then echo Missing XML: ${name} fi done jpg_names.txt # 找出存在于xml但不存在于jpg的文件名缺图片 while read name; do if [ ! -f ${name}.jpg ]; then echo Missing JPG: ${name} fi done xml_names.txt # 统计总数去重 cat jpg_names.txt xml_names.txt | sort | uniq -d | wc -l这段脚本的核心逻辑是用文件名前缀做交集对比。先把jpg和xml的扩展名去掉各自存成列表然后双向遍历确认是否一一对应。uniq -d那一步是找出两边共有的名字正常结果应该是4362。如果这个数字小于4362说明有文件没配对成功需要去原始包重新下载或检查传输是否被中断。参数说明路径/path/to/dataset要替换成你实际解压的位置脚本假设jpg和xml在同一目录下如果你的文件分散在子文件夹先find . -name *.jpg再处理。这套校验逻辑不仅适用于VOC格式对COCO等格式的数据也是通用的只是字段换一下而已。2.2 XML标注结构与其他格式的差别随便打开一个xml文件你会看到典型的VOC结构annotation根节点下面有filename、size宽高和通道数、object列表每个object里有name类别名和bndboxxmin、ymin、xmax、ymax。这类标注是labelImg工具生成的矩形框像素坐标是图片的实际像素值。但这份数据集有个需要留意的细节xml不包含VOC原始格式的分割标注信息比如segmented标签也没有额外的txt分割文件。也就是说它更像“只标注了检测框的简化版VOC”转换成YOLO格式时更省事但如果你想跑需要分割掩码的模型就没戏了。参考官方文档使用说明.txt里特别强调了“不包含分割的txt文件”所以别指望拿它去做实例分割任务。四个类别的框数我已经统计过了分别是closed_eye 2485框、closed_mouth 3343框、open_eye 4903框、open_mouth 936框。从数量上就能看出open_eye占比最高模型会偏向学“睁眼”特征而open_mouth只有936框是少数类。训练如果不开数据增强mAP里open_mouth大概率会拉胯。另外我注意到一个细节这张表里closed_mouth的框数比closed_eye多不少这意味着驾驶员“闭嘴”这个状态在数据里出现得更频繁对于疲劳判定来说闭眼张嘴的组合才是关键信号。2.3 一份240MB左右的数据包里能拿到什么解压之后的实测大小约240MB全部是jpg和xml没有其他花里胡哨的文件。图片尺寸不统一原始xml里的width和height各不相同常见的是1280x720和1920x1080两种少数图片是720x576。这种尺寸不统一在目标检测里很正常但你要注意后续如果直接resize成640x640那些小目标比如远处驾驶员的眼皮状态会损失细节。这份资源的边界在于它只提供准确且合理的标注不做任何模型的精度保证。也就是说数据集的标注质量可以相信但用它训出来的模型能不能商用取决于你的算法、训练策略和场景。对于快速验证“睁眼闭眼分类是否可行”这个命题它已经完全够用了。3. 把VOC转成YOLO格式转换脚本与四个边界坑3.1 为什么要转格式以及转换的数学逻辑VOC格式的矩形框是绝对坐标像素值而YOLO格式需要的是归一化相对坐标。YOLO要求每个框的中心点x、y坐标和宽w、高h都除以图片尺寸取值在0到1之间。这一步是后续用yolov8或yolov5训练自己的数据集绕不开的坎。转换时最常翻车的地方就是忘记归一化或者把xmax当成了框宽。3.2 用Python脚本一步到位完成转换import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, class_names, output_dir): 将单个VOC XML转换为YOLO格式的txt文件 :param xml_file: xml文件路径 :param class_names: 类别列表顺序要与训练时的data.yaml一致 :param output_dir: 输出目录 tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_width int(size.find(width).text) img_height int(size.find(height).text) yolo_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.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) # 计算中心点和宽高并归一化 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height # 防止归一化后出现超过1或负数 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if yolo_lines: base os.path.basename(xml_file).replace(.xml, .txt) with open(os.path.join(output_dir, base), w) as f: f.write(\n.join(yolo_lines)) # 使用示例 class_names [closed_eye, closed_mouth, open_eye, open_mouth] xml_dir /path/to/voc_xmls output_dir /path/to/yolo_labels os.makedirs(output_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), class_names, output_dir)这段脚本的逻辑核心在于把边界框从像素坐标映射到归一化坐标。x_center用的是(xmin xmax) / 2再除以宽度这比用xmin w/2更直观不容易出错。归一化后加了一层min/max裁剪是因为labelImg手动标注时偶尔会手滑把框拖出图片边界裁剪掉能避免训练时出现loss爆炸或NaN。参数说明class_names的顺序必须和后续训练时data.yaml里的names列表严格一致否则标签就错位了。YOLOv8的data.yaml里names顺序写错最常见的结果是训练loss降不下去或者推理时张冠李戴。另外注意输出的txt文件名要和对应的jpg名一致并且所有标签txt最好统一放在一个labels目录里图片另放在images目录这样后续划分训练集才清晰。3.3 转换后必须做的验证步骤转换完不验证等于白干。我习惯写一个小脚本检查每个txt文件的类别ID是否合法以及坐标值范围是否在0到1之间。另一个更实用的方式是直接看图用OpenCV把txt里的框画到jpg上肉眼确认框的位置和类别是否对得上。import cv2 def draw_yolo_box(img_path, txt_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) x_c, y_c, bw, bh map(float, parts[1:]) xmin int((x_c - bw / 2) * w) ymin int((y_c - bh / 2) * h) xmax int((x_c bw / 2) * w) ymax int((y_c bh / 2) * h) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (xmin, ymin - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) # 随机抽查10张 import os, random jpg_files [f for f in os.listdir(/path/to/images) if f.endswith(.jpg)] sample random.sample(jpg_files, 10) for f in sample: draw_yolo_box(f/path/to/images/{f}, f/path/to/labels/{f.replace(.jpg, .txt)}, class_names) cv2.waitKey(0)这个验证方式是我自己常用的土办法比任何自动化检测都直观。随机抽10张循环里cv2.waitKey(0)会一张张弹出来你只需要确认框是不是贴着眼睛和嘴巴、类别名对不对。如果发现某个框偏移严重先检查对应的xml原始坐标是否有问题不要一上来就改转换脚本。4. 训练配置与模型选型从YOLOv8到更细的疲劳状态判断4.1 YOLOv8n做基线的参数组合用这份数据集做训练我个人建议先拿YOLOv8n当基线因为它的体量小、训练快而且对于闭眼张嘴这种目标不算太小的检测任务精度已经够用。训练的前提是data.yaml和images、labels目录结构正确。data.yaml的内容要注意path是咱们自己的目录名train和val路径如果用同一份数据就把val指到train相同位置。names: 0: closed_eye 1: closed_mouth 2: open_eye 3: open_mouth path: /home/user/fatigue_dataset train: images val: images这里train和val都指向同一个images目录因为这份数据集没有自带划分文件。如果你不想在训练时打乱分布可以自己用脚本按照9:1划分把val单独指向一个shuffle后的images子集。我个人不推荐直接把train和val设为同一个目录虽然能跑通但val的损失曲线会失真没法准确判断过拟合。训练命令我一般这么敲yolo train data/home/user/fatigue_dataset/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0epochs设100imgsz选640batch看显卡显存。如果显卡只有8GB显存batch降到8如果显存紧张imgsz降到480也可以但代价是小目标的检测精度会下降。这里有个参数选择容易被忽视ampTrue混合精度训练YOLOv8默认开启能显著降低显存占用但如果爆出loss不稳定先把amp关掉再试。4.2 处理类别不平衡的两种思路前面已经提到open_mouth只有936个框是open_eye的1/5左右。如果直接训练模型学到的判别面会往睁眼和闭嘴偏移。常见的做法有两个方向一是调loss权重YOLOv8支持设置cls0.7这类类别损失的系数但官方对特定类别权重没有直接开放的参数二是做离线增强对open_mouth样本做随机旋转、亮度抖动、水平翻转把数量提上去。我个人更倾向于在训练前做增强而不是直接改loss权重因为从这份数据的实际场景看张嘴样本少是因为驾驶员清醒状态下很少张大嘴但如果疲劳了会打哈欠张嘴这个动作本身有特殊意义把augmentation集中在少数类上能帮模型学到更多姿态变化。实现方式可以用Albumentations库对open_mouth的图片做增强但要注意增强后要生成对应的标注框变换这比想象中麻烦。另一个简化方案是直接改YOLOv8的loss权重具体做法是给每个类别设置不同的cls_pw权重但YOLOv8的配置文件里没有这个选项需要改源码不太推荐新手碰。4.3 睁闭眼和嘴部状态判断的边界这份数据集的逻辑是通过睁闭眼、张嘴闭嘴来判断疲劳状态。训练完成后你会得到一个能框出四个部位的模型但框出来不等于判断出疲劳。更高层的策略是对每一帧视频做检测然后按时间窗口统计闭眼和哈欠的持续时间用PERCLOS眼部闭合时间比例这类指标来判定疲劳。你要知道单帧的closed_eye和open_eye只是一个检测结果连续帧的时序判断才是疲劳驾驶的判定核心。5. 避坑专题这批数据的常见翻车记录5.1 标注框越界的处理方式现象转换YOLO格式时某个txt里出现负坐标或者大于1的数值训练时报错“AssertionError: bbox must be in [0, 1]”。原因labelImg手动标注时框被拖到了图片边缘外侧导致xml里的xmin、ymin出现负数或者xmax、ymax超过宽度高度。解决第3章的转换脚本里已经有min/max裁剪但要注意只改txt不够xml原始值最好也修正一下。有一种场景是裁剪后框面积变成了0比如xmin和xmax都是负数这会导致该框完全失效应该直接删掉这个object否则训练时模型会学到异常信号。5.2 类别名不一致导致的训练崩溃现象训练一开始就报“class index out of range”或者loss里出现NaN。原因xml里某个object的name不是四个类别之一比如标注时打错成了“closed_eye_”或“openeyes”这种情况下转换脚本里的if name not in class_names: continue会把这个框丢掉但如果你跳过了这层判断类别ID就会变成4或5超出names列表长度。解决转换前先跑一遍脚本扫描全部xml里的name字段去重后看有没有预期之外的类别名。如果发现脏数据直接改xml里的name字段而不是改转换脚本让ID硬拱上去。我在实际项目里遇到过把“open_mouth”标成“opne_mouth”的情况这种错误如果不提前扫描会在训练中期突然爆出来。5.3 图片与xml尺寸不一致的隐患现象转换后画的框位置对不上明明标注的是眼睛框却飘到额头上。原因xml里的size和实际jpg的像素尺寸不同。常见情况是xml里写的width是1920但这张jpg实际是1280宽导致归一化坐标虽然合法但反算回像素时位置整体偏移。解决转换前加一步校验读xml里的size再拿PIL或cv2读图片实际尺寸对比不一致就跳过这个文件并输出警告。从根源上说这种问题通常是标注时用了一张图片的副本但尺寸信息在保存时被错误记录。5.4 训练集和验证集重叠导致的虚高mAP现象训练时val的mAP很高但拿到真实视频上测漏检和误检特别多。原因这份数据没有划分文件很多人图省事把train和val都指向同一个目录。YOLOv8虽然会引入shuffle但验证时看到的图片是训练过的mAP自然虚高。解决花五分钟自己按9:1划分一下。划分时注意按文件名随机抽不是按顺序切因为文件名的数字顺序可能和拍摄时间相关顺序切会导致验证集全是同一时间段的数据缺少姿态变化。5.5 labelImg标注工具的默认分辨率容易忽视现象用labelImg打开xml后发现框的数值很大比如xmin1800但在自己的可视化脚本里画出来框就超界了。原因labelImg保存xml时如果图片被缩放显示仍然保存的是原始像素坐标这是正确的。但如果你用自己写的可视化脚本时用了缩放后的图片尺寸去计算就会对不上。解决可视化时用cv2.imread读取原始尺寸不要用labelImg里显示的缩放尺寸。6. 自制数据集与进阶把这份VOC资源变成你自己的验证集训练和验证之外这份数据集最容易被低估的价值是做自制数据集时的“对照基准”。我之前自己收集过一批行车记录仪截屏想要验证“疲劳驾驶检测算法是否有效”但没有标准答案模型效果好坏说不清。这份VOC数据的四类标注框给了我很自然的参考系让我能估算自己标的框和公开数据之间的偏差。我后来通常的做法是先拿这份数据集跑出一个基准模型标记为Baseline mAP。然后用同样的训练脚本去训自己数据集的模型对比两个模型的PR曲线和混淆矩阵。如果自制数据集的mAP明显低于Baseline我能确认问题一定出在数据质量上比如标注一致性差、场景分布太单一而不是训练代码的缺陷。这种感觉很踏实比拿着一个模型盲目调参要省力得多。有一个细节特别值得提——这份数据的闭眼和睁眼框是分两个类别标注的而不是一个“eye_state”类别归属到两种状态。这给了我们在推理后处理阶段的额外灵活性你可以先用模型同时检测出closed_eye和open_eye两个框然后对它们的置信度做差得到一个连续的“闭合程度”度量而不是简单的二分类结果。我做过一个实验用conf(closed_eye) - conf(open_eye)作为特征比直接用argmax选类别做疲劳判定的准确率高了好几个点。另外如果你手头有多份VOC数据比如用labelImg自己标注的可以把这份数据的xml和你的xml混在一起重新训练。由于大家都在VOC格式下操作文件名冲突是唯一需要处理的问题。我习惯在混合前先把所有jpg和xml统一重命令名为fatigue_000001到fatigue_004362这样的连续编号再拼上自己数据的前缀。这一步不难但漏掉了容易让训练集里出现覆盖或重复血泪教训。想把它复现成一次实际可行的训练验证流程我还有个小习惯每次训练后都把val的metrics存储下来同时记录data.yaml的类别顺序和batch大小。如果哪天结果不对翻回去查记录能快速锁定改动点。从那以后我每次换数据集都强制走一遍“统计类别分布、校验文件配对、转换格式、可视化抽查”这个流程既省时间又很少翻车。这份资源值得你花一个下午跑通希望帮到你。本文还有配套的精品资源点击获取
返回列表