☰
红外疲劳驾驶数据集实战:2000张带标签图像YOLO训练与避坑指南
2026/9/28 13:49:53 网站建设 项目流程

简介:这是一份面向疲劳驾驶检测与红外图像目标识别任务的YOLO系列算法数据集,适合从事智能驾驶、行为识别方向的研究生、算法工程师及竞赛选手使用。数据集共2000个文件,包含1400个VOC格式xml标注与600个YOLO格式txt标注,压缩包约62.46MB,已按训练与验证需求划分完毕,并附带data.yaml配置文件,可直接接入yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流框架训练与测试。YOLO标注采用类别索引与归一化中心点、宽高比例的标准格式,便于快速转换与迁移。资源同时提供两种标签体系,方便读者在检测模型与数据格式转换之间灵活切换,省去自行标注与清洗的时间成本。目前已有128人学习下载,适合需要快速搭建红外疲劳驾驶检测基线、验证模型效果或进行课程实践的用户参考使用。

1. 红外疲劳驾驶数据集拿到手:2000张带标签的图,先别急着喂给 YOLO

红外图像做疲劳驾驶检测,最反直觉的一点是:白天可见光下效果拔群的模型,到了夜间红外场景经常集体翻车。原因不复杂——红外成像靠的是热辐射差异,人脸、眼睑、方向盘、座椅在热图上呈现的纹理和可见光完全不是一回事,你拿 COCO 预训练权重直接 finetune,模型学到的边缘和纹理先验基本用不上。这个标题里的「2000张图像带标签」数据集,价值就在于它把红外域下的疲劳特征(闭眼、打哈欠、低头、长时间注视)用标注框固定了下来,让你不用从零采集和标定。它适合三类人:想快速跑通 YOLO 训练闭环的新手、需要红外域 baseline 做对比的实验者、以及要把疲劳检测落到车载或工地场景的工程团队。2000张不算大,但足够验证一条完整的「数据检查 → 格式转换 → 训练 → 推理」链路,前提是你得先把数据本身摸清楚,而不是解压完直接yolo train。

2. 红外疲劳数据集的结构拆解与 YOLO 格式对齐

拿到一个带标签的图像数据集,第一件事不是训练,是搞清楚它的目录结构、标签格式和类别定义。红外疲劳驾驶数据集常见的组织方式是images/放图、labels/放同名 txt,或者用一个大 CSV 记录文件名和框坐标。不同来源的标注格式差异很大:有的是 VOC 的 XML,有的是 COCO 的 JSON,有的是已经转好的 YOLO txt。你得先判断手里这份属于哪种,再决定要不要转换。

2.1 先跑一遍数据体检脚本,别信「带标签」三个字

「带标签」只说明有标注文件,不代表标注质量合格。我一般会先写一个体检脚本,统计图像尺寸分布、标签文件缺失情况、类别分布和框的宽高比。红外图像有个特点:分辨率往往不统一,有的 640×512,有的 384×288,直接 resize 到 640×640 会引入形变,影响闭眼这种小目标的检测。

import os from pathlib import Path from collections import Counter from PIL import Image img_dir = Path("images") lbl_dir = Path("labels") sizes = Counter() cls_counter = Counter() missing_label = [] empty_label = [] bad_box = [] for img_path in img_dir.glob("*.jpg"): with Image.open(img_path) as im: sizes[im.size] += 1 lbl_path = lbl_dir / (img_path.stem + ".txt") if not lbl_path.exists(): missing_label.append(img_path.name) continue lines = lbl_path.read_text().strip().splitlines() if not lines: empty_label.append(img_path.name) continue for line in lines: parts = line.split() if len(parts) != 5: bad_box.append((img_path.name, line)) continue c, x, y, w, h = int(parts[0]), *map(float, parts[1:]) cls_counter[c] += 1 if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1): bad_box.append((img_path.name, line)) print("尺寸分布:", sizes.most_common()) print("类别分布:", cls_counter) print("缺标签:", len(missing_label), missing_label[:5]) print("空标签:", len(empty_label), empty_label[:5]) print("异常框:", len(bad_box), bad_box[:5])

这段脚本做四件事:统计图像尺寸、统计每个类别的框数量、找出没有对应标签文件的图、找出坐标越界或格式错误的框。参数上,img_dir和lbl_dir按你实际解压后的路径改;类别编号c从 0 开始,如果你的数据集从 1 开始编号,需要在转换时统一减 1。跑完之后重点看两个信号:如果某个类别框数极少(比如「打哈欠」只有几十个),训练时这个类基本学不出来;如果异常框数量超过总数的 1%,说明标注质量有问题,得考虑清洗。

