疲劳驾驶检测数据集处理:VOC/COCO/YOLO标签转换与YOLO训练避坑指南
简介面向从事目标检测的学习者与开发者这套疲劳驾驶目标检测数据集以一千张真实场景图片为核心场景覆盖多种路况与光照且使用标注工具完成高质量标注同时提供voc、coco、yolo三种格式的标签切换到不同系列模型时无需额外转换。压缩包内共两千个文件除图片与标签外还包含六份图文教程、三个数据集划分脚本与一个配置文件xml和txt分别对应voc与yolo标注体系python脚本可以自动划分训练集、验证集和测试集html文档覆盖了Windows与Linux环境搭建以及训练案例修改。压缩包整体大小约77.53MB目前已有166人学习或下载。拿到手即可直接使用可训练的数据集、划分工具与配套教程省去自行采集标注和反复调试环境的时间尤其适合刚接触yolo或缺少疲劳驾驶场景训练样本的开发者按需取用也可以在此基础上继续扩充类别与数据量。1. 疲劳驾驶检测数据集到手先别急着训练盘清1000张图和三套标签才是正经事接手疲劳驾驶目标检测项目时我最怕的不是选模型而是拿到手的数据集根本没法直接开训。这套YOLO疲劳驾驶目标检测数据集1000张图片不算大但它的价值在于把“数据集落地”最麻烦的一步提前做完了每张图同时给了voc、coco、yolo三种格式的标签外加划分脚本和训练教程。也就是说你不用再花两三天写转换脚本解压后就有条件用ultralytics或老版darknet直接跑通训练流程。适合两类人一是0基础纯小白想把疲劳驾驶检测从纸面落到本地二是短工期项目想快速验证yolo在这个场景的上限到底怎么样。我按“先盘目录、再改格式、最后调训练”的顺序把这套包拆开讲透坑也会逐个点名。2. 先盘清家底1000张图片和voc/coco/yolo标签到底怎么对应解压之后不要急着跑训练命令先把目录结构摸清楚。疲劳驾驶场景里图片大多是驾驶员面部、手部或上半身类别一般就是闭眼、打哈欠、打电话、正常驾驶这几种有时还会细分到“打哈欠且闭眼”这种复合状态。先弄清每个文件夹的职责后面改配置时才知道去哪里找对应文件才知道划分脚本生成的东西喂给谁吃。2.1 解压后的目录长什么样rar里那堆文件夹每个都有明确分工常见打包结构是这样的我一般会先用tree或ls走一遍. ├── JPEGImages/ # 原始图片1000张jpg ├── Annotations/ # VOC标签每张图对应一个xml ├── coco/ # COCO标签通常是annotations/train.json ├── labels/ # YOLO标签每张图对应一个txt归一化坐标 ├── 划分脚本.py / split.py # 划分训练集和验证集的脚本 └── 训练教程.md / train.py # 训练参考说明或可直接改的启动脚本各目录的分工很清楚JPEGImages是所有原始图像Annotations里是VOC格式的xml文件coco目录里是json标注labels里是YOLO训练直接吃的txt。划分脚本负责生成train.txt、val.txt这类清单有些打包版本还会额外给一份data.yaml里面写好了类别名称和路径映射。我拿到手的第一件事是数文件数JPEGImages里jpg数量应该等于Annotations里xml数量也等于labels里txt数量。三个数对不上就先找原因再训练别指望训练时报错帮你定位到具体哪张图。还有一个更隐蔽的问题疲劳驾驶图片很多来自视频抽帧连续帧之间画面高度相似如果划分脚本是纯随机切分训练集和验证集里会出现几乎一样的图后面第4章会详细讲这个坑。先记住一个判定标准验证集图片和训练集图片如果肉眼难分那mAP再高也是虚高。2.2 三种格式的设计逻辑xml是给标注工具看的json是给coco api看的txt是给yolo训练吃的先说结论voc、coco、yolo三套标签记录的目标信息完全一致都是类别名加边界框坐标区别只在存储方式和坐标单位。VOC用xml每个目标一个object节点框坐标是像素整数COCO用json框写成[x, y, width, height]单位也是像素YOLO的txt每一行是class_id x_center y_center width height后面四个值已经除以图片宽高做过归一化是0到1之间的小数。为什么要归一化因为YOLO训练时输入图片会被letterbox拉伸到640x640或1280x1280归一化坐标能保证框跟着图一起缩放不用重新改标签。| 格式 | 文件扩展名 | 坐标单位 | 一个目标怎么写 | | VOC | xml | 像素整数 | xmin, ymin, xmax, ymax | | COCO | json | 像素整数 | x, y, width, height | | YOLO | txt | 0~1归一化 | class_id, x_center, y_center, width, height |表格只是概括我举个具体例子。假设一张640x360的图里有一只眼睛框左上角(300, 120)右下角(380, 190)。VOC里就是xmin300, ymin120, xmax380, ymax190COCO里是[300, 120, 80, 70]因为宽是380-30080高是190-12070YOLO里x_center(300380)/2再除以640约等于0.53125y_center(120190)/2除以360大约是0.43056width和height也做同样处理。转换脚本的核心就是在做这件事。不过这里最容易翻车的是类别顺序VOC和COCO记录的是类别名比如closed_eye、yawningYOLO文本里只写数字IDID对应哪个名字取决于data.yaml里names列表的顺序。如果转换脚本里把classes写成一个顺序训练时data.yaml里又是另一个顺序模型就会把闭眼学成打哈欠而且这种错位在loss曲线上看不出来。2.3 划分脚本到底划的是什么train.txt、val.txt与图片列表的三种玩法划分脚本的作用不是切文件而是生成一份“哪些图片进训练、哪些进验证”的清单。最常见玩法有三种第一种生成train.txt和val.txt每个txt一行一个图片路径老版darknet和yolov5都用这种第二种直接在images和labels下建train/val子目录把图片和标签物理挪过去ultralytics的dataset结构常这么干第三种生成包含图片路径和标签路径的csv或json索引。三种玩法的最终目的都一样让训练框架通过索引找到图片和对应标签。下面是最基础的一种生成清单脚本import os import random random.seed(42) img_dir JPEGImages label_dir labels train_ratio 0.8 val_ratio 0.1 imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(imgs) n_train int(len(imgs) * train_ratio) n_val int(len(imgs) * val_ratio) train_imgs imgs[:n_train] val_imgs imgs[n_train:n_train n_val] test_imgs imgs[n_train n_val:] def write_split(imgs, name): with open(f{name}.txt, w) as f: for img in imgs: label img.replace(.jpg, .txt) if os.path.exists(os.path.join(label_dir, label)): f.write(f{os.path.join(img_dir, img)}\n) write_split(train_imgs, train) write_split(val_imgs, val) write_split(test_imgs, test) print(ftrain{len(train_imgs)} val{len(val_imgs)} test{len(test_imgs)})这段脚本按8:1:1切分核心是random.seed(42)固定随机种子保证每次运行生成同样的结果。write_split函数用图片文件名反查同名txt标签只有图片和标签都齐了才写入清单。这个细节很多教程不讲只检查图片存在而不检查标签存在训练到一半报找不到标签文件回头排查才发现是漏了几张没标注的图。如果你用的是ultralytics全家桶可以不用test.txttrain和val分开给就行。切分比例上我建议至少预留0.1给test因为疲劳驾驶模型上线前需要一组完全没见过的图来评估泛化而不是拿验证集凑合。3. 从voc到yolo动手改转换脚本之前先把类别顺序定死拿到包之后先确认labels文件夹是不是已经能直接用。很多标注工具默认导出VOC打包的人额外转了一遍YOLO但如果转的时候类别顺序写错或者归一化时除错了宽高再新的训练脚本也白搭。我习惯自己从头跑一遍转换脚本不迷信“别人已经转好”毕竟训练跑十几个小时才发现标签错位那才是真正的血泪经验。3.1 为什么ultralytics吃yolo格式data.yaml的路径规则与类别映射ultralytics的yolov8、yolov11训练默认要求图片和标签处于同一个父目录下图片放在images/traintxt标签放在labels/traindata.yaml里写train和val的图片路径。为什么不直接用voc或coco训练因为yolo的损失函数期望直接拿到归一化坐标省去训练时解析xml或json的开销COCO的json格式还要额外依赖pycocotools算mAP训练管线里多一个依赖就多一个坑位。所以先看data.yaml的标准写法# data.yaml train: ./dataset/images/train val: ./dataset/images/val nc: 4 names: [closed_eye, yawning, phone_use, normal]train和val指向图片目录ultralytics会自动在同级的labels目录里找对应txt。nc是类别数量必须和labels里出现的最大class_id加1相等names的顺序就是txt里数字ID到类别名的映射。很多翻车现场都出在这里转换脚本用旧顺序生成了txtdata.yaml里names却写成另一种顺序于是训练时0变成打哈欠、1变成闭眼验证集mAP看起来不低部署后全废。yolov8以上版本会在检测到names和标签类别数不一致时报警但不会帮你纠正语义映射所以定names的时机要在转换格式之前转换之后不要轻易改。3.2 xml转txt完整脚本归一化、类别映射、越界框裁剪一起做下面这版脚本是我常用的VOC转YOLO方案核心逻辑是遍历Annotations下的xml用xml.etree.ElementTree解析每个object读xmin、ymin、xmax、ymax先除以图片宽高归一化再剔除越界和无效框import xml.etree.ElementTree as ET import os from PIL import Image classes [closed_eye, yawning, phone_use, normal] src_xml_dir Annotations src_img_dir JPEGImages dst_label_dir labels os.makedirs(dst_label_dir, exist_okTrue) for ann_file in os.listdir(src_xml_dir): if not ann_file.endswith(.xml): continue tree ET.parse(os.path.join(src_xml_dir, ann_file)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(src_img_dir, img_name) w, h Image.open(img_path).size out_lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) x1, x2 max(0, min(x1, w)), max(0, min(x2, w)) y1, y2 max(0, min(y1, h)), max(0, min(y2, h)) if x2 x1 or y2 y1: continue x_center ((x1 x2) / 2) / w y_center ((y1 y2) / 2) / h box_w (x2 - x1) / w box_h (y2 - y1) / h out_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) dst_path os.path.join(dst_label_dir, ann_file.replace(.xml, .txt)) with open(dst_path, w) as f: f.write(\n.join(out_lines))这个脚本有两个容易忽略的细节。第一我用PIL读真实图片宽高而不是信xml里的size节点。标注工具写的size经常和实际图片不一致尤其遇到经过裁剪、压缩或转码的图用错宽高会导致所有归一化坐标整体偏移。第二先裁剪越界框再归一化因为手滑把框拖出画面的情况很常见不裁剪会出现width大于1的异常值yolo训练时框回归会不稳定。classes列表的顺序就是后面data.yaml的names顺序转换前必须定死转换后不要动。另外注意xml里filename字段有时带目录前缀或大写后缀比如JPEGImages/001.JPG我在真实项目里见过因为大小写不匹配导致PIL打不开图片的情况稳妥做法是用os.path.basename处理后再拼路径。3.3 先划分还是先转换格式按视频段分组切分才是疲劳驾驶的正解很多教程先转换全部标签再做随机划分这最简单。但如果你的图片来自视频抽帧我建议先按场景或视频段划分再对训练集做格式转换。原因很简单随机切分容易让同一条视频的连续帧同时出现在训练和验证集模型等于提前看过答案验证分数虚高。疲劳驾驶视频连续性太强一个打哈欠动作通常涵盖几十帧前20帧进训练、后20帧进验证模型在验证时看到的就是训练帧的轻微位移mAP高得没有说服力。出问题的是数据包里已经给好的划分脚本直接跑之前要看它划分依据是什么。有的脚本是纯随机打乱文件名有的是按时间戳采样有的按文件夹分。如果是纯随机且文件名里有视频段标识就按组切分import random from collections import defaultdict random.seed(7) img_dir JPEGImages imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] groups defaultdict(list) for img in imgs: key img.split(_)[0] # video01_frame_001.jpg 取 video01 groups[key].append(img) group_names list(groups.keys()) random.shuffle(group_names) n_train int(len(group_names) * 0.8) train_groups set(group_names[:n_train]) val_groups set(group_names[n_train:]) train_imgs [img for g in train_groups for img in groups[g]] val_imgs [img for g in val_groups for img in groups[g]] with open(train.txt, w) as f: f.write(\n.join(train_imgs)) with open(val.txt, w) as f: f.write(\n.join(val_imgs))这段逻辑的关键是以组为单位切分组粒度是video01这种前缀保证同一个视频的帧不会跨进两个集合。random.seed(7)可以换成你自己的种子建议至少试三个种子各跑一轮挑验证结果稳定的一次如果三次波动特别大说明样本量不足或类别分布不均衡后期要针对少数类做重采样。img.split(_)[0]是按video01_frame_001命名约定写的如果文件名是face_0612_001拆出来的分组可能和时间段无关这时要改用正则提取真正的视频段标识。分组划分会让验证集mAP比纯随机低0.05到0.1但那个数字才真实反映你的模型在没见过的视频片段上的表现。4. 疲劳驾驶数据集避坑指南5个让训练翻车的常见问题这个数据集看起来简单训练起来坑比想象的多。我把这几年在疲劳驾驶检测上踩过的坑以及从数据集角度最容易出的问题整理成5条每条按“现象、原因、解决”的顺序写你照这个排查能省下大量试错时间。4.1 类别定义模糊闭眼和正常眯眼、打哈欠和说话边界划不清现象训练时loss降得很漂亮验证mAP有0.8以上但实际部署后正常驾驶经常被误报成闭眼打哈欠却没识别出来。原因疲劳驾驶的类别本身是连续状态半闭眼到底算闭眼还是正常不同标注员的标法可能不一致。如果voc标签里closed_eye既有完全闭合的图也有只垂了百分之二十的图模型就会在两类之间反复横跳。解决先把标签全量扫一遍把界定模糊的样本单独挑出来重新定规则。我一般会写个统计脚本算出所有closed_eye框的长短轴比例比如框高除以框宽小于0.25的才算闭眼然后手动删除或改掉中间地带的样本。这个步骤没法完全自动化但至少能帮你发现标注风格是否统一。在训练教程里看到别人把打哈欠和说话标成两类时你要特别警惕两类边界说话张嘴幅度大时和打哈欠几乎无法靠单帧区分要么合并成一类要么引入时序特征。4.2 只有1000张图yolo照样过拟合类别不均衡和增强策略的问题现象训练到第80轮train loss还在降val loss开始反弹典型过拟合。再看各类别样本数normal占比通常过半closed_eye和phone_use可能只有一百多张。原因1000张对yolo这种深度模型来说只是入门量级尤其是小目标比如远处的人脸或手部需要更多样本类别不均衡会让模型倾向预测样本多的类。解决先统计每个类别的标签数量不要急着加增强。如果某类只有150张优先做离线增强包括亮度扰动、高斯噪声、随机遮挡以及把同一类的目标区域裁剪出来拼到新背景图上。疲劳驾驶的闭眼和打哈欠对颜色不敏感网上那些调hsv参数的增强效果很有限把样本数量做起来比调颜色有用得多。调增强参数在疲劳驾驶场景里多少有点玄学我一般先用基础增强跑一版再叠加亮度抖动和随机灰度化看验证集有没有提升。4.3 夜间、戴墨镜、侧脸样本几乎为零验证集虚高的真相现象白天测试视频效果好一到夜间或司机戴墨镜就全崩mAP直接腰斩。原因数据集大概率是在白天、驾驶舱正面视角拍的夜间红外和墨镜遮挡的样本根本没进1000张模型没见过这个分布白天验证集测出来自然虚高。解决不要指望一个数据集解决所有光照场景。先用划分脚本把图片按“白天、夜间、逆光、墨镜”这类条件做标记确认分布再决定是补数据还是训练时针对性地做灰度化和亮度抖动。灰度化对墨镜场景有一定帮助因为模型被迫学习纹理而不是颜色但真实夜间红外图像还得靠补数据。这个坑没有完全的后悔药前期盘点样本分布比调任何参数都值。数据包里如果只给了1000张白天图上线夜间场景前一定要做专门的夜间采集哪怕只补几百张。4.4 标签对不上图xml与txt行数不一致、文件名后缀踩坑现象训练时偶尔报错提示图片和标签不匹配或者某个txt行数明显比图片里的目标数多。原因转换脚本按xml文件名生成txt但xml里filename字段与JPEGImages里实际文件名不一致比如图片是001.jpgxml里写的是001.JPG甚至带了目录前缀导致labels匹配不上。解决写一个校验脚本在训练前遍历所有图片检查同名txt是否存在再逐行解析txt里的class_id是否在nc范围内、归一化坐标是否在0到1之间。我一般把校验放在训练命令之前一旦有脏数据直接打印文件名。这个问题越早暴露越省时间等训练到一半才暴露就是既耗电又耗神。如果用ultralytics自带的数据校验它也能报路径问题但报错信息往往指向目录而不是具体哪个文件反而不如一个小脚本直接。4.5 划分脚本把连续帧拆散喂给训练和验证验证集被污染现象用数据包自带的划分脚本跑出来的val mAP能到0.9换一批实拍视频掉到0.6落差大得吓人。原因1000张图片里如果有同一段视频的连续抽帧随机划分就会把第10帧放训练、第11帧放验证这两帧构图几乎一样模型等于见过答案验证集失去意义。解决按3.3的分组划分方案先按视频段分组再切分。如果文件名里没有视频段标识就需要靠时间戳或用感知哈希计算图片相似度做聚类把相似帧归到同一组。不要偷懒用随机打乱疲劳驾驶视频连续性太强随机切分几乎必然翻车。另外分组划分后的mAP会显得“不如”随机切分漂亮但那个低一点的数字才对部署有参考意义。5. 从数据到部署0基础纯小白也能跑通的最小训练与导出验证5.1 先跑通最小训练命令别一上来就堆参拿到整理好的数据集我建议第一次训练不追求mAP只求流程走通。用ultralytics的话命令缩到最短pip install ultralytics yolo detect train datadata.yaml modelyolov8n.pt epochs50 imgsz640data.yaml里的路径改成你机器上的实际路径。yolov8n.pt是官方预训练权重第一次跑会联网下载放在本地后也可以直接指定本地路径。epochs50只是为了快速看loss曲线是否下降不代表收敛后期要加到150以上。跑通之后把model换成yolov11的变体或加了efficient head的改进版在同一份划分好的数据上对比mAP再决定要不要在注意力机制上花时间。硬件底线是8G显存6G也能跑但batch size要降到4。数据包里如果带了训练教程先照着跑通再调自己的路径。5.2 导出onnx做真实部署验证预处理对齐和时序过滤不能省训练完别只看val mAP直接导出onnx跑一段真实视频才见真章from ultralytics import YOLO model YOLO(runs/detect/train_v1/weights/best.pt) model.export(formatonnx, imgsz640)导出后推理最容易踩坑的是预处理不对齐。训练时ultralytics用letterbox把原图缩放到640并填充灰色边导出onnx后如果直接用opencv的resize把图粗暴拉到640宽高比变了框就全部偏移。解决方法是推理脚本里也做letterbox或者直接用ultralytics内置的模型预测接口加载onnx别自己手工拼接resize。疲劳驾驶场景通常要接入RTSP摄像头或本地视频文件我还习惯加一个滑窗过滤连续多帧判定为闭眼或打哈欠才触发告警避免单帧抖动造成误报。做过几次疲劳驾驶项目之后我发现最有用的不是模型结构多新而是第2章的目录盘点和第4章的每个坑都踩一遍。数据集的1000张图只是一个起点训练命令跑通只算第一步真正投入使用时要把夜间、墨镜、侧脸这些边界情况当成独立项目来补数。希望你拿到这套东西少走弯路希望帮到你。本文还有配套的精品资源点击获取