疲劳驾驶检测数据集实战:VOC/COCO/YOLO三格式转换与YOLO训练全流程
发布时间:2026/10/1 18:47:52来源:尧图网络
简介本资源为面向疲劳驾驶检测场景的YOLO目标检测数据集适合从事智能驾驶、行为识别方向的研究者与算法工程师用于训练和验证疲劳驾驶状态下的目标检测模型。数据集包含1000张真实场景采集的高质量图片场景丰富均使用labelImg标注标注框质量较高并同步提供vocxml、cocojson与yolotxt三种格式标签分别存放于不同文件夹可直接接入YOLO系列训练流程。压缩包共约2000个文件以xml与txt标签文件为主另含少量html教程、py划分脚本与yaml配置文件整体约77.53MB。资源附赠YOLO环境搭建、训练案例教程及数据集划分脚本可按需自行划分训练集、验证集与测试集帮助读者快速完成从数据准备到模型训练的全流程。目前已有165人学习下载适合需要快速搭建疲劳驾驶检测实验的中高级开发者参考使用。1. 疲劳驾驶检测数据集从1000张图片到三种标签格式的完整落地路径疲劳驾驶检测这件事真正卡住大多数团队的往往不是模型结构而是数据。你拿到的这份资源——1000张疲劳驾驶场景图片配上VOC、COCO、YOLO三种格式标签外加划分脚本和训练教程——本质上解决的是「从零到能训」这一段最耗人力的环节。1000张听起来不多但在疲劳驾驶这个垂直场景里它覆盖了闭眼、打哈欠、低头、正常驾驶等关键状态足够跑通一个可用的基线模型。这篇文章不讲空泛的算法原理而是把这份数据集从解压到训练出第一个权重文件的完整链路拆开三种标签格式各自什么结构、划分脚本怎么改、YOLO训练时哪些参数必须调、以及我在实际项目中踩过的那些坑。适合刚接手疲劳驾驶检测任务、手里有数据但不知道怎么高效用起来的工程师。2. 三种标签格式到底怎么选VOC、COCO、YOLO的结构差异与转换逻辑2.1 为什么同一批图片要存三种格式疲劳驾驶检测的数据集通常不会只服务一个框架。VOC格式是Pascal VOC时代的遗产用XML逐图描述目标框很多老牌检测代码库和标注工具默认输出这个COCO格式用单个JSON管所有图片的标注是当前学术评测和多数预训练模型的标准输入YOLO格式则是每张图配一个txt归一化坐标直接喂给YOLO系列训练脚本。三种格式并存的好处是你不需要为了换一个模型重新标注转换脚本跑一遍就行。但这里有个容易被忽略的点三种格式对「类别」和「坐标」的表达方式不同。VOC的坐标是绝对像素值原点在左上角COCO的bbox是[x_min, y_min, width, height]也是绝对像素YOLO则是[x_center, y_center, width, height]全部除以图片宽高做归一化。如果你手动转格式时搞混了模型学到的就是错位的框。我一般会先写一个校验脚本随机抽10张图把三种格式的框画出来叠在原图上肉眼确认对齐后再批量处理。2.2 VOC格式的目录结构与解析要点VOC格式的标准目录长这样VOCdevkit/ └── VOC2007/ ├── Annotations/ # 每张图一个XML ├── JPEGImages/ # 原图 ├── ImageSets/ │ └── Main/ # train.txt / val.txt / trainval.txt └── SegmentationClass/一个典型的疲劳驾驶XML标注文件内容如下annotation folderJPEGImages/folder filenamefatigue_0001.jpg/filename size width640/width height480/height depth3/depth /size object nameclosed_eye/name bndbox xmin210/xmin ymin155/ymin xmax268/xmax ymax190/ymax /bndbox /object object nameyawn/name bndbox xmin195/xmin ymin200/ymin xmax280/xmax ymax260/ymax /bndbox /object /annotation解析时要注意size里的宽高必须和实际图片一致有些标注工具会写错name字段就是类别名疲劳驾驶场景常见的有closed_eye、open_eye、yawn、no_yawn、head_down等。如果你的数据集里类别名不统一比如有的写closedeye有的写closed_eye训练前必须做一次映射归一化否则模型会把它们当成不同类别。2.3 COCO格式的JSON字段与读取方式COCO格式把所有标注塞进一个JSON核心字段是images、annotations、categories。用Python读取和检查的代码如下import json import os # 加载COCO标注文件 with open(annotations/instances_train.json, r) as f: coco json.load(f) # 建立image_id到文件名的映射 img_id_to_name {img[id]: img[file_name] for img in coco[images]} # 建立category_id到类别名的映射 cat_id_to_name {cat[id]: cat[name] for cat in coco[categories]} print(f图片总数: {len(coco[images])}) print(f标注框总数: {len(coco[annotations])}) print(f类别列表: {list(cat_id_to_name.values())}) # 统计每个类别的框数量检查是否严重不均衡 from collections import Counter cat_counter Counter(ann[category_id] for ann in coco[annotations]) for cid, count in cat_counter.items(): print(f{cat_id_to_name[cid]}: {count} 个框)这段代码的作用是快速摸清数据分布。疲劳驾驶数据集里open_eye和no_yawn这类正常状态样本往往远多于closed_eye和yawn如果比例超过5:1训练时就需要考虑类别权重或者过采样。annotations里的bbox字段是[x, y, w, h]category_id对应categories里的id不要和列表索引搞混。2.4 YOLO格式的归一化坐标与文件组织YOLO格式最简单也最容易出错。每张图片对应一个同名txt每行一个目标class_id x_center y_center width height所有数值都是相对于图片宽高的归一化值范围0到1。类别从0开始编号编号顺序由classes.txt或data.yaml里的names列表决定。一个常见的翻车场景是你从VOC转YOLO时类别映射表写错了closed_eye和open_eye的id对调模型训练loss正常下降但推理时闭眼被识别成睁眼。这种错误不看混淆矩阵根本发现不了。转换的核心逻辑用下面这段代码说明import xml.etree.ElementTree as ET import os # 类别映射必须和data.yaml里的names顺序完全一致 class_map {closed_eye: 0, open_eye: 1, yawn: 2, no_yawn: 3, head_down: 4} def voc_to_yolo(xml_path, img_w, img_h, output_txt): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: continue # 跳过未定义类别 cls_id class_map[name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 转成归一化的中心点宽高 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 # 裁剪到[0,1]防止标注越界 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) w max(0, min(1, w)) h max(0, min(1, h)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(output_txt, w) as f: f.write(\n.join(lines))参数说明class_map必须和训练配置里的类别顺序一致img_w和img_h从XML的size字段读取但建议用PIL重新读一次实际图片尺寸做校验归一化后的值保留6位小数足够YOLO训练脚本会自动解析。如果某个目标框的宽或高为0说明标注有问题应该记录并跳过。3. 划分脚本怎么写训练集、验证集、测试集的正确切分方式3.1 按图片划分而不是按标注框划分疲劳驾驶数据集的划分有个基本原则同一张图片的所有标注框必须落在同一个集合里。如果你按标注框随机划分同一张图的闭眼框在训练集、打哈欠框在验证集模型会学到「这张图里既有闭眼又有哈欠」的虚假关联验证指标虚高。正确做法是先拿到所有图片文件名列表打乱后按比例切分再把对应的标注文件跟着移动或复制。常见的比例是训练集:验证集:测试集 7:2:1 或 8:1:1。1000张图片的话700张训练、200张验证、100张测试是比较稳妥的。如果某些类别样本特别少比如head_down只有50个框可以考虑分层抽样保证每个集合里都有一定数量的稀有类别。3.2 一个可直接用的划分脚本import os import random import shutil from pathlib import Path random.seed(42) # 固定随机种子保证可复现 def split_dataset(img_dir, label_dir, output_dir, ratios(0.7, 0.2, 0.1)): img_dir: 原图目录 label_dir: YOLO格式标签目录 output_dir: 输出根目录下建images/labels的train/val/test ratios: 训练/验证/测试比例 img_dir Path(img_dir) label_dir Path(label_dir) output_dir Path(output_dir) # 收集所有图片文件名不含扩展名 exts {.jpg, .jpeg, .png, .bmp} stems [f.stem for f in img_dir.iterdir() if f.suffix.lower() in exts] stems.sort() random.shuffle(stems) n len(stems) n_train int(n * ratios[0]) n_val int(n * ratios[1]) splits { train: stems[:n_train], val: stems[n_train:n_train n_val], test: stems[n_train n_val:] } for split_name, split_stems in splits.items(): img_out output_dir / images / split_name lbl_out output_dir / labels / split_name img_out.mkdir(parentsTrue, exist_okTrue) lbl_out.mkdir(parentsTrue, exist_okTrue) for stem in split_stems: # 找原图可能有不同扩展名 for ext in exts: src_img img_dir / f{stem}{ext} if src_img.exists(): shutil.copy2(src_img, img_out / src_img.name) break # 复制标签 src_lbl label_dir / f{stem}.txt if src_lbl.exists(): shutil.copy2(src_lbl, lbl_out / src_lbl.name) else: print(f警告: {stem} 没有对应标签文件) print(f划分完成: train{len(splits[train])}, val{len(splits[val])}, test{len(splits[test])}) # 调用示例 split_dataset( img_dirdatasets/raw/images, label_dirdatasets/raw/labels, output_dirdatasets/fatigue_split, ratios(0.7, 0.2, 0.1) )逻辑说明脚本先收集所有图片的主文件名排序后固定种子打乱按比例切分。对每个子集分别复制图片和同名txt标签到对应目录。如果某张图没有标签文件会打印警告但不中断——这种情况通常是负样本或者标注遗漏需要人工确认。参数ratios可以根据数据量调整数据少于500张时建议用8:1:1验证集太小会导致指标波动大。3.3 划分后必须做的三项校验划分完不要直接开训先跑三个检查。第一确认每个集合的图片数和标签数一致差一个都说明有文件丢失。第二统计每个集合里各类别的框数量如果验证集里某个类别一个框都没有那这个类别在验证指标里就是NaN。第三随机抽几张图用YOLO格式的标签画框可视化确认坐标没有偏移。我一般会写一个verify_split.py把这三项检查固化成脚本每次换数据集跑一遍能省掉大量事后排查的时间。4. YOLO训练参数怎么设从data.yaml到超参数的实操配置4.1 data.yaml的字段含义与常见错误YOLO训练的第一步是写data.yaml它告诉训练脚本去哪里找数据、有哪些类别path: /home/user/datasets/fatigue_split train: images/train val: images/val test: images/test names: 0: closed_eye 1: open_eye 2: yawn 3: no_yawn 4: head_downpath是数据集根目录train/val/test是相对于path的图片路径。YOLO会自动把images替换成labels去找标签所以你的目录结构必须是images/train和labels/train并列。常见错误有三个names的编号不从0开始、类别名和标注文件里的id对不上、路径用了绝对路径但换机器后失效。建议path用相对路径或者通过环境变量传入。4.2 从预训练权重开始训练的命令与参数解读疲劳驾驶数据集只有1000张图从零训练几乎不可能收敛到可用精度。正确做法是加载COCO预训练权重做迁移学习。以YOLOv8为例训练命令如下yolo detect train \ datadatasets/fatigue_split/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience20 \ device0 \ projectruns/fatigue \ nameexp1逐个参数说明modelyolov8n.pt用的是nano版本1000张图这个量级用n或s就够了x版本反而容易过拟合epochs100配合patience20如果20轮验证指标没提升就早停imgsz640是标准输入尺寸如果你的图片分辨率远大于640可以适当调大但显存占用会平方增长batch16在8GB显存上跑640尺寸基本安全显存不够就降到8lr00.01是初始学习率迁移学习时这个值比较稳如果loss震荡厉害可以降到0.001lrf0.01是最终学习率因子余弦退火到初始值的1%。4.3 疲劳驾驶场景下必须调整的三个参数第一个是close_mosaic。YOLO默认开启mosaic增强把4张图拼成1张训练这对通用目标检测很有效但疲劳驾驶场景里人脸和眼睛的局部特征很重要mosaic会破坏空间关系。我一般设close_mosaic10即最后10轮关闭mosaic让模型在真实分布上微调。第二个是degrees。默认旋转增强是0但驾驶员头部倾斜是疲劳的典型表现适当增加旋转角度能让模型对头部姿态更鲁棒。建议设degrees10不要超过15否则眼睛和嘴巴的相对位置会被破坏。第三个是类别权重。如果closed_eye和yawn的样本数远少于正常状态可以在训练时用cls损失权重或者过采样。YOLOv8没有直接的类别权重参数替代方案是在数据集层面做平衡或者用focal loss的变体。我通常先跑一轮看混淆矩阵如果稀有类别召回率低于0.5再回头处理数据。4.4 训练过程中的关键监控指标训练日志里重点看三个box_loss、cls_loss和mAP50。box_loss下降说明框的位置在收敛cls_loss下降说明分类在改善。如果box_loss正常下降但cls_loss震荡通常是类别不均衡或者学习率偏大。mAP50是IoU阈值0.5时的平均精度疲劳驾驶检测里closed_eye的AP通常最高head_down最低因为低头姿态的类内差异大。验证集指标不涨但训练集指标持续涨就是过拟合的信号。1000张图训练100轮过拟合几乎必然发生早停和强增强是主要对策。如果验证mAP50在30轮后就不动了可以把patience设小一点别浪费算力。5. 避坑与排查疲劳驾驶数据集训练中最容易翻车的五个问题5.1 现象训练loss正常下降但推理时框全偏了原因VOC转YOLO时坐标归一化用错了图片尺寸。有些标注XML里的size字段和实际图片尺寸不一致转换脚本如果直接读XML的宽高就会得到错误的归一化坐标。解决转换时用PIL重新读取实际图片尺寸和XML里的size做对比不一致的以实际尺寸为准。转换完成后随机抽20张画框验证确认框和眼睛、嘴巴对齐。5.2 现象验证集mAP忽高忽低波动超过10个点原因验证集太小。1000张图按7:2:1划分验证集只有200张如果某些类别在验证集里只有几个样本指标就会剧烈波动。解决把验证集比例提高到0.2以上或者用交叉验证。另一个办法是固定验证集不要每次训练重新划分这样指标至少是可比的。5.3 现象模型把闭眼和睁眼搞混混淆矩阵对角线模糊原因两类样本的视觉差异在低分辨率下不明显或者标注时边界不清晰。疲劳驾驶场景里半闭眼状态到底算闭眼还是睁眼不同标注员标准不一致。解决先检查标注一致性把模棱两可的样本挑出来重新标注或丢弃。训练时提高输入分辨率到640以上让眼部区域有更多像素。如果还是混淆考虑把闭眼和睁眼合并成一个「眼睛状态」类别用回归而不是分类来预测开合程度。5.4 现象训练到一半loss突然变成NaN原因学习率太大导致梯度爆炸或者某个批次的标注框宽高为0导致除零。解决先把学习率降到0.001重跑。如果还在中间崩检查标注文件里有没有0 0 0 0 0这样的空行或者宽高为0的框。YOLO的损失函数里会对宽高取log0值直接导致NaN。写个脚本扫一遍所有标签文件把非法行删掉。5.5 现象训练完模型在测试集上表现正常但实际视频推理时漏检严重原因训练数据是静态图片实际视频里有运动模糊、光照变化、驾驶员姿态连续变化分布不一致。解决从视频里抽帧补充训练数据特别是黄昏、夜间、逆光等困难场景。推理时用跟踪算法如ByteTrack把多帧结果关联起来单帧漏检可以通过时序信息弥补。另外测试集的指标只能反映静态图片上的表现不要把它当成实际部署的精度。6. 用混淆矩阵和PR曲线验证模型是否真的学会了疲劳特征训练完拿到best.pt之后别只看mAP就收工。我习惯跑一遍验证脚本把混淆矩阵和PR曲线拉出来看。YOLO自带val模式可以生成这些图yolo detect val \ modelruns/fatigue/exp1/weights/best.pt \ datadatasets/fatigue_split/data.yaml \ imgsz640 \ plotsTrue跑完后在输出目录里找confusion_matrix.png和PR_curve.png。混淆矩阵看的是类别之间的误判方向如果closed_eye大量被预测成open_eye说明模型对眼部开合程度的判别力不够需要补充半闭眼状态的样本如果yawn被预测成no_yawn通常是嘴巴区域分辨率不足可以尝试提高输入尺寸或者对嘴部区域做裁剪增强。PR曲线看的是每个类别的精度-召回权衡。曲线下的面积就是AP但更重要的是曲线的形状如果某个类别的曲线在低召回区就急剧下降说明模型对这个类别的置信度普遍偏低可能是样本太少或者标注质量差。我一般会设一个阈值比如AP低于0.4的类别就回头查数据而不是盲目加训练轮数。还有一个容易被忽略的验证手段把模型在测试集上的预测结果导出成图片人工翻看。指标再好看如果框的位置歪了或者漏掉了明显的闭眼指标就是假的。我通常会随机抽50张测试图用yolo predict跑一遍把原图、真值框、预测框拼在一起看。这个过程花不了多少时间但能发现很多指标反映不出来的问题。最后说一个我自己的习惯每次训练完把data.yaml、训练命令、关键参数、最终指标记在一个experiment_log.md里。疲劳驾驶检测这个方向数据集的类别定义和标注标准很容易在迭代中漂移没有记录的话三个月后你根本想不起来当时为什么设了那个参数。这个习惯帮我省过很多次重复排查的时间。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网