资讯详情

资讯详情

基于深度学习的驾驶员分心驾驶行为识别:数据集划分、模型选型与避坑指南

简介这份资源面向人工智能、深度学习方向的本科生与研究生尤其适合正在准备毕业设计、课程设计或期末大作业的同学用于完成驾驶员分心驾驶行为识别这一典型计算机视觉课题。包内提供完整项目源码、数据集、训练好的模型以及配套毕设论文代码含详细注释新手也能看懂并快速部署运行。资源共31个文件以ipynb实验笔记、html可视化报告、py脚本为主另含pdf与docx论文文档、gif演示动图及md说明压缩包约65.36MB。项目覆盖VGG16、VGG19、ResNet50、InceptionV3、Xception等多种骨干网络的迁移学习与微调实验并配有数据可视化、瓶颈特征提取等模块便于对比不同模型效果。目前已有739人学习下载可作为完整课题方案直接参考帮助读者理清从数据处理、模型训练到结果展示的全流程思路。1. 分心驾驶识别到底在识别什么从一张方向盘截图说起前阵子帮一个做车载安全的团队看模型他们拿一张驾驶员单手离开方向盘去拿水杯的截图问我这算分心吗我说算但你的模型未必认。因为绝大多数公开数据集里分心驾驶的标签是按「手在干什么」定义的而不是按「注意力在哪」定义的。这就是这个方向最容易翻车的地方——你以为在做行为识别其实在做手部区域分类。基于深度学习实现驾驶员分心驾驶行为识别本质是一个细粒度图像分类任务输入是车内摄像头拍到的一张驾驶员上半身图输出是「正常驾驶 / 打电话 / 喝水 / 吃东西 / 抽烟 / 调收音机 / 与乘客交谈」这类行为标签。它解决的是车队管理、保险风控、辅助驾驶提醒里的「驾驶员状态感知」需求。适合谁做做计算机视觉毕设的学生、想入门行为识别的新手、以及需要快速搭一个可演示原型的工程师。它不需要你从零训练骨干网络迁移学习加合理的数据划分就能跑到可用的精度但前提是你得先搞清楚标签体系和数据分布这两个黑匣子。2. 数据集怎么选与怎么切别让同一段视频同时进训练和验证2.1 分心驾驶数据集的三种常见来源与取舍做这个项目数据集决定了你天花板在哪。常见做法有三类第一类是公开的驾驶员行为数据集通常按行为类别分文件夹存放图像分辨率在 640×480 上下类别数在 10 类左右第二类是自己用手机或行车记录仪拍的模拟数据灵活但标注成本高第三类是合成数据用 3D 人体模型渲染省标注但域差距大。我一般会建议先用公开数据集跑通流程再考虑补充自有数据。原因是公开数据集的类别定义已经被大量论文验证过你复现出来的精度有参照系。自己拍的数据往往光照、角度、遮挡都不可控新手很容易在数据清洗上耗掉大半时间。选数据集时重点看三个指标类别是否均衡、是否有同一驾驶员的重复出现、图像是否包含完整上半身。第三点最容易被忽略——如果裁剪太紧只剩一双手模型学到的就是手部纹理换个驾驶员立刻失效。2.2 按驾驶员划分训练验证集一个被低估的关键操作很多人做图像分类习惯用随机划分train_test_split一调就完事。在分心驾驶场景里这是血泪教训级的错误。因为同一段视频抽帧出来的图像高度相似随机划分会让几乎相同的图同时出现在训练集和验证集验证精度虚高到 99%一上真实场景就崩。正确做法是按驾驶员 ID 划分把属于同一驾驶员的所有图像整体分到训练集或验证集绝不交叉。下面是一个按文件夹结构划分的脚本示例。import os import shutil import random # 原始数据结构dataset/行为类别/驾驶员ID/图像.jpg # 目标按驾驶员ID划分同一驾驶员不跨集 SRC dataset DST split RATIO {train: 0.7, val: 0.15, test: 0.15} random.seed(42) for cls in os.listdir(SRC): cls_path os.path.join(SRC, cls) if not os.path.isdir(cls_path): continue drivers os.listdir(cls_path) # 每个驾驶员一个子文件夹 random.shuffle(drivers) n len(drivers) n_train int(n * RATIO[train]) n_val int(n * RATIO[val]) splits { train: drivers[:n_train], val: drivers[n_train:n_train n_val], test: drivers[n_train n_val:] } for split, ids in splits.items(): for did in ids: src_dir os.path.join(cls_path, did) dst_dir os.path.join(DST, split, cls, did) os.makedirs(dst_dir, exist_okTrue) for img in os.listdir(src_dir): shutil.copy(os.path.join(src_dir, img), os.path.join(dst_dir, img)) print(按驾驶员划分完成)这段代码的逻辑是按「类别 → 驾驶员」两级遍历先打乱驾驶员列表再切分保证同一驾驶员的所有图像只进一个集合。RATIO是划分比例7:1.5:1.5 是常见配置如果数据量小可以把验证和测试合并。random.seed(42)固定随机种子保证每次划分结果一致方便复现。跑完之后建议抽查一下split/train和split/val里有没有同名驾驶员文件夹有就说明划分逻辑写错了。2.3 数据增强的边界哪些增强会破坏行为语义数据增强是提精度最便宜的手段但分心驾驶场景有几类增强要慎用。水平翻转要小心打电话这个动作左手拿手机和右手拿手机翻转后语义变了如果数据集里左右手样本不均衡翻转会引入噪声。随机裁剪也要控制幅度裁太狠可能把手部裁掉标签就失效了。我一般会用的增强组合是小角度旋转±15 度以内、亮度对比度微调、轻微缩放、随机擦除。随机擦除模拟遮挡对车内场景很实用因为方向盘、后视镜经常挡住部分身体。下面是一个基于 torchvision 的增强配置。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomRotation(15), # 小角度旋转 transforms.ColorJitter(0.2, 0.2, 0.2, 0.05), # 亮度/对比度/饱和度 transforms.RandomAffine(degrees0, scale(0.9, 1.1)), # 轻微缩放 transforms.ToTensor(), transforms.RandomErasing(p0.3, scale(0.02, 0.1)), # 随机擦除 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]) ])RandomRotation(15)控制旋转角度超过 20 度驾驶员姿态就不自然了。ColorJitter四个参数分别是亮度、对比度、饱和度、色调的扰动幅度色调扰动要小否则肤色会失真。RandomErasing的scale控制擦除面积占比0.02 到 0.1 之间比较温和。归一化用的均值方差是 ImageNet 的统计值因为骨干网络是在 ImageNet 上预训练的保持一致能加速收敛。3. 模型选型与训练从 ResNet 到轻量骨干的取舍3.1 骨干网络怎么选精度、速度与部署成本的三角分心驾驶识别不是 ImageNet 那种千类任务类别通常不超过 10 类所以不需要特别深的网络。常见选择有 ResNet50、EfficientNet-B0、MobileNetV3、以及各种轻量 ViT。选型时看三个维度验证集精度、单帧推理耗时、模型文件大小。我的经验是如果只是做毕设演示ResNet50 微调最稳社区资料多出问题好查。如果考虑部署到车机或边缘设备MobileNetV3 或 EfficientNet-B0 更合适精度掉一两个点但推理快好几倍。ViT 类模型在小数据集上容易过拟合除非你有上万张图否则不建议新手直接上。骨干网络参数量级单帧推理CPU适合场景ResNet5025M80-120ms毕设、服务器端EfficientNet-B05.3M30-50ms边缘设备、精度均衡MobileNetV3-Small2.5M15-25ms车机、实时性优先ViT-Base86M200ms大数据集、研究向3.2 迁移学习训练脚本冻结策略与学习率设置迁移学习的核心是「先冻结骨干只训分类头再解冻微调」。直接全网络微调容易把预训练权重带偏尤其是小数据集。下面是一个两阶段训练的核心代码。import torch import torch.nn as nn from torchvision import models def build_model(num_classes, freeze_backboneTrue): model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V2) if freeze_backbone: for p in model.parameters(): p.requires_grad False in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) return model # 第一阶段只训分类头 model build_model(num_classes10, freeze_backboneTrue) optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) # 第二阶段解冻后四层小学习率微调 for name, p in model.named_parameters(): if layer4 in name or fc in name: p.requires_grad True optimizer torch.optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr1e-4 )freeze_backboneTrue时只训练最后的全连接层学习率可以设大一点1e-3 量级。第二阶段解冻layer4和fc学习率降到 1e-4避免破坏底层特征。Dropout(0.3)放在分类头前小数据集上能明显抑制过拟合。如果你用的是 EfficientNet层名不一样解冻时把layer4换成features.7或features.8这类靠后的 stage。3.3 训练过程要看哪些曲线损失不降和精度震荡的排查训练时至少盯三条曲线训练损失、验证损失、验证精度。训练损失降但验证损失先降后升是过拟合加增强或加 Dropout。两条损失都不降先查学习率是不是太大再查数据标签有没有错。验证精度剧烈震荡通常是 batch size 太小或学习率调度没配好。我一般会加一个余弦退火调度和早停。余弦退火让学习率周期性下降比固定学习率更容易收敛到好的局部解。早停则是在验证精度连续若干轮不提升时停止省时间也防过拟合。scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max20, eta_min1e-6 ) best_acc 0.0 patience, wait 5, 0 for epoch in range(50): train_one_epoch(model, train_loader, optimizer) acc evaluate(model, val_loader) scheduler.step() if acc best_acc: best_acc acc torch.save(model.state_dict(), best.pth) wait 0 else: wait 1 if wait patience: print(f早停于第 {epoch} 轮最佳精度 {best_acc:.4f}) breakT_max20表示学习率在 20 轮内从初始值降到eta_min。patience5是容忍轮数连续 5 轮不提升就停。保存时只存state_dict文件小且加载灵活。注意早停的判定要用验证集精度不能用测试集否则测试集就失去意义了。4. 避坑与排查那些让精度虚高又突然崩掉的细节4.1 验证精度 99% 但实际演示全错现象训练日志里验证精度很快冲到 99%但拿手机拍一段自己开车的视频去测几乎全错。原因按帧随机划分导致同一段视频的相邻帧同时进了训练和验证模型记住了背景而不是行为。解决改成按驾驶员或按视频片段划分确保同一来源的数据只出现在一个集合。验证时也要用完全没见过的驾驶员数据。4.2 模型把「方向盘角度」当成了分类依据现象模型在测试集上表现不错但换个车型就崩。原因数据集里不同行为的拍摄角度和方向盘位置有相关性模型学到了「方向盘偏左就是调收音机」这种伪特征。解决增强里加入随机裁剪和透视变换削弱位置相关性或者用 Grad-CAM 可视化看模型关注区域是不是集中在手部和脸部如果热力图落在方向盘或车窗上就说明学偏了。4.3 类别不均衡导致少数类几乎不被识别现象打电话、抽烟这些类别召回率极低正常驾驶类别精度很高。原因正常驾驶样本占了七八成模型倾向于全预测成多数类。解决用加权交叉熵权重按类别频率的倒数设置或者用重采样对少数类过采样。加权损失比过采样更简单不用改数据加载逻辑。class_counts [1200, 300, 250, 180, 150, 100, 90, 80, 70, 60] weights 1.0 / torch.tensor(class_counts, dtypetorch.float) weights weights / weights.sum() * len(class_counts) criterion nn.CrossEntropyLoss(weightweights)class_counts是每个类别的样本数权重取倒数后归一化让少数类的损失贡献更大。注意权重不要设得太极端否则多数类精度会掉得厉害整体指标反而下降。4.4 推理时预处理和训练时不一致现象训练精度很高部署后精度掉一大截。原因推理时的 resize 方式、归一化参数、通道顺序和训练时不一致。比如训练用了双线性插值推理用了最近邻训练归一化用了 ImageNet 均值推理忘了减。解决把预处理封装成一个函数训练和推理共用同一份代码杜绝手写两套。4.5 模型文件太大塞不进部署环境现象ResNet50 权重 100MB 左右车机或移动端放不下。原因没做模型压缩。解决换轻量骨干或者对现有模型做动态量化。动态量化对全连接层效果明显精度损失通常在一个点以内。import torch.quantization model build_model(num_classes10, freeze_backboneFalse) model.load_state_dict(torch.load(best.pth, map_locationcpu)) model.eval() quantized torch.quantization.quantize_dynamic( model, {nn.Linear}, dtypetorch.qint8 ) torch.save(quantized.state_dict(), best_quant.pth)quantize_dynamic只量化线性层对卷积层不动适合分类头占比较大的场景。qint8是 8 位整数量化模型体积能压到原来的四分之一左右。量化后一定要在验证集上重新跑一遍精度确认掉点可接受。5. 从能跑到好用Grad-CAM 验证与一个提点技巧模型训完不是终点你得知道它到底在看哪里。Grad-CAM 是最实用的可视化工具把模型最后卷积层的梯度加权回特征图生成热力图叠加在原图上。如果热力图集中在手部、脸部、手机这些区域说明模型学对了如果落在座椅、车窗、方向盘上就得回头查数据。import cv2 import numpy as np import torch def grad_cam(model, img_tensor, target_layer): model.eval() features, grads [], [] def forward_hook(m, i, o): features.append(o) def backward_hook(m, gi, go): grads.append(go[0]) h1 target_layer.register_forward_hook(forward_hook) h2 target_layer.register_full_backward_hook(backward_hook) out model(img_tensor.unsqueeze(0)) cls out.argmax(dim1).item() model.zero_grad() out[0, cls].backward() f features[0].detach().squeeze(0) # C,H,W g grads[0].detach().squeeze(0) # C,H,W w g.mean(dim(1, 2)) # 每个通道的权重 cam torch.relu((w[:, None, None] * f).sum(0)) cam cam / (cam.max() 1e-8) h1.remove(); h2.remove() return cam.cpu().numpy(), cls cam, cls grad_cam(model, img_tensor, model.layer4[-1]) cam cv2.resize(cam, (224, 224)) heatmap cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET) overlay cv2.addWeighted(orig_img, 0.6, heatmap, 0.4, 0) cv2.imwrite(cam.jpg, overlay)target_layer一般选最后一个卷积 stageResNet 是layer4[-1]EfficientNet 是最后一个features块。register_full_backward_hook拿梯度register_forward_hook拿特征图。权重是梯度在空间维度上的平均这是 Grad-CAM 的标准做法。torch.relu只保留正贡献负值置零。归一化后叠加到原图addWeighted的 0.6/0.4 是原图和热力图的混合比例热力图太浓会盖住细节太淡看不清这个比例可以按需调。一个提点技巧如果 Grad-CAM 显示模型关注区域偏了别急着换网络先试试在训练时加一个辅助损失让模型同时预测手部关键点或人脸框。多任务学习能逼着模型关注正确区域通常能提两三个点。具体做法是在骨干后面接两个头一个分类头一个关键点回归头损失加权求和。关键点标注如果没有可以用现成的手部检测器自动生成伪标签噪声大但够用。我自己踩过最深的坑是迷信精度数字。有一次验证集 98%兴冲冲去演示结果摄像头一开模型把「手放在档把上」识别成了「喝水」因为训练集里喝水样本的手部位置和档把区域高度重合。后来加了 Grad-CAM 逐类检查才发现问题。从那以后我养成的习惯是任何分类模型上线前必须逐类看热力图确认关注区域符合人类直觉再谈精度。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →