首页/新闻资讯/正文详情

Python+OpenCV疲劳驾驶检测:dlib关键点与EAR/MAR阈值实战

发布时间:2026/9/26 5:22:51 来源:云帆数科 栏目:资讯中心
Python+OpenCV疲劳驾驶检测:dlib关键点与EAR/MAR阈值实战
简介这是一套面向计算机相关专业学生与项目实战学习者的疲劳驾驶检测完整项目基于Python与OpenCV实现可直接用于毕业设计、课程设计或期末大作业。项目围绕驾驶员面部特征与眼部状态进行疲劳判定帮助读者理解计算机视觉在安全驾驶场景中的落地思路。压缩包共4个文件包含py主程序、dat人脸关键点模型、ttc字体文件与txt依赖说明整体约74.52MB下载后按依赖说明配置环境即可运行省去自行搜集模型与素材的麻烦。目前已有1173人学习下载说明该方案在同类毕设选题中具备一定参考价值。读者可获得完整可运行的源码结构、关键点检测模型与依赖清单便于快速复现效果、理解检测流程并在此基础上做功能扩展或论文撰写适合作为视觉类毕设的起步模板。1. 从一张摄像头截图说起疲劳驾驶检测到底在检测什么凌晨两点跑高速车道保持还稳人却已经开始点头——这种场景下真正先出问题的不是方向盘而是眼睛和嘴。基于 Python OpenCV 的疲劳驾驶检测核心就是用普通摄像头或行车记录仪视频连续抓帧从人脸区域里提取眼睛闭合、嘴巴张合、头部姿态这几类信号再按时间窗口判断是不是困了。它不需要红外、不需要脑电一台笔记本加一个 USB 摄像头就能跑这也是它常年霸榜计算机毕业设计选题的原因门槛低、演示直观、能讲清楚原理。这套方案适合三类人一是做计算机/电子信息/物联网方向毕业设计需要一个能跑通、能截图、能答辩的完整项目二是刚学完 Python 基础语法和 OpenCV 图像处理想找一个综合练手项目三是想给车队、网约车做低成本预警原型的工程师。它解决的是实时性 可解释性——每一帧为什么报警你能从 EAR、MAR 数值上看到而不是一个黑匣子模型丢给你一个概率。下面按环境怎么搭 → 人脸和关键点怎么拿 → 疲劳指标怎么算 → 怎么调参不误报 → 怎么验证的顺序讲透。2. 环境搭建Python、OpenCV 与 dlib 的版本组合怎么选2.1 为什么这套项目绕不开 dlib 和 shape_predictor纯 OpenCV 能做 Haar 级联人脸检测但它给不了 68 个面部关键点而疲劳检测恰恰依赖眼睛、嘴巴的精确坐标。常见做法是用 dlib 的shape_predictor_68_face_landmarks.dat它把一张人脸标成 68 个点第 36–41 是左眼42–47 是右眼48–67 是嘴巴。有了这些点EAR眼睛纵横比和 MAR嘴巴纵横比才有得算。选型上有个现实取舍dlib 精度够、代码短但 CPU 上跑 640×480 大概 15–25 FPS够用如果你要更高帧率或想上 GPU可以换 MediaPipe FaceMesh它给 468 个点、速度更快但坐标索引和 dlib 完全不同网上大部分现成代码是 dlib 版改起来要重写指标计算。毕业设计场景我一般建议先用 dlib 跑通答辩稳再考虑优化。提示shape_predictor_68_face_landmarks.dat是独立模型文件约 100MB不在 pip 包里需要单独下载后放到项目目录代码里用相对路径引用。2.2 三个必装依赖与一条验证命令环境这块翻车最多的是版本。Python 3.8–3.10 对 dlib 最友好3.11 以上编译 dlib 经常报 CMake 错误。下面是我常用的安装顺序# 建议 Python 3.9先建虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate # 先装编译工具链依赖再装 dlib顺序反了容易失败 pip install cmake pip install dlib19.24.2 pip install opencv-python4.8.1.78 pip install numpy scipy imutils装完先跑一条验证命令确认三个库都能导入、摄像头能打开import cv2 import dlib import numpy as np print(opencv:, cv2.__version__) print(dlib:, dlib.__version__) # 打开默认摄像头0 是内置外接 USB 通常也是 0 或 1 cap cv2.VideoCapture(0) if not cap.isOpened(): print(摄像头打开失败检查是否被其他程序占用) else: ret, frame cap.read() print(首帧读取:, ret, 尺寸:, frame.shape if ret else None) cap.release()逻辑说明先打印版本确认没装错包再验证摄像头。参数上cv2.VideoCapture(0)的 0 是设备索引笔记本内置摄像头一般是 0外接 USB 摄像头可能是 1如果读出来是黑屏或retFalse先把索引换成 1 试。frame.shape返回(高, 宽, 通道)正常是 480×640×3。注意如果报ModuleNotFoundError: No module named cv2八成是虚拟环境没激活或者 pip 装到了另一个 Python 解释器上。用which pythonLinux/macOS或where pythonWindows确认当前解释器路径再pip list看包在不在。2.3 项目目录怎么摆路径才不会报错毕业设计压缩包解压后最常见的报错是找不到模型文件。我一般把目录固定成下面这样代码里全部用相对路径换电脑也能跑fatigue_detection/ ├── main.py # 主程序入口 ├── utils.py # EAR/MAR 计算函数 ├── models/ │ └── shape_predictor_68_face_landmarks.dat ├── data/ │ └── test_video.mp4 # 测试视频 └── requirements.txtmain.py里引用模型写成dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat)只要在项目根目录运行python main.py就不会找不到。很多人用绝对路径C:\Users\...一换机器就崩这是血泪经验。3. 人脸检测与 68 关键点从一帧图像到眼睛嘴巴坐标3.1 用 dlib 拿到关键点先跑通单帧在算疲劳指标之前得先确认关键点定位是准的。下面这段是最小可运行版本只做检测和画点不做判断方便你肉眼验证import cv2 import dlib detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # dlib 检测用灰度图更快 faces detector(gray, 0) # 第二个参数 0 表示不上采样1 会放大图像提精度但更慢 for face in faces: landmarks predictor(gray, face) for i in range(68): x landmarks.part(i).x y landmarks.part(i).y cv2.circle(frame, (x, y), 2, (0, 255, 0), -1) cv2.imshow(landmarks, frame) if cv2.waitKey(1) 0xFF 27: # 按 ESC 退出 break cap.release() cv2.destroyAllWindows()逻辑说明detector(gray, 0)的第二个参数是上采样次数0 最快1 会把图像放大一倍再检测小脸更容易检出但帧率掉一半。predictor(gray, face)返回 68 个点对象用.part(i).x/.y取坐标。参数上cv2.circle的半径 2、颜色(0,255,0)只是可视化不影响计算。跑通后你应该能看到脸上 68 个绿点眼睛周围一圈、嘴巴一圈。如果点飘、抖动大先检查光照——dlib 对侧光和逆光很敏感正面均匀光最好。3.2 EAR 和 MAR 的公式与索引别抄错点眼睛纵横比 EAR 的思路是眼睛睁开时上下眼睑距离大闭合时接近 0。以左眼36–41为例公式是EAR (|p38−p42| |p39−p41|) / (2 × |p37−p40|)分母是眼睛水平宽度分子是两段垂直距离之和。右眼用 42–47对应点整体加 6。嘴巴 MAR 用 48–67 里的 51、57、62、66 等点常见简化公式是MAR |p51−p57| / |p49−p53|下面把计算函数单独放utils.py主程序 import 调用结构清晰也好答辩import numpy as np def euclidean(a, b): # 两点欧氏距离 return np.linalg.norm(np.array(a) - np.array(b)) def eye_aspect_ratio(eye_points): # eye_points 是 6 个 (x, y)顺序必须是 [p1..p6] # 垂直距离两段 v1 euclidean(eye_points[1], eye_points[5]) v2 euclidean(eye_points[2], eye_points[4]) # 水平距离一段 h euclidean(eye_points[0], eye_points[3]) return (v1 v2) / (2.0 * h 1e-6) # 加极小值防除零 def mouth_aspect_ratio(mouth_points): # mouth_points 取内唇 4 个点左上、左下、右上、右下 v euclidean(mouth_points[0], mouth_points[1]) h euclidean(mouth_points[2], mouth_points[3]) return v / (h 1e-6)逻辑说明euclidean用 numpy 算距离比手写平方根简洁。eye_aspect_ratio里1e-6是防止人脸侧转时水平距离趋近 0 导致除零崩溃这个坑很多人踩过。参数上EAR 阈值经验值在 0.2–0.25 之间MAR 阈值在 0.5–0.7 之间但这两个数跟摄像头分辨率、人脸在画面里的大小强相关不能照抄必须自己标定。3.3 把关键点索引映射成眼睛和嘴巴数组dlib 给的是 68 个点得按索引切片。下面这段把左眼、右眼、嘴巴分别取出来def get_eye_mouth_points(landmarks): # dlib 68 点索引约定 left_eye_idx list(range(36, 42)) # 左眼 36-41 right_eye_idx list(range(42, 48)) # 右眼 42-47 mouth_idx [48, 50, 52, 54, 49, 51, 53, 55] # 外唇轮廓 left_eye [(landmarks.part(i).x, landmarks.part(i).y) for i in left_eye_idx] right_eye [(landmarks.part(i).x, landmarks.part(i).y) for i in right_eye_idx] mouth [(landmarks.part(i).x, landmarks.part(i).y) for i in mouth_idx] return left_eye, right_eye, mouth逻辑说明左眼 36–41 的顺序是顺时针正好对应 EAR 公式里的 p1–p6直接传进eye_aspect_ratio即可。嘴巴这里取的是外唇 8 个点如果你用内唇公式索引要换成 60–67。参数上索引顺序错了EAR 会算出离谱的值比如 1.5 以上这是最常见的翻车点建议先把点画出来核对。4. 疲劳判定逻辑连续帧计数、阈值与报警触发4.1 为什么不能单帧判断必须用计数器单帧 EAR 低于阈值不代表疲劳——正常眨眼也会瞬间闭合。所以工程上用的是连续 N 帧低于阈值才判定闭眼。经典做法是设一个计数器COUNTEREAR 低于阈值就 1高于就清零超过EYE_AR_CONSEC_FRAMES比如 20 帧就报警。这个帧数怎么定取决于帧率30 FPS 下 20 帧约 0.67 秒正常眨眼 0.1–0.4 秒不会误报真困了闭眼通常超过 1 秒必然触发。EYE_AR_THRESH 0.22 # EAR 阈值需按自己摄像头标定 EYE_AR_CONSEC_FRAMES 20 # 连续帧数阈值 MOUTH_AR_THRESH 0.6 # MAR 阈值 counter 0 yawn_counter 0 alarm False # 在主循环里拿到 left_eye, right_eye, mouth 之后 ear (eye_aspect_ratio(left_eye) eye_aspect_ratio(right_eye)) / 2.0 mar mouth_aspect_ratio(mouth) if ear EYE_AR_THRESH: counter 1 if counter EYE_AR_CONSEC_FRAMES: alarm True cv2.putText(frame, DROWSINESS ALERT!, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) else: counter 0 alarm False if mar MOUTH_AR_THRESH: yawn_counter 1 else: yawn_counter 0逻辑说明EAR 取左右眼平均比单眼稳。counter在 EAR 恢复后清零保证只统计连续闭眼。yawn_counter同理统计打哈欠。参数上EYE_AR_THRESH和MOUTH_AR_THRESH是全局最敏感的两个数下面单独讲怎么标定。4.2 阈值标定用你自己的脸跑一遍统计照抄 0.22 大概率不准因为人脸在画面里的大小直接影响 EAR 绝对值。正确做法是录一段自己正常睁眼、闭眼、张嘴的视频把每帧 EAR/MAR 打印出来看分布# 标定脚本只打印数值不判断 import csv rows [] # ... 在循环里 ... rows.append([ear, mar]) # 循环结束后写入文件 with open(calib.csv, w, newline) as f: writer csv.writer(f) writer.writerow([ear, mar]) writer.writerows(rows)逻辑说明把 EAR/MAR 存成 CSV用 Excel 或 pandas 看睁眼时的最小值、闭眼时的最大值阈值取两者中间。参数上如果睁眼 EAR 在 0.28–0.35、闭眼在 0.10–0.18那阈值 0.22 就合适如果睁眼只有 0.20说明人脸太小或侧转得先解决检测质量而不是硬调阈值。提示标定时保持和实际使用一样的光照、距离、摄像头。换环境后阈值可能要重标这是这类方案的固有局限答辩时如实说明反而加分。4.3 把报警做成可扩展的声音、日志、截图光在画面上写字不够实际项目一般要触发声音或记录。用winsoundWindows或playsound播提示音同时把报警时刻的帧存下来当证据import time import cv2 last_alarm_time 0 ALARM_INTERVAL 3 # 秒避免报警音连续轰炸 if alarm and (time.time() - last_alarm_time ALARM_INTERVAL): last_alarm_time time.time() # 保存报警帧文件名带时间戳 ts time.strftime(%Y%m%d_%H%M%S) cv2.imwrite(fdata/alarm_{ts}.jpg, frame) # Windows 下播放系统提示音 # import winsound; winsound.Beep(1000, 500)逻辑说明ALARM_INTERVAL防止每帧都触发报警实际体验会好很多。保存报警帧既方便调试也是毕业设计里系统有记录功能的加分项。参数上winsound.Beep(1000, 500)是 1000Hz 响 500msLinux/macOS 换成os.system调系统命令或playsound库。5. 避坑与排查那些让检测忽好忽坏的细节5.1 现象白天正常晚上或逆光就检测不到人脸原因dlib 的 HOG 人脸检测器基于梯度特征对光照变化敏感逆光时人脸变成剪影梯度信息丢失。解决先做直方图均衡化再检测或者换 OpenCV 的 DNN 人脸检测器cv2.dnn加载 res10 模型后者对光照鲁棒得多。代码上在cvtColor后加一行gray cv2.equalizeHist(gray)就能改善不少。5.2 现象EAR 值一直在 0.3 以上闭眼也不下降原因关键点索引取错了或者人脸检测框偏移导致 predictor 定位到错误区域。解决先把 68 个点画出来肉眼核对确认 36–41 确实在眼睛上再检查detector(gray, 0)的返回值是否为空就进入 predictor空 face 会抛异常或给出乱点。加个if len(faces) 0: continue保护。5.3 现象帧率只有 5–8 FPS画面卡顿原因每帧都在做全图人脸检测且图像分辨率太高。解决把摄像头分辨率降到 640×480cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)检测时把灰度图缩小到一半再检测坐标乘 2 还原。另外 dlib 的detector(gray, 1)上采样很吃性能非必要不用。5.4 现象戴眼镜时反光导致眼睛关键点乱跳原因镜片反光在灰度图里形成高亮区域干扰梯度特征。解决调整光源角度避免正对镜片或在检测前对眼部区域做局部对比度限制CLAHE。如果项目允许采集数据时让测试者摘掉眼镜是最省事的办法但要在文档里说明适用范围。5.5 现象多人出现在画面里报警逻辑混乱原因代码默认只处理faces[0]或者对每个 face 共用同一个 counter。解决给每个 face 维护独立的 counter 字典用 face 的中心坐标或跟踪 ID 做 key。毕业设计如果只做单人就在文档里明确单驾驶员场景并在代码里取面积最大的 face避免误检背景人脸。6. 进阶与验证让这套疲劳检测经得起答辩追问跑通只是起点答辩老师最爱问的是你怎么证明它准。我的习惯是准备一段带标注的测试视频人工标出每段闭眼/打哈欠的起止时间然后跑程序输出报警时间点算准确率和误报率。下面这个验证脚本把检测结果和人工标注对比# 假设人工标注格式[(start_frame, end_frame, eye), ...] # 程序输出每帧的 alarm 状态列表 def evaluate(gt_intervals, pred_frames, total_frames): tp fp fn 0 gt_set set() for s, e, _ in gt_intervals: gt_set.update(range(s, e)) pred_set set(pred_frames) tp len(gt_set pred_set) # 正确报警 fp len(pred_set - gt_set) # 误报 fn len(gt_set - pred_set) # 漏报 precision tp / (tp fp 1e-6) recall tp / (tp fn 1e-6) print(f精确率: {precision:.3f} 召回率: {recall:.3f})逻辑说明gt_set是人工标注的疲劳帧集合pred_set是程序报警帧集合交集是正确报警。参数上精确率低说明误报多调高EYE_AR_CONSEC_FRAMES召回率低说明漏报多调低阈值或帧数。这两个指标此消彼长答辩时给出你选的平衡点及理由比只报一个准确率 95%可信得多。再往上走可以加头部姿态估计用cv2.solvePnP配合 68 点里的鼻尖、下巴等算俯仰角点头频繁也是疲劳信号或者把 EAR/MAR 序列喂给一个轻量 LSTM 做时序分类比固定阈值更抗个体差异。但我不建议毕业设计一上来就上深度学习——阈值法你能解释每一帧为什么报警模型法一旦被问为什么这帧判疲劳就容易卡壳。先用阈值法把整条链路跑稳、把验证数据做扎实再谈优化。最后说个我自己的习惯每次改完阈值或逻辑一定用同一段测试视频重跑一遍把 EAR/MAR 曲线和报警点画在一张图上对比。肉眼看不出的参数漂移画成曲线一目了然。这套东西不难难的是肯花时间标定和验证而不是抄个阈值就交差。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

