基于CNN的驾驶员疲劳检测与预警系统:从模型到部署
简介这份资源是面向高校计算机相关专业学生的Python毕业设计完整项目主题为基于卷积神经网络的人脸识别驾驶员疲劳检测与预警系统适合用作毕业设计、期末大作业或课程设计也适合想入门深度学习与计算机视觉实战的初学者。压缩包共37个文件约500.41MB包含16个py源码文件、9个pyc编译文件、3个pth模型权重、5张jpg效果图以及txt说明与log日志等覆盖SSD网络结构、VGG主干、损失函数、数据增强、摄像头与视频检测等模块并附带数据集与训练权重下载后可直接运行调试。项目已获导师指导并通过代码完整、结构清晰读者可据此掌握人脸检测、疲劳状态判别与预警逻辑的完整实现流程理解模型训练、评估与推理各环节的衔接方式并参考效果图与日志快速定位问题。目前已有905人学习适合需要一份可落地高分项目参考的读者。1. 从一张打哈欠的截图说起这套疲劳检测系统到底在做什么凌晨两点跑长途的司机眼皮开始打架方向盘轻微跑偏等反应过来已经压线——这类事故的根因不是技术不行而是疲劳没有在临界点之前被拦住。基于卷积神经网络的人脸识别驾驶员疲劳检测与预警系统要解决的就是这件事用摄像头抓人脸用 CNN 判断眼睛睁闭和嘴巴张合再换算成疲劳指标超阈值就报警。它适合做 Python 毕业设计的同学也适合想入门计算机视觉落地的工程师。整套链路的核心不是识别出这是谁而是识别出这个人现在困不困所以人脸识别在这里更多是定位与对齐真正的判断交给卷积神经网络。热词里常出现的 opencv人脸识别、cnn卷积神经网络代码、python下载cv2基本都绕不开这条主线。下面我按自己搭过的一版方案把选型、代码、参数和踩过的坑讲清楚。2. 为什么用 CNN 而不是传统特征疲劳检测的技术选型2.1 眼睛和嘴巴的状态判断传统方法为什么容易翻车早期做法是用 Haar 级联定位人脸再在眼睛区域算像素灰度均值或者用 EAREye Aspect Ratio眼睛纵横比判断睁闭。这套方法在实验室灯光下能用一到真实驾驶舱就崩逆光时眼睛区域一片死黑戴眼镜时镜片反光侧脸时关键点漂移。我最早那版就是纯 EAR白天准确率还行傍晚迎着夕阳直接误报率飙到三成以上属于典型的玄学现场。卷积神经网络的价值在于它不依赖手工设计的阈值。它从像素里自己学半睁眼眯眼正常睁眼的纹理差异对光照和角度的鲁棒性明显更好。代价是需要标注数据、需要训练、需要算力但对毕业设计这个体量用迁移学习完全扛得住。2.2 人脸识别与疲劳检测的分工别把两件事混成一件很多人一上来就想训一个疲劳/清醒二分类网络输入整张图。这样做的坑是背景、座椅、方向盘全被卷进特征模型学到的可能是这个人穿蓝衣服所以清醒。正确分工是两段式第一段人脸检测与对齐把脸抠出来、摆正去掉背景干扰。常用 MTCNN 或 OpenCV 的 DNN 人脸检测器。第二段在裁剪后的人脸或眼睛、嘴巴子区域上跑 CNN 分类输出睁闭眼、张闭嘴的概率。这样每一段都简单、可调试、可替换。人脸识别在这里的作用是把人脸框稳定住不是做身份识别别被名字带偏。2.3 数据集与标签怎么定三类状态比二分类更实用只分疲劳/清醒太粗。我一般会标三类正常、疑似疲劳打哈欠或频繁眨眼、疲劳闭眼持续超过阈值。数据来源可以是公开的驾驶员疲劳数据集也可以自己用手机录几十段视频抽帧。每类至少几百张注意要覆盖戴眼镜、夜间、侧脸几种情况否则模型上线就露馅。提示抽帧时同一段视频的相邻帧高度相似训练集和验证集要按视频划分不能按帧随机划分否则验证准确率虚高这是最常见的翻车点之一。2.4 最小可跑通的环境搭建先把环境弄干净别在系统 Python 里乱装。用 conda 建虚拟环境装这几个核心包# 创建并激活虚拟环境 conda create -n fatigue python3.9 -y conda activate fatigue # 核心依赖opencv 做人脸检测torch 跑 CNNnumpy 做数值 pip install opencv-python numpy torch torchvision pillow -i https://pypi.tuna.tsinghua.edu.cn/simple逻辑说明opencv-python提供cv2负责读摄像头和做人脸检测torch/torchvision负责 CNN 的构建与推理-i指定国内源避免下载超时。参数上 Python 选 3.9 是因为 torch 各版本对 3.9 支持最稳别追最新版。装完用python -c import cv2, torch; print(cv2.__version__, torch.__version__)验证能打印版本号就说明环境通了。这一步对应热词里的 vscode配置python、pycharm配置python环境IDE 用哪个都行关键是解释器指向这个 conda 环境。3. 把 CNN 模型搭起来并训练从网络结构到训练脚本3.1 一个够用的轻量 CNN 结构毕业设计不需要 ResNet 那么重自己搭一个 4 层卷积的小网络就够推理快、好解释。结构是卷积-池化-卷积-池化-卷积-池化-全连接-输出。热词里提到的 lenet5卷积神经网络 就是这个思路的经典版本我在此基础上加了 BatchNorm 和 Dropout 提升稳定性。import torch import torch.nn as nn class FatigueCNN(nn.Module): def __init__(self, num_classes3): super().__init__() # 输入假设为 64x64 灰度图通道数 1 self.features nn.Sequential( nn.Conv2d(1, 32, 3, padding1), # 卷积层1提取边缘纹理 nn.BatchNorm2d(32), # 归一化加速收敛 nn.ReLU(), nn.MaxPool2d(2), # 64-32 nn.Conv2d(32, 64, 3, padding1), # 卷积层2提取局部形状 nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), # 32-16 nn.Conv2d(64, 128, 3, padding1), # 卷积层3提取眼睛嘴巴语义 nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2), # 16-8 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 8 * 8, 128), nn.ReLU(), nn.Dropout(0.5), # 防过拟合 nn.Linear(128, num_classes) # 输出3类 ) def forward(self, x): return self.classifier(self.features(x))逻辑说明features负责逐层提取特征通道数从 32 到 64 到 128 递增空间尺寸从 64 逐次降到 8classifier把特征图拉平后接全连接。参数上padding1保证卷积不缩尺寸MaxPool2d(2)每次减半Dropout(0.5)是防过拟合的关键。num_classes3对应正常、疑似疲劳、疲劳三类如果你只做二分类改成 2 即可。输入统一成 64x64 灰度是为了降低计算量实测对睁闭眼判断精度损失很小。3.2 数据加载与训练循环数据按文件夹分好类用ImageFolder直接读省得自己写 Dataset。from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集做增强验证集只做归一化 train_tf transforms.Compose([ transforms.Grayscale(), # 转灰度 transforms.Resize((64, 64)), # 统一尺寸 transforms.RandomHorizontalFlip(), # 随机翻转增强泛化 transforms.RandomRotation(10), # 小角度旋转模拟头部晃动 transforms.ToTensor(), transforms.Normalize([0.5], [0.5]) # 单通道归一化 ]) val_tf transforms.Compose([ transforms.Grayscale(), transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize([0.5], [0.5]) ]) train_ds datasets.ImageFolder(data/train, transformtrain_tf) val_ds datasets.ImageFolder(data/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) val_loader DataLoader(val_ds, batch_size32, shuffleFalse)逻辑说明训练集加RandomHorizontalFlip和RandomRotation是为了让模型见过更多姿态验证集绝不能加增强否则评估失真。Normalize([0.5],[0.5])把像素从 [0,1] 映射到 [-0.5,0.5]是单通道的常用做法。batch_size32是显存和收敛速度的折中显存小就降到 16。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model FatigueCNN(num_classes3).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() out model(imgs) loss criterion(out, labels) loss.backward() optimizer.step() # 每个 epoch 后在验证集上评估 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})逻辑说明Adam学习率1e-3是分类任务的稳妥起点30 个 epoch 对这个小网络足够。model.train()和model.eval()的切换不能省因为 Dropout 和 BatchNorm 在两种模式下行为不同忘了切会导致验证结果乱跳。训练完把权重存下来torch.save(model.state_dict(), fatigue_cnn.pth)。3.3 关键参数怎么调三个必看指标参数建议值调整方向学习率 lr1e-3验证 loss 震荡就降到 1e-4batch_size32显存不足降到 16 或 8Dropout0.5训练准确率高但验证低就加大判断过拟合看训练准确率和验证准确率的差距差距超过 10 个百分点就说明模型在背训练集加大 Dropout 或补数据。判断欠拟合看两个都低那就加层数或加 epoch。4. 实时推理与预警逻辑把模型接到摄像头上4.1 摄像头读取与人脸裁剪训练好的模型要跑在实时视频流上流程是读帧 → 检测人脸 → 裁剪 → 送模型 → 拿分类结果。import cv2 import numpy as np import torch # 加载人脸检测器OpenCV 自带 DNN 模型 face_net cv2.dnn.readNetFromCaffe( deploy.prototxt, res10_300x300_ssd_iter_140000.caffemodel) model FatigueCNN(num_classes3) model.load_state_dict(torch.load(fatigue_cnn.pth, map_locationcpu)) model.eval() cap cv2.VideoCapture(0) # 0 表示默认摄像头 while True: ret, frame cap.read() if not ret: break h, w frame.shape[:2] # 构造 blob 送人脸检测网络 blob cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104, 177, 123)) face_net.setInput(blob) detections face_net.forward() for i in range(detections.shape[2]): conf detections[0, 0, i, 2] if conf 0.5: # 置信度阈值低于 0.5 丢弃 continue box detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 box.astype(int) face frame[y1:y2, x1:x2] if face.size 0: continue # 预处理成模型输入 gray cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, (64, 64)) tensor torch.tensor(gray, dtypetorch.float32).unsqueeze(0).unsqueeze(0) / 255.0 tensor (tensor - 0.5) / 0.5 with torch.no_grad(): pred model(tensor).argmax(1).item() cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, [normal, drowsy, fatigue][pred], (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(fatigue, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明blobFromImage的均值(104,177,123)是 OpenCV 人脸检测模型的固定参数不能改。置信度阈值0.5是漏检和误检的平衡点光线差可以降到 0.4。预处理必须和训练时一致灰度、64x64、除以 255 再减 0.5 除 0.5任何一步不一致推理结果就是错的这是血泪经验。4.2 疲劳判定不能只看单帧加时间窗口单帧判断会抖闭眼一帧就报警太敏感。正确做法是维护一个滑动窗口统计最近 N 帧里疲劳类占比。from collections import deque window deque(maxlen30) # 最近30帧 fatigue_count 0 # 在每帧拿到 pred 后 window.append(pred) if len(window) 30: fatigue_ratio sum(1 for p in window if p 2) / 30 if fatigue_ratio 0.6: # 60% 以上判为疲劳 trigger_alarm()逻辑说明maxlen30对应约 1 秒按 30fpsfatigue_ratio 0.6是报警阈值。这两个参数要按实际帧率调帧率低就减小窗口。报警可以是播放声音、弹窗或串口触发硬件毕业设计里用playsound播一段提示音最简单。4.3 预警系统的三种触发方式声音报警playsound(alarm.wav)最直接。界面弹窗用 Tkinter 或 PyQt 弹红色警告框。硬件联动通过串口给单片机发指令点亮 LED 或震动马达对应热词里的基于stm32的人脸识别门禁系统设计思路。选哪种看你的硬件条件纯软件方案用前两种就够答辩时演示效果好。5. 避坑与排查这套系统最容易翻车的五个地方5.1 摄像头能开但画面全黑现象cap.read()返回 True但 frame 全黑。原因摄像头被其他程序占用或索引不对。解决关掉其他调用摄像头的软件把VideoCapture(0)换成VideoCapture(1)试Linux 下用ls /dev/video*确认设备号。5.2 训练准确率 99% 但实际用起来乱报现象验证集准确率很高接摄像头却频繁误判。原因训练数据太干净和真实光照差异大或者验证集按帧划分导致泄漏。解决补录真实场景数据验证集按视频划分训练时加亮度、对比度增强。5.3 推理速度慢画面卡成幻灯片现象每帧处理超过 100ms视频不流畅。原因模型在 CPU 上跑或每帧都重新加载模型。解决模型只加载一次放在循环外有 GPU 就.to(cuda)把输入从 64x64 降到 48x48人脸检测可以隔帧做不必每帧都检测。5.4 戴眼镜就判成闭眼现象不戴眼镜正常一戴眼镜就报疲劳。原因训练集里戴眼镜样本太少镜片反光被当成闭眼特征。解决专门补戴眼镜的数据训练时加随机亮度扰动模拟反光必要时在检测前做一次直方图均衡化。5.5 报警太频繁或从不报警现象要么一直响要么困了也不响。原因滑动窗口长度和比例阈值没调好。解决先打印每帧的 pred 和 fatigue_ratio观察真实数据分布再定阈值。别拍脑袋设 0.5我一般从 0.6 起调配合 30 帧窗口。6. 让这套系统更抗造几个能加分的进阶技巧把基础版跑通之后想拿高分或者真正能用可以在这几个方向上加码。第一是多特征融合现在只用眼睛嘴巴可以再加头部姿态——低头、点头频率也是疲劳信号用cv2.solvePnP估个粗略的头部角度和 CNN 结果加权。第二是模型轻量化把训练好的网络用torch.quantization做动态量化模型体积能压到原来的四分之一CPU 推理速度翻倍嵌入式部署时特别有用对应热词里基于树莓派的人脸识别那类场景。第三是数据增强的针对性设计与其无脑翻转旋转不如模拟真实干扰——随机加高斯噪声模拟夜间噪点随机调 gamma 模拟隧道进出光变化随机遮挡一小块模拟手挡脸。这些增强比通用增强更能提升实际鲁棒性。第四是预警分级别只有报警/不报警两档可以设三级——轻度疲劳弹提示、中度播声音、重度持续报警并记录时间戳答辩时这个分级逻辑很能体现工程思维。验证方法上别只看准确率。做一个混淆矩阵重点看疲劳类被误判成正常的比例这个漏报比误报危险得多。再录几段真实驾驶或模拟驾驶视频统计从真正疲劳到系统报警的延迟控制在 2 秒内算合格。我自己踩过最深的坑是一开始追求模型精度把网络堆到十几层结果树莓派上跑不动答辩现场卡成 PPT。后来砍回四层卷积加量化精度只掉了一个点速度却上来了。能跑起来的简单模型永远比跑不动的复杂模型值钱这个习惯我保持到现在。希望帮到你。本文还有配套的精品资源点击获取