手把手教你基于CNN的疲劳检测实战:从数据到实时推理 简介基于CNN的疲劳驾驶检测项目面向Python开发者与计算机视觉入门者解决行车场景中眨眼与打哈欠状态的自动识别问题。项目以卷积神经网络为核心结合openCV完成面部、眼睛及嘴部区域的定位与特征提取覆盖数据预处理、模型训练与实时检测完整链路。压缩包内共15个文件含6个Python脚本负责不同检测与预处理环节、3个pickle格式的已训练模型参数、图片示例及说明文档整体约3.94MB结构轻量适合快速部署实验。目前已有3765人学习下载。通过阅读源码可掌握CNN在图像分类中的实际调用方式、眨眼与打哈欠的判定逻辑以及基于Haar级联和pickle模型的状态识别流程便于在此基础上扩展实时告警或接入车载摄像头系统。 新手友好手把手教你搭一版基于 CNN 的疲劳检测实战项目如果你最近也在研究“用深度学习做疲劳检测”这件事想找一个能跑通、能改、能讲的 Python 源码工程那这篇总结应该能让你少走不少弯路。我前阵子正好基于 CNN卷积神经网络做了一版疲劳检测的完整流程从数据准备、模型训练到实时摄像头推理全部用 Python 实现。这篇就把整个项目的核心思路、关键代码、踩坑记录一次讲清楚适合刚入门深度学习的同学也适合想做课程设计或毕设方向参考的朋友。这类项目的本质就是把“人是否疲劳”这件事转化成一个可计算的视觉问题。最常见的做法是先定位人脸再从人脸区域中提取眼睛和嘴巴的状态最后用一个 CNN 模型判断当前帧的眼睛是睁开还是闭合、嘴巴是否在大幅度张开打哈欠再结合连续帧的统计结果得出疲劳得分。整个链路清晰数据也好获取非常适合作为图像分类项目的练手场景。1. 项目整体设计与思路拆解1.1 核心需求解析疲劳检测的应用场景很直接驾驶员驾驶途中打瞌睡、长时间盯着屏幕的办公人员、需要保持注意力集中的监控岗位等。早期方案主要靠传统图像处理比如计算眼睛纵横比EAR、嘴部纵横比MAR再用阈值判断。这种方式简单、速度快但缺点也明显——对光照变化敏感、对角度和遮挡的鲁棒性差不同人脸的形态差异也容易造成误判。引入 CNN 之后检测任务变成“图像分类”问题给模型一张眼睛区域的图像让它输出“睁开”或“闭合”的概率给一张嘴巴区域图像让它输出“正常”或“打哈欠”的概率。模型通过学习大量标注样本中的纹理、边缘、形状特征泛化能力比手工特征强不少。也就是说整个项目的核心技术点包含三块人脸检测与人脸关键点定位从画面中找到脸并定位眼睛、嘴巴所在的位置。局部区域图像提取把眼睛、嘴巴从人脸区域裁剪出来统一尺寸后作为 CNN 的输入。分类模型训练与推理用 CNN 对局部区域进行分类结合时序统计输出疲劳状态。1.2 为什么选择 CNN 而不是传统方法或更复杂的模型从工程角度讲CNN 是“性价比”最高的选择。传统图像处理虽然不依赖训练数据但算法设计非常依赖经验换个环境可能就得重新调参。而像 Transformer 这类模型虽然近年也火但训练所需的数据量、显存资源和调参成本远高于小型 CNN。我用的 CNN 结构参考了 MobileNet 的轻量思想不直接堆大模型。原因很现实疲劳检测经常要跑在嵌入式设备或普通笔记本摄像头上推理速度必须足够快。一个几兆大小的模型单帧推理时间控制在 10ms 级比动辄上百兆的大网络实用得多。1.3 技术方案选型关于开发环境我直接用的是 Python 3.8 TensorFlow 2.x配合 OpenCV 做人脸检测和图像预处理。人脸关键点检测用的 dlib 的 68 点模型这个模型很经典定位眼睛和嘴巴的坐标点足够用。把每个部分拆开来看任务边界很清楚。技术栈如下模块工具/库作用开发语言Python 3.8整体流程控制人脸检测与关键点dlib OpenCV定位人脸及眼睛、嘴巴区域数据增强与预处理OpenCV、NumPy、imutils图像裁剪、缩放、归一化等模型搭建与训练TensorFlow 2.x / Keras构建 CNN 分类模型实时推理OpenCV VideoCapture摄像头视频流读取2. 数据准备与预处理这一步决定了模型的上限2.1 数据集怎么来疲劳检测模型需要两类数据睁眼/闭眼、打哈欠/正常。公开数据集有不少比如 Closed Eyes in the Wild (CEW)、YawDD但如果你需要特定场景的数据或者想要标注格式统一自己采集 扩充会更好控制。我自己采集时用的是笔记本摄像头连续录制不同人的脸部视频。操作步骤大致是录制时确保光线均匀尽量覆盖不同的角度和距离。提取视频帧确保每一帧都能检测到人脸如果检测不到就跳过。通过 dlib 的 68 点关键点模型提取眼睛和嘴巴所在的矩形区域。手动筛选和重命名图片。一个容易踩的坑是数据量不够。二分类模型每类至少准备 2000 张以上如果只有几百张模型非常容易过拟合。为了扩充数据我用了几种常规的图像增强方式水平翻转、小幅旋转、调整亮度对比度、添加高斯噪声。2.2 眼睛和嘴巴区域怎么提取dlib 的 68 点模型把关键点分布对应得非常清晰左眼是点 36 到 41右眼是点 42 到 47嘴巴外轮廓是点 48 到 59。每次拿到一帧图像我先检测人脸框再计算这些关键点的坐标然后向外扩展一个固定边距截取局部图像。这块有一个细节特别影响后面模型效果扩展边距的尺寸不要太大也不能太小。太大会带进过多额头、鼻子、脸颊的背景信息干扰分类太小会直接裁掉部分眼睛轮廓导致特征不完整。我自己测试下来在 48x48 的输入尺寸下眼睛区域扩展 8 到 10 个像素比较合适。做数据预处理时还有一个顺序问题。一定要先统一输入尺寸再做归一化。不要在一个 batch 里混入不同尺寸的图像否则训练时会直接报 shape 不匹配的错误。def extract_eye_mouth(img_path): detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: landmarks predictor(gray, face) # 左右眼坐标点 left_eye [landmarks.part(i).x for i in range(36, 42)] ... # 裁剪、归一化、缩放2.3 标注和文件组织数据准备好之后我记得最好用的组织方式是“一个文件夹一个类别”。比如data/train/closed_eye、data/train/open_eye、data/train/yawn、data/train/no_yawn配合ImageDataGenerator或者tf.keras.preprocessing.image_dataset_from_directory加载数据完全不用手动写数据加载器。文件名可以带上标签前缀也可以直接靠目录区分。我自己习惯在目录层面区分然后在训练时用validation_split0.2自动划分验证集。这里提醒一句ImageDataGenerator的增强参数不要开太大尤其是brightness_range调过头会让模型学到错误的颜色映射关系。3. CNN 模型搭建与训练3.1 轻量 CNN 结构设计针对眼睛和嘴巴这种小尺寸图像不需要很深很宽的网络。我用了一个四层卷积 全连接的结构经过几次对比效果和速度都比较均衡。你完全可以在这个基础上加深或者加宽但一定要先跑通小模型再考虑扩容。一个典型的示例结构如下model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(48, 48, 3)), MaxPooling2D(pool_size(2, 2)), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(pool_size(2, 2)), Conv2D(128, (3, 3), activationrelu), MaxPooling2D(pool_size(2, 2)), Flatten(), Dropout(0.5), Dense(128, activationrelu), Dense(NUM_CLASSES, activationsoftmax) ])这个结构里Dropout(0.5)是关键。疲劳检测数据集通常不大全连接层一旦参数过多训练集准确率能冲到 99%验证集却迟迟上不去。中间加一层 Dropout 基本能解决这种典型的过拟合问题。如果你发现训练集和验证集差距还是很大把 Dropout 调到 0.6 再试。另一个经验之谈batch size 不宜太大16 或者 32 就足够了。因为局部图像尺寸小大 batch 反而容易让梯度方向震荡太大收敛不稳定。3.2 训练关键参数我用的是Adam优化器初始学习率 0.001。相比 SGDAdam 对超参不敏感很适合这类中小规模数据集。损失函数当然是交叉熵配合softmax输出。训练轮次方面不用一上来就设 100 轮。我习惯设置 50 轮同时加入EarlyStopping监控验证集准确率连续 5 轮不提升就自动停止。这里建议配合ReduceLROnPlateau当验证集 loss 不再下降时自动把学习率降为原来的 0.2往往能让模型在最后阶段再涨几个点。checkpoint ModelCheckpoint(best_model.h5, monitorval_accuracy, verbose1, save_best_onlyTrue, modemax) early_stop EarlyStopping(monitorval_accuracy, patience5, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.2, patience3, min_lr1e-6)3.3 训练效果与评估我在自己的数据集上大概用了 8000 张眼睛图像和 6000 张嘴巴图像训练 45 轮后验证集准确率稳定在 96% 左右。眼睛“闭合”类别的召回率会略低一些原因之一是很多闭眼样本是在眨眼瞬间抓到的人眼尚未完全闭合特征比较模糊。经验上如果对实时视频流做检测“漏报”比“误报”更危险。漏报意味着人已经瞌睡了但系统没有告警。所以我会在训练时给正样本闭眼 / 打哈欠稍微提高 loss 权重或者在数据增强时对正样本多做一些随机裁剪让模型更“敏感”。4. 实时检测流程与工程化细节4.1 完整检测流程模型训练好之后实时检测就变成了一个多步骤的流水线。大致如下读取摄像头帧图像。执行人脸检测。对每个人脸提取 68 个关键点计算眼睛和嘴巴区域。分别送入眼睛模型和嘴巴模型得到状态标签。维护一个队列记录最近 N 帧的眼睛和嘴巴状态。根据 PERCLOS眼睛闭合时间占比和打哈欠频率综合判断疲劳状态。4.2 PERCLOS 判断逻辑PERCLOS 是疲劳检测里非常经典的指标简单来说就是单位时间内眼睛闭合帧数占总帧数的比例。工程实现上不复杂在一段时间窗口内统计闭合帧数除以窗口总帧数超过阈值就判定为疲劳。if eye_status closed: closed_frames 1 total_frames 1 perclos closed_frames / total_frames if perclos 0.4: # 经验阈值 alarm(疲劳驾驶提醒)我实际调试中发现阈值设为 0.4 比较合适。太高了告警迟钝太低了正常眨眼都会被误判。为了减少偶发误报我还会加一个“连续 3 秒”的判定条件只有持续疲劳才触发提醒。嘴巴的哈欠检测同理如果 1 分钟内有多次长时间张嘴事件也加入疲劳评分。4.3 实时性能优化用 dlib 做人脸检测是整套流程中最耗时的部分。在普通 CPU 上单帧 640x480 图像大概需要 80 到 150ms。为了提升速度两个方向是有效的降低检测分辨率。把人脸检测的图像缩放为原来的一半甚至三分之一关键点定位的坐标再按比例还原到原图。跳帧处理。并非每一帧都需要做完整的人脸检测可以每隔 2 到 3 帧做一次关键点定位中间帧沿用上一次的结果。如果你用的是 GPU 版本的 TensorFlowCNN 推理速度会非常快瓶颈依然在人脸检测环节。因此在实际部署时可以考虑用 OpenCV 的 DNN 人脸检测器替换 dlib虽然精度略微下降但 CPU 推理速度可以提升十倍以上。5. 常见问题与排查技巧实录5.1 模型不收敛loss 一直震荡遇到这类问题首先检查输入数据是否归一化。图像像素值没有缩放到 [0,1] 或者 [-1,1]卷积网络非常容易陷入振荡。其次检查标签是否错位尤其是手动整理数据时文件名和标签容易搞反。最后降低学习率再试0.001 通常没问题但有些数据集 0.0001 会更稳。5.2 训练集表现好验证集差不用怀疑这就是过拟合。解决办法按优先级排序增加数据量、增加数据增强强度、提高 Dropout、减少全连接层神经元个数。我在做嘴巴分类时就遇到过类似问题后来发现是采集的数据里打哈欠的样本大多是侧脸正脸样本太少。补充了一批正脸打哈欠数据后验证集准确率一下就提上来了。数据均衡性比数据量更重要这是我反复踩坑后的切身体会。5.3 实时检测时画面卡顿不要同时跑多个高分辨率模型。常见做法是眼睛和嘴巴模型在推理时复用同一个会话不要每帧重新加载模型。代码里也应该避免在循环内做模型加载、文件读写这类耗时操作。第一次加载模型后放在全局变量里持续复用。5.4 新环境下检测效果变差换了个环境、换了个摄像头效果明显下降大概率是图像分布变了。最有效的办法不是盲目改模型而是做“数据域适配”。举个例子把新环境录制的少量视频帧加入原数据集重新微调模型。不需要太多几百张就能显著改善。如果不想重新训练也可以考虑在图像预处理阶段加入自适应直方图均衡化CLAHE减少光照差异的影响。这个改动很轻量但经常能带来肉眼可见的效果提升。5.5 常见问题速查表问题可能原因排查思路loss 不下降学习率过大、数据未归一化降低学习率检查预处理流程验证集准确率低数据不均衡、增强不够检查每类样本数量增加增强强度检测画面卡顿人脸检测耗时过大降低检测分辨率跳帧处理新环境下误报多场景分布差异大补充少量新环境数据微调模型打哈欠检测不灵敏嘴巴区域裁剪不准确检查关键点坐标扩展裁剪边距最后分享一点我的体会疲劳检测这个项目表面看是一个图像分类任务但真正让它“可用”的往往是工程侧的细节区域裁剪是否精准、阈值设定是否合理、判断逻辑是否抗抖动。模型准确率只是其中一环。我自己的习惯是先把完整流程跑通再逐步优化不建议一上来就追求大模型高精度。这个方向后面还能加很多扩展比如用 LSTM 或 Transformer 对时序建模判断连续动作趋势也可以把模型转换成 TensorFlow Lite 或 ONNX部署到边缘设备上。如果你也正在做类似项目希望这篇笔记能帮你节省一些摸索的时间。本文还有配套的精品资源点击获取