Nemotron-3-Diarization搭配流式ASR实现谁说了什么:说话人归属转写完整实战指南
Nemotron-3-Diarization搭配流式ASR实现谁说了什么:说话人归属转写完整实战指南

Nemotron-3-Diarization搭配流式ASR实现谁说了什么:说话人归属转写完整实战指南 【免费下载链接】Nemotron-3-Diarization 项目地址: https://ai.gitcode.com/hf_mirrors/nvidia/Nemotron-3-Diarization Nemotron-3-Diarization 是 NVIDIA 开源的说话人分离… · 2026/9/26 5:22:51

Java同城租房系统源码:Spring Boot+MyBatis-Plus完整项目实现
Java同城租房系统源码:Spring Boot+MyBatis-Plus完整项目实现

手头这套 Java 同城租房系统源码,是我在两轮课程设计基础上重构出来的完整项目实现。最初只是想着把房东发布房源、租客在线找房这条主链路跑通,后来越做越发现,真正的难点不是增删改查,而是“同城”这两个字怎么落进设计里。如果… · 2026/9/26 5:22:51

CAD对象捕捉失效排查:从OSMODE到硬件加速的完整指南
CAD对象捕捉失效排查:从OSMODE到硬件加速的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:22:51

Claude MCP工作流:CLI驱动的协议网关实战指南
Claude MCP工作流:CLI驱动的协议网关实战指南

