ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

驾驶员疲劳驾驶图像分类数据集:从数据检查到模型上车的全链路实战

驾驶员疲劳驾驶图像分类数据集:从数据检查到模型上车的全链路实战 简介这是一份面向深度学习入门与计算机视觉实践者的驾驶员行车状态疲劳驾驶图像分类数据集适用于疲劳检测、驾驶行为识别等场景的模型训练与算法验证。数据按类别目录组织同类样本归入同一文件夹共含睁眼、闭眼、打哈欠等4个类别具体类别名称可参考包内json类别文件方便直接对接ImageFolder等常见加载方式。资源包共约2000个文件以jpg图像为主体另附1个py脚本与1个json类别文件压缩包整体约198.79MB并已划分训练集约5000张、验证集约500张、测试集约200张便于快速开展训练与评估。目前已有556人学习下载适合希望省去数据采集与清洗环节、专注模型搭建与调参的读者可据此完成分类模型训练、迁移学习对比与效果验证。1. 驾驶员疲劳驾驶图像分类数据集从「这是什么」到「怎么跑通」的落地判断疲劳驾驶这件事真正难的不是模型结构而是数据。你手上如果有一批驾驶员行车状态图像想做成一个能判断「清醒 / 疲劳」的分类器第一步不是选 ResNet 还是 ViT而是先搞清楚这批数据到底长什么样、类别怎么分、标注是否可信。驾驶员行车状态疲劳驾驶图像分类数据集本质上是一批在驾驶舱场景下采集的人脸或上半身图像按疲劳程度打上类别标签常见分法是二分类疲劳 / 非疲劳或三分类清醒 / 轻度疲劳 / 重度疲劳。它解决的是「让模型学会从眼睛开合、打哈欠、头部姿态这些视觉线索里判断驾驶员状态」的问题适合做安全驾驶预警、车队管理、车载 DMS 算法验证的从业者。但这类数据集有几个天然坑红外夜视和可见光混采、戴眼镜反光、驾驶员肤色和光照差异大、类别极度不均衡。我见过太多人拿到数据直接套 ImageNet 预训练权重开跑结果验证集准确率 95%上车一测全是误报——因为验证集和训练集来自同一段视频模型记住的是人不是状态。所以这篇不聊虚的从数据检查、划分、训练到排查把这条链路走一遍。2. 先看清数据再动手驾驶员疲劳图像的类别定义与质量检查2.1 疲劳驾驶分类的标签体系怎么定拿到数据集第一件事不是写 DataLoader而是把标签文件打开统计每个类别的样本数、图像尺寸分布、拍摄模态。驾驶员疲劳分类的标签体系通常有三种粒度二分类只分疲劳/非疲劳适合快速验证三分类加入「疑似疲劳」或按 PERCLOS 阈值分轻中重更细的会标注眼睛状态、嘴巴状态、头部朝向做多任务。我一般建议先做二分类跑通链路再考虑加类别。原因是疲劳本身是一个连续状态硬切三类的边界样本连人眼都难判模型学到的噪声会拖垮整体指标。检查标签时重点看三件事类别是否均衡、有没有同一驾驶员的多帧泄漏、标签是否和图像内容对得上。下面这段脚本是我常用的数据体检代码直接跑就能出报告。import os import cv2 import pandas as pd from collections import Counter # 假设目录结构: dataset/fatigue/*.jpg, dataset/normal/*.jpg root dataset records [] for cls in os.listdir(root): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath os.path.join(cls_dir, fname) img cv2.imread(fpath) if img is None: records.append({cls: cls, file: fname, h: -1, w: -1, c: -1}) continue h, w, c img.shape records.append({cls: cls, file: fname, h: h, w: w, c: c}) df pd.DataFrame(records) print(类别分布:\n, Counter(df[cls])) print(尺寸分布:\n, df.groupby([h, w]).size().sort_values(ascendingFalse).head(10)) print(通道异常(非3通道):\n, df[df[c] ! 3]) print(读取失败文件:\n, df[df[h] -1])这段代码做四件事统计类别数量、看图像尺寸是否统一、找出非三通道的灰度或红外图、列出损坏文件。参数上没什么可调的root 指向你的数据根目录即可。跑完你大概率会发现尺寸不统一、有灰度图混在里面、某个类别样本数只有另一个的十分之一。这些就是后面要处理的坑。2.2 用感知哈希找出跨集泄漏的同一驾驶员疲劳驾驶数据集最隐蔽的问题是同一驾驶员、同一段行车记录的多帧被随机划分到训练集和验证集。模型在验证集上表现很好但它认的是这张脸不是疲劳状态。检测方法是用感知哈希pHash对图像做指纹相似度高的归为一组按组划分而不是按图划分。import imagehash from PIL import Image import os def build_hash_groups(root, threshold5): hashes {} for cls in os.listdir(root): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath os.path.join(cls_dir, fname) try: h imagehash.phash(Image.open(fpath).convert(RGB)) except Exception: continue hashes[fpath] h # 简单贪心分组: 汉明距离小于阈值视为同组 groups [] used set() items list(hashes.items()) for i, (p1, h1) in enumerate(items): if p1 in used: continue group [p1] used.add(p1) for p2, h2 in items[i1:]: if p2 in used: continue if h1 - h2 threshold: group.append(p2) used.add(p2) groups.append(group) return groups groups build_hash_groups(dataset, threshold5) print(总组数:, len(groups)) print(最大组样本数:, max(len(g) for g in groups))threshold 设 5 是比较保守的值汉明距离小于等于 5 认为视觉上高度相似。这个值调大分组更粗调小可能漏掉同源帧。跑完看最大组样本数如果超过总样本的 5%说明泄漏严重必须按组划分。这一步做完你后面所有指标才有参考意义。2.3 按驾驶员分组划分训练验证集分组有了划分就简单了以组为单位随机分配保证同一组的所有图只出现在训练或验证之一。常见比例 8:1:1 或 7:2:1疲劳驾驶数据量通常不大我一般用 7:2:1 留出测试集。import random random.seed(42) random.shuffle(groups) n len(groups) train_end int(n * 0.7) val_end int(n * 0.9) split {train: groups[:train_end], val: groups[train_end:val_end], test: groups[val_end:]} import json with open(split.json, w) as f: json.dump({k: [item for g in v for item in g] for k, v in split.items()}, f, indent2)seed 固定住保证可复现。split.json 里存的是文件路径列表后面 DataLoader 直接读这个文件不要再用 ImageFolder 按目录扫否则划分就白做了。这一步是很多教程跳过的但恰恰是疲劳驾驶分类能不能落地的分水岭。3. 训练链路搭起来从 DataLoader 到图像分类模型选型3.1 驾驶员疲劳图像的预处理与增强策略驾驶舱图像的特点是光照变化剧烈、有红外和可见光两种模态、人脸区域占比不固定。预处理我一般做三步人脸检测裁剪、尺寸统一到 224×224、按通道做归一化。如果数据里混了红外灰度图统一转三通道再归一化不要分开处理否则模型要学两套分布。增强策略要克制。水平翻转可以用但垂直翻转绝对不行——倒着的脸不是疲劳的脸。颜色抖动在红外图上没意义可见光图上可以轻微用。随机裁剪要小心裁太狠可能把眼睛裁掉而眼睛恰恰是疲劳判定的关键区域。我一般用 RandomResizedCrop 配合 scale(0.8, 1.0)再叠加小角度旋转。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])Normalize 的均值方差用的是 ImageNet 统计值这是迁移学习的常规做法。如果你的数据全是红外图建议自己统计一遍均值和方差替换掉效果会有可见提升。RandomRotation 控制在 10 度以内再大就不符合真实驾驶场景了。3.2 图像分类模型选型ResNet、EfficientNet 还是 ViT疲劳驾驶分类不是细粒度分类判别特征相对明确所以不需要一上来就上大模型。我的选型顺序是ResNet18/50 打底EfficientNet-B0 做轻量化对比ViT 系列在数据量足够单类超过 5000 张时再考虑。最新的图像分类模型比如 ConvNeXt、Swin 在公开数据集上确实强但疲劳驾驶数据量通常撑不起从头训练用预训练权重微调才是正路。模型参数量适合场景注意事项ResNet1811M快速验证、边缘部署预训练权重好找收敛稳ResNet5025M精度优先小数据容易过拟合加 DropoutEfficientNet-B05.3M车载端侧输入分辨率敏感别乱改ViT-B/1686M数据量充足需要更强增强和更长训练选型时还要看部署目标。如果是车载 DMS算力有限EfficientNet-B0 或 MobileNetV3 更实际如果是云端离线分析ResNet50 微调就够。不要为了刷点用大模型推理延迟在疲劳预警场景里是硬指标。3.3 一个能直接跑通的训练脚本与关键参数下面这个脚本用 ResNet18 做二分类微调包含分组划分读取、类别权重处理不均衡、余弦退火学习率。参数我都标了推荐值。import json import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader from torchvision import transforms, models from PIL import Image from sklearn.metrics import f1_score import numpy as np class FatigueDataset(Dataset): def __init__(self, paths, labels, tf): self.paths paths self.labels labels self.tf tf def __len__(self): return len(self.paths) def __getitem__(self, idx): img Image.open(self.paths[idx]).convert(RGB) return self.tf(img), self.labels[idx] # 读取分组划分 with open(split.json) as f: split json.load(f) def build_ds(paths, tf): labels [0 if normal in p else 1 for p in paths] return FatigueDataset(paths, labels, tf) train_ds build_ds(split[train], train_tf) val_ds build_ds(split[val], val_tf) # 类别权重: 样本少的类别权重大 labels_all [0 if normal in p else 1 for p in split[train]] class_counts np.bincount(labels_all) weights torch.tensor(1.0 / class_counts, dtypetorch.float32) weights weights / weights.sum() train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 2) model model.to(device) criterion nn.CrossEntropyLoss(weightweights.to(device)) optimizer torch.optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20) best_f1 0 for epoch in range(20): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() scheduler.step() model.eval() preds, gts [], [] with torch.no_grad(): for x, y in val_loader: x x.to(device) p model(x).argmax(1).cpu().numpy() preds.extend(p) gts.extend(y.numpy()) f1 f1_score(gts, preds, averagebinary) print(fepoch {epoch} f1{f1:.4f}) if f1 best_f1: best_f1 f1 torch.save(model.state_dict(), best.pth)关键参数说明lr3e-4 是微调预训练模型的常用起点太大容易破坏预训练特征太小收敛慢weight_decay1e-4 抑制过拟合T_max20 和训练轮数一致让学习率完整退火一轮batch_size32 在 224 分辨率下对显存要求适中显存不够降到 16。评估用 F1 而不是准确率因为疲劳驾驶数据几乎必然不均衡准确率会被多数类带偏。类别权重用 1/样本数 归一化缓解不均衡但如果某一类样本极少少于 50 张权重会过大导致训练震荡这时候优先补数据而不是调权重。4. 疲劳驾驶分类的避坑与排查那些指标好看上车就废的原因4.1 验证集 F1 很高但实际误报多现象验证集 F1 到 0.95 以上拿一段新视频逐帧推理正常驾驶被频繁判成疲劳。原因通常是训练验证同源泄漏模型记住了驾驶员身份特征而不是疲劳特征。解决回到 2.2 的感知哈希分组确认划分按组进行再检查验证集里是否混入了训练集同一段视频的相邻帧。如果分组后指标掉到 0.8 左右那才是真实水平。4.2 红外图和可见光图混训导致指标波动现象训练 loss 震荡验证指标忽高忽低。原因两种模态的像素分布差异大模型在两种分布间来回拉扯。解决要么按模态分层采样保证每个 batch 里两种图都有要么统一转灰度只保留亮度信息牺牲颜色线索换稳定性。我一般先看数据里红外占比超过 30% 就做分层采样。4.3 戴眼镜和口罩样本被系统性误判现象戴眼镜的驾驶员疲劳样本大量漏检。原因眼镜反光遮挡眼部特征而眼部恰恰是模型最依赖的区域。解决训练时加入随机遮挡增强RandomErasing模拟眼镜和口罩遮挡同时检查数据集中戴眼镜样本的比例如果训练集里太少模型没机会学到遮挡下的特征。这个坑没有捷径只能靠数据覆盖。4.4 类别不均衡导致少数类召回率极低现象疲劳类召回率只有 0.5正常类接近 1.0。原因疲劳样本远少于正常样本模型倾向于全判正常。解决除了类别权重还可以用重采样让每个 epoch 里两类样本数接近或者用 Focal Loss 替代 CrossEntropyLoss降低易分样本的权重。但要注意重采样会让训练集分布偏离真实分布验证集必须保持原始分布否则指标又失真了。4.5 图像尺寸不统一导致 DataLoader 报错现象训练中途报 tensor 尺寸不匹配。原因数据集中混有不同分辨率的图Resize 只写了训练增强没写验证增强或者某些损坏图被跳过。解决在 Dataset 的getitem里统一做 Resize不要依赖增强管道体检脚本里已经列出了读取失败的文件提前清理掉。这个坑最没技术含量但最耽误时间。5. 把疲劳分类做到能上车阈值调优与跨驾驶员验证模型训练完只是半成品真正决定能不能用的是推理阈值和跨驾驶员泛化。默认 argmax 等价于阈值 0.5但疲劳预警场景里漏检把疲劳判成清醒的代价远大于误报所以阈值要往召回率方向偏。做法是在验证集上画 PR 曲线选一个召回率不低于 0.9 的阈值点哪怕精确率掉一些。下面这段代码输出不同阈值下的指标帮你做决策。import torch import numpy as np from sklearn.metrics import precision_recall_curve model.eval() probs, gts [], [] with torch.no_grad(): for x, y in val_loader: x x.to(device) p torch.softmax(model(x), dim1)[:, 1].cpu().numpy() probs.extend(p) gts.extend(y.numpy()) prec, rec, thresholds precision_recall_curve(gts, probs) for t in [0.3, 0.4, 0.5, 0.6, 0.7]: pred (np.array(probs) t).astype(int) tp ((pred 1) (np.array(gts) 1)).sum() fn ((pred 0) (np.array(gts) 1)).sum() fp ((pred 1) (np.array(gts) 0)).sum() recall tp / (tp fn 1e-6) precision tp / (tp fp 1e-6) print(f阈值 {t}: 召回 {recall:.3f} 精确 {precision:.3f})跑完你会看到阈值从 0.5 降到 0.3召回率明显上升精确率下降。疲劳预警我一般选召回优先的阈值因为漏检一个疲劳驾驶的代价可能是事故误报只是多一次提醒。但阈值不能拍脑袋定要在独立测试集上验证而且最好按驾驶员分组验证——留出几个驾驶员完全不参与训练看模型在他们身上的表现。如果跨驾驶员指标掉得厉害说明模型过拟合了训练集里的身份特征需要加更多驾驶员的数据或者用更强的身份无关增强。还有一个实操习惯每次改完数据划分或增强策略先跑 5 个 epoch 看验证 F1 趋势不要一上来就训 50 轮。疲劳驾驶数据量小5 轮足够看出方向对不对。我在这上面翻过车训了一晚上发现划分泄漏全部重来。希望帮到你。本文还有配套的精品资源点击获取
返回列表