ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

红外疲劳驾驶数据集与YOLO实战:标签校验、预处理到部署避坑指南

红外疲劳驾驶数据集与YOLO实战:标签校验、预处理到部署避坑指南 简介这是一份面向红外疲劳驾驶场景的YOLO系列目标检测数据集适合目标检测学习者、算法工程师及车载安全开发者使用可用于乘员疲劳状态检测、驾驶行为识别等任务。压缩包整体62.46MB共2000个文件其中1400个XML标签文件采用VOC格式600个TXT标签文件采用YOLO格式两种标签分别存放并配有data.yaml数据集配置训练集与验证集已划分完毕可直接在YOLOv5、YOLOv8、YOLOv9等版本中加载训练与验证。目前已有127人学习/下载。借助两种标注格式使用者可在VOC与YOLO工具链间灵活切换省去手工标注与格式转换的时间YOLO格式中类别索引、归一化中心点与宽高的标注规则也便于对照理解适合快速验证红外图像下的检测算法并进一步做疲劳驾驶相关的模型调优与部署实验。1. 红外疲劳驾驶数据集2000 张带标签图为什么比两万张可见光图更难搞定夜间跑高速可见光摄像头拍不清驾驶员的眼睛红外图像却能稳定捕捉瞳孔和嘴部的热辐射轮廓。这正是“yolo算法-红外图像疲劳驾驶数据集-2000张图像带标签”的核心价值目标锁定在疲劳驾驶场景眼睛开合、嘴巴打哈欠的坐标框全部打好到手就能喂给 YOLO 训练。适合做车载 DMS 的工程师也适合研究红外目标检测或驾驶员状态识别的人前者拿它替换“白天能跑、晚上翻车”的旧模型后者拿它做对比实验。2000 张的量级意味着不能直接开跑标签校验、类别分布、红外预处理省掉一步后面都会加倍找回来。2. 拿到 zip 先别训解包、标签校验与类别分布摸底2.1 解压与目录结构YOLO 格式的标签 txt 长什么样数据集是 zip 压缩包先把包解开。多数人习惯双击解压但命令行操作更可控尤其是文件名带中文和横杠时图形化解压偶尔会乱码unzip yolo算法-红外图像疲劳驾驶数据集-2000张图像带标签.zip -d fatigue_infrared cd fatigue_infrared find . -type f | head -30unzip的-d参数指定解压目标目录避免文件散落得满地都是find加head -30是快速浏览目录结构的做法。解包后先不要急着写训练代码把图片和标签对起来看。YOLO 的标签是 txt 文件每张 jpg/png 对应一个同名 txt一行一个目标。如果解压结果里是images/和labels/两个平级目录图片IMG_0001.jpg对应labels/IMG_0001.txt如果套了train/val子目录结构和前者一样只是路径多一层。txt 内容长这样0 0.682 0.334 0.152 0.089。五个数字依次是类别 ID、归一化中心 x、归一化中心 y、归一化宽、归一化高。注意“归一化”三个字坐标是除以原图宽高后的比例值0 到 1 之间。很多人把 labelimg 导出的像素坐标当成 YOLO 坐标直接开训训练时 loss 直接炸这是最经典的入门坑。先写几行 Python 抽查一个标签文件with open(labels/IMG_0001.txt, r, encodingutf-8) as f: for line in f: values line.strip().split() if len(values) 5: cls, xc, yc, w, h map(float, values) print(fclass{int(cls)}, center({xc:.3f}, {yc:.3f}), size({w:.3f}, {h:.3f}))map(float, values)把文本解析成浮点数f-string里的:.3f控制输出精度。如果打印出的中心坐标有大于 1 的标签没归一化如果宽高出现 0标注框退化成了点这种目标训练时不会报错但会拖垮回归损失。2.2 用 Python 全量校验标签越界、空标签、类别缺失一次查清肉眼抽查几行远远不够2000 张图必须全量脚本扫一遍。YOLO 对标签格式极其苛刻一个坏标签就能让训练过程变玄学——不报错但 mAP 停在某个值上不去。校验脚本我一般写成可复用的换数据集只改label_dir和valid_classes两个地方import os label_dir labels valid_classes {0, 1, 2} # 按数据集实际类别数调整 bad_labels [] for fname in sorted(os.listdir(label_dir)): if not fname.endswith(.txt): continue fpath os.path.join(label_dir, fname) with open(fpath, r) as f: lines [ln.strip() for ln in f.readlines() if ln.strip()] if len(lines) 0: bad_labels.append((fname, empty label)) continue for i, ln in enumerate(lines): parts ln.split() if len(parts) ! 5: bad_labels.append((fname, fline {i}: {len(parts)} fields)) continue cls, xc, yc, w, h map(float, parts) if int(cls) not in valid_classes: bad_labels.append((fname, fline {i}: class {cls} out of range)) if not (0 xc 1 and 0 yc 1): bad_labels.append((fname, fline {i}: center out of [0,1])) if w 0 or h 0 or w 1 or h 1: bad_labels.append((fname, fline {i}: size abnormal w{w} h{h})) print(f共检查 {len(os.listdir(label_dir))} 个标签文件) if bad_labels: print(f发现 {len(bad_labels)} 处异常前 20 条) for item in bad_labels[:20]: print(item) else: print(全部标签通过基础校验)脚本做四件事查空标签、查字段数、查类别 ID 合法性、查归一化坐标越界。valid_classes必须按数据集的真实类别数改比如只有眼睛开合两个类时就是{0, 1}否则类别 ID 是 2 的标签会被误报。跑完优先处理越界坐标和空标签不要带病训练。2.3 类别分布摸底与训练/验证划分校验完格式统计类别分布。这一步直接决定训练策略如果闭眼样本只有睁眼的十分之一需要在增强阶段做文章如果某个类别数量为 0说明标注文件命名或类别 ID 跟实际内容对不上。from collections import Counter cls_counter Counter() total_boxes 0 for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname)) as f: for ln in f: ln ln.strip() if not ln: continue cls_counter[int(ln.split()[0])] 1 total_boxes 1 print(f目标框总数: {total_boxes}) for cls_id, cnt in sorted(cls_counter.items()): print(f类 {cls_id}: {cnt} 个目标 ({cnt/total_boxes*100:.1f}%))打印出的比例就是你的“困难清单”。然后做训练/验证划分。2000 张图我一般按 9:1 切1800 张训练、200 张验证。验证集不建议再拆测试集量太小测试指标的抖动会比模型差别还大。mkdir -p dataset/images/train dataset/images/val dataset/labels/train dataset/labels/val python - EOF import os, random, shutil random.seed(42) image_dir images label_dir labels train_ratio 0.9 all_imgs [f for f in os.listdir(image_dir) if f.endswith((.jpg, .png, .jpeg))] random.shuffle(all_imgs) split int(len(all_imgs) * train_ratio) for f in all_imgs[:split]: shutil.copy(os.path.join(image_dir, f), dataset/images/train/) label os.path.splitext(f)[0] .txt if os.path.exists(os.path.join(label_dir, label)): shutil.copy(os.path.join(label_dir, label), dataset/labels/train/) for f in all_imgs[split:]: shutil.copy(os.path.join(image_dir, f), dataset/images/val/) label os.path.splitext(f)[0] .txt if os.path.exists(os.path.join(label_dir, label)): shutil.copy(os.path.join(label_dir, label), dataset/labels/val/) EOFrandom.seed(42)固定随机种子保证每次运行结果一致shutil.copy保留原始文件不破坏数据集。train_ratio0.9是经验值2000 张再少就保证不了训练多样性。这里有一个关键提醒如果数据来源是视频抽帧按视频片段分组再划分。同一段视频的连续帧极度相似纯随机切分会把相邻帧同时放进训练和验证后面验证指标虚高、实车一测就露馅这在第 5 章避坑里会专门展开。3. 红外图像别直接喂给 YOLO两点校正、盲元检测与对比度增强3.1 红外单通道图与可见光的三个本质差异用 YOLO 做疲劳驾驶很多人直接把可见光那套语义迁过来这是最容易踩空的地方。红外图像和可见光有三个本质差异直接影响训练参数和预处理策略。维度可见光图像红外图像通道RGB 三通道颜色是强特征单通道灰度无颜色信息噪声高斯噪声为主分布稳定盲元/坏线呈盐椒噪声位置随机对比度动态范围大边缘清晰动态范围窄目标与背景灰度接近第一YOLO 的训练管线默认按三通道加载图像单通道红外图会复制成三份颜色分支学不到任何区分性特征等于白算。第二红外焦平面存在盲元和坏线表现为孤立亮点或暗点尺寸一两个像素和“眼睛”这个目标在灰度形态上容易混淆尤其当输入分辨率被压到 640 时。第三红外动态范围窄不做处理的话整体灰蒙蒙目标边缘不锐利网络需要更多 epoch 才能收敛到同样的精度。这三点问题不会让训练崩掉但会把 mAP 卡在某个上不去的值。处理手段是接下来的两步先查盲元坏图再做对比度增强。3.2 训练前做盲元检测坏点筛掉而不是留给网络去学盲元是红外焦平面阵列的常见缺陷固定位置出现异常亮暗像素。疲劳驾驶数据集如果来自多台设备或多次采集盲元位置和数量都不一样。我的做法是训练前跑一个盲元检测脚本把坏图挑出来要么剔除要么中值滤波修复。注意这一步处理必须施加在全部训练图像上并且预处理参数在训练和验证时要保持一致否则验证集分布偏移指标失真。import cv2 import numpy as np import os image_dir images blind_thresh 30 # 邻域灰度差阈值 max_blind_ratio 0.01 # 盲元占比超过 1% 视为坏图 for fname in sorted(os.listdir(image_dir)): if not fname.lower().endswith((.jpg, .png, .jpeg)): continue img cv2.imread(os.path.join(image_dir, fname), cv2.IMREAD_GRAYSCALE) if img is None: print(f{fname}: 读取失败) continue med cv2.medianBlur(img, 3) diff np.abs(img.astype(np.int16) - med.astype(np.int16)) blind_count np.sum(diff blind_thresh) ratio blind_count / (img.shape[0] * img.shape[1]) if ratio max_blind_ratio: print(f{fname}: 疑似坏图, 盲元比例{ratio:.4f})两个参数说明blind_thresh30是灰度差阈值8bit 红外图动态范围 0 到 255真实盲元与邻域差通常超过 50取 30 已足够覆盖max_blind_ratio0.01表示盲元占比超过 1% 就判为坏图超过这个比例网络会把盲元当成稳定特征换台设备推理直接失效。如果坏图不多5 张以内用cv2.medianBlur(img, 3)修复后覆盖原文件坏图很多说明采集设备有问题先换设备再谈训练。另外提醒一点如果数据集里混有伪彩色红外图经颜色映射转成三通道先转成灰度再检测盲元。伪彩色映射会把单点盲元扩散成彩色斑块直接灰度化仍然保留盲元特征但检测前必须统一图片格式不要灰度图和伪彩图混用。3.3 对比度增强三选一直方图均衡、CLAHE 还是伽马校正红外图像对比度低直接喂进 YOLO 会让卷积核把大量响应花在灰度震荡上。常见的增强有三招全局直方图均衡HE、限制对比度自适应直方图均衡CLAHE、伽马校正。我的选择很明确优先 CLAHEimport cv2 def apply_clahe(img, clip_limit2.0, tile_grid8): clahe cv2.createCLAHE(clipLimitclip_limit, tileGridSize(tile_grid, tile_grid)) return clahe.apply(img) img cv2.imread(images/IMG_0001.jpg, cv2.IMREAD_GRAYSCALE) enhanced apply_clahe(img, clip_limit2.0, tile_grid8) cv2.imwrite(images_enhanced/IMG_0001.jpg, enhanced)clipLimit2.0控制对比度放大幅度越大增强越剧烈tileGridSize8把图像划分成 8×8 的局部块在块内做直方图均衡避免全局均衡把大片均匀区域的微小噪声放大成伪纹理。红外疲劳场景里这两个参数是稳妥起点如果发现噪声被放大clipLimit降到 1.5如果图像还是偏灰升到 3.0。全局直方图均衡的问题是当红外图里有大块挡风玻璃或车厢背景这类均匀区域时均衡会把微小噪声放大成伪纹理。伽马校正适合整体偏暗或偏亮的图像但偏置量要逐张调批量处理时不可控。CLAHE 是三者里最不需要调参、最稳的方案。增强完注意标签坐标不用改。增强只改变像素灰度值不改变目标几何位置训练代码里把图像路径指向增强后的目录标签目录保持不变即可。4. 用 YOLOv8 在 Anaconda 里训练红外疲劳数据集环境、参数与置信度门限4.1 Anaconda 环境配置CUDA、PyTorch 与 ultralytics 的版本搭配训练 YOLO 第一步不是写代码是装环境。网上环境配置翻车帖的数量比训练教程还多绝大多数问题是 CUDA、PyTorch、显卡驱动三者版本对不上。用 Anaconda 建独立环境是最省心的方式conda create -n yolo python3.9 -y conda activate yolo pip install ultralytics上面默认装的是 CPU 版 PyTorch训练速度慢得让人怀疑人生。要 GPU 加速先到 PyTorch 官网按你机器的 CUDA 版本复制安装命令装完 torch 再执行pip install ultralytics。常见组合是 CUDA 11.8 配 PyTorch 2.0 系列兼容性最稳。装完验证python -c import torch; print(torch.__version__); print(torch.cuda.is_available())输出True说明 GPU 可用。如果输出False先nvidia-smi看驱动支持的 CUDA 版本再决定重装哪个 PyTorch。这是纯环境问题跟数据集无关但几乎每个新手都在这里耗掉半天。4.2 编写 data.yaml 与类别映射标签里的类别 ID 必须和 names 对齐环境就绪后配置数据描述文件。最容易犯的错是类别名顺序和标签文件里的 class ID 对不上。标签里0是睁眼yaml 里把0写成了闭眼训练不会报错但所有指标和可视化全部反着。# infrared_fatigue.yaml path: ./dataset train: images/train val: images/val nc: 3 names: 0: eye_open 1: eye_closed 2: mouth_opennc是类别总数names的索引 0、1、2 严格对应标签 txt 每行第一列。如果第 2 章统计类别分布时发现只有两类把nc改成 2删掉多余名称。这一步的验收标准随机抽三张图打开原图再打开对应 txt确认类别 ID 和图像内容一致。不要相信“文件名带 open 就一定是开眼”抽检永远是最快路径。4.3 训练命令与关键参数imgsz、batch、epochs 怎么定YOLOv8 的训练命令一条就能起来但参数要按红外小目标场景调yolo detect train \ datainfrared_fatigue.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ device0逐项说参数逻辑。modelyolov8n.pt是 nano 版预训练权重。虽然 COCO 上预训练过但红外图域差异很大预训练提供不了太多语义先验选 nano 主要是训练速度快显存充足追求精度可以换yolov8s.pt。imgsz640是速度和精度的平衡点但这套数据有个特殊性——眼睛目标很小。如果原图分辨率是 640×512用 640 没问题如果原图是 1280×1024训练时缩到 640 会把眼睛缩成几个像素此时建议把imgsz提到 960 或 1280代价是训练时间接近翻倍。batch16受显存限制8GB 显存跑 640 输入时 16 是安全的报 CUDA out of memory 就降到 8。patience20表示 20 个 epoch 验证指标不提升就提前停止红外数据集类别少2000 张图一般 50 epoch 左右就能看到 mAP 平台期。训练完成后runs/detect/train/weights/best.pt是要的产物。先在验证集上跑指标yolo detect val \ datainfrared_fatigue.yaml \ modelruns/detect/train/weights/best.pt输出的 mAP50、mAP50-95 是决定要不要调参的依据。红外场景下眼睛这类小目标的 mAP50-95 普遍低于可见光大目标。如果 mAP50 超过 0.8 但 mAP50-95 很低说明定位精度不够优先提输入分辨率而不是加训练轮数。4.4 损失函数与置信度门限红外低对比度下哪些默认参数要动YOLOv8 的损失由分类损失、回归损失和 DFL 三部分组成默认权重是官方在 COCO 上调好的。红外场景下手动改损失权重属于高风险操作容易把模型练崩。真正值得动手的是推理时的置信度门限。默认conf0.25是在明暗均衡的可见光图片上调出来的红外图误检更多。推理时我直接拉到 0.4yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetest_infrared.jpg \ conf0.4 \ imgsz640conf0.4过滤掉低置信度误检框。红外图里车窗反光、仪表盘亮区都容易产生高响应调高门限是成本最低的过滤手段。如果 0.4 把真目标也滤掉了说明训练不充分或验证集分布和真实场景不一致不要为了指标好看把门限拉到 0.9——那等于没训练。5. 避坑红外疲劳数据集从训练到部署的 4 个翻车现场5.1 训练到一半 loss 炸成 NaN训练日志里 loss 突然变成 nan之后所有指标全部失效。这个坑从 YOLOv5 时代就存在多数原因是标签里有非法值——比如归一化坐标里混进了像素坐标中心点 x 超过 1或者某个框宽高为 0 或负数导致损失计算里的对数项崩溃。解决分三步。第一立刻停止训练别怀疑学习率先怀疑标签。第二跑第 2 章的全量校验脚本重点看xc、yc是否在 [0,1] 区间w、h是否大于 0。第三对异常标签修复而不是删除坐标只超出零点几的比如 1.05clip 到 0.99 继续用宽高为 0 的直接删掉那一行标注。修复后重跑校验确认零异常再重新训练。5.2 闭眼样本 AP 是 0模型成了“睁眼检测器”验证集上闭眼类精确率和召回率全为 0模型把所有眼睛都判成睁开。原因几乎总是类别不平衡2000 张图里闭眼可能只有 100 张网络把闭眼当成了误检负样本。解决先看第 2 章打印的类别分布。如果比例小于 1:5做样本增强一是水平翻转闭眼样本眼睛左右对称翻转不改变语义二是复制粘贴增强把闭眼区域小图贴到其他睁眼图片的合理位置同时复制标注框三是调整损失权重给闭眼类配更高的cls权重但对新手不友好容易把超参带偏。最务实的是先把闭眼样本扩 3 倍再看 AP。5.3 验证集 mAP 很高夜间实车测试几乎全漏训练指标漂亮一上夜间行车视频就漏检。最常见原因是数据泄漏2000 张图如果是视频每隔几帧抽出来的相邻帧之间相似度极高随机划分会把同一段视频的连续帧同时分进训练集和验证集验证集指标天然虚高模型没有真正泛化到新场景。解决办法是划分时按视频片段分组而不是按单张图随机分。每一段视频的所有帧作为一个整体要么全进训练要么全进验证。划分后测试时换一个没见过的拍摄时段和路段。这个坑最隐蔽因为训练过程全程正常只能靠划分逻辑规避。5.4 labelimg 打标完的 txt 放进 YOLO 训练目标框全部偏移labelimg 导出 YOLO 格式后训练出来的框位置不对总是偏半个身位。原因是 labelimg 的 YOLO 格式默认是类别 归一化中心坐标加宽高但某些版本导出时没有按原图尺寸归一化或者图像被后期缩放标注和图片没有同步变换。排查方法很直接把标注用 OpenCV 画回原图肉眼对比贴合度import cv2 img cv2.imread(images/IMG_0001.jpg) h_img, w_img img.shape[:2] for line in open(labels/IMG_0001.txt): cls, xc, yc, w, h map(float, line.split()) x1 int((xc - w / 2) * w_img) y1 int((yc - h / 2) * h_img) x2 int((xc w / 2) * w_img) y2 int((yc h / 2) * h_img) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_IMG_0001.jpg, img)画回原图是准的问题出在数据加载管线检查data.yaml的train路径是否指向了和标注不一致的另一份图常见场景是图片被复制时压缩过分辨率。画回原图就不准重新导出标注。打标之后到训练之前这个可视化回验步骤建议每次都做成本低、收益大。6. 部署最后一公里夜间视频流上如何调置信度门限和过滤检测框抖动训练指标的终点不是模型能用的起点。红外疲劳检测最终要跑在车载设备或实时视频流里到这一步两个问题会冒出来检测框闪烁和误检率随环境变化。先解决闪烁。单帧检测里某一帧闭眼、下一帧睁眼、再下一帧又闭眼这在物理上不可能——眼睛状态不会在几十毫秒内反复横跳。我的做法是时序滤波记录最近 5 帧的检测结果连续 3 帧都检测到闭眼才输出“闭眼”事件连续 2 帧没检测到才清除事件。from collections import deque import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) state_queue deque(maxlen5) CONF_THRESHOLD 0.4 FAST_CONFIRM 3 cap cv2.VideoCapture(night_drive.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame, confCONF_THRESHOLD)[0] closed_score 0.0 for box in results.boxes: if int(box.cls[0]) 1: # 类别 1 为 eye_closed closed_score max(closed_score, float(box.conf[0])) state_queue.append(closed_score) if len(state_queue) FAST_CONFIRM: recent list(state_queue)[-FAST_CONFIRM:] if all(v CONF_THRESHOLD for v in recent): frame_id int(cap.get(cv2.CAP_PROP_POS_FRAMES)) print(fframe {frame_id}: fatigue event) cap.release()这个队列逻辑不是最优工程实现但说明了部署的两个要点model(frame, conf0.4)在推理阶段过滤低置信度框all(v CONF_THRESHOLD for v in recent)要求最近 3 帧都有置信度高于 0.4 的闭眼框才触发事件。这个机制能滤掉九成以上的单帧误报。边缘设备算力有限时大模型在视频流上会掉帧模型先导出再接入yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出后要在目标设备上重新验证不同设备推理延迟不同检测框帧间跳变幅度也不一样时序滤波窗口需要相应调整。我的习惯是上车实测前先拿 3 段不同光线条件的夜间录像跑一遍记录闭眼事件的漏报和误报次数再决定要不要把conf从 0.4 升到 0.5——这个微调往往能把误报砍半同时不漏掉真正的疲劳状态。希望这个从数据集校验到部署调优的流程能帮你把 2000 张红外图真正变成一套能上路的疲劳检测模型。本文还有配套的精品资源点击获取
返回列表