
简介这份深度学习数据集面向从事疲劳驾驶检测、驾驶员状态识别研究的开发者与算法学习者围绕睁眼、闭眼、打哈欠等四类行车状态提供图像分类素材可用于训练与评估卷积神经网络模型适合入门图像分类或开展安全驾驶相关课题。资源包共约2000个文件以jpg图像为主体另附1个py脚本与1个json类别文件压缩包大小约198.79MB图像按类别目录存放训练集约5000张、验证集约500张、测试集约200张便于直接接入常见训练流程。目前已有556人学习下载。借助清晰的目录结构与类别标注读者可快速完成数据加载、模型训练与效果对比省去自行采集与清洗图像的成本为疲劳驾驶识别实验提供可复用的基础数据。1. 疲劳驾驶图像分类数据集4 类状态、5700 张实拍图值不值得下跑过驾驶员状态监测DSM项目的同行大概都有体会模型结构选型半小时就能定找一批带标注、光照真实、闭眼和打哈欠不混淆的图能耗掉两周。这份「驾驶员行车状态疲劳驾驶图像分类数据集」就是冲着这个痛点来的——按目录分好类训练/验证/测试三段切分类别覆盖睁眼、闭眼、打哈欠等 4 种行车状态总量在 5700 张上下。它不解决算法问题解决的是「你还没开始训练就已经在清洗数据」的问题。适合谁做疲劳驾驶预警、网约车/货运车队安全监控、边缘端驾驶员状态识别的团队也适合拿它当图像分类练手项目因为类别少、目录规整从torchvision.datasets.ImageFolder到yolov8分类头都能直接吃。不适合谁需要时序视频流、需要人脸关键点坐标、需要夜间红外成像的这份是静态 RGB 图像别指望它替你做时序建模。下面按「先看清结构 → 再跑通训练 → 再避坑 → 最后进阶」的顺序拆。2. 数据集结构与类别映射先搞清目录怎么摆、json 怎么读2.1 目录组织与三段切分这份数据集最省心的地方是「相同数据放在同一目录下」也就是标准的ImageFolder结构。你解压后大概率看到的是这样的层级dataset/ ├── train/ │ ├── open_eye/ # 睁眼 │ ├── closed_eye/ # 闭眼 │ ├── yawn/ # 打哈欠 │ └── 第四类/ # 具体名称以 json 类别文件为准 ├── val/ │ ├── open_eye/ │ ├── closed_eye/ │ ├── yawn/ │ └── 第四类/ └── test/ ├── open_eye/ ├── closed_eye/ ├── yawn/ └── 第四类/数量上训练集约 5000 张、验证集约 500 张、测试集约 200 张比例大致 10:1:0.4。这个切分比例对分类任务是合理的验证集够你调 early stopping 和选 checkpoint测试集够你出一份不掺水的最终指标。注意类别目录名不要自己改改了之后 json 里的索引就对不上了。提示解压后先find dataset -type d看一眼真实目录名别照着本文的open_eye硬套以你本地 json 类别文件为准。2.2 读 json 类别文件别硬编码类别名项目正文明确说「具体类别参考 json 类别文件」这是很多人翻车的地方——直接classes [open_eye,closed_eye,yawn,xxx]写死结果第四类名字猜错训练时标签全错位还不报错。正确做法是先解析 json再据此构建映射import json import os # 读取类别定义文件路径按你本地实际调整 with open(dataset/classes.json, r, encodingutf-8) as f: class_info json.load(f) # json 常见两种结构{0: open_eye, ...} 或 [open_eye, ...] if isinstance(class_info, dict): # 按 key 排序保证索引稳定 idx2name {int(k): v for k, v in sorted(class_info.items(), keylambda x: int(x[0]))} else: idx2name {i: name for i, name in enumerate(class_info)} name2idx {v: k for k, v in idx2name.items()} print(类别映射:, idx2name) print(类别数:, len(idx2name))逻辑说明idx2name保证索引到类别名的映射稳定name2idx供后续自定义 Dataset 用。参数上encodingutf-8必须加中文类别名不加会乱码sorted那一步是为了防止 json 里 key 顺序被打乱导致索引漂移。跑完打印一下确认类别数是 4名字和你目录名一一对应再往下走。2.3 用 ImageFolder 快速验证数据可读性在写任何训练脚本前先花两分钟确认数据能被正确加载、类别数对得上from torchvision import datasets, transforms tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) train_ds datasets.ImageFolder(dataset/train, transformtf) print(类别:, train_ds.classes) # 应与 json 一致 print(类别索引:, train_ds.class_to_idx) print(训练样本数:, len(train_ds)) # 抽一张看看形状和标签 img, label train_ds[0] print(图像张量形状:, img.shape, 标签:, label)逻辑说明ImageFolder会自动按子目录名排序生成class_to_idx这一步能立刻暴露「目录名和 json 不一致」的问题。参数上Resize((224,224))是给 ResNet/MobileNet 这类骨干用的如果你后面换 112 输入的轻量模型这里要同步改。如果len(train_ds)明显不是 5000 左右说明解压不完整或目录嵌套多了一层先解决这个再谈训练。3. 训练与验证落地从基线模型到指标解读3.1 选骨干为什么先上 ResNet18 而不是直接冲最新模型热搜里「最新的图像分类模型」常年挂着但这份数据集只有 4 类、5700 张图直接上 ViT 或大号 ConvNeXt 大概率过拟合而且训练成本不划算。我的习惯是先跑 ResNet18 或 MobileNetV3 建立基线拿到一个可复现的准确率数字再决定要不要换模型。理由很直接类别少、类间差异睁眼 vs 闭眼在像素层面就很明显轻量骨干足够而且边缘端部署疲劳检测MobileNet 系列才是常态。import torch import torch.nn as nn from torchvision import models def build_model(num_classes4, backboneresnet18): if backbone resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, num_classes) elif backbone mobilenet_v3: model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.DEFAULT) model.classifier[-1] nn.Linear(model.classifier[-1].in_features, num_classes) return model model build_model(num_classes4) print(model.fc if hasattr(model, fc) else model.classifier[-1])逻辑说明替换最后一层全连接输出维度对齐 4 类。参数上weightsDEFAULT用预训练权重小数据集上这是提点最快的一招num_classes一定从前面 json 解析出的类别数传进来别写死。换 MobileNet 时注意它的分类头叫classifier不是fc改错地方会静默不生效。3.2 训练循环与关键超参下面是一段能直接跑的最小训练循环重点看超参怎么设from torch.utils.data import DataLoader from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), # 行车图像左右翻转合理 transforms.ColorJitter(brightness0.2, contrast0.2), # 模拟光照变化 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) train_ds datasets.ImageFolder(dataset/train, transformtrain_tf) val_ds datasets.ImageFolder(dataset/val, transformval_tf) 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 build_model(4).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) for epoch in range(15): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) print(fepoch {epoch} val_acc{correct/total:.4f})逻辑说明RandomHorizontalFlip对行车图像是安全的增强但别加垂直翻转——倒过来的脸不真实。ColorJitter模拟白天/隧道光照差异是这份数据提泛化的关键。参数上lr1e-4配 AdamW 是微调预训练模型的稳妥起点weight_decay1e-4抑制过拟合batch_size32在 8G 显存上跑 ResNet18 没问题。num_workers4按你 CPU 核数调Windows 上如果报错就设 0。3.3 指标别只看 accuracy混淆矩阵才是重点疲劳驾驶场景里把「闭眼」误判成「睁眼」的代价远大于反过来。所以训练完必须看混淆矩阵而不是一个总准确率数字from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.eval() all_pred, all_true [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) pred model(imgs).argmax(1).cpu().numpy() all_pred.extend(pred) all_true.extend(labels.numpy()) print(confusion_matrix(all_true, all_pred)) print(classification_report(all_true, all_pred, target_nameslist(train_ds.classes)))逻辑说明classification_report会给出每一类的 precision/recall/f1重点盯「闭眼」和「打哈欠」两类的 recall。参数上target_names用train_ds.classes保证和目录一致。如果闭眼类 recall 明显偏低说明模型把闭眼和正常睁眼混了这时候再回头加数据增强或调类别权重而不是盲目加 epoch。注意验证集只有 500 张左右单类可能就 100 多张指标波动会比较大。别拿一次验证结果下结论跑 3 个不同随机种子取平均更靠谱。4. 避坑与排查这几处我踩过你别再踩4.1 现象训练 loss 正常降验证准确率卡在 25% 不动原因四分类随机猜就是 25%这基本是标签错位的典型症状——目录名和 json 索引对不上或者ImageFolder按字母序排的类别顺序和你以为的不一样。解决打印train_ds.class_to_idx和 json 解析出的name2idx逐项比对不一致就统一以ImageFolder的顺序为准重建标签映射别两套标准混用。4.2 现象验证准确率虚高到 99%测试集一跑就崩原因训练集和验证集里存在同一段视频抽出来的近邻帧人脸几乎一样模型等于在背答案。这份数据是图像切分不是按人/按视频切分所以近邻泄漏是真实存在的风险。解决如果原始素材能追溯到视频源按视频源重新切分 train/val追溯不到就接受验证指标偏乐观最终以测试集 200 张的结果为准别拿验证集数字对外报。4.3 现象显存爆了报 CUDA out of memory原因batch_size32配 224 输入在 4G 显存卡上会炸或者num_workers开太大导致内存被吃满。解决先把batch_size降到 16 或 8再不行把输入降到 160 或 128num_workers从 4 降到 2。梯度累积可以救小显存batch_size8累积 4 步等效 32。4.4 现象中文类别名读 json 报 UnicodeDecodeError原因json 文件不是 UTF-8 编码或者用了 GBK 保存。解决open(..., encodingutf-8)显式指定如果文件本身是 GBK先转码再读别硬扛。Windows 上尤其常见血泪经验。4.5 现象测试集准确率比验证集低一大截原因测试集只有 200 张样本少导致方差大也可能测试集分布和训练集不同比如更多夜间图。解决先确认测试集没混进训练集再看测试集里哪一类拖后腿用混淆矩阵定位。样本少就别纠结小数点后两位看整体趋势。5. 进阶用法把分类头接到实时检测流程里数据集跑通只是第一步真正落地时你要的是「从摄像头帧里判断驾驶员状态」。一个常见做法是先用这份数据训好分类器再用一个人脸/眼部检测器比如轻量 YOLO 或 MTCNN裁出眼部区域送进分类器出状态。这里给一个把分类模型导出并做单帧推理的收尾技巧import torch from PIL import Image from torchvision import transforms # 加载训练好的权重 model build_model(4) model.load_state_dict(torch.load(best_resnet18.pth, map_locationcpu)) model.eval() infer_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def predict(img_path, idx2name): img Image.open(img_path).convert(RGB) # 强制三通道防灰度图翻车 x infer_tf(img).unsqueeze(0) # 加 batch 维 with torch.no_grad(): prob torch.softmax(model(x), dim1)[0] idx prob.argmax().item() return idx2name[idx], prob[idx].item() # 用前面解析出的 idx2name print(predict(test_sample.jpg, idx2name))逻辑说明convert(RGB)是关键数据集里若有灰度图不转通道会在Normalize那步报维度错。unsqueeze(0)补 batch 维softmax出置信度方便你在业务里设阈值——比如置信度低于 0.6 就不触发告警避免误报。参数上map_locationcpu让你在没有 GPU 的机器上也能加载权重做验证。导出成部署格式时torch.onnx.export或torch.jit.trace都行输入固定成(1,3,224,224)。验证导出是否正确的土办法拿同一张图分别跑 PyTorch 和 ONNX Runtime比对输出最大绝对误差超过 1e-3 就说明导出有问题别急着上线。从那以后我每次拿到新数据集都强制先跑一遍「目录名 vs json 类别」的比对脚本再抽 20 张图肉眼过一遍确认没有标签错位才开训——这一步花五分钟能省掉后面一整天的排查。希望帮到你。本文还有配套的精品资源点击获取