1. 项目概述:这不是一个“模板库”,而是一套面向 Claude 开发者的 CLI 工作流中枢你搜到“claude-code-templates”时,大概率正被一堆报错卡住:unable to connect to anthropic services、unable to locate the codex cli binary、… · 2026/9/26 6:34:11

Agent-Native应用实战:从概念、设计到落地踩坑全解析
Agent-Native应用实战:从概念、设计到落地踩坑全解析

直接说结论:agent-native不是给你正在跑的微服务换个名字,也不是把所有逻辑都塞给大模型就算完了。它是一种反过来的设计思路——把“智能体(Agent)”当成应用的主角,LLM是它的大脑,工具是它的手脚&#xf… · 2026/9/26 6:34:11

Hadoop+Spark+Hive空气质量预测系统:从环境搭建到答辩全流程实践指南
Hadoop+Spark+Hive空气质量预测系统:从环境搭建到答辩全流程实践指南

带过好几届大数据方向的毕业设计,每年都能见到不少同学捧着一个看似牛气冲天的题目,却卡在环境搭建或者数据处理的环节动弹不得。所以一看到"hadoopsparkhive空气质量预测系统"这个题,我反倒是有点欣慰:这题选得聪明。它… · 2026/9/26 6:34:11

Hadoop+Spark+Hive空气质量预测系统全流程设计与答辩指南
Hadoop+Spark+Hive空气质量预测系统全流程设计与答辩指南

如果你正在做或者打算做“HadoopSparkHive空气质量预测系统”这个毕业设计,我先说句实话:这个题目真正的难点其实不在代码本身,而在于把一条冗长的大数据技术链路讲清楚、演示流畅、并且能写进毕业论文。很多同学下载了一套源码,跑… · 2026/9/26 6:34:11

从零搭建金融数据服务:架构设计与工程实践
从零搭建金融数据服务:架构设计与工程实践

1. 金融数据服务从零搭建的核心思路拆解1.1 为什么我要自己动手做一套金融数据服务先说清楚这套东西是干什么的。financial-services,直译就是“金融服务”,但在我这里,它指的是一套面向个人开发者和小型团队的自建金融数据服务层。它能做什么… · 2026/9/26 6:34:11

阿里开源 Qwen3-VL 轻量版 4B/8B 本地部署:TaoToken 统一 Key 接入与 config.toml 配置骨架
阿里开源 Qwen3-VL 轻量版 4B/8B 本地部署:TaoToken 统一 Key 接入与 config.toml 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 6:34:05

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码