2.2 类别映射与 data.yaml 的写法

YOLO 训练依赖一个data.yaml描述数据路径和类别名。红外疲劳驾驶数据集常见的类别是closed_eye、open_eye、yawn、head_down这几类,但不同来源命名不统一,有的用中文,有的用拼音缩写。你需要建一张映射表,把原始类别名统一成英文小写下划线风格,因为 YOLO 的类别名会直接写进权重文件,中文名在某些部署环境里会出乱码。

# data.yaml path: /home/user/fatigue_ir train: images/train val: images/val nc: 4 names: 0: closed_eye 1: open_eye 2: yawn 3: head_down

path是数据集根目录,train和val是相对路径。nc必须等于names的条目数,多一个少一个都会在训练启动时报错。划分训练验证集时,2000 张图我一般按 8:2 切,但如果某个类别的样本特别少,要做分层抽样,保证验证集里每个类别都有出现,否则验证指标会失真。红外图像还有一个坑:同一个人连续多帧的图如果被随机切分,训练集和验证集会高度相似,验证指标虚高。正确做法是按「人」或按「采集片段」划分,而不是按帧随机切。

2.3 从 VOC/COCO 转到 YOLO txt 的转换脚本

如果你拿到的标签是 XML 或 JSON,需要转成 YOLO 的归一化格式。核心公式是:x_center = (xmin + xmax) / 2 / width,y_center = (ymin + ymax) / 2 / height,w = (xmax - xmin) / width,h = (ymax - ymin) / height。下面是从 VOC XML 转换的脚本。

