基于CNN的人脸识别疲劳驾驶检测与预警系统实战解析
2026/9/23 18:22:45 网站建设 项目流程

简介:一份面向计算机类专业毕业设计的高分项目资料,围绕基于卷积神经网络的人脸识别驾驶员疲劳检测与预警系统展开,适合正在准备毕设、课程设计或期末大作业的学生,以及希望练习深度学习实战的学习者。资源提供完整的Python源码与配套数据集,可直接运行调试。压缩包共19个文件,涵盖11个Python源码、模型权重文件(hdf5)、OpenCV人脸检测所需的XML配置、依赖说明txt、可执行程序exe及README文档等,整体78.33MB,目录结构清晰,便于快速定位核心代码、模型与数据预处理脚本。已有244人学习下载。项目内置mini_XCEPTION模型权重、人脸提取与数据加载脚本、tkinter界面程序等,有助于理解从人脸检测、疲劳状态判别到预警展示的完整流程,兼具工程落地与教学参考价值。

1. 疲劳驾驶预警系统:卷积神经网络在这道毕设题里的真实分量

凌晨三点的高速上,驾驶员眼皮闭合超过两秒,车辆还在以百公里时速前进——疲劳检测系统的价值就在这一瞬间。基于卷积神经网络的人脸识别驾驶员疲劳检测与预警系统,是 Python 毕业设计里长期热门的题目:它把 CNN 图像分类、OpenCV 人脸检测、实时视频流处理和预警机制串成一条完整链路,既有学术上的模型训练内容,又有工程上的部署调试内容。这篇笔记面向两类人:正在选毕业设计题目、想确认这个方向能不能撑起一篇论文的在校生,以及想把疲劳检测接进车载或者工位监控场景的开发者。我会把数据怎么准备、模型怎么选、参数怎么调、现场怎么踩坑讲清楚。

2. 从传感器选型到系统架构:疲劳检测方案为什么走到 CNN 这条路上

2.1 三种主流技术路线对比:生理信号、驾驶行为、视觉特征

疲劳检测不是只有看脸这一条路。做方案选型之前,先把手头的选项盘一遍,避免开题答辩时被问到“为什么不做脑电”而答不上来。目前工程上形成过三条路线。

基于生理信号的方案采集脑电(EEG)、心电(ECG)或肌电信号,通过电极贴片接触人体。优点是生理指标和疲劳的相关性有大量医学研究支撑,准确率上限最高;缺点是电极佩戴麻烦,长时间驾驶不现实,一套多导联设备的价格也远超毕设预算。这类方案在实验室做机理研究可以,落地到驾驶舱基本没有可能。

基于驾驶行为的方案通过方向盘转角、车道偏移量、油门刹车节奏来反推驾驶员状态。传感器成本低,装在车上不打扰人,但问题在于行为变化是疲劳的结果而非原因,存在明显的滞后性——等方向盘开始蛇形摆动,危险往往已经发生。而且不同驾驶员的行为基线差异很大,阈值很难标定。

基于视觉特征的方案用摄像头采集驾驶员面部图像,通过眼睛开合程度、嘴巴张合状态、头部姿态来判断疲劳。非接触、硬件成本低(一个普通 USB 摄像头就行),响应及时,是目前商用驾驶监控系统的主流选择。它的问题集中在光照鲁棒性和个体差异上,而这恰恰是后面卷积神经网络要解决的核心矛盾。

技术路线传感器优点主要缺点部署成本
生理信号EEG / ECG 电极精度上限高、机理清晰佩戴不适、设备贵
驾驶行为方向盘角度、车道相机无侵入、成本低滞后明显、个体差异大
视觉特征驾驶舱摄像头无接触、响应快受光照和遮挡影响

三种路线对比下来,视觉方案是毕业设计性价比最高的选择:数据好采集、模型可视化效果好、答辩时能现场演示。后面的内容都围绕第三条路线展开。

2.2 传统图像处理能检测疲劳吗:EAR 阈值方案的边界

很多人第一反应是用 OpenCV 的人脸关键点检测算眼睛纵横比(EAR)来判断闭眼,不训练任何模型。这个方案确实能跑,而且代码量极小,但它在真实场景里扛不住。

EAR 的原理是取眼睛周围六个关键点,计算纵向距离与横向距离的比值。睁眼时 EAR 稳定在 0.25 到 0.3 之间,闭眼时迅速降到 0.15 以下。配合 PERCLOS 指标(单位时间内闭眼帧占比,阈值通常取 0.4),能实现一个朴素的疲劳判定。问题出在三个地方。

第一,EAR 的绝对值因人而异。单眼皮和双眼皮、眼睛大和眼睛小,睁开程度对应的 EAR 基准值差很多,一个全局阈值没法覆盖所有驾驶员。第二,关键点检测在戴墨镜、逆光、侧脸角度大时定位会漂,关键点一飘 EAR 就剧烈抖动,误报率居高不下。第三,打哈欠和说话在嘴部特征上高度相似,单靠几何距离很难把两者分开。

卷积神经网络解决的是特征表达问题。CNN 不需要人手工定义“闭眼是什么样”,而是让卷积核在大量标注数据上自行学习眼睛、嘴巴在不同状态下的纹理模式。训练好的模型对个体差异和光照变化有更好的容错性——这也是这个方向值得作为毕业设计深挖的关键点。方案演进路径很清晰:先跑通传统方法建立基线,再对比 CNN 方法的优势,论文的对比实验就有了。

2.3 系统整体架构与数据流:从摄像头到预警输出的完整闭环

整个系统按数据流划分可以拆成五个模块,每一块都有独立的调试空间。摄像头采集模块负责读取视频帧,处理分辨率、帧率和曝光;人脸检测模块在每一帧里定位人脸位置并裁剪出 ROI;预处理模块对裁剪出的人脸做缩放、归一化;CNN 推理模块输出疲劳概率;预警模块对连续多帧的判定结果做平滑处理,决定是否触发声光报警。

架构上要注意的细节是推理频率。实时视频流每秒 30 帧,如果把每一帧都送进 CNN 推理,对 CPU 来说压力偏大,对 GPU 来说又是浪费。常见的做法是设置跳帧策略:每秒只对其中 5 到 10 帧做人脸检测和疲劳分类,中间未推理的帧沿用最近一次的判定结果。这个策略在保证实时性的同时把计算负载降一个量级,后面第 6 章会给出具体实现。

数据流的方向是单向的:摄像头产生帧,帧经过人脸检测变成人脸 ROI,ROI 进 CNN 变成概率值,概率值经过时序平滑变成预警信号。不要在任何一个环节做复杂的双向反馈,毕设阶段保持流水线清晰比什么都重要。整个系统在 Python 里用 OpenCV 加 PyTorch 就能搭完,不需要额外的中间件。

3. 训练数据从哪来:疲劳数据集构建与预处理

3.1 公开数据集与自采数据的取舍,以及类别怎么设计

数据集是疲劳检测项目里最容易被低估的一环。模型能不能收敛、答辩时演示会不会翻车,七成取决于数据质量而不是网络结构。

公开数据集方面,NTHU-DDD(台湾清华大学驾驶数据集)、YawDD 和 DROZY 是这个方向比较常用到的选项。它们包含不同受试者在清醒、困倦状态下的面部视频,有的还提供了头部姿态和打哈欠的标注。用公开数据集的优势是省时间、结果可对比,缺点是样本和真实驾驶舱环境存在 gap,而且类别分布不一定符合你的需求。

数据集内容适合用途注意点
NTHU-DDD多受试者驾驶视频,含困倦标注疲劳分类训练需申请、视频体积大
YawDD驾驶中打哈欠视频哈欠检测类别较单一
DROZY多模态生理+视频数据扩展对比实验含生理信号,处理复杂

自采数据是这个项目比较推荐的补充方式。方法不复杂:找一台带摄像头的电脑,让 3 到 5 位同学分别录制正常状态和模拟疲劳状态的视频各 10 分钟。模拟疲劳的关键在于表演要真实——频繁眨眼、长时间闭眼、打哈欠、点头。录完之后按每 3 帧抽 1 帧的方式切图,大概能拿到几千到上万张图片。

类别设计有两种路线。第一种是二分类:清醒和疲劳,最简单,模型容易收敛。第二种是多分类:清醒、闭眼、打哈欠,信息量更大,论文可以做的分析更多。我一般建议采用多分类,因为闭眼和打哈欠这两个中间状态单独成类之后,可以分别统计频率,为预警策略提供更细的输入。无论选哪种,都要保证每个类别样本量均衡,后面会用数据增强处理这个问题。

3.2 人脸检测与对齐:裁剪出稳定的人脸 ROI

拿到原始图片后,第一步是把人脸从背景里裁出来。人脸检测器有很多选择:OpenCV 的 Haar Cascade、OpenCV DNN 模块的 SSD 检测器、dlib 的 HOG 检测器、MediaPipe 的 Face Detection。从速度和精度的平衡来看,OpenCV DNN 的 SSD 检测器对毕设来说是比较稳的选择,它比 Haar 鲁棒,又不像 MediaPipe 那样引入太多额外依赖。

import cv2 def load_face_detector(prototxt_path, model_path): net = cv2.dnn.readNetFromCaffe(prototxt_path, model_path) return net def detect_and_crop_face(frame, net, target_size=(64, 64), conf_threshold=0.7): h, w = frame.shape[:2] # 构建 blob:缩放、减均值、交换通道顺序,适配 Caffe 模型的输入格式 blob = cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0)) net.setInput(blob) detections = net.forward() best_confidence = conf_threshold best_box = None # 遍历所有检测框,保留置信度最高的一个人脸 for i in range(detections.shape[2]): confidence = detections[0, 0, i, 2] if confidence > best_confidence: best_confidence = confidence best_box = detections[0, 0, i, 3:7] if best_box is None: return None # 坐标还原到原图尺寸 x1 = int(best_box[0] * w) y1 = int(best_box[1] * h) x2 = int(best_box[2] * w) y2 = int(best_box[3] * h) # 边界保护:检测框越界时截断,避免切片溢出 x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) face_roi = frame[y1:y2, x1:x2] if face_roi.size == 0: return None return cv2.resize(face_roi, target_size)

这个函数有两个参数需要根据实际场景调整。conf_threshold 默认 0.7,如果发现该检测到的脸没检测到,就降到 0.5;如果背景里的非人脸物体频繁被框出来,就往 0.8 以上调。target_size 决定了后面 CNN 的输入分辨率,64×64 是个起步值,数据充足时可以提到 112×112 提升精度。要注意的是这里的均值 (104.0, 177.0, 123.0) 是 Caffe 模型自带的标准化参数,换成别的检测模型要跟着换,否则检测效果会明显退化。

3.3 数据增强与样本均衡:别让模型只学会“清醒”

疲劳样本的采集难度天然高于清醒样本——没人能随时随地表演出自然的困倦状态。这会导致数据集中醒着的图片远多于疲劳图片,训练出来的模型会对一切输入都倾向于输出“清醒”,这个现象在项目里太常见了。

解决思路有两个方向。第一个是数据增强,在训练时对图片做随机的翻转、旋转、亮度扰动和对比度扰动,相当于从有限的原始样本里变出更多变体。第二个是采样策略,用 PyTorch 的 WeightedRandomSampler 或者直接在 Loss 里给少数类更高的权重。

