简介这是一套面向计算机相关专业学生与项目实战学习者的疲劳驾驶检测完整项目包基于Python与OpenCV实现可直接用于毕业设计、课程设计或期末大作业。项目围绕人脸关键点定位与眼部状态分析展开通过摄像头实时判断驾驶员疲劳程度属于计算机视觉与图像处理的典型应用场景适合具备一定Python基础、希望积累完整项目经验的学习者。压缩包共4个文件包含py主程序源码、dat人脸关键点模型数据、ttc字体文件与txt依赖说明整体约74.52MB下载后按说明配置环境即可运行省去自行搜集模型与调试代码的时间。目前已有1173人学习下载说明该方案在同类毕设选题中具备较高参考价值。读者可获得一套结构清晰、经过严格调试的完整源码直接理解检测流程与关键算法实现并在此基础上进行功能扩展或论文撰写降低从零搭建项目的门槛。1. 从一张打哈欠的截图说起这套疲劳驾驶检测源码到底能跑出什么实验室里最常见的场景是这样的导师丢过来一句“做个疲劳驾驶检测”你打开搜索引擎翻到第三页发现要么是只有一段cv2.VideoCapture的玩具代码要么是论文里语焉不详的算法框图。这套基于 Python OpenCV 的疲劳驾驶检测项目源码加全部数据解决的正是这个断层——它把摄像头采集、人脸检测、眼睛和嘴巴区域定位、疲劳判定这条链路完整地串了起来并且附带了可以直接跑通的数据。它适合三类人正在做计算机毕业设计、需要一份能演示能答辩的完整工程的同学刚学完 Python 基础语法和 OpenCV 图像处理、想找一个综合项目练手的入门者以及需要快速验证疲劳检测思路、不想从零搭框架的开发者。技术栈上它依赖 Python 解释器、OpenCV 的图像处理与视频流能力核心逻辑围绕人脸关键点展开不需要 GPU 也能在普通笔记本上跑起来。下面我按“先搞懂判定逻辑再动手复现最后避开那些让人熬夜的坑”这个顺序把这份资源拆开讲清楚。2. 疲劳判定的两条主线EAR 与 MAR 是怎么算出来的拿到源码先别急着python main.py否则报错会让你怀疑人生。先理解它凭什么判断你困了。主流做法是两条线并行眼睛闭合程度用 EAREye Aspect Ratio眼睛纵横比衡量嘴巴张开程度用 MARMouth Aspect Ratio嘴巴纵横比衡量。这两个指标都是纯几何计算不依赖深度学习模型所以对算力友好这也是它能跑在普通电脑上的原因。2.1 EAR 的几何含义与阈值设定EAR 的思路很朴素眼睛睁开时上下眼睑的垂直距离大闭合时垂直距离趋近于零。用六个关键点表示一只眼睛取垂直方向的两组距离之和除以水平方向距离的两倍就得到一个归一化的比值。睁眼时这个值通常在 0.25 到 0.35 之间闭眼时会掉到 0.15 以下。import numpy as np def eye_aspect_ratio(eye_points): # eye_points 为 6 个 (x, y) 坐标顺序为左角、上左、上右、右角、下右、下左 # 垂直距离上左到上右、下左到下右 vertical_1 np.linalg.norm(eye_points[1] - eye_points[5]) vertical_2 np.linalg.norm(eye_points[2] - eye_points[4]) # 水平距离左角到右角 horizontal np.linalg.norm(eye_points[0] - eye_points[3]) ear (vertical_1 vertical_2) / (2.0 * horizontal) return ear这段代码里np.linalg.norm算的是两点欧氏距离。分母乘 2.0 是为了归一化避免人脸远近导致数值漂移。实际使用时阈值不能拍脑袋定因为不同人眼型差异很大——有人天生眼睛小EAR 常年 0.2你按 0.25 判他会一直报警。常见做法是先跑一段正常睁眼视频统计 EAR 均值再往下取 70% 左右作为闭眼阈值。源码里如果给了默认值那是给标准脸型用的你得根据自己的数据微调。2.2 MAR 与打哈欠的区分嘴巴的判定比眼睛麻烦因为说话、咀嚼、微笑都会让嘴巴动。MAR 的计算方式和 EAR 类似取嘴巴上下唇的关键点算垂直距离除以左右嘴角的水平距离。打哈欠的特征是MAR 持续高于阈值较长时间通常超过 1 秒而说话时的张嘴是短促的、间歇的。def mouth_aspect_ratio(mouth_points): # mouth_points 为嘴巴区域关键点取上下唇中点和左右嘴角 vertical np.linalg.norm(mouth_points[1] - mouth_points[5]) horizontal np.linalg.norm(mouth_points[0] - mouth_points[4]) mar vertical / horizontal return mar参数上MAR 阈值一般设在 0.5 到 0.7 之间配合一个持续帧数计数器。如果只是单帧超过阈值就报警那打个哈欠、说句话都会触发误报率高到没法用。所以源码里通常会有frame_counter这样的变量连续 N 帧满足条件才判定为疲劳。这个 N 值对应的时间窗口就是你要根据帧率换算的关键参数。2.3 关键点从哪来人脸检测与定位的衔接EAR 和 MAR 都依赖关键点坐标那这些点怎么来的常见方案有两种一是用 OpenCV 自带的 Haar 级联检测器先框出人脸再在脸部区域做眼睛和嘴巴的检测二是用 dlib 的 68 点人脸关键点模型直接拿到每只眼睛 6 个点、嘴巴 20 个点的精确坐标。前者依赖 OpenCV 生态安装简单后者精度高但需要额外下载模型文件。import cv2 # 方案一Haar 级联OpenCV 自带无需额外模型 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) eye_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_eye.xml ) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: roi_gray gray[y:yh, x:xw] eyes eye_cascade.detectMultiScale(roi_gray)detectMultiScale的后两个参数是scaleFactor和minNeighbors。scaleFactor1.3表示每次图像尺寸缩小 1.3 倍来搜索值越小检测越慢但越全minNeighbors5控制误检值越大越严格但可能漏检。Haar 的优点是零依赖缺点是侧脸和戴眼镜时容易翻车。如果你的数据里人脸角度变化大建议换 dlib 方案代价是多一个模型文件和环境配置步骤。3. 把源码跑起来环境配置与主循环拆解理解判定逻辑之后动手环节的核心就两件事让依赖装对让视频流跑通。这两步卡住的人最多尤其是 OpenCV 的安装和摄像头权限问题。3.1 Python 环境与 OpenCV 安装的稳妥路径先确认 Python 版本建议 3.8 到 3.10太新的版本某些依赖轮子还没跟上。安装 OpenCV 用 pip 即可但要注意包名是opencv-python而不是cv2。# 创建虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装核心依赖 pip install opencv-python numpy # 如果源码用到 dlib 关键点 pip install dlibopencv-python是包含主模块的包opencv-contrib-python额外包含一些实验性模块。如果你只需要基础图像处理和视频读取前者足够。安装完成后验证import cv2 print(cv2.__version__) # 能打印出版本号说明安装成功如果报ModuleNotFoundError: No module named opencv九成是装到了全局环境而你的脚本跑在虚拟环境里或者包名拼错。检查pip list里有没有opencv-python以及当前终端激活的是哪个环境。3.2 视频流读取与主循环结构疲劳检测的主循环本质是一个逐帧处理管道读帧、转灰度、检测人脸、定位眼睛嘴巴、算 EAR/MAR、累计疲劳计数、画框标注、显示结果。源码的主循环通常长这样import cv2 import numpy as np cap cv2.VideoCapture(0) # 0 表示默认摄像头也可传视频文件路径 EYE_AR_THRESH 0.25 EYE_AR_CONSEC_FRAMES 48 # 连续闭眼帧数阈值 counter 0 alarm False while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 人脸检测与关键点定位此处省略具体检测代码 # 假设已得到左右眼和嘴巴的关键点坐标 ear (left_ear right_ear) / 2.0 if ear EYE_AR_THRESH: counter 1 if counter EYE_AR_CONSEC_FRAMES: alarm True cv2.putText(frame, FATIGUE!, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 3) else: counter 0 alarm False cv2.imshow(Frame, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()EYE_AR_CONSEC_FRAMES 48这个值不是随便写的。假设摄像头 30 帧每秒48 帧约等于 1.6 秒。人正常眨眼一次约 0.2 到 0.4 秒也就是 6 到 12 帧所以 48 帧能过滤掉正常眨眼只对持续闭眼报警。如果你换用 60 帧的摄像头这个值要翻倍到 96 左右否则正常眨眼也会触发。这就是为什么同一份代码在不同机器上表现不一样——帧率变了帧数阈值没跟着变。cv2.waitKey(1)里的 1 表示等待 1 毫秒保证画面实时刷新。如果写成 0程序会卡在第一帧等你按键视频流就死了。 0xFF是处理某些系统下按键值超过 255 的兼容写法属于 OpenCV 的经典细节。3.3 用视频文件替代摄像头做调试开发阶段用摄像头调试很痛苦因为每次测试都要真人对着镜头做表情。更高效的做法是先用录好的视频文件跑通逻辑确认判定准确后再切摄像头。# 把 0 换成视频文件路径 cap cv2.VideoCapture(test_video.mp4) # 读取视频帧率用于动态计算帧数阈值 fps cap.get(cv2.CAP_PROP_FPS) EYE_AR_CONSEC_FRAMES int(fps * 1.5) # 1.5 秒对应的帧数cap.get(cv2.CAP_PROP_FPS)返回视频帧率用它乘以你想要的时间窗口秒数就得到自适应的帧数阈值。这样无论视频是 25 帧还是 60 帧判定逻辑都一致。这个技巧在答辩演示时特别有用——你可以准备一段包含正常驾驶和疲劳状态的视频稳定复现报警效果不用担心现场光线或摄像头角度问题。4. 避坑与排查那些让程序跑不起来的常见问题这一章是我踩过的坑里挑出来的高频问题每条按现象、原因、解决来写。你如果卡在某一步先在这里找找。4.1 摄像头打不开或画面全黑现象cap.read()返回False或者cv2.imshow窗口一片黑。原因通常是摄像头被其他程序占用比如视频会议软件没退干净或者VideoCapture(0)的索引不对有些笔记本内置摄像头是 1 而不是 0。解决办法先关掉所有可能占用摄像头的程序然后试cv2.VideoCapture(1)或cv2.VideoCapture(2)。在 Linux 下还可以用ls /dev/video*确认设备节点。如果是在虚拟机里跑摄像头需要手动挂载到虚拟机这个坑很多人第一次遇到会以为是代码问题。4.2 人脸检测框闪烁或频繁丢失现象检测框在脸上跳来跳去或者几帧有框几帧没框。原因是 Haar 级联对光照和角度敏感单帧检测没有时序平滑。解决办法一是改善光照避免逆光和侧光二是对检测结果做平滑比如连续 3 帧都检测到才认为有效三是降低scaleFactor到 1.1 提高检测密度代价是速度变慢。如果源码里用的是 dlib检查模型文件路径是否正确路径含中文或空格也会导致加载失败。4.3 EAR 值始终偏高或偏低现象明明睁着眼EAR 却低于阈值一直报警或者闭眼了 EAR 还很高不报警。原因是关键点定位不准或者阈值不适合你的脸型。解决办法先把 EAR 值实时打印出来观察自己睁眼和闭眼时的数值范围然后取中间值作为阈值。如果关键点明显偏移检查人脸检测框是否准确——关键点是在人脸框内计算的框歪了点就歪了。戴眼镜的人尤其要注意镜框反光会干扰眼睛区域的关键点定位可以尝试摘掉眼镜测试或者换用对眼镜更鲁棒的关键点模型。4.4 程序运行越来越卡现象刚开始流畅跑几分钟后帧率明显下降。原因是主循环里累积了未释放的资源或者每帧都在做重复的昂贵计算。解决办法检查有没有在循环内重复加载级联分类器或模型文件这些应该只加载一次检查有没有把每帧图像都存进列表导致内存增长如果用了cv2.imshow确保waitKey参数不为 0。另外把灰度转换、缩放等操作放在检测之前能显著降低计算量。4.5 报警逻辑误报或漏报现象正常眨眼触发报警或者真困了却不报警。原因是帧数阈值和帧率不匹配或者 EAR 和 MAR 的判定没有做逻辑组合。解决办法按 3.3 节的方法用帧率动态计算阈值把眼睛和嘴巴的判定做“或”逻辑还是“与”逻辑要根据场景定——闭眼和打哈欠任一发生都算疲劳用“或”要求两者同时发生才报警用“与”后者误报低但可能漏报。答辩演示建议用“或”保证能触发效果。5. 进阶技巧让检测从“能跑”到“敢演示”前面四章解决了从零跑通的问题这一章讲怎么把它打磨到能拿得出手。核心思路是单帧判定不可靠时序平滑才是关键单一指标不够用多特征融合才稳。5.1 用滑动窗口替代硬计数硬计数的问题是一旦中间有一帧 EAR 回升计数器就清零导致持续闭眼但偶尔抖动时无法触发。滑动窗口的做法是维护最近 N 帧的 EAR 值计算其中低于阈值的比例比例超过 80% 就判定疲劳。from collections import deque window_size 30 ear_window deque(maxlenwindow_size) # 在主循环内 ear_window.append(ear) if len(ear_window) window_size: closed_ratio sum(1 for e in ear_window if e EYE_AR_THRESH) / window_size if closed_ratio 0.8: alarm Truedeque(maxlenwindow_size)自动丢弃旧数据保持窗口大小固定。closed_ratio 0.8表示最近 30 帧里超过 24 帧是闭眼状态。这个方式比硬计数鲁棒得多因为偶尔一帧关键点抖动不会让整个判定归零。参数上窗口大小对应时间长度30 帧在 30fps 下是 1 秒你可以根据实际响应速度需求调整。5.2 融合 PERCLOS 指标提升说服力如果答辩时导师问“你这个判定标准有什么依据”只回答 EAR 阈值会显得单薄。PERCLOSPercentage of Eyelid Closure over the Pupil over Time是疲劳检测领域公认的指标含义是单位时间内眼睛闭合时间所占比例。用滑动窗口算出来的closed_ratio本质上就是 PERCLOS 的简化版。你可以在代码注释和论文里明确写上这个对应关系把工程实现和学术指标挂上钩。指标计算方式典型阈值适用场景EAR眼睛纵横比0.2~0.25闭眼检测MAR嘴巴纵横比0.5~0.7打哈欠检测PERCLOS闭眼时间占比0.4~0.8综合疲劳评估这张表可以直接放进你的毕业设计论文作为参数选取的依据。注意阈值那一列给的是范围具体值必须用你自己的数据标定直接抄别人的数值大概率翻车。5.3 报警输出的工程化处理检测到疲劳之后输出方式也有讲究。控制台打印print(fatigue)在演示时根本看不见cv2.putText在画面上写字是基本操作但还可以加声音报警。import winsound # Windows 专用 # 在报警触发时 winsound.Beep(1000, 500) # 频率 1000Hz持续 500msmacOS 和 Linux 下可以用os.system调用系统提示音或者用pygame.mixer播放音频文件。注意报警不要每帧都触发否则声音会连成一片。加一个冷却时间比如报警后 3 秒内不再重复报警。import time last_alarm_time 0 cooldown 3 # 秒 if alarm and (time.time() - last_alarm_time) cooldown: last_alarm_time time.time() # 触发报警time.time()返回当前时间戳和上次报警时间比较超过冷却时间才再次触发。这个细节在演示时很重要——连续不断的报警声会让评委烦躁有节奏的提示音才显得专业。5.4 我踩过的最大的坑说一个血泪经验。我第一次做这个项目时在实验室调得好好的拿到答辩教室一跑检测框疯狂闪烁报警完全乱套。排查了半天才发现教室的投影仪光源和实验室的日光灯色温不同导致摄像头自动白平衡一直在调整灰度图每帧都在变Haar 检测自然不稳定。从那以后我每次做视觉项目都强制在代码开头加一句手动锁定摄像头参数cap.set(cv2.CAP_PROP_AUTO_WB, 0) # 关闭自动白平衡 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0) # 关闭自动曝光这两行能避免环境光变化导致的检测抖动代价是需要根据现场光线手动调一次曝光值。但比起答辩现场翻车这点调试成本不值一提。希望帮到你。本文还有配套的精品资源点击获取