基于深度学习的疲劳驾驶检测系统设计与源码实现
简介这份源码面向深度学习入门者与计算机视觉方向的开发者提供一套完整的智能疲劳驾驶检测系统实现方案可用于课程设计、毕业设计或算法练手。压缩包共84个文件、约105.27MB以34个Python源文件为核心覆盖数据预处理、特征提取、模型训练与预测全流程另有11个txt记录配置与日志、11个pyc字节码、7张jpg与4张webp/png图像样本及界面素材、4个xml参数配置并附带pt与onnx模型文件便于直接推理部署。目录中可见深度学习与numpy基础、人脸检测、注册识别、语音响应、疲劳检测、系统界面等模块结构清晰适合按模块拆解学习。目前已有106人学习下载读者可从中获得从数据到模型再到界面交互的完整工程思路并参考排错与二次开发方式。1. 从一张夜间驾驶截图说起疲劳驾驶检测到底在检测什么凌晨两点的高速公路行车记录仪拍下这样一幕车辆在车道内反复小幅偏移驾驶员头部缓慢下垂又猛地抬起。这个场景里疲劳不是一个可以直接测量的物理量它只能通过行为特征间接推断。基于深度学习的智能疲劳驾驶检测系统本质上就是一套从摄像头画面里提取面部与眼部特征、再用神经网络判断驾驶员是否进入疲劳状态的软硬件方案。它要解决的核心问题是在驾驶员自己还没意识到困倦之前系统先给出预警。适合做这个方向的人有三类想找深度学习落地项目的学生、需要给商用车队加装主动安全模块的嵌入式工程师、以及想理解「模型怎么从实验室走到车载环境」的算法初学者。源码层面的关键不在于网络多深而在于整条链路能不能在真实光照和抖动下稳定跑起来。2. 疲劳检测的技术路线选型为什么多数方案绕不开眼睛和嘴巴2.1 三条主流路线的对比与取舍做疲劳驾驶检测第一步不是写网络而是决定「看什么」。目前能落地的路线基本收敛为三类基于面部关键点、基于眼部区域分类、基于多模态融合。路线输入核心判据优点局限面部关键点68/98 点人脸关键点EAR、MAR、头部姿态可解释强、算力低依赖关键点精度眼部区域分类眼睛小图睁/闭二分类模型小、速度快丢失上下文多模态融合面部方向盘车道多特征联合鲁棒性最好工程复杂度高我一般会推荐从面部关键点路线起步原因是它的中间结果肉眼可验证。你可以在调试时把 EAR 曲线画出来直接看到「闭眼」对应的波谷而不是面对一个黑匣子般的分类概率。对于标题里说的「系统设计源码」关键点路线也最容易拆成清晰模块人脸检测、关键点回归、疲劳判据、报警逻辑。2.2 EAR 与 MAR两个必须吃透的几何判据EAREye Aspect Ratio是眼睛纵横比用眼睛六个关键点的垂直距离和水平距离算出来。睁眼时 EAR 大约在 0.25 到 0.35 之间闭眼时会掉到 0.1 以下。MARMouth Aspect Ratio同理打哈欠时嘴巴张开MAR 会明显升高。import numpy as np def eye_aspect_ratio(eye): # eye: 6 个点的坐标顺序为 [左角, 上1, 上2, 右角, 下2, 下1] # 垂直距离上两点与下两点 A np.linalg.norm(eye[1] - eye[5]) B np.linalg.norm(eye[2] - eye[4]) # 水平距离左右眼角 C np.linalg.norm(eye[0] - eye[3]) # 加 1e-6 防止除零 ear (A B) / (2.0 * C 1e-6) return ear def mouth_aspect_ratio(mouth): # mouth: 8 个点取上下唇内侧三点 A np.linalg.norm(mouth[2] - mouth[6]) B np.linalg.norm(mouth[3] - mouth[7]) C np.linalg.norm(mouth[0] - mouth[4]) mar (A B) / (2.0 * C 1e-6) return mar这段代码里有两个参数需要你根据实际摄像头调整。第一是坐标顺序不同关键点模型如 dlib 的 68 点、MediaPipe 的 468 点索引完全不同直接套用会算出错误的 EAR。第二是分母的防零项人脸侧转时水平距离会趋近于零不加保护会出现数值爆炸。我踩过的坑是用 468 点模型时直接照搬 68 点的索引结果 EAR 曲线完全乱掉排查了半天才发现是索引错位。2.3 从单帧判据到时间序列疲劳是「持续」出来的单帧 EAR 低不代表疲劳可能只是正常眨眼。真正区分眨眼和疲劳闭眼的是持续时间。常见做法是维护一个计数器EAR 低于阈值就加一高于阈值就清零计数器超过 N 帧才触发报警。EAR_THRESHOLD 0.21 # 闭眼判定阈值 CONSEC_FRAMES 20 # 持续帧数阈值 counter 0 alarm False for frame in video_stream: ear eye_aspect_ratio(landmarks) if ear EAR_THRESHOLD: counter 1 if counter CONSEC_FRAMES: alarm True else: counter 0 alarm False参数怎么定EAR_THRESHOLD 不能拍脑袋要先用你自己录的视频统计睁眼和闭眼时的 EAR 分布取两者之间的分界。CONSEC_FRAMES 和帧率强相关30fps 下 20 帧约等于 0.67 秒这个值太短会把正常眨眼误判太长会漏掉真正的疲劳闭眼。我一般会先设 15 到 25 帧再用实际视频回放微调。3. 把检测模型跑起来从人脸检测到疲劳判定的完整链路3.1 环境搭建与依赖选择这套系统对环境的依赖比想象中敏感。核心依赖是 OpenCV、NumPy以及一个关键点检测器。关键点检测器有两条常见路线dlib 的 68 点模型精度稳定但编译麻烦MediaPipe 的 FaceMesh 安装简单、点数多但对侧脸和遮挡更敏感。# 推荐用 conda 建独立环境避免和系统 Python 冲突 conda create -n fatigue python3.9 -y conda activate fatigue # 基础依赖 pip install opencv-python numpy # 关键点方案二选一 pip install mediapipe # 方案 A安装简单 # pip install dlib # 方案 B需要 cmake 和编译器选 MediaPipe 的理由是它自带人脸检测和关键点回归一条流水线搞定适合快速验证。选 dlib 的理由是 68 点模型在侧脸和低光照下更稳适合做产品化。如果你只是想把系统跑通看效果先用 MediaPipe如果要写进论文或做车载部署dlib 的确定性更强。3.2 主循环一帧画面要经过哪几步整个检测主循环可以拆成五步读帧、人脸检测、关键点提取、EAR/MAR 计算、状态判定与报警。下面是一个可运行的最小骨架。import cv2 import mediapipe as mp import numpy as np mp_face_mesh mp.solutions.face_mesh face_mesh mp_face_mesh.FaceMesh( max_num_faces1, refine_landmarksTrue, min_detection_confidence0.5, min_tracking_confidence0.5 ) # MediaPipe 468 点中左右眼和嘴巴的索引 LEFT_EYE [362, 385, 387, 263, 373, 380] RIGHT_EYE [33, 160, 158, 133, 153, 144] MOUTH [61, 81, 13, 311, 291, 178, 14, 402] cap cv2.VideoCapture(0) counter 0 EAR_THRESHOLD 0.21 CONSEC_FRAMES 20 while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results face_mesh.process(rgb) if results.multi_face_landmarks: h, w frame.shape[:2] lm results.multi_face_landmarks[0] pts np.array([[p.x * w, p.y * h] for p in lm.landmark]) left_ear eye_aspect_ratio(pts[LEFT_EYE]) right_ear eye_aspect_ratio(pts[RIGHT_EYE]) ear (left_ear right_ear) / 2.0 mar mouth_aspect_ratio(pts[MOUTH]) if ear EAR_THRESHOLD: counter 1 else: counter 0 if counter CONSEC_FRAMES: cv2.putText(frame, FATIGUE ALERT, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3) cv2.putText(frame, fEAR: {ear:.3f}, (30, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.putText(frame, fMAR: {mar:.3f}, (30, 130), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(Fatigue Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明MediaPipe 返回的是归一化坐标必须先乘以画面宽高转成像素坐标否则 EAR 计算没有意义。左右眼分别算 EAR 再取平均比只算一只眼更稳因为侧脸时一只眼可能被遮挡。参数方面min_detection_confidence 和 min_tracking_confidence 设 0.5 是速度和稳定性的折中光照差时可以降到 0.3但误检会增多。3.3 报警逻辑与状态机设计只靠一个计数器做报警实际用起来会很吵。更合理的做法是引入状态机正常、疑似疲劳、确认疲劳、恢复中。疑似疲劳时先给一次轻提示确认疲劳才触发强报警恢复后要有冷却时间避免报警反复横跳。class FatigueState: def __init__(self): self.state NORMAL self.counter 0 self.cooldown 0 def update(self, ear, threshold0.21, frames20, cooldown_frames90): if self.cooldown 0: self.cooldown - 1 return self.state if ear threshold: self.counter 1 else: self.counter max(0, self.counter - 2) # 缓慢恢复 if self.counter frames: self.state FATIGUE self.cooldown cooldown_frames self.counter 0 elif self.counter frames // 2: self.state SUSPECT else: self.state NORMAL return self.state这里 counter 的衰减用减二而不是清零是为了容忍眨眼造成的短暂 EAR 回升。cooldown_frames 设 90 帧约等于 3 秒防止报警刚结束又立刻触发。这套状态机是我在实际调试里改出来的纯计数器版本在驾驶员频繁眨眼时几乎无法使用。4. 避坑与排查那些让检测率一夜归零的细节4.1 现象白天正常晚上 EAR 曲线整体下移原因红外或低照度下眼睛关键点会向内收缩导致 EAR 整体偏低原本的阈值把正常睁眼判成闭眼。解决不要用固定阈值改成动态基线。维护一个滑动窗口取窗口内 EAR 的高分位数作为该时段的睁眼基线阈值设为基线的 0.6 倍左右。4.2 现象戴眼镜的人误报率明显升高原因镜片反光和镜框遮挡会让关键点漂移尤其是上眼睑点。解决优先用带 refine_landmarks 的模型它对视部区域有额外优化同时在预处理阶段做一次直方图均衡降低反光影响。如果还不行就退回到眼部区域分类路线用 CNN 直接判断睁闭眼绕开关键点。4.3 现象摄像头稍微偏一点EAR 就整体偏移原因头部姿态变化会改变眼睛在图像中的投影形状纯几何 EAR 对俯仰角很敏感。解决引入头部姿态估计当俯仰角超过阈值时暂停 EAR 判定或者用姿态角对 EAR 做补偿。常见做法是用 solvePnP 估计头部旋转超过 20 度就只依赖 MAR 和车道偏移。4.4 现象程序跑几分钟后帧率骤降原因每帧都重新初始化 FaceMesh 或重复加载模型内存和句柄泄漏。解决模型对象在循环外初始化一次循环内只调用 process。另外 OpenCV 的 imshow 在高分辨率下也会拖慢速度调试时可以先把画面缩到 640 宽。4.5 现象报警触发但驾驶员毫无反应原因报警方式太单一只有屏幕文字。解决加声音报警并且用状态机区分提示音和强报警音。我一般会把疑似疲劳设成短促提示音确认疲劳设成持续蜂鸣这样驾驶员能区分严重程度。5. 进阶技巧用滑动窗口和模型融合把误报压下去把系统跑通只是第一步真正决定它能不能用的是误报率。我最后悔的一件事是早期只盯着准确率看忽略了实际驾驶里「每分钟误报几次」才是驾驶员能不能接受的关键。后来我固定了一套验证习惯录三段视频分别是正常驾驶、轻度疲劳、重度疲劳每段至少五分钟然后统计误报次数和漏报次数而不是只看单帧准确率。一个立竿见影的技巧是把单帧判据换成滑动窗口统计。不要用「连续 N 帧闭眼」这种硬判据而是维护一个 30 帧的窗口计算窗口内闭眼帧的占比。占比超过 0.4 判疑似超过 0.6 判确认。这样对偶发关键点抖动天然免疫。from collections import deque class WindowFatigue: def __init__(self, size30): self.window deque(maxlensize) def update(self, ear, threshold0.21): self.window.append(1 if ear threshold else 0) if len(self.window) self.window.maxlen: return NORMAL ratio sum(self.window) / len(self.window) if ratio 0.6: return FATIGUE elif ratio 0.4: return SUSPECT return NORMAL第二个技巧是融合 MAR。很多人只做眼睛结果驾驶员睁着眼打哈欠时系统毫无反应。把 MAR 超过 0.6 且持续 15 帧以上作为独立疲劳证据和 EAR 证据做或运算能明显提升召回。第三个技巧是给不同证据加权闭眼权重 0.7打哈欠权重 0.3加权和超过 0.8 才报警。这套加权逻辑在夜间和戴眼镜场景下比单判据稳得多。如果你要把它做成完整系统下一步是加车道偏移检测做交叉验证以及把模型量化后部署到边缘设备。但在此之前先把上面这套滑动窗口加状态机的组合调稳它决定了你的系统是「能演示」还是「能用」。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →