拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于CNN人脸识别的疲劳驾驶检测系统:从关键点定位到PERCLOS预警

基于CNN人脸识别的疲劳驾驶检测系统:从关键点定位到PERCLOS预警 简介一份基于卷积神经网络人脸识别技术的驾驶员疲劳检测与预警系统毕业设计源码包经导师指导并认可通过面向计算机相关专业正在准备毕设的学生和需要项目实战的Python学习者适合直接用于毕业设计、课程设计或期末大作业。压缩包共含19个文件整体约78.33MB以Python源码为主辅以OpenCV人脸检测所需的haar级联XML配置、训练完成的CNN模型权重hdf5、依赖与运行说明txt并附带可直接运行的exe图形界面程序。源码采用模块化组织覆盖人脸提取、数据预处理、模型训练与评估、实时疲劳判别以及Tkinter界面展示等完整流程项目已经过严格调试可确保正常跑通。已有490人学习浏览下载后按说明配置环境即可复现系统同时读者也可围绕CNN结构或疲劳判断逻辑做二次改进适合作为深度学习或计算机视觉方向的项目参考。1. 疲劳驾驶检测为什么非走卷积神经网络人脸识别这条路高速上打盹几秒车就窜出去几十米。方向盘转角、车道偏移这类间接检测法受路况干扰太大弯道和颠簸路段经常误报。真正要盯的是人不是车。基于卷积神经网络人脸识别的驾驶员疲劳检测与预警系统思路是摄像头对准驾驶员面部先做人脸检测和关键点定位再把眼睛、嘴部区域交给CNN做睁眼、闭眼、打哈欠分类结合PERCLOS等统计指标算出疲劳程度并触发声光报警。这个源码把数据整理、模型训练、实时预警全链路串在了一起适合毕业设计要出完整成果的同学也适合车队做低成本主动安全改造。下文把每一段实现怎么落地、参数怎么设、哪里容易翻车摊开讲透。2. 从摄像头帧到疲劳评分系统四层结构与算法选型疲劳检测系统看起来复杂拆开就是一串串行的数据流。先搞清楚每一层的职责后面调参和排错才有方向。整个系统可以切成四层采集层、检测层、判定层、预警层。层与层之间用明确的接口连接某一层换实现不影响其他层。2.1 四层结构采集、检测、判定、预警采集层负责把物理世界变成图像流。几十块钱的USB摄像头或者行车记录仪都能干这个活720p、30fps就够。分辨率再往上走人脸检测在CPU上的耗时直线上升每帧多出的那几毫秒在实车系统里都是真金白银的性能开销。摄像头装仪表盘上方偏左的位置偏左是为了让驾驶员正视前方时面部正好完整落在画面中央偏右的区域给右手操作留出余量。检测层回答两个问题人在画面里哪个位置眼睛和嘴巴在什么坐标。常见做法是把MTCNN和dlib的68点关键点模型串起来MTCNN输出人脸框和五个关键点对侧脸和大角度头部姿态更稳CPU单帧大约20msdlib再补嘴部轮廓的更多细节为后面裁剪眼睛、嘴部ROI做准备。68点模型里36到41是左眼轮廓42到47是右眼轮廓48到67是嘴巴轮廓这套索引是模型发布的固定协议代码里可以直接写死。判定层是核心把裁剪出来的眼睛小图送进CNN分类器输出睁眼、闭眼的概率嘴部小图送进另一个CNN分类器输出正常、打哈欠的概率。这里有个常见误区试图用一个大模型直接对整张人脸做疲劳分类。人脸之间的长相差异远大于同一个人睁眼闭眼的差异模型会走捷径去学“这是谁”而不是“这人困不困”。拆成ROI分类之后输入简单样本需求量小收敛也快得多。预警层把单帧判定结果积累成统计量通过状态机判断当前是不是进入疲劳状态。单帧误判无法避免状态机和统计窗口就是用来吞掉这些抖动的。前三层做得再好最后一层设计失误整套系统照样没法用。2.2 模型选型MTCNN加dlib做检测轻量CNN做分类人脸检测这部分MTCNN性价比最高。RetinaFace精度更高但权重文件大CPU推理慢在毕业设计和车载边缘设备上没必要。Haar级联虽然快但对遮挡和侧脸非常敏感夜间光线一差就丢脸。MTCNN输出的人脸框和五个关键点足够定位眼睛中心但要精准裁剪嘴部轮廓还需要dlib的68点模型补充。所以检测层我用MTCNN加dlib的组合输出68个关键点坐标后续所有ROI裁剪都依赖这份坐标稳定性比单独用任何一方都要好。状态判定层选自定义的小型CNN而不是ResNet-50、VGG这类大模型。原因是输入只有64x64单通道眼部小图类别只有两个。小模型十几万参数CPU单次推理约5ms在笔记本上就能跑到30fps。ResNet-50前几百层卷积是为ImageNet的1000类设计的拿来做闭眼二分类是巨大的能力浪费在小数据集上还更容易过拟合。我的习惯是先用小模型跑通整条链路确认瓶颈再决定要不要上大模型。这里要注意疲劳检测和门禁人脸识别是两码事。门禁只关心“你是谁”疲劳检测关心“你困不困”。门禁系统常用的那套人脸比对算法在这个项目里完全不适用。如果一开始就借用了门禁那套身份识别逻辑后面会发现模型对“这个人认不认识”敏感对“闭没闭眼”反而不敏感方向直接跑偏。2.3 疲劳判定指标PERCLOS、EAR与打哈欠频率疲劳判定不能只看“这一刻闭没闭眼”要看一段时间里的统计。论文里最常引的是PERCLOS全称是眼睑闭合时间占瞳孔覆盖时间的百分比P80标准定义为眼睑遮住瞳孔80%以上记作一次闭眼。工程实现时取30秒或60秒的滑动窗口统计窗口内闭眼时间的比例超过0.4就认为驾驶员进入疲劳状态。0.4不是拍脑袋是认知清醒度研究的经验值写毕业论文时可以直接引这个来源。EAREye Aspect Ratio是单帧的闭眼辅助判定指标。它只需要六个关键点坐标计算规则是眼睛竖直方向两个距离之和除以水平方向距离。眼睑下垂时竖直方向距离明显缩水EAR数值下降一般低于0.2就判定为闭眼。EAR的作用是给CNN当“第二票”CNN偶尔会碰到没见过的大角度头部姿态输出不靠谱但EAR是从几何坐标算出来的不受光照和图像纹理影响。CNN说睁眼但EAR已经低于0.18时以EAR为准更稳。打哈欠用嘴部关键点的嘴部长宽比检测。嘴巴完全张开时高度变化远大于宽度长宽比会超过0.5并维持一段时间。所以哈欠判定条件是长宽比超过0.5且持续时间超过1秒避免说话、喝水被误判。每记一次哈欠给疲劳评分加1两个评分窗口内两次哈欠且PERCLOS超限强制进入报警。三个指标互为冗余单一信号抖动不会让系统整体翻车。3. 环境搭建与数据集准备从源码到能跑的第一步源码拿到手第一步不是跑模型而是把环境立住。这个项目的依赖集中在Python、PyTorch、OpenCV、dlib四个大件上看着简单装起来第一个坑就是dlib。这一章把环境安装、数据抽帧、增强配置一次说清楚照着做就不会卡在起步阶段。3.1 Python环境安装conda安装dlib避开编译坑Python版本建议3.9PyTorch 2.0以上opencv-python 4.8dlib 19.24。Python 3.9这个选择非常关键dlib的预编译轮子在3.11以上经常找不到用3.9能省掉一大半环境问题。安装方式我推荐condaconda create -n fatigue python3.9 -y conda activate fatigue conda install conda-forge::dlib -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy scipy逻辑说明dlib用conda-forge通道安装拿到的是预编译包跳过了源码编译步骤。Windows下直接pip install dlib会现场编译需要Visual Studio C工具链很容易在半夜装了一堆编译环境最后仍然报错这步直接用conda是最省事的解法。torch用pytorch官方源安装因为conda默认源里的版本可能滞后尤其要保证torch和torchvision版本匹配。参数说明python3.9不要随手换成3.12或3.13dlib轮子跟不上的风险极高。如果用的是macOS或Linuxpip装dlib之前先确认cmake和GCC工具链就位否则还是会掉进编译陷阱。装完验证一下python -c import dlib, torch, cv2, numpy; print(dlib.__version__, torch.__version__, cv2.__version__, numpy.__version__)四个版本号都能正常输出环境就是通的。不管你在pycharm还是vscode里写代码解释器一定要指向这个conda环境否则运行时找不到dlib那种“明明装了却import失败”的问题多半就出在解释器选错。3.2 数据集目录结构抽帧、ROI裁剪、半自动标注疲劳检测数据集不用做成COCO那种复杂标注按类别分目录就够了。目录结构如下data/ train/ eyes_open/ # 睁眼样本 eyes_closed/ # 闭眼样本 mouth_normal/ # 正常嘴部 mouth_yawn/ # 打哈欠 val/ eyes_open/ eyes_closed/ mouth_normal/ mouth_yawn/PyTorch的ImageFolder可以直接加载这种目录结构不需要手写Dataset类。数据来源有两种一是用公开数据集里的视频抽帧二是自己录制驾驶视频。自录数据里最缺的是闭眼和哈欠样本因为清醒驾驶时不可能长时间闭眼需要司机专门做几段模拟疲劳的录像来补。抽帧和ROI裁剪是这个环节的核心脚本用dlib的68点模型import cv2 import dlib import numpy as np detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) def crop_roi(gray, landmarks, idx_start, idx_end, target64, margin10): pts np.array([[landmarks.part(i).x, landmarks.part(i).y] for i in range(idx_start, idx_end)]) x, y, w, h cv2.boundingRect(pts) x max(0, x - margin) y max(0, y - margin) w min(gray.shape[1] - x, w 2 * margin) h min(gray.shape[0] - y, h 2 * margin) roi gray[y:yh, x:xw] if roi.size 0 or roi.shape[0] 10 or roi.shape[1] 10: return None return cv2.resize(roi, (target, target)) cap cv2.VideoCapture(drowsy_drive.avi) count 0 while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 1) if len(faces) 0: continue landmarks predictor(gray, faces[0]) left_eye crop_roi(gray, landmarks, 36, 42) right_eye crop_roi(gray, landmarks, 42, 48) mouth crop_roi(gray, landmarks, 48, 67) if left_eye is not None and right_eye is not None: cv2.imwrite(fdata/train/eyes_open/eye_{count:05d}.jpg, left_eye) cv2.imwrite(fdata/train/eyes_open/eye_{count 1:05d}.jpg, right_eye) count 2 if mouth is not None: cv2.imwrite(fdata/train/mouth_normal/mouth_{count:05d}.jpg, mouth) count 1逻辑说明crop_roi先框出关键点的外接矩形向外扩展10像素再把矩形边界对齐到图像边缘防止裁剪越界最后统一resize到64x64。这样不管人脸在画面里的尺寸是大是小模型看到的都是同一尺度。换一个包含闭眼或打哈欠镜头的视频重新跑一遍同一个脚本把输出目录改成对应的类别文件夹就完成了半自动标注。参数说明margin10是经验值太小会把眼睛边缘裁掉太大会混入额头和脸颊的纹理。target64是精度和计算量的折中32太小特征丢失128对二分类任务浪费。每个类别建议至少攒2000张以上的样本验证集拿20%再往下不要低于这个数不然训练阶段过拟合的概率会明显上升。3.3 数据增强给数据集补光照、补翻转、补灰度统一原始样本直接训练会有三个问题光照极端、左右脸不对称、部分样本是彩色。增强要解决的就是这三件事。用torchvision的transform按顺序处理train_transform transforms.Compose([ transforms.RandomRotation(15), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.3, contrast0.3), transforms.Grayscale(num_output_channels1), transforms.ToTensor(), transforms.Normalize([0.5], [0.5]) ]) val_transform transforms.Compose([ transforms.Grayscale(num_output_channels1), transforms.ToTensor(), transforms.Normalize([0.5], [0.5]) ])逻辑说明RandomRotation允许旋转15度模拟司机头部轻微转动RandomHorizontalFlip做水平翻转左右眼样本共享同时模拟左右脸受光不一致。ColorJitter把亮度和对比度在原样本的0.7到1.3倍之间随机调整补光照鲁棒性。Grayscale把所有样本统一成单通道这之后再也不会出现彩色图混进模型的通道数错误。参数说明brightness0.3的扰动范围不能开太大开到0.5会把低光样本推到过曝模型在夜间场景反而失灵。Normalize的mean0.5和std0.5把0到255的灰度映射到-1到1这个归一化参数与模型推理时的预处理必须保持一致否则训练精度再高部署出来也是废的。增强配好后用ImageFolder直接加载from torchvision.datasets import ImageFolder train_dataset ImageFolder(data/train, transformtrain_transform) val_dataset ImageFolder(data/val, transformval_transform) print(train_dataset.classes) # [eyes_closed, eyes_open, mouth_normal, mouth_yawn]注意classes顺序是按目录名排序的之后做推理时要保持这份索引映射一致。把这个映射关系存成JSON模型训练完部署时不会搞错类别顺序。4. 用CNN把闭眼和哈欠训出来模型结构、训练参数、日志排查训练阶段是最容易让人产生“模型是黑匣子”感觉的地方。实际上疲劳状态分类的输入很小任务也不复杂只要模型结构不过度、训练参数符合常识、日志会看训练过程完全可控。这一章给出可以直接抄走的结构和参数再把训练日志里的常见异常讲清楚。4.1 CNN模型结构三层卷积为什么够用眼睛状态分类的输入是64x64单通道ROI输出是睁眼、闭眼两个类别。三层卷积加池化足够提取特征参数量只有十几万CPU推理单帧5ms级别。结构如下import torch.nn as nn class EyeStateCNN(nn.Module): def __init__(self, num_classes2): super(EyeStateCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 64 - 32 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 32 - 16 nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 16 - 8 nn.AdaptiveAvgPool2d(1) # 8 - 1x1x128 ) self.classifier nn.Linear(128, num_classes) def forward(self, x): x self.features(x) x torch.flatten(x, 1) return self.classifier(x)逻辑说明输入经过三层卷积逐级提炼特征每一层都比上一层通道数翻倍空间尺寸减半。最后AdaptiveAvgPool2d把8x8的特征图压成1x1x128再交给全连接层映射到两个类别。BatchNorm放在ReLU前面能让训练更稳定对小批量数据也友好。参数说明padding1保持卷积不缩小特征图尺寸。如果觉得眼部纹理特征不够强可以把第一层通道数从32提到64但不建议再加卷积层层数在几千张样本上很容易过拟合。嘴部哈欠分类直接复用这个模型换成嘴部数据集训练就行输入尺寸没变不需要改结构。4.2 训练参数采样权重、Adam、早停的完整配置疲劳数据最麻烦的是类别不平衡。闭眼样本可能只有睁眼样本的三分之一直接训练会让模型倾向把所有样本都判成睁眼整体准确率还能到70%实际完全没有检测能力。解决办法是加权采样from collections import Counter from torch.utils.data import DataLoader, WeightedRandomSampler labels [label for _, label in train_dataset.samples] counts Counter(labels) weights [1.0 / counts[label] for label in labels] sampler WeightedRandomSampler(weights, num_sampleslen(weights), replacementTrue) train_loader DataLoader(train_dataset, batch_size64, samplersampler) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse)逻辑说明每个样本的采样权重与其所属类别样本数成反比闭眼类样本少被抽到的概率就高。replacementTrue允许同一个样本在一个epoch里被多次抽到弥补样本量不足的问题。优化器与学习率配置model EyeStateCNN(num_classes2).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50)参数说明Adam的学习率1e-3是这类小模型的默认起点训练时如果loss波动剧烈就降到3e-4。CosineAnnealingLR让学习率在50个epoch内沿余弦曲线衰减到接近0比固定学习率通常能高几个百分点的精度。训练循环加早停best_val_acc 0.0 patience 0 for epoch in range(50): model.train() total_loss, total_correct, total_samples 0.0, 0.0, 0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) total_correct (outputs.argmax(1) labels).sum().item() total_samples images.size(0) train_acc total_correct / total_samples val_acc validate(model, val_loader) if val_acc best_val_acc: best_val_acc val_acc patience 0 torch.save(model.state_dict(), best_eye_model.pth) else: patience 1 if patience 8: break逻辑说明validate函数把val_loader跑一遍只计算正确率。早停的基准是验证集精度不是训练集loss因为训练loss降得再低也不代表泛化能力好反而可能是过拟合的信号。patience8意味着连续8个epoch验证精度不涨就停止。4.3 训练日志怎么读三个典型异常与排查顺序典型情况一train loss一直降val acc卡在60%不动。先别急着加模型深度打印分类别混淆矩阵看闭眼类召回率。如果闭眼类召回率只有30%说明模型把大多数闭眼样本都错判成了睁眼增强后的闭眼样本模式还是太单一。回去补闭眼样本或者把ColorJitter的扰动范围调小一点。典型情况二loss变成nan。这几乎一定是学习率问题把lr从1e-3降到1e-4重跑一遍。还nan就检查输入数据看有没有全黑图片、标签有没有超出类别范围。典型情况三train acc到98%val acc只有80%。这是过拟合的标准症状。优先做两件事在优化器里加weight_decay1e-4或者在模型全连接前加Dropout。一般weight_decay就能压住不要一上来就删卷积层删层会连带损失特征提取能力。训练结束把模型和类别映射一起存下来import json with open(class_map.json, w) as f: json.dump(train_dataset.class_to_idx, f, indent2)提示class_to_idx里存的是类别名到索引的映射推理时读回这个JSON确保传入模型的索引和训练时一致。5. 疲劳检测系统避坑手记五个高频坑的现象、原因、修正流程跑通demo很容易跑通实车场景才是真正考验。这一章整理了我在疲劳检测部署里遇到的五个高频坑每一条都按现象、原因、解决三步说明把这些坑提前排掉实车测试会少走很多弯路。5.1 大白天误报过曝让睁眼ROI看起来像全白现象车辆开到太阳底下系统频繁报警把睁眼判成闭眼。原因正午阳光打在人脸上眼睛区域过度曝光灰度值顶到255附近虹膜和眼皮纹路全部丢失。CNN看到几乎一片亮的区域和训练集里闭眼图像高度相似自然给出闭眼的高概率。解决第一道防线是输入侧做CLAHE直方图均衡化在推理的ROI裁剪之后加一步clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) roi_clahe clahe.apply(roi)逻辑说明CLAHE把局部区域的对比度拉高过曝边缘里的细节能被恢复一部分。第二道防线是训练时把ColorJitter的brightness范围提到0.4让模型见过更极端的亮度。第三道防线是相信EAR指标EAR从关键点几何坐标算出不受过曝影响真实睁眼时EAR不会掉到0.2以下拿EAR作为闭眼判定的强制否决票。5.2 人脸框抖动导致报警时断时续现象司机保持同一个姿势报警一会儿响一会儿停。原因MTCNN对单帧独立推理不保证相邻帧输出的框位置连续。呼吸、头发遮挡、光线微妙变化都会让检测框上下跳动框子动了裁剪出的ROI内容也随之变化同一个睁眼瞬间会被CNN判成不同结果。解决对关键点坐标做一阶低通滤波每一帧输出前先和历史坐标做平滑def smooth_landmarks(prev, curr, alpha0.4): smoothed [] for p_prev, p_curr in zip(prev, curr): x int(alpha * p_curr.x (1 - alpha) * p_prev.x) y int(alpha * p_curr.y (1 - alpha) * p_prev.y) smoothed.append((x, y)) return smoothed逻辑说明alpha0.4表示当前帧坐标权重占40%历史坐标占60%。帧率高时历史坐标更值得信任因为人脸在相邻帧之间的真实位移非常小。alpha往0.6调会更跟手往0.3调更稳但会带来一两帧的延迟在30fps下几乎不可感知。5.3 戴眼镜的驾驶员识别失灵现象戴眼镜测试时闭眼识别不出来偶尔睁眼误报。原因眼镜框在ROI里是高对比度边缘CNN学会的是“有镜框等于睁眼”这种相关性而不是真正的眼皮状态。镜框在闭眼时仍然存在所以闭眼样本反而被错判成睁眼。解决训练集必须混入戴眼镜和不戴眼镜两类样本这是根治手段。推理时如果遮挡严重把ROI裁剪范围向内缩一圈把镜框边缘裁掉减少干扰。偏光镜和墨镜遮挡导致的失效任何单目视觉方案都救不了这是物理遮挡硬件的解法是上红外摄像头RGB摄像头做到这一步已经是极限。5.4 车载环境帧率掉到8fps现象demo在台式机上30fps搬到车载笔记本只剩8fps。原因实车推理时摄像头采集、MTCNN、关键点、CNN分类、报警逻辑全在一条串行流水线上MTCNN在CPU上单帧要20ms加上其他环节每帧消耗轻松超过一百毫秒。解决人脸检测不需要每帧都做。30fps的视频里人脸在短时间内几乎不动每3帧做一次完整检测中间两帧直接沿用上一帧关键点坐标frame_count 0 detect_interval 3 while True: ret, frame cap.read() if not ret: break if frame_count % detect_interval 0: faces detector(gray, 1) if len(faces) 0: landmarks predictor(gray, faces[0]) # 中间帧直接用上一帧landmarks裁剪ROI进入CNN分类 left_eye crop_roi(gray, landmarks, 36, 42) ... frame_count 1逻辑说明检测频率从每帧一次降到每三帧一次计算量直接砍到三分之一。中间帧沿用上一帧的坐标只要司机没有剧烈运动坐标误差在几个像素以内对CNN分类结果几乎没有影响。配合这个改动整条链路能稳定到25fps以上。5.5 过减速带时人脸框丢失报警延迟现象实车过减速带连续十几帧人脸检测返回空系统静默再检测回来以后报警延迟明显。原因车辆振动带来运动模糊人脸检测对模糊图像很敏感检测框消失后上游坐标断供下游分类没有输入。解决给系统加一个人脸丢失计时。短暂丢失时保持当前状态不动作超过2秒仍没检测到人脸就提示“请正对摄像头”避免整个颠簸路段完全失联。另一个做法是在上一帧人脸框基础上向外扩展30%做局部搜索大部分模糊帧的人脸只是位置偏移了几个像素扩展后重新检测能找回来。6. 把模型接进预警系统状态机防抖、阈值调节与验收习惯单帧模型输出不能直接报警眨眼这个动作本身就会造成一两帧的闭眼误判。状态机就是用来吞掉这类短时干扰的。一个可靠的预警状态机核心是两个计数器和一个状态迁移逻辑。class FatigueStateMachine: def __init__(self): self.state awake self.drowsy_count 0 self.awake_count 0 def update(self, eye_closed: bool): if eye_closed: self.awake_count 0 self.drowsy_count 1 if self.drowsy_count 10: self.state alarm else: self.drowsy_count 0 self.awake_count 1 if self.awake_count 5 and self.state alarm: self.state awake return self.state逻辑说明闭眼连续计数到10帧才进入alarm大约0.4秒正常眨眼不会超过0.2秒。睁眼连续5帧才退出alarm防止抬头看后视镜的瞬间就撤销报警。真实部署时可以把state扩成awake、suspicious、alarm三态这里两态已经是可运行的最小主体。参数表是我跑通时的初始配置全部放进config.py统一管理参数初始值表达作用DROWSY_FRAMES10连续闭眼多少帧触发报警AWAKE_FRAMES5连续睁眼多少帧撤销报警PERCLOS_WINDOW_SEC30PERCLOS统计窗口长度PERCLOS_THRESHOLD0.4窗口内闭眼比例达到多少判疲劳EAR_CLOSE0.2EAR低于多少判闭眼CLAHE_CLIP2.0直方图均衡化对比度限制这套参数的核心思路是宁可早报不可漏报。正常驾驶30秒内的闭眼比例通常在0.1以下0.4的PERCLOS阈值留了很大余量。如果误报还是偏多优先加大DROWSY_FRAMES而不是调PERCLOS因为连续闭眼帧数更直观、更好校准。实车或毕设验收前我最常用的是三遍回放法。准备三段视频正常驾驶10分钟、模拟疲劳驾驶10分钟、颠簸路段10分钟。第一段看误报次数第二段看漏报和报警延迟第三段看检测稳定性。目标是从驾驶员闭眼到系统触发报警不超过2秒从睁眼到撤销报警不超过1秒。提示闭眼置信度阈值不要固定在0.5。闭眼漏报的代价远高于睁眼误判我把阈值从0.5降到0.35后夜间真实闭眼帧的漏检率降低了大约三分之一。最后说一个我的习惯模型参数和判定阈值全部独立成配置文件运行时通过参数入口调整而不是改代码重新启动。实车测试时一边开车一边让助手改配置最快半小时就能定出一版适合当前司机和当前光照习惯的参数。逐项迭代下来整个疲劳检测预警系统才算真正落地。希望帮到你。本文还有配套的精品资源点击获取
返回列表