import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image cls_map = {"closed_eye": 0, "open_eye": 1, "yawn": 2, "head_down": 3} xml_dir = Path("annotations") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) for xml_path in xml_dir.glob("*.xml"): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") W, H = int(size.find("width").text), int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in cls_map: continue 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) xc = (xmin + xmax) / 2 / W yc = (ymin + ymax) / 2 / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f"{cls_map[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") (out_dir / (xml_path.stem + ".txt")).write_text("\n".join(lines))

转换时注意三点:坐标要除以图像真实宽高,不是网络输入尺寸;保留 6 位小数足够,再多没必要;如果 XML 里的宽高和实际图像尺寸不一致(有些标注工具会写错),以PIL.Image.open读到的实际尺寸为准。转换完再跑一遍 2.1 的体检脚本,确认没有异常框。

3. 用 YOLOv8 在红外疲劳数据集上跑通训练

数据对齐之后,训练本身反而是最标准的一步。但红外域有几个参数需要特别调,不能照搬可见光的默认配置。这一章讲清楚模型选型、训练命令、关键参数和训练过程中的观察点。

3.1 模型选型:n/s/m 怎么选,预训练权重从哪来

YOLOv8 提供 n/s/m/l/x 五个尺度。2000 张图属于小数据集,我一般从yolov8n或yolov8s起步。n 参数量约 300 万,训练快、过拟合风险低;s 约 1100 万,精度略高但小数据集上容易过拟合。如果你要做部署到边缘设备,n 是首选;如果只是刷精度做实验,可以试 s 然后看验证集曲线。

预训练权重方面,YOLOv8 官方在 COCO 上训练的yolov8n.pt可以直接加载。虽然红外域和 COCO 差异大,但底层卷积核学到的边缘、角点特征仍有迁移价值,比随机初始化收敛快很多。加载方式是训练命令里指定model=yolov8n.pt,Ultralytics 会自动下载。如果你的环境不能联网,需要提前把权重文件放到本地,用绝对路径指定。

注意:红外图像是单通道灰度图,但 YOLOv8 默认输入是三通道。Ultralytics 在数据加载时会自动把灰度图复制成三通道,不需要你手动改。但如果你自己写 dataloader,记得处理这个通道数问题,否则会报维度不匹配。

3.2 训练命令与红外场景下的参数调整

标准训练命令如下,我标注了红外场景下需要特别关注的参数。

yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ warmup_epochs=3 \ hsv_h=0.0 \ hsv_s=0.0 \ hsv_v=0.2 \ degrees=5.0 \ translate=0.1 \ scale=0.3 \ fliplr=0.5 \ mosaic=0.5 \ patience=30 \ device=0

逐个说关键参数。imgsz=640是输入尺寸,如果你的红外原图是 384×288,放大到 640 会模糊,可以改成 416 或 512,但要注意必须是 32 的倍数。hsv_h和hsv_s设为 0,因为红外图像没有色相和饱和度概念,开了反而是噪声;hsv_v=0.2保留亮度扰动,模拟不同热辐射强度。degrees=5.0小角度旋转增强,疲劳驾驶场景中头部倾斜是真实存在的,但角度太大会让闭眼特征变形。mosaic=0.5比默认的 1.0 低,因为红外图像拼接后热分布不连续,过强的 mosaic 会让模型学到虚假纹理。patience=30是早停,小数据集上过拟合来得快,30 轮验证指标不升就停。

batch=16在 8GB 显存上跑 640 尺寸基本够用,如果 OOM 就降到 8 或改用batch=-1让 Ultralytics 自动选。lr0=0.01是初始学习率,配合lrf=0.01余弦退火到 0.0001。如果你发现 loss 在前几轮震荡剧烈,把warmup_epochs加到 5。

3.3 训练过程看什么:loss 曲线、混淆矩阵和 BN 崩溃

训练启动后,重点盯三个东西。第一是train/box_loss和val/box_loss的走势:如果 train 持续下降但 val 在 20 轮后开始上升,就是过拟合,需要加数据增强或减模型容量。第二是混淆矩阵,红外疲劳检测最容易混的是closed_eye和open_eye,因为红外下眼睑的热特征差异很小,如果这两个类互相误判严重,说明特征区分度不够,可以考虑加一个「眼睛状态」的二分类辅助头,或者提高输入分辨率。

第三是 BN 崩溃。YOLO 训练中 BN 层偶尔会出现 running mean 和 variance 发散,表现为 loss 突然变成 nan。红外数据集小、batch 小的时候更容易触发。解决办法有两个:把batch调大(至少 8),或者改用yolov8n.pt里已经冻结 BN 统计量的方式训练。如果已经崩了,从上一个 checkpoint 恢复,把学习率降一半再跑。

# 从崩溃前的 checkpoint 恢复,降低学习率 yolo detect train \ data=data.yaml \ model=runs/detect/train/weights/last.pt \ epochs=100 \ lr0=0.005 \ resume=True

resume=True会接着上次的 epoch 继续,优化器状态也恢复。如果last.pt已经损坏,用best.pt重新开始,但学习率要调低,因为 best 已经是在较好状态上。

4. 推理、验证与红外图像特有的翻车点排查

训练完不是终点,推理阶段的坑往往比训练还多。这一章讲怎么验证模型真的学到了疲劳特征,而不是记住了背景,以及红外图像上常见的几类翻车。

4.1 用验证集和单张图推理做双重检查

先跑验证集看整体指标:

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=data.yaml \ imgsz=640 \ conf=0.25 \ iou=0.5

重点看mAP50和mAP50-95。红外疲劳检测里,closed_eye的 AP 通常最低,因为目标小、特征弱。如果closed_eye的 AP 低于 0.3,而其他类都在 0.7 以上,说明小目标检测能力不足,可以尝试提高imgsz到 800,或者在数据里对眼睛区域做裁剪增强。

然后拿几张训练集里没有的图做单张推理,肉眼看框的位置和置信度:

yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=test_images/ \ conf=0.25 \ save=True \ save_txt=True

save_txt=True会把预测框存成 YOLO 格式,方便你写脚本对比预测和真值。我一般会挑几张「模型自信但错了」的图,这类样本对改进最有价值。

4.2 红外图像盲元与两点校正对检测的影响

红外探测器存在盲元(坏点),表现为图像上固定的白点或黑点。如果数据集里盲元没做校正,模型可能把盲元当成特征。判断方法很简单:统计所有图像同一位置的像素值,如果某个位置在所有图里都是极值,那就是盲元。处理方式是在预处理阶段做盲元替换,用邻域中值填充。

两点校正则是红外成像的常规非均匀性校正,目的是让不同温度对应的灰度更线性。如果你的数据集来自不同设备或不同时间,灰度分布可能不一致,模型会学到设备相关的偏差。一个实用的检查是:把训练集和验证集的灰度直方图画出来,如果分布差异大,说明需要做灰度归一化,比如按 1% 和 99% 分位数做线性拉伸。

import numpy as np from PIL import Image def normalize_ir(img_path, out_path): img = np.array(Image.open(img_path).convert("L")).astype(np.float32) lo, hi = np.percentile(img, 1), np.percentile(img, 99) img = np.clip((img - lo) / (hi - lo + 1e-6), 0, 1) * 255 Image.fromarray(img.astype(np.uint8)).save(out_path)

这个归一化对红外图像很关键,尤其是多来源数据混合时。参数1和99是分位数,如果图像里噪声多,可以改成2和98更鲁棒。

4.3 避坑:红外疲劳检测的 5 个血泪教训

现象一:验证集 mAP 很高,但实际视频推理时框乱跳。原因:训练集按帧随机划分,相邻帧高度相似,验证集泄漏。解决:按采集片段或按人划分,确保验证集里的人没在训练集出现过。

现象二:模型把方向盘或座椅头枕误判为疲劳类别。原因:红外图像里这些物体的热特征和人体接近,且标注时可能漏标了这些干扰物。解决:在数据里补充「背景负样本」,或者增加一个other类别把干扰物标出来。

现象三:闭眼和睁眼混淆严重。原因:红外分辨率下眼睑开合的热差异只有几个灰度级,特征太弱。解决:提高输入分辨率到 800 或 960,或者用眼睛区域的局部裁剪图单独训一个分类器做二次判断。

现象四:训练 loss 正常下降,但推理时所有框置信度都低于 0.1。原因:验证集和训练集的灰度分布不一致,模型在验证集上实际是域外推理。解决:统一做 4.2 的灰度归一化,或者用val集的部分数据做少量 finetune。

现象五:BN 层 running stats 发散导致 loss 变 nan。原因:batch 太小或学习率太高。解决:batch 至少 8,lr0降到 0.005,加warmup_epochs=5。如果还崩,在模型里冻结前几层 BN。

5. 把 2000 张红外图用到极致:小数据集上的进阶技巧

2000 张图在深度学习里算小数据集,但红外疲劳检测这个任务本身类别少、场景固定,用对方法完全能训出可用的模型。这一章讲几个我实际用过、能明显提升效果的技巧,以及怎么判断这个方向值不值得继续投入。

第一个技巧是用预训练模型做特征提取,只训检测头。YOLOv8 加载 COCO 权重后,冻结 backbone 和前几层 neck,只训检测头 20 轮,再解冻全部微调。这样在小数据集上收敛更稳,过拟合更晚。命令上通过freeze参数控制:

# 先冻结 backbone 训练 20 轮 yolo detect train data=data.yaml model=yolov8n.pt epochs=20 freeze=10 lr0=0.01 # 再解冻全部微调 yolo detect train data=data.yaml model=runs/detect/train/weights/best.pt epochs=100 lr0=0.001

freeze=10表示冻结前 10 层。第一段训练让检测头先适应红外域,第二段低学习率全局微调,避免破坏预训练特征。

第二个技巧是难例挖掘。训练完第一版后,用模型在训练集上推理,挑出置信度低或预测错的图,人工检查这些图的标注是否有误,或者把它们复制多份加入训练集。红外疲劳检测里,戴眼镜、侧脸、夜间低对比度这几类样本通常就是难例。2000 张里可能只有一两百张是真正难学的,把它们找出来重点训,比盲目加数据有效。

第三个技巧是用测试时增强(TTA)提升推理稳定性。Ultralytics 推理时加augment=True会做多尺度翻转增强再合并结果,对小目标检测有 1-3 个点的 mAP 提升,代价是推理速度慢一倍。如果部署环境算力够,值得开。

yolo detect predict model=best.pt source=test/ augment=True conf=0.2

判断这个方向值不值得做,我的标准是看三个数:闭眼类 AP 能不能到 0.5 以上、误报率(把正常驾驶判为疲劳)能不能压到 5% 以下、单帧推理延迟在目标硬件上能不能进 50ms。如果这三个数达标,2000 张红外图训出来的模型就具备落地价值;如果闭眼 AP 始终上不去,要么补数据,要么换更高分辨率的热像仪重新采集。我自己在这个任务上翻过最大的车,是早期没做灰度归一化,模型在实验室数据上 mAP 0.85,换一台设备直接掉到 0.4,后来老老实实把预处理管线补上才稳定。红外这个域,数据一致性比模型结构重要得多。希望帮到你。

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

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

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

立即咨询