ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLO疲劳驾驶检测:三种标签格式对齐与训练全流程

YOLO疲劳驾驶检测:三种标签格式对齐与训练全流程 简介YOLO疲劳驾驶目标检测数据集面向计算机视觉目标检测方向的开发者与学生提供真实驾驶场景下高质量图片共1000张场景覆盖日间、夜间、不同光照及视角可用于疲劳驾驶行为识别模型的训练与验证。压缩包内共2000个文件以xml、txt两种标签文件为主体另有html格式的环境搭建与训练教程、py格式数据集划分脚本和yaml模型配置文件分别对应VOC、COCO和YOLO三种常用标注格式能够直接接入YOLO系列模型进行训练。附赠的Linux/Windows双版本环境搭建与训练教程配合三个划分脚本可一键生成训练集、验证集、测试集省去手动整理数据的流程。资源还包含详细的案例修改思路适合刚入门目标检测的学生快速上手也可供研究人员在疲劳驾驶场景下对比不同标注格式的模型效果。目前已有165人学习下载包体约77.53MB是一份结构清晰、可直接落地的数据集配套包。1. 拿到YOLO疲劳驾驶目标检测数据集先别急着训练一千张图片的YOLO疲劳驾驶目标检测数据集看着不大却带齐了VOC、COCO和YOLO三种格式的标签。多数人拿到压缩包后第一反应是解压、改路径、跑训练这个顺序在数据充足时问题不大放到疲劳驾驶这类小目标场景里就有风险眼睛和嘴巴的标注框只有几十个像素三种标签一旦坐标口径不一致损失函数会被少量错样本带偏。三种格式并存不是为了好看而是不同工具链的入场券关键工作是确认三份标注由同一份坐标派生再让划分和训练都基于这个前提。下面从格式映射、数据划分、训练命令到训练后自检逐步展开适合做智能座舱疲劳检测、或刚接触目标检测流程的同学照着跑通。2. 三种格式标签先对齐VOC、COCO与YOLO的映射关系2.1 目录里真正需要关心的四类文件解压这类数据包后目录命名可能各不相同但有效文件逃不出四种图片、VOC格式的XML、COCO格式的JSON、YOLO格式的TXT。与其背目录名不如先找出它们之间的对应规律。绝大多数情况下图片去掉扩展名之后的主文件名就是整套数据的同步键001.jpg对应001.xml和001.txt这比任何字段都可靠。拿到压缩包我一般会先整理成下面这种结构后面所有脚本都只认这套结构fatigue_dataset/ ├── JPEGImages/ # 图片统一为 jpg文件名是主键 │ ├── 001.jpg │ └── 002.jpg ├── Annotations/ # VOC每张图一个 XML │ ├── 001.xml │ └── 002.xml ├── annotations.json # COCO整个数据集合并成一个 JSON └── labels_yolo/ # YOLO每张图一个 TXT ├── 001.txt └── 002.txt如果原始压缩包不是这样命名的建议先写一个几行的小脚本把图片统一重命名为数字序号再同步修改三个标签目录里的文件名。这里的坑在于重命名时只改图片不改标签等于直接废掉一组标注。整理目录这事看起来繁琐但它决定后面划分脚本是写一遍就能用还是在每个环节都加特判。2.2 VOC、COCO、YOLO三份标注的对照表三种格式的差异用一张表就能看明白维度VOC XMLCOCO JSONYOLO TXT文件组成每张图片一个XML整个集合一个JSON每张图片一个TXT边界框表示(xmin, ymin, xmax, ymax)左上角(x, y)加宽高(w, h)中心点(xc, yc)加宽高(w, h)坐标单位像素绝对值像素绝对值归一化相对值范围0到1类别写法字符串标签名category_id加categories映射表整数ID从0开始读取特点大量小文件IO适合人工核对一次载入内存开销整体可控每行一个目标训练直接读取拆开看最核心的差异只有两处。一是坐标参数化VOC用左上右下两个对角点YOLO用中心点加宽高COCO则取左上角加宽高二是类别表达VOC给的是字符串YOLO给的是整数索引。只要把这两组规则理清三种格式互换就变成了机械操作不再需要对着报错信息猜原因。2.3 VOC的XML转换成YOLO的TXT核心只有四行换算以疲劳驾驶数据集最常用的场景为例把VOC坐标转成YOLO训练要吃的TXT格式import xml.etree.ElementTree as ET def convert_one(xml_path, out_txt, img_w, img_h, class_map): root ET.parse(xml_path).getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: # 标签名不在映射表里跳过 continue b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) open(out_txt, w).write(\n.join(lines))调用这个函数前有三个参数必须确认。class_map是标签名字符串到数字ID的字典例如{eye_open: 0, eye_closed: 1, mouth_open: 2}不能靠猜测需要先把全部XML里的标签名去重再定义。img_w和img_h必须是图片真实尺寸直接读图片获取不可以用标注文件里的推测值归一化一旦用错尺寸训练时所有框都会偏移。转换后输出的TXT每行是五个值类别ID、中心点x、中心点y、框宽、框高全部落在0到1之间。这里最容易踩的坑是图片EXIF旋转信息。部分手机或行车记录仪拍摄的图片带旋转角读图库拿到的宽高和YOLO解码后的实际宽高不一致转出来的坐标会整体错位。我一般会在数据处理阶段先把图片统一转成基准朝向并重存再做坐标换算省掉后面的不可复现问题。2.4 为什么三种格式要并存YOLO训练最终只吃TXT那为什么还要保留VOC和COCO两份标签疲劳驾驶场景的数据往往要进入不同的下游处理。有人要把检测结果接进目标跟踪模块有人要用COCO评估协议算mAP还有人先做分类再切检测分支。保留原生格式本质上是保留工具选择的空间不是数据冗余。关键约束在于三份标注必须镜像同一份坐标来源。如果在VOC里修正了一个框却没有同步更新JSON和TXT后续训练对的样本就会变成脏数据。处理这类资源时我建议只维护一份基准标注其余格式用脚本生成而不是在三个目录里分别手工改。3. 划分脚本的实现思路与可直接复制的代码3.1 为什么划分层级必须落在文件名上训练集、验证集、测试集的划分脚本听起来只是做一次随机打乱实际要解决的是图片和三套标签的同步问题。常见做法是先对图片文件名做随机划分再依据图片主文件名去复制对应的标签文件。原因有两点图片是数据集的事实来源按图片划分不会出现“有标注无图片”或“有图片无标注”的孤儿文件后续想调整比例时也只需要重新跑一遍脚本不需要动原始数据。疲劳驾驶数据里还有一个容易忽略的背景这类图片大多从视频里抽帧得到相邻帧内容高度相似。如果直接对全部图片做全局随机同一段视频的相似帧会同时进入训练集和验证集造成验证分数虚高换一段真实场景视频后性能明显下跌。我一般会先按视频片段分组在组层面做划分如果数据包里没有提供视频分组信息至少要知道这个局限不要在结果里过度解读验证集分数。3.2 一份可直接跑的train/val/test划分脚本下面这个脚本以YOLO格式的标签为例按8:1:1划分数据import random import shutil from pathlib import Path IMG_DIR Path(fatigue_dataset/JPEGImages) LAB_DIR Path(fatigue_dataset/labels_yolo) OUT_DIR Path(fatigue_dataset/split) train_ratio, val_ratio, test_ratio 0.8, 0.1, 0.1 random.seed(20241001) imgs sorted(IMG_DIR.glob(*.jpg)) # 前置校验缺标签的图片进问题清单不参与划分 broken [p.name for p in imgs if not (LAB_DIR / (p.stem .txt)).exists()] if broken: raise SystemExit(f发现{len(broken)}张图片缺标签先处理: {broken[:3]}) random.shuffle(imgs) n len(imgs) train_end int(n * train_ratio) val_end train_end int(n * val_ratio) for split_name in [train, val, test]: (OUT_DIR / images / split_name).mkdir(parentsTrue, exist_okTrue) (OUT_DIR / labels / split_name).mkdir(parentsTrue, exist_okTrue) for part, split_name in [ (imgs[:train_end], train), (imgs[train_end:val_end], val), (imgs[val_end:], test), ]: for img in part: shutil.copy2(img, OUT_DIR / images / split_name / img.name) shutil.copy2(LAB_DIR / (img.stem .txt), OUT_DIR / labels / split_name / (img.stem .txt)) print(f划分完成: train{train_end}, val{val_end-train_end}, test{n-val_end})这个脚本有两个设计值得说明。一是前置校验放在最前面绝大多数划分脚本的失败不是因为复制出错而是标签缺失没有被发现等到训练时才暴露这里直接抛异常逼着你先解决数据完整性问题。二是用copy2而不是move保留原始文件方便后续重跑或调整比例多占一点磁盘换来的是低成本试错。划分完成后结果目录里images/train与labels/train的文件名一一对应data.yaml里直接指到这两个目录就能开始训练。3.3 划分比例与三个边界参数参数作用1000张图建议值random.seed固定随机序列保证每次划分结果一致任一整数记录到训练文档里train_ratio训练集占比0.8val_ratio每轮训练结束后做验证0.1test_ratio最终模型评估用只在最后碰0.1这里特别强调 test 集合的纪律。验证集每个 epoch 都在参与模型选择本质上已经被“用过了”测试集应该只在最终权重上跑一次如果反复拿测试集调阈值、调置信度测试集就慢慢变成了第二个验证集最终评估数字会失真。这个小数据集本身就不适合频繁试探测试集。另一个要注意的是类别不平衡。疲劳驾驶检测里睁眼框数量通常远大于闭眼打哈欠的嘴部开合样本更少。如果随机划分后闭眼样本全部砸进验证集训练集就学不到闭眼特征。轻量做法是在划分前扫描每个TXT的类别ID把小类别样本按比例分配到三个集合中其余样本再随机划分不需要搞复杂的样本加权只要保证每个小类别在训练集里出现的次数足够多就行。3.4 COCO标注如何跟着划分走YOLO格式的标签是一张图一个TXT直接复制文件就能完成划分。COCO格式不同整个集合只有一个JSON文件不能用文件复制的方式同步。常见做法是把JSON加载进来按划分结果里的图片ID过滤图片列表和标注列表再写一个新JSON。用ultralytics训练时其实用不到COCO JSON但如果你后续要接mmdetection或其他以COCO为输入的框架这一步就得做。import json def split_coco(json_path, img_ids, out_path): whole json.load(open(json_path)) img_set set(img_ids) imgs [im for im in whole[images] if im[id] in img_set] annos [a for a in whole[annotations] if a[image_id] in img_set] save dict(whole) save[images], save[annotations] imgs, annos json.dump(save, open(out_path, w), ensure_asciiFalse, indent2)img_ids从哪来划分脚本在切分图片时同时把图片主文件名映射到COCO JSON里的image_id即可不要用文件名去比对COCO的image_id才是唯一标识。过滤完后顺手打印一下每个split里的类别分布能提前发现划分严重失衡的问题。4. YOLO训练教程从data.yaml到训练命令4.1 选择YOLO版本的实判标准yolo第几代了这个问题在社区里几乎每周都有人问但做项目不是追新而是看生态稳定性。以目前资料完整度和出错后能快速检索到答案的程度来看我一般直接用YOLOv8及后续版本的ultralytics命令行因为yolo detect train这个入口和参数命名在多个大版本里保持稳定换版本时迁移成本很低。对于疲劳驾驶这种单卡就能跑的轻量场景优先用nano或small规格不要一上来就上超大模型。1000张图的数据量撑不起大模型的参数量强行训练的结果往往是验证集分数虚高、真实场景泛化差。4.2 data.yaml的写法和类别顺序陷阱给出一份最小可用的配置文件# fatigue_dataset/split/data.yaml path: /data/fatigue_dataset/split train: images/train val: images/val test: images/test nc: 3 names: 0: eye_open 1: eye_closed 2: mouth_openpath建议写绝对路径避免不同终端工作目录不一致导致图片路径解析失败。names的顺序必须和TXT里的整数ID一一对应顺序一旦写错训练不会报错但推理时类别张冠李戴。最快的核对方法是随便打开几个TXT看每行第一个数字是几0就代表names[0]里的类别。如果标签文件里出现了大于等于nc的整数ID训练会直接出错或静默跳过。建议划分完成后写一个扫描目录里所有TXT、输出最大类别ID的小命令把这个错误挡在训练前。4.3 训练命令、参数速查表与显存考量yolo detect train \ data/data/fatigue_dataset/split/data.yaml \ modelyolov8n.pt \ epochs60 \ imgsz640 \ batch16 \ patience15 \ project/data/fatigue_dataset/runs \ namefatigue_v8n \ device0逐参数说明一下。modelyolov8n.pt表示加载COCO预训练权重而不是从头训练1000张图从零开始训练收敛很慢预训练权重能提供基础视觉特征如果想彻底从头训练把模型参数写成yolov8n.yaml但要接受明显更长的训练时间和更低的上限。epochs60配patience15表示最多训练60轮若验证集指标连续15轮不提升就提前停止防止小数据集过拟合。参数默认值本项目建议什么情况要改epochs10060数据量更小则继续下调batch168到16显存不足时降到8imgsz640640或960标注框过小时升到960patience10015小数据集依赖早停防过拟合devicecpu0有多张卡时指定卡号关于imgsz要多说一句。疲劳驾驶场景里眼睛和嘴巴的框都很小如果原图尺寸达到1080p以上640的输入会把小目标压得更小。可以先跑一版640看mAP再试960对比小目标召回率。显存不够时优先降batch而不是降imgsz因为imgsz对检测精度的影响更直接。如果团队里有人用AMD显卡跑YOLO通常走ROCm版PyTorchdevice填0或1即可安装时注意PyTorch与ROCm的版本匹配否则驱动层报错会浪费大量时间。4.4 训练输出里必须检查的四个文件训练结束后到runs/fatigue_v8n目录下重点看这四类产物runs/fatigue_v8n/ ├── weights/ │ ├── best.pt │ └── last.pt ├── args.yaml ├── results.csv └── confusion_matrix.pngargs.yaml记录了完整训练参数排查“为什么别人复现不出我的结果”时先看它。results.csv里有每一轮的box_loss、cls_loss和mAP指标要同时看训练集和验证集的loss曲线只降训练loss不降验证loss是过拟合的明显信号。目标检测的损失由边框回归损失和分类损失叠加构成如果验证集box_loss已经稳定但mAP50-95仍在波动说明框定位够了问题更多出在类别区分上优先检查困难样本和类别样本数。weights/best.pt是验证指标最好的一次权重last.pt是最后一轮权重正式部署取best不要在测试集上拿last反复试。5. 训练前先自检标签训练后做时序统计验证5.1 训练前用五段式校验脚本拦截脏标签疲劳驾驶小目标数据里最常见的脏数据是框宽高为0、坐标归一化后超出0到1区间、类别ID越界。这类问题会让训练过程忽上忽下还容易被误判为模型问题。我一般会在划分之后跑一个标签自检脚本from pathlib import Path def scan_yolo_labels(img_dir, label_dir, nc): problem [] for img in sorted(img_dir.glob(*.jpg)): txt label_dir / (img.stem .txt) if not txt.exists(): problem.append(f{img.name}: 缺标签) continue for idx, line in enumerate(txt.read_text().splitlines(), 1): parts line.split() if len(parts) ! 5: problem.append(f{img.name} 第{idx}行 非5段) continue cls int(parts[0]) xc float(parts[1]); yc float(parts[2]) w float(parts[3]); h float(parts[4]) if cls nc: problem.append(f{img.name} 类别ID越界: {cls}) if w 0 or h 0: problem.append(f{img.name} 框宽高异常: {w}x{h}) if any(v 0 or v 1 for v in [xc, yc, w, h]): problem.append(f{img.name} 坐标越界) return problem校验维度覆盖四类问题标注行数量是否5段、类别ID是否在合法范围、框宽高是否非零、归一化坐标是否越界。运行时把结果输出到文件而不是直接打印在终端几十条错误刷屏会盖住后面的训练日志。自检全部通过再进训练能省掉大量因数据问题导致的训练发散排查时间。5.2 用best.pt做一次测试集推理验证训练完成后用测试集做一轮推理比只看指标更直观yolo detect predict \ model/data/fatigue_dataset/runs/fatigue_v8n/weights/best.pt \ source/data/fatigue_dataset/split/images/test \ conf0.25 \ saveTrue预测图会输出到runs/fatigue_v8n/predict目录。打开图片看三件事闭眼小框是否贴合眼睛轮廓、睁眼是否被误判成闭眼、大幅侧脸时嘴巴框是否漂移。指标只能告诉你整体水平图片能告诉你错在哪里。5.3 疲劳驾驶评估的重点在时间窗口而不是单帧mAP疲劳驾驶检测与通用目标检测最大的区别在于单帧指标不能直接对应实际告警质量。一个司机闭眼2秒和眨眼0.2秒在单帧图上可能都是“闭眼”类别但对疲劳判断的意义完全不同。落地时常见的做法是引入时间窗口统计例如闭眼帧占比、连续N帧闭眼、单位时间内打哈欠次数再结合阈值触发报警。检测模型负责提供可靠的单帧结果判断逻辑负责在时间序列上做聚合mAP50-95反映的是检测器的基础能力而真正决定系统是否可用的是这些统计指标在真实驾驶视频上的稳定性。先验证检测器再验证时间窗口参数这条路比单独盯着测试集数字更接近工程实际。本文还有配套的精品资源点击获取
返回列表