from torchvision import transforms train_transform = transforms.Compose([ transforms.RandomHorizontalFlip(p=0.5), # 水平翻转,模拟不同朝向 transforms.RandomRotation(degrees=10), # 小角度旋转,容忍侧脸 transforms.ColorJitter(brightness=0.3, contrast=0.3), # 亮度对比度扰动,模拟光照变化 transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]), ])

参数需要注意几点。RandomRotation 的 degrees 不要超过 15,转多了人脸关键特征会失真。ColorJitter 的幅度在 0.3 左右比较合适,太大图片会发灰发白,让模型学到不真实的颜色分布。Normalize 的均值和标准差这里用的是 0.5 系列,如果你的数据整体偏暗,可以改成按实际统计值计算。

数据增强不是越多越好。翻转和旋转在疲劳检测里是安全的,但像 RandomErasing(随机遮挡)这类增强要谨慎使用,因为真实驾驶场景中人脸被大面积遮挡往往意味着姿态异常,不一定是疲劳。增强策略定下来之后,把它同时用在验证集上是不对的,验证集只用 ToTensor 和 Normalize,保证评估结果真实反映模型在未增强数据上的表现。

4. 用 PyTorch 搭 CNN 疲劳分类模型:从模型定义到训练调参

4.1 模型选型:LeNet-5、ResNet18 还是 MobileNetV2

CNN 模型选型是这个项目的核心决策。常见的选择集中在三个方向,各有各的适用场景。

LeNet-5 是经典的卷积网络,结构简单,参数量小,CPU 上跑得飞快。但它的特征提取能力有限,面对真实拍摄、光照不均的人脸图片,准确率往往不够理想,做毕设演示时容易在复杂场景露怯。它更适合作为课程实验,而不是毕业设计的最终方案。

ResNet18 是目前平衡性最好的选择。残差结构解决了深层网络的梯度消失问题,18 层的深度对人脸分类来说足够,权重文件也只有四十多兆,训练速度快,推理速度在 CPU 上也能达到实时。论文里有 ResNet 的消融实验可写,答辩时也讲得出设计思路。

MobileNetV2 的优势在参数量和推理速度,适合后续要移植到树莓派或 Jetson Nano 的边缘设备场景。如果题目里明确写了“嵌入式”,选它更合理。代价是精度比 ResNet18 略低,调试时需要更仔细地调学习率。

一般建议以 ResNet18 为基线模型,先跑通流程,再根据设备情况决定是否换成 MobileNetV2。直接上 ResNet50 或 VGG16 对毕设来说都是过度设计,训练时间长、过拟合风险高,收益却很小。

4.2 模型定义与训练循环:最小可跑通的完整代码

用 PyTorch 实现整个训练流程,代码量在 150 行左右。这里给出一个可以直接改路径就跑的最小版本,包含模型定义、数据加载、训练循环三部分。

import torch import torch.nn as nn from torchvision import datasets, transforms from torch.utils.data import DataLoader # 定义一个轻量 CNN,适合作为 ResNet 之外的基线对比 class FatigueCNN(nn.Module): def __init__(self, num_classes=3): super(FatigueCNN, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.MaxPool2d(2), ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Dropout(0.5), nn.Linear(128, 64), nn.ReLU(inplace=True), nn.Linear(64, num_classes), ) def forward(self, x): return self.classifier(self.features(x)) # 数据集路径按 ImageFolder 目录结构组织 # dataset/train/awake, dataset/train/closed_eye, dataset/train/yawning train_dataset = datasets.ImageFolder( root='dataset/train', transform=transforms.Compose([ transforms.Resize((64, 64)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.5], [0.5]), ]) ) val_dataset = datasets.ImageFolder( root='dataset/val', transform=transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize([0.5], [0.5]), ]) ) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2) # 使用 ResNet18 作为主模型,自定义 CNN 作为对比 from torchvision.models import resnet18 model = resnet18(num_classes=len(train_dataset.classes)) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=15, gamma=0.1) for epoch in range(30): model.train() running_loss = 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in val_loader: outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() scheduler.step() print(f"Epoch {epoch+1}: loss={running_loss/len(train_loader):.4f}, " f"val_acc={100.0 * correct / total:.2f}%")

这里把两种网络都写进来了:FatigueCNN 是一个三层卷积的基线模型,适合做对比实验;resnet18 是主模型,直接迁移预训练权重也可以。batch_size=32 是 8GB 显存下比较稳妥的值,如果训练时内存不足就降到 16。StepLR 的参数 step_size 设为 15 表示每 15 轮学习率降为原来的 0.1,让模型在训练后期用小学习率精细调整。num_workers=2 在 Windows 上如果报错就改为 0,这是 PyTorch 在 Windows 下的一个已知坑。

训练结束后将模型保存为 state_dict,方便推理阶段加载。保存模型的格式用 .pt 或 .pth,内容包含模型权重,后续在预警程序里加载后转换为 eval 模式即可。

torch.save(model.state_dict(), 'fatigue_model.pth') print("模型已保存")

4.3 训练参数设置:学习率、batch、损失函数与过拟合控制

训练参数的设置直接决定模型能不能收敛、会不会过拟合,下面几个是我在实际调试中总结出的可复现经验。

学习率方面,Adam 优化器配 1e-3 是通用起点,但人脸图像分类任务如果直接加载 ImageNet 预训练的 ResNet18,建议把学习率降到 3e-4,因为预训练权重已经处于一个较优的解空间,过大的学习率会破坏这些特征。学习率衰减策略用 StepLR 即可,每 15 个 epoch 降 0.1 倍,总训练轮数 30 到 40 轮就能收敛。

batch size 的选择和显存绑定。8GB 显存跑 ResNet18 输入 64×64,batch 32 不会爆显存;如果用的是自定义小 CNN,可以放心调到 64。batch size 越大,模型收敛越稳定,但也越容易收敛到尖锐极小值,泛化性能不一定更好,这个需要在实验中权衡。

损失函数选 CrossEntropyLoss 就可以,它内部已经包含了 Softmax,不要在模型输出层再手动加 Softmax。如果类别不平衡严重,给 CrossEntropyLoss 传 class_weight 参数,用 sklearn 的 compute_class_weight 算出各类权重。这一步对模型效果的影响比换网络结构更明显。

过拟合的控制有三个手段组合使用。第一个是前面提到的数据增强,第二个是 Dropout,第三个是早停:记录验证集准确率,连续 5 个 epoch 不提升就终止训练。训练过程中要同时观察训练集和验证集的 loss,如果训练 loss 持续下降而验证 loss 不降反升,就是过拟合的信号,优先加重数据增强而不是增加模型复杂度。

5. 避坑指南与常见问题排查

5.1 现象:人脸检测框抖动导致预警乱跳

人脸检测器在视频流上单帧独立检测时,检测框会随轻微头部晃动而上下左右抖动,裁剪出的人脸 ROI 内容跟着变化,传给 CNN 的分类结果在清醒和疲劳之间来回横跳,预警信号触发又取消,体验非常差。

原因在于没有对检测结果做时序平滑。每一帧都是独立决策,而检测器对同一张脸的定位本身存在几个像素的随机噪声。

解决方法是引入指数移动平均(EMA)对检测框坐标做平滑。具体实现是维护一个平滑框,每帧用smooth_box = alpha * current_box + (1 - alpha) * smooth_box更新,alpha 取 0.3 到 0.5。人脸丢失时保留最后一个平滑框,连续 10 帧检测不到人脸才清空,避免短暂低头就导致 ROI 消失。

5.2 现象:夜间与逆光场景下模型性能骤降

白天演示效果很好,一到晚上或者逆光环境,检测准确率明显下滑,失效概率很高。这是因为训练数据以正常光照为主,模型没有见过低照度的人脸,卷积核学到的纹理特征在暗光下提取不出来。

解决思路分两层。数据层面,在数据增强里加入亮度扰动并录制部分夜间数据,让模型见过这种场景。图像处理层面,在送入检测器之前对帧做直方图均衡化,用 OpenCV 的cv2.equalizeHist先将 BGR 转成 YUV 对 Y 通道做均衡再转回。如果条件允许,直接换红外摄像头是最省事的方案,红外光下眼睛和面部特征对比度更稳定,不受可见光影响。

5.3 现象:数据集类别不均衡,模型对一切都说“清醒”

训练结束时验证集准确率很高,但实际测试时无论怎么输入模型都输出“清醒”。这是典型的类别不均衡症状:清醒样本占总样本八成以上,模型只要全预测为清醒就有八成准确率,反向传播计算出的梯度也被多数类主导,少数类的模式根本学不到。

解决方法有两个,优先同时使用。第一个是在 Dataset 构建时统计每类样本数,给少数类做重采样,用torch.utils.data.WeightedRandomSampler让每个 batch 里的疲劳样本比例保持在一个合理水平。第二个是给 CrossEntropyLoss 设置 class_weight,计算方式推荐class_weight = total_samples / (num_classes * class_counts)。两个手段都执行后,观察模型在疲劳类别上的召回率是否明显上升。

5.4 现象:摄像头帧率与推理速度不匹配,程序卡顿

摄像头是 30 帧每秒,模型在 CPU 上单帧推理需要 200 毫秒还要多,视频画面明显掉帧,预警响应跟着变慢。

这个问题的本质是串行处理导致瓶颈累积。人脸检测加 CNN 分类在 CPU 上跑全帧率本身就不现实,需要在工程上做降载处理。我采用的做法是跳帧推理:设置一个推理间隔参数,每 3 帧挑 1 帧做完整推理,其余帧沿用上次结果。再配合跳过人脸检测的平滑框跟踪,CPU 占用能降一半以上。如果项目允许使用 GPU,把模型和数据都放到 CUDA 上,推理速度提升一个量级。

5.5 现象:打哈欠和说话在图像上太相似,误报频发

用户反馈最多的场景是驾驶员说话时嘴巴持续张合,模型反复判定为打哈欠。二者在单帧图像上的确有相似性——嘴巴都是张开状态,单纯对单帧做分类从理论上就无法彻底区分。

解决这个问题的思路有两个方向。一个是在模型输入层不只看嘴巴,同时输入眼部和嘴部的联合特征,打哈欠时眼睛往往也是半闭的,说话时眼睛通常是睁开的,模型可以学习到这种联合模式。另一个是引入时序信息,统计连续 N 帧内的嘴部打开帧数和眼睛闭合帧数,打哈欠是一个持续 3 到 5 秒、具有明确开始和结束的事件,而说话是间歇性张合,两者的时序特征差异明显。后一种方案不增加模型复杂度,是我在实际项目中更常用的做法。

6. 预警系统的实时落地与验证方法

6.1 跳帧推理与连续帧触发的预警逻辑

训练好模型之后,部署端的核心逻辑是控制推理频率和消除误报。推理频率问题前面提过,用跳帧解决。误报问题则需要连续帧触发机制:不因为单帧的疲劳判定就报警,而是连续 N 帧都被判定为疲劳才触发预警,N 通常在 10 到 15 之间,对应约 0.5 秒的持续疲劳状态。

import cv2 import torch from torchvision import transforms model = resnet18(num_classes=3) model.load_state_dict(torch.load('fatigue_model.pth', map_location='cpu')) model.eval() cap = cv2.VideoCapture(0) transform = transforms.Compose([ transforms.ToPILImage(), transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize([0.5], [0.5]), ]) frame_count = 0 fatigue_streak = 0 FATIGUE_THRESHOLD = 15 # 连续帧阈值 while True: ret, frame = cap.read() if not ret: break frame_count += 1 if frame_count % 3 != 0: # 每 3 帧推一次 continue face = detect_and_crop_face(frame, detector) # 复用第 3 章的函数 if face is None: fatigue_streak = 0 continue tensor = transform(face).unsqueeze(0) with torch.no_grad(): probs = torch.softmax(model(tensor), dim=1) fatigue_prob = probs[0][1].item() # 假设第二类是疲劳 if fatigue_prob > 0.7: fatigue_streak += 1 else: fatigue_streak = max(0, fatigue_streak - 1) if fatigue_streak >= FATIGUE_THRESHOLD: cv2.putText(frame, "FATIGUE WARNING - Take a Rest!", (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3) # 在此处接入声光报警或语音提示 cv2.imshow("Driver Fatigue Monitor", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

注意代码中连续帧累加和衰减的细节:判定为疲劳时连续帧数加 1,判定为清醒时不是直接清零而是减 1。这个设计避免了一次模型误判就把计时清零,同时又能让短暂清醒状态逐渐拉低疲劳计数。fatigue_prob 的阈值 0.7 是经验值,实际使用时要根据测试视频的误报率调整,阈设高了系统迟钝,设低了自己吓自己。

6.2 用带标注的测试视频量化评估

演示不能只靠现场感觉,需要一个可量化的评估流程。操作方法如下:录制一段 10 分钟的真实驾驶模拟视频(可以用行车记录仪画面替代),在时间轴上标注出疲劳发生的起止时间段,误差控制在 1 秒内。然后让系统跑一遍完整视频,把每次报警时间输出成日志文件,与标注结果对比。

评估指标用三个:准确率(所有报警中真疲劳的比例)、召回率(所有疲劳时间段中被成功报出的比例)、误报间隔(平均多久出现一次虚假报警)。这三个指标往往不能同时最优,调阈值就是在它们之间找平衡点。如果误报率太高,先提高疲劳概率阈值;如果召回率太低,先检查连续帧阈值是否设得太大,再看是不是模型本身对疲劳样本不敏感。

毕设答辩的核心演示逻辑是跑通实时检测并展示这段量化评估结果。训练准确率是一回事,系统在真实视频流上的鲁棒性是另一回事,能拿出后者,整个项目的工程完成度就立住了。

这个项目里我吃过最大的亏是在数据增强上偷懒,导致模型在实验室灯光下表现优异,换到现场偏暗环境就直接失灵。后来养成一个习惯:从第一天起就在训练数据里混合多场景光照样本,并且每次调整参数后先跑一段测试视频而不是只看训练 loss。做这类实时检测系统的落地项目,数据覆盖度永远比模型结构更值得投资,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询