基于深度学习的疲劳驾驶检测:目标检测、图像分类与实时推理实战
发布时间:2026/10/17 16:00:11 锦皓数字建站

我还记得去年做毕设选题那会儿导师给了我几个方向我一眼就看中了基于深度学习的疲劳驾驶检测这个题目。原因很直接疲劳驾驶导致的事故率一直居高不下而市面上很多所谓智能驾驶预警要么靠红外打哈欠检测要么只是定时提醒真正能在普通摄像头下实时判定驾驶员疲劳状态的开源方案并不算多。做这个题目既能覆盖目标检测、图像分类、时序状态判定这些深度学习核心知识点又能做成一个看得见、摸得着的系统答辩时效果展示也很有说服力。后面我又把驾驶行为检测一并加进去了比如分心、打电话、抽烟这些整个系统就从疲劳预警升级成了驾驶状态监控。这篇文章把我从选题、数据准备、模型训练到实时推理、踩坑优化的完整过程都整理出来给准备做相关毕设或者想入门计算机视觉落地的人一个可直接参考的路线。1. 为什么选疲劳检测做毕设需求背景与方案选型1.1 课题价值一个真实而高频的驾驶风险场景疲劳驾驶的典型表现很统一眼皮打架、频繁打哈欠、长时间低头、视线游离。这些状态在车载摄像头画面里其实是能捕捉到的关键在于怎么从连续的视频帧里把这些微状态稳定地识别出来。我当时的判断是这个题目有三层价值。第一层是安全价值疲劳预警系统可以部署在长途货运车辆、网约车、公交车上属于有真实需求的应用场景第二层是技术价值它不是一个玩具 Demo要同时处理人脸检测、眼部状态分类、时序统计、报警决策链路完整第三层是展示价值毕设答辩时可以直接现场演示摄像头对着自己闭上眼两秒就报警效果非常直观。也正因为如此这个题目放在很多高校的毕设选题库里属于既不太难、也不简单到没有工作量的稳妥选择。如果你是计算机视觉方向的学生想找一个能同时展示检测、分类、实时处理和工程化能力的题目疲劳驾驶检测是很好的切入点。1.2 技术路线对比为什么不用单一分类模型确定了方向之后第一个问题就是技术路线怎么选。我调研了三种主流实现思路方案核心做法优点缺点传统特征组合Haar 人脸检测 边缘特征判断眼睛闭合度轻量、跑得快光照和角度一变化就崩鲁棒性差端到端疲劳分类输入整帧图像直接用 CNN 输出疲劳/正常思路简单决策黑盒无法定位疲劳来源且需要大量实拍疲劳数据检测分类两阶段先用检测模型定位人脸和局部区域再用分类模型判断眼睛、嘴巴状态每个环节可控可解释数据容易针对性地凑需要串联两个模型工程复杂度略高我最终选了第三套方案。原因有三点一是疲劳数据的本质是局部状态眼睛闭合、打哈欠这些都是在人脸内部的局部区域体现的先定位再分类最符合物理规律二是数据容易准备眼睛开闭、嘴巴开闭的数据集比整帧疲劳数据集好找得多三是每层模型都可以单独评估、单独调优出了问题能定位到具体环节这在毕设调试阶段非常关键。1.3 整体架构三层链路让系统不失控系统整体分三层感知层、状态层、决策层。感知层负责从摄像头画面中找到人脸并进一步提取眼睛区域、嘴巴区域。状态层用两个轻量分类模型分别判断左眼/右眼是否闭合嘴巴是否打哈欠。决策层把连续若干帧的状态结果汇总成疲劳指标包括闭眼时长占比、哈欠频率、低头时长再按阈值触发分级报警。这个架构最大的好处是每一层都可以独立替换。比如人脸检测模型可以从一个版本换到另一个版本状态分类模型也单独训练、单独测试。决策层的报警策略更是完全独立于模型可以在不重新训练的情况下调整灵敏度。对毕设来说这意味着即使某些模块效果不理想也可以快速定位并改进不会出现整个系统崩了却不知道哪里出了问题的窘境。2. 数据集怎么凑公开数据、半自动标注与样本增强2.1 眼睛开闭数据公开数据集是起点但必须清洗眼睛开闭状态识别是整个系统的核心环节数据质量直接决定了模型上限。公开渠道有不少人脸关键点数据集和眼部状态数据集里面会标注眼睛睁开/闭合或者提供嘴巴开合的标签。我当时下载了跨多个来源的人脸图片然后按眼睛区域裁剪成固定尺寸的小图再重新分拣。这里必须提醒一句很多公开数据集的标注是不统一的。有的把眯眼标成了闭合有的把有明显反光的眼睛标成了闭合如果直接拿来做训练模型很容易学到错误规律。我的做法是写一个小脚本把裁剪后的眼部图像按亮度、正脸/侧脸、戴眼镜/不戴眼镜几个维度分组人工快速筛选一遍把明显标注错误的样本剔掉。这个步骤看起来枯燥实际上非常必要——一个几百张的脏数据子集足以让模型的准确率从98%跌到90%以下。2.2 半自动标注法一个周末标出几千张数据集纯人工标注眼睛和嘴巴区域太耗时我的做法是目标检测器辅助 人工复核。先训练一个临时的人脸关键点检测模型用公开的预训练权重把视频帧输入进去自动裁剪出左眼、右眼、嘴巴三个区域然后由人快速判断每个区域属于什么状态批量打标。具体流程是采集若干段行车记录仪或自拍的驾驶视频覆盖白天、夜晚、戴眼镜/不戴眼镜等场景用预训练关键点模型逐帧检测人脸裁剪出眼部、嘴部区域图把这些区域图按视频顺序排列用脚本生成带候选标签的样本列表人工翻看列表对误标或漏标的样本做快速修正随机抽取一部分样本做二次校验确保标签一致性。这套方法下来眼部数据一个周末能整理出四五千张嘴巴区域也能有个两三千张。对分类任务来说这个量已经足够训练一个精度不错的轻量模型了。2.3 样本增强防误报比刷精度更重要训练分类模型时我做了四类数据增强随机亮度/对比度扰动、水平平移和轻微旋转、随机遮挡、水平翻转。前两类很好理解驾驶场景里光线变化极快车辆颠簸也会导致人脸位置抖动不增强的话模型一到真实场景就见光死。随机遮挡这条值得多说几句。实际驾驶中驾驶员偶尔会抬手遮挡面部或者阳光在眼镜上形成大面积反光我在训练时特意把眼部图片的某个区域随机置为黑色块模拟遮挡。这招很有效模型不会因为某一小块区域信息丢失就乱判状态。另外戴眼镜样本和未戴眼镜样本的比例不能太偏否则模型会对眼镜框的轮廓产生过拟合把镜框阴影当成闭合眼睛。这个问题我最初没意识到后来在实测时发现模型对戴眼镜的测试者几乎总是误报回查数据才发现眼镜样本只占不到两成重新平衡后误报明显下降。3. 模型选型与训练调参目标检测定位 轻量分类识别3.1 人脸检测用检测网络而不是传统盒子早期做毕设的人脸检测一般用通用的检测模型或者干脆用传统的方法。我一开始也试图直接用自带检测器但在侧脸、低头、暗光场景下定位框经常漂移导致后续裁剪出的眼睛区域不准分类结果自然跟着错。所以我换成了目标检测网络来做人脸检测和人脸关键点回归。检测网络我选的是 YOLO 系列的轻量版本。原因很朴素这类模型生态成熟预训练权重多在低算力设备上也能跑出可以接受的帧率而且可以直接输出检测框和置信度配合后续 ROI 裁剪非常顺。训练时我没有从头训而是用了预训练权重做迁移学习自己补充了一批驾驶位视角的样本。之所以要补样本是因为车载摄像头通常装在挡风玻璃上方是俯角拍摄这和通用人像数据集的拍摄角度有明显差异不微调的话检测框会偏。3.2 眼睛与嘴巴状态分类轻量 CNN 是性价比之王眼睛开闭、嘴巴张合这两个分类任务输入都是很小的区域图我统一缩放到 64×64任务本身并不复杂不需要用大模型。我用的是一种深度可分离卷积结构的轻量分类网络参数量在几百万级别单次推理在普通 CPU 上也就几毫秒在 GPU 上更快。训练时我单独训练了三个分类器左眼、右眼、嘴巴。左眼和右眼用同一套数据结构只是训练数据分开这样做的好处是后续统计时可以分别判断哪只眼睛先闭合——实际驾驶中很多人疲劳时是双眼不同步地频繁眨动分眼统计更灵敏。分类模型的输出不是简单的0/1我用的是两个输出节点的 Softmax分别表示张开概率和闭合概率。决策层拿的是概率而不是硬标签这样在后续做时序平滑时能保留更多信息。比如某一帧输出的闭合概率是0.65直接取硬标签会被判为闭合但如果结合前后几帧来看这其实更可能是眨眼过程的中间状态概率值本身能帮助时间滤波做出更合理的判断。3.3 训练细节优化器、损失函数与指标观察训练分类器我用的是交叉熵损失优化器选择了带动量的 SGD初始学习率设成 0.01配合余弦退火调度。批量大小取 64训练大约 80 个 epoch。相比 AdamSGD 在这个小数据集上的泛化表现更稳定不容易过拟合到训练集的噪声扰动上。模型评估不能只看准确率。因为眼睛闭合样本的比例通常低于张开样本一个永远输出张开的模型准确率也会很高但没有任何实际价值。我主要看的是精确率、召回率和 F1 分数并重点观察混淆矩阵里的闭眼被误判为睁眼这一格——这种漏检是最危险的。打个比方如果模型把睁开眼睛识别成闭合系统顶多误报几声提醒但要是把闭合的眼睛识别成张开疲劳状态就会被完全漏掉整个系统的意义就没了。我训练时把闭眼类的召回率作为优先优化目标哪怕牺牲一点精确率也能接受。4. 实时检测链路帧处理、定时检测与疲劳状态机4.1 从摄像头帧到疲劳状态一条完整的数据管线实时推理链路比很多人想象的要琐碎。摄像头采集原始帧后不可能每帧都跑完整的检测加分类流程那样计算量太大。我的管线设计是摄像头以 30 FPS 采集帧先做一次缩放把长边压到 640 像素以内每帧跑人脸检测拿到人脸框坐标如果连续多帧检测到同一位置的人脸就开启跟踪模式每隔 3 帧做一次完整检测跟踪算法负责中间帧的框位置预测根据人脸框的相对位置用固定比例关系裁剪出左眼、右眼、嘴巴区域三个小区域图分别送入三个轻量分类模型得到状态概率状态概率连同时间戳写入一个环形缓冲区供决策层计算疲劳指标。因为跟踪模式大幅减少了对检测模型的调用系统整体帧率可以稳定保持在 25 FPS 以上。这是整个工程里性价比最高的一个优化比换什么高性能模型都管用。4.2 疲劳判定PERCLOS、哈欠频率与低头时长的融合决策层我用了三个疲劳指标。第一个是最经典的 PERCLOS即单位时间内眼睛闭合时间所占的比例。计算方式是统计最近 30 秒窗口内双眼闭合帧数占总帧数的比例。当比例超过 0.4 时判定为轻度疲劳超过 0.7 时判定为重度疲劳。这个阈值的设定参考了相关研究但实际使用时要根据摄像头安装角度和人的正常眨眼频率微调。正常眨眼一般持续 100~200 毫秒在 30 FPS 下就是 3~6 帧窗口统计天然能把眨眼和持续闭合区分开所以不用刻意过滤单帧抖动。第二个是哈欠频率。记录最近 3 分钟内嘴巴张开且张开持续时间超过 1 秒的次数每分钟超过 3 次可视为疲劳信号。这里强调持续时间超过 1 秒是因为说话、唱歌也会张嘴但很少持续 1 秒以上。第三个是头部姿态。通过人脸关键点估算头部俯仰角低头角度大于 20 度且持续超过 2 秒判定为低头。开车低头看手机或打盹都是这个姿态和疲劳高度相关。最终报警不是三个指标各自独立的简单 OR而是加权评分制。每个指标有一个权重综合评分超过阈值就升级报警等级。我用状态机把系统状态分成四档正常、轻度疲劳、中度疲劳、重度疲劳。轻度疲劳时只在界面闪烁提示中度时发出声音提醒重度时连续声光报警并保存现场截图。这个设计避免了轻微闭眼 0.5 秒就疯狂报警的糟糕体验。4.3 报警策略宁可晚报不可误报报警策略里我最想分享的一点是报警一定不能太灵敏。实测时我发现自己坐在电脑前盯着摄像头只要眨几次眼系统就频繁提示疲劳烦得要命。后来我给 PERCLOS 加了确认机制——必须连续两个统计窗口都超过阈值才真正进入疲劳状态。也就是说判定疲劳有延时确认的缓冲期。这样虽然会让报警慢一两秒但几乎杜绝了眨眼带来的误报。报警动作我分了三路一是 GUI 界面弹出黄色/红色警告框二是调用系统播放音频文件不同等级用不同频率和音量的提示音三是把触发报警前后的 10 秒视频帧序列保存成图片组方便事后查看。第三点非常重要毕设答辩和后续效果分析都靠这个留证功能。5. 驾驶行为检测的扩展玩法分心、打电话、抽烟识别5.1 从疲劳到分心扩展目标检测的物品种类做完疲劳检测之后系统底层的目标检测模型已经能很好地在驾驶位视角下定位人脸了。在此基础上扩展驾驶行为检测成本其实很低——只需要在训练数据里增加新的目标类别比如手机、香烟、水杯、方向盘、安全带。我实际给检测模型增加了一个手持物体检测分支专门识别手机、香烟、水瓶这三类物品。电话和抽烟是驾驶行为里最典型的分心行为也是交规里明确禁止的。识别逻辑用上了目标之间的关系如果检测到手机且手机框与人脸框的下半部分重叠就判定打电话如果香烟框出现在嘴部区域且保持静止就判定抽烟。这种基于空间关系的规则比端到端行为识别模型稳得多。端到端的视频行为分类模型需要大量连续标注的数据而且对机位变化、人物姿态变化非常敏感动不动就失效。而检测物体 判断相对位置的方式每个检测结果都是有据可查的出错了也容易调试。5.2 方向盘握持与视线方向的低配版实现除了物品检测我还用现有的人脸关键点做了两个低配版行为判断。一是方向盘握持通过手部关键点检测模型定位左右手的位置如果双手在画面中的位置都远离方向盘区域且持续 3 秒以上就判定为双手脱离方向盘。二是视线方向。这个稍微复杂一点我用人脸关键点里的眼睛关键点结合头部姿态角度做一个粗粒度的视线估计判断驾驶员是看着正前方还是往右下角看。粗粒度的好处是实现简单且足够用——我只区分正前方/左下方/右下方/低头四种状态不做精确的视线落点追踪。即便如此也已经能把长时间低头看手机和正常情况下偶尔瞟一眼中控台区分开。5.3 行为检测的边界条件和融合思路必须承认这种视觉检测方案有明确的能力边界。夜间无红外补光时摄像头画面噪点很大行为检测效果会大幅下降逆光环境下手机可能过曝成一团白光检测模型会失效还有一种常见情况是驾驶员拿手机凑近耳朵手机离摄像头很近画面上只剩一个模糊的大块目标检测模型反而识别不到。所以我把行为检测的结果作为疲劳报警的辅助信号而不是独立报警源。打个比方疲劳检测是主法官行为检测是参谋。当疲劳状态和分心行为同时出现时系统会把疲劳等级提示调得更高并给行为检测单独的一个提示图标但不会因为一次误判的手机检测就触发声光报警。这种融合策略在实际体验上稳很多也符合一个安全辅助系统的定位。6. 开源代码的组织方式目录结构、配置与界面设计6.1 代码模块划分让毕设代码一眼就能看懂既然决定开源代码组织就得讲究。评判标准很简单另一个人克隆仓库之后能不能在两个小时内搞清楚哪段代码负责检测、哪段代码负责分类、哪段代码负责报警、模型权重放哪里。我的目录结构大致是config/ # 配置文件 data/ # 数据集存放、数据加载器 models/ # 网络结构定义和权重 utils/ # 辅助工具图像增强、PERCLOS计算、日志 inference/ # 实时推理主流程 interface/ # GUI展示、报警逻辑 scripts/ # 训练脚本、评估脚本、视频转帧脚本 README.md # 项目说明 requirements.txt # 依赖清单最关键的几个设计决策是所有可调参数包括检测阈值、PERCLOS窗口长度、哈欠时间阈值、摄像头编号、报警音文件路径全部放在 config 的 YAML 文件里不散落在代码中。这样无论是你自己调参还是别人复现改一个文件就够了。训练用的数据加载器独立成模块支持文件夹即标签的格式新增类别不用改模型代码只要按目录放图片即可。6.2 配置文件与依赖别人克隆后能直接跑起来的保障依赖管理我用了一个 requirements.txt把框架、视觉库、GUI 库的版本都锁定了。这里有个教训毕设项目经常用了很多库的最新魔法特性但别人的机器上版本不同一跑就报错。我后来干脆把所有依赖的大版本号写清楚并且在 README 里给出一套验证过的组合注明以下组合在某主流平台上实测通过。配置文件里我还要特别留一个运行模式开关分成两种模式一种是摄像头模式接 USB 摄像头或内置摄像头实时检测另一种是视频回放模式读入一段录像文件按相同逻辑处理并输出检测结果视频。第二种模式对于没有摄像头环境的复现者特别友好也是我提交毕设效果视频的生产方式。6.3 可视化界面与留证不炫技但足够实用界面我用的非常简单——其实就是 OpenCV 的窗口加文字绘制不额外引入复杂的 GUI 框架。画面上实时显示几块信息最上方是视频帧帧上绘制人脸框和人脸关键点右侧或下方显示三个状态条左眼状态、右眼状态、嘴巴状态用颜色区分正常/异常再往下是疲劳评分进度条颜色从绿到黄再到红。看起来没那么炫但实用性很好。答辩的时候我可以用一台笔记本摄像头直接演示闭眼、打哈欠、低头三个动作轮流做现场观众能直观看到状态条变化和报警触发。另外每次触发重度和中度报警程序会把当前帧和前后各 5 帧保存到一个以时间戳命名的文件夹里这就是我前面说的留证功能也是论文里系统运行效果那一节的素材来源。7. 实测踩坑误报、漏检、卡顿三类问题的完整排查链路7.1 眨眼被当成疲劳时序平滑和窗口确认缺一不可系统第一次跑起来的时候最尴尬的表现是我盯着摄像头正常说话系统隔十几秒就警报一次。排查过程是这样的——我先看状态分类模型的输出发现眼睛闭合概率经常在眨眼瞬间冲到 0.8 以上这其实是正确的因为眨眼过程中眼睑确实会短暂覆盖眼球。问题出在决策层把单帧闭合概率超过阈值直接当成了关闭状态。我的修复分两层第一层是概率平滑对最近的 5 帧概率做中值滤波消除个别帧的抖动第二层是状态确认必须连续 3 帧判定为闭合才把该帧序列视为一次闭眼事件。这两层叠加之后眨眼引起的单次或双次闭合事件被自然过滤而真正持续闭眼超过半秒的疲劳状态能被稳定捕获。这个排坑过程让我体会到模型的输出只是证据决策层的滤波和确认机制才是系统的定海神针。7.2 眼镜、刘海、口罩带来的干扰用针对性样本而不是改算法测试时换上眼镜系统立刻就开始误报闭眼。原因是镜框边缘的深色轮廓和眼睑闭合后的曲线在灰度图上很相似分类模型被视觉上的相似性骗过了。我一开始尝试过很多图像预处理方法比如边缘检测、轮廓滤波效果都不理想后来还是靠数据侧解决专门采集和标注了大量戴眼镜的眼睛图像并且在数据增强里增加镜框阴影模拟。训练之后眼镜误报率下降了九成。刘海遮眼的情况更麻烦。如果刘海把眼睛几乎整个挡住了眼前的两个方案都没有用。我的处理是加了一个脸部遮挡检测逻辑当检测到人脸框内眼睛区域的关键点置信度过低时判定为眼睛不可见此时疲劳统计暂停而不是把不可见当成闭合。这个逻辑很关键因为把不可见当闭合会导致长时间误报而把不可见当睁开又会漏掉真正闭眼的场景暂停统计反而是最稳妥的中间状态。7.3 白天到夜晚的跨度光照归一化比换模型更见效模型在白天室内测试效果很好一到傍晚自然光下就开始乱跳。排查发现眼部区域图像在不同光照下差异极大人眼看起来还是那么回事但模型看到的是完全不同的像素分布。我的解决思路是让输入图片更像标准状态对裁剪出的小图先做直方图均衡化再按固定的均值和方差做归一化最后才送入分类网络。这一步操作简单改善却非常明显。夜间场景我加了两种应对手段。一是降低检测置信度阈值因为夜间画面普遍模糊模型输出的置信度整体偏低阈值卡得太高会直接漏检人脸二是提醒在实际部署时要配红外补光灯视觉方案在完全无光的环境下是有限度的。这也算是坦诚地承认方案的边界而不是在论文里掩盖问题。7.4 推理性能优化量化、裁剪与跟踪的三板斧性能优化的排查最初让我很头痛全流程检测加分类在 CPU 上只有 8 帧每秒根本没法看。我的优化按顺序做了三件事。第一件事是模型量化把三个模型的权重都从浮点数压到 8-bit 整数。量化后精度损失不到 1%推理速度提升了近一倍。第二件事是把输入尺寸进一步优化眼部小图的尺寸从 96×96 降到 64×64速度进一步提升精度损失可以忽略。第三件事就是前面提到的检测跟踪机制人脸框连续帧内复用只在需要的时候补测。三件事叠完之后CPU 推理稳定在 25 FPS 左右已经完全满足实时性要求。如果你用的是带 GPU 的机器还能再快一些但这也暴露了一个事实模型的轻量化设计比硬件堆料重要得多。最后再分享一个小经验。做这类视觉检测毕设千万别只盯着准确率跑一定要把模型放到真实场景里连续跑上半小时观察它在正常对话、打哈欠、揉眼睛、戴墨镜、侧脸、逆光这一堆实际情况下的表现。很多问题比如眨眼误报、刘海遮挡、逆光过曝只在长时间连续运行里才会暴露出来而这些恰恰是答辩时评委最可能现场给你做的压力测试。我的建议是项目临近收尾时留出至少一周时间专门做真实场景实测把所有异常表现记成表格再逐条分析是数据问题、模型问题还是决策层问题这个过程绝对比你多跑 20 个 epoch 有价值得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。