简介:基于Python与OpenCV的疲劳驾驶检测完整项目,面向计算机相关专业毕业设计、课程设计及需要实战演练的开发者。项目采用人脸关键点检测技术,可实时分析眼睛闭合程度、打哈欠频率等疲劳特征,并触发报警提示,覆盖从环境配置到核心算法的完整实现链路。资源共包含4个文件,以Python主程序为核心,辅以OpenCV预训练关键点模型、中文标注字体及依赖清单,压缩包整体74.52MB,结构精简清晰,便于直接调试运行。源码已经本地编译通过并严格调试,经导师指导认可,评审得分高达98分,内容包含全部数据与运行配置,难度适中,适合作为高分毕业设计参考、期末大作业或项目实战练手。目前已有154人学习使用,资源质量可靠,下载后即可开展二次开发与效果验证。
1. 疲劳驾驶检测毕业设计:先弄清楚这份源码能解决什么问题
答辩现场演示翻车,是很多毕业设计最大的噩梦。而疲劳驾驶检测这类项目,最容易翻车的地方恰恰是“看起来简单,跑起来全是坑”——模型文件缺一个、中文字体没配、dlib 装不上,任何一个环节都能让你当场黑屏。这份基于 python+opencv 的疲劳驾驶检测源码包,把 dlib 的 68 点人脸关键点模型、宋体字体文件、主程序和依赖清单全部配齐了,解压之后按顺序装依赖就能跑通。它做的事情很明确:通过摄像头实时检测人脸,用眼睛纵横比 EAR 判断闭眼,用嘴巴纵横比 MAR 判断打哈欠,再结合 PERCLOS 统计在窗口期内的疲劳程度并触发报警。适合正在写毕业设计、课程设计,或者想拿 OpenCV 做完整项目练手的人。
2. 拆解源码与检测原理:68点关键点、EAR和PERCLOS的配合方式
2.1 资源包四个核心文件各干什么
这个项目拿到手之后,先别急着运行,花两分钟把文件组织看清楚。我拆过不少这类毕设包,很多同学翻车不是因为代码写得不对,而是压根不知道每个文件是干嘛的、谁依赖谁。
| 文件 | 大小量级 | 作用 |
|---|---|---|
| driverFatigue.py | 几 KB | 主程序,负责摄像头采集、人脸检测、关键点提取、疲劳判定和告警 |
| shape_predictor_68_face_landmarks.dat | 约 100MB | dlib 预训练模型,输入人脸区域,输出 68 个面部关键点坐标 |
| simsun.ttc | 十几 MB | 宋体字体,OpenCV 自带 putText 不支持中文,需要它来绘制汉字提示 |
| requirements.txt | 1KB 以内 | 依赖清单,声明 opencv-python、dlib、numpy、imutils 等库 |
依赖关系是这样的:driverFatigue.py 在运行时先调 dlib 的人脸检测器找到人脸框,然后把框交给 shape_predictor_68 模型提取 68 个点,拿到眼睛和嘴巴的点位之后计算 EAR 和 MAR;显示中文提示时,需要 simsun.ttc 配合 PIL 来绘制。所以四样东西缺一不可,尤其是那个 100MB 的 dat 文件,很多人下载不完整,加载时报错还以为是代码问题。
2.2 疲劳判定核心:EAR眼睛纵横比的计算
先看最核心的闭眼判断。dlib 的 68 点模型把眼睛区域标成 6 个关键点:左眼是索引 36 到 41,右眼是 42 到 47。这 6 个点围成一个近似椭圆,眼睛睁开时椭圆比较“圆”,闭眼时垂直方向的距离趋近于零。EAR(Eye Aspect Ratio,眼睛纵横比)就是把这种形状变化量化成一个数值。
# ear_example.py —— 眼睛纵横比计算示例 import numpy as np def eye_aspect_ratio(eye_pts: np.ndarray) -> float: # eye_pts 是单眼 6 个关键点坐标,顺序按 dlib 68 点定义排列 # 计算两组垂直距离的平均值,再除以水平距离 vertical_1 = np.linalg.norm(eye_pts[1] - eye_pts[5]) vertical_2 = np.linalg.norm(eye_pts[2] - eye_pts[4]) horizontal = np.linalg.norm(eye_pts[0] - eye_pts[3]) if horizontal == 0: return 1.0 return (vertical_1 + vertical_2) / (2.0 * horizontal)逻辑说明:vertical_1 和 vertical_2 是眼睛上下眼睑的两组垂直距离,horizontal 是眼睛左右眼角的水平距离。EAR 的本质是一个比例,正常睁眼时这个比值通常在 0.3 左右,闭眼时会掉到 0.1 以下。之所以用比值而不是直接用像素距离,是因为比值是尺度无关的——人离摄像头近的时候眼睛像素面积大,离得远的时候小,但比值基本稳定。这一点也是让新手最容易迷惑的地方,很多人一开始直接用眼睛的像素高度做判断,结果换个坐姿就失灵了。
参数说明:主程序里会分别算左眼和右眼的 EAR,然后取平均值。如果平均值小于 EAR_THRESHOLD(通常取 0.25),就认为当前帧是闭眼状态。注意单帧闭眼不能说明疲劳,正常人每分钟要眨眼十几次,每次闭眼就一两帧,所以还需要配合连续帧数判断。
2.3 打哈欠与PERCLOS:用状态统计代替单帧判断
单帧判断闭眼之后,下一步是把“瞬时状态”累积成“疲劳证据”。这个项目里用了两个统计手段。
第一个是连续帧计数。用一个计数器记录连续闭眼的帧数,当连续闭眼帧数超过 CONSEC_FRAMES(默认 20 帧左右)时,判定为一次“微睡眠”事件。为什么还要加这个条件?因为人在正常眨眼时 EAR 也会短暂下降,但通常只有 1 到 3 帧,而真正的疲劳闭眼会持续更久。在 30fps 的摄像头下,20 帧大约是 0.67 秒,这个时长已经能明显区分眨眼和瞌睡了。
第二个是打哈欠检测。嘴巴区域的 6 个关键点也可以用同样的纵横比逻辑,只是把眼睛的垂直水平比值换成嘴巴的:
# mar_example.py —— 打哈欠的 MAR 计算 def mouth_aspect_ratio(mouth_pts: np.ndarray) -> float: # 嘴巴关键点:外唇或内唇一组 6 点均可,需保持索引一致 # 纵向距离取两组,横向距离取左右嘴角 vertical = np.linalg.norm(mouth_pts[2] - mouth_pts[6]) horizontal = np.linalg.norm(mouth_pts[0] - mouth_pts[4]) return vertical / horizontal参数说明:MAR 在正常说话时也会有波动,所以不能单帧触发,一般要求 MAR 超过 0.6 且持续一定帧数才记一次哈欠。实际项目中还会加一个“哈欠计数 + 时间窗口”的组合判断:比如在 5 分钟内哈欠超过 3 次就升级疲劳等级。
PERCLOS(Percentage of Eye Closure)是这套系统的最后一道统计手段。它的定义是:在指定时间窗口内,眼睛闭合时间所占的比例。程序里会维护一个滑动的帧序列,比如最近 300 帧,统计其中闭眼帧的占比。当 PERCLOS 超过阈值(常见取 0.2 或 0.4,视标准而定)时,说明驾驶员的闭眼行为已经不是偶发,而是持续性的疲劳状态,此时触发声音和画面双重报警。整个流程是:单帧关键点提取 → 计算 EAR/MAR → 连续帧状态确认 → 窗口期内 PERCLOS 统计 → 报警输出。
3. 跑通环境与调参:从requirements.txt到driverFatigue.py的完整落地上线
3.1 环境准备:Python 3.8与虚拟环境
这个项目比较老牌,依赖的是 dlib 和 OpenCV 的经典组合。我的习惯是用虚拟环境隔离,避免把系统 Python 环境搞乱。版本上,Python 3.8 是最稳的选择,dlib 对 3.10 以上的新版本支持偶尔有编译问题,没必要在这个环节浪费答辩时间。
# 创建虚拟环境并激活,Windows 和 Linux 命令略有差异 conda create -n fatigue python=3.8 -y conda activate fatigue如果不用 conda,用 python 自带的 venv 也可以:
python -m venv fatigue_env # Windows 激活 fatigue_env\Scripts\activate # Linux/macOS 激活 source fatigue_env/bin/activate逻辑说明:虚拟环境的作用是让项目依赖与系统其他项目隔离。疲劳检测项目用到的 dlib 版本如果和系统里其他项目的 opencv-contrib-python 版本冲突,现场折腾起来非常痛苦。conda 的好处是它能自动处理 dlib 的二进制依赖,而 pip 安装 dlib 时在 Windows 上经常要现场编译,容易翻车。
3.2 依赖安装:opencv-python和dlib的常见安装路线
激活环境之后,先看 requirements.txt 的内容。这个文件一般声明了 opencv-python、dlib、numpy、imutils 这几个核心库。直接用 pip 批量安装:
pip install -r requirements.txt如果一切顺利,这一步就结束了。但据我实测的经验,Windows 平台上 90% 的概率会卡在 dlib 这一步——它需要 cmake 和 C++ 编译器现场编译源码,输出一大堆英文日志,最后报一堆链接错误。常见做法是先装好编译工具链,再单独装 dlib:
# Windows 下先装 Visual Studio Build Tools,勾选 C++ 工作负载 # 再装 cmake pip install cmake # 单独编译安装 dlib pip install dlib更省事的路线是用 conda 的预编译包,不需要本地编译器:
conda install -c conda-forge dlib注意:如果第 2 条命令报错 ModuleNotFoundError: No module named 'dlib',同时又看到一堆 CMake 相关的报错,说明是编译环节的问题,直接去按第 4 章的避坑引导处理。装完之后验证一下:
python -c "import cv2, dlib, numpy; print(cv2.__version__, dlib.__version__)"能看到两个版本号输出,说明环境这块已经过关了,可以进入主程序运行阶段。
3.3 运行主程序与阈值参数调整
环境就绪后,进入项目目录,确认四个核心文件都在同一个目录下。其中 shape_predictor_68_face_landmarks.dat 必须在当前目录或者路径被正确指定,否则运行时直接抛 RuntimeError。启动命令很简单:
python driverFatigue.py运行之后,摄像头灯亮起,画面窗口会显示实时视频,并且用矩形框标出人脸,在脸上绘制 68 个关键点。在眼睛区域会显示当前 EAR 值,嘴巴区域显示 MAR 值。当检测到闭眼超过连续帧数,或者 PERCLOS 超标时,画面顶部会显示“疲劳驾驶”的中文警告,同时触发蜂鸣声。
主程序头部一般有一个参数区,这就是整个系统最需要动手调的地方。典型参数长这样:
# driverFatigue.py 内的参数区示例,按实际文件位置为准 EAR_THRESHOLD = 0.25 # 眼睛纵横比低于此值视为闭眼 CONSEC_FRAMES = 20 # 连续闭眼达到此帧数,记为一次疲劳事件 MAR_THRESHOLD = 0.60 # 嘴巴纵横比阈值,判断打哈欠 PERCLOS_WINDOW = 300 # PERCLOS 统计窗口,单位:帧 SOUND_ALARM = True # 是否启用声音告警参数说明:EAR_THRESHOLD 是所有参数里最敏感的。戴眼镜、眼睛小、离摄像头远近都会影响这个数值,我用过 0.22 到 0.28 的区间。CONSEC_FRAMES 决定“连续闭眼多久才算犯困”,别设太低,否则普通眨眼都会被误报,也别设太高,否则人已经睡着了好几秒才报警。PERCLOS_WINDOW 是统计窗口长度,300 帧在 30fps 下对应 10 秒,这个窗口要跟 CONSEC_FRAMES 配合,是整体疲劳趋势的判定基础。
跑通之后,答辩演示基本没问题了。但真正要拿高分,光会运行还不够,还得知道阈值怎么调、报警逻辑怎么解释,这些在后面的章节里展开。
4. 避坑指南:复现这个项目最容易翻车的五个环节
4.1 dlib装不上:ModuleNotFoundError vs 编译报错
现象:执行pip install dlib时报错,有时直接提示 ModuleNotFoundError: No module named 'dlib',有时是一大段 CMake 编译日志然后失败退出。
原因:dlib 在 Windows 上没有预编译的 wheel 时,pip 会从源码编译,而编译需要 cmake 和一个完整的 C++ 编译器。如果系统只有 Python 自带的编译器,或者 VS 版本不对,编译过程就会在生成 C++ 项目文件阶段中断。
解决:先安装 Visual Studio Build Tools,注意勾选“使用 C++ 的桌面开发”工作负载,然后pip install cmake,再重新pip install dlib。不想折腾编译器的话,直接用conda install -c conda-forge dlib,用预编译的二进制品,省掉整个编译过程。我的习惯是能 conda 就 conda,毕设阶段不要在编译上赌运气。
4.2 shape_predictor加载失败:那个100MB的dat文件
现象:运行时提示RuntimeError: Unable to open shape_predictor_68_face_landmarks.dat,或者能打开但报文件格式损坏。
原因:两种情况最常见——文件路径不对,程序在相对路径下找不到模型文件;或者下载不完整,这个 dat 文件有约 100MB,下载过程断掉之后留下一个大小缩水的文件,加载时自然报错。
解决:检查文件大小,正常应该在 99MB 左右,只有几十 MB 说明下载不完整,重新下载覆盖。代码里把相对路径改成绝对路径,或者确保运行命令的当前目录就是项目目录。一个小技巧是启动时打印一下os.path.abspath("shape_predictor_68_face_landmarks.dat"),确认程序实际找的路径。
4.3 中文乱码:OpenCV的putText不认中文
现象:画面里的中文告警文字变成了方块或者问号,英文和数字正常显示。
原因:OpenCV 的 cv2.putText 底层只支持英文字符集,画中文时编码对不上,输出就是一堆乱码。这个项目带 simsun.ttc 就是为了解决这个问题,但得有代码配合才生效。
解决:项目里常用的方案是用 PIL 绘制中文,再转换回 OpenCV 的 BGR 格式。核心代码逻辑是这样:
from PIL import Image, ImageDraw, ImageFont def draw_text_cn(img, text, pos, size=24): # OpenCV 的 putText 画不了中文,改用 PIL 绘制后转回 BGR pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(pil_img) font = ImageFont.truetype("simsun.ttc", size) draw.text(pos, text, font=font, fill=(0, 0, 255)) return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)参数说明:fill 是文字颜色,我这里用 (0, 0, 255) 对应红色。字体路径是相对路径,如果运行目录变了,同样需要用绝对路径。注意每次绘制都会做两次色彩空间转换,帧率会受一点影响,但英文告警和中文告警对照着写,让原有的 putText 和 PIL 绘制并存就行。
4.4 摄像头打不开:VideoCapture(0)总返回False
现象:程序启动后没有画面,控制台输出显示读取失败,或者画面黑屏但程序不退出。
原因:最常见的是摄像头索引不对。cv2.VideoCapture(0) 里的 0 表示默认摄像头,笔记本内置摄像头一般用 0,但外接 USB 摄像头可能占用索引 1 或 2。还有可能是摄像头被其他软件(微信、腾讯会议)占用,OpenCV 拿不到流。
解决:先写一个三行的测试脚本,用循环遍历索引 0 到 2,看哪个能打开:
import cv2 for idx in range(3): cap = cv2.VideoCapture(idx) if cap.isOpened(): print(f"camera index {idx} works") cap.release()搞定索引问题后,再把 driverFatigue.py 里的 VideoCapture 参数改成对应数字。如果还是打不开,检查系统的相机隐私设置,Windows 里给“桌面应用”授权访问摄像头。
4.5 频繁误报:先调阈值还是先调光线
现象:人明明睁着眼,程序却不停地报疲劳;或者正常说几句话,哈欠一直触发。
原因:误报基本是两个来源——阈值和输入源质量。EAR_THRESHOLD 取值偏高时,眼睛稍微眯一点就被判定为闭眼;光线太暗或者逆光时,dlib 检测人脸后关键点落在阴影区域,EAR 会被压到阈值以下。反差大的环境还会让脸部关键点抖动,单帧数值忽高忽低。
解决:调整优先级有讲究——先把摄像头摆在光线充足、人脸正对的位置,确保关键点稳定不跳动,再谈调阈值。我一般会打印一长段清醒状态下的 EAR 数据,取其中的最小值向下让 20% 作为阈值。比如清醒时 EAR 最低到过 0.28,阈值就设在 0.22 到 0.25 之间,留出合理余量,而不是拍脑袋定死 0.25。光线问题永远是第一位的,光线不对,任何阈值都救不回来。
5. 进阶验证:用视频文件给疲劳检测做一次参数标定
论文和答辩材料里,光有“能运行”是不够的,老师大概率会问“你的效果怎么验证,参数为什么取这个值”。最实用的验证手段是把摄像头输入换成视频文件,通过控制变量法做参数标定。
先把输入源切换成视频。在 driverFatigue.py 里找到摄像头初始化那一行,把参数从摄像头索引改成视频文件路径:
# 原:cap = cv2.VideoCapture(0) cap = cv2.VideoCapture("road_test.mp4")建议用一段 40 到 60 秒的驾驶模拟视频,要求包含正常的睁眼驾驶、频繁眨眼、持续闭眼和打哈欠这几个阶段。把视频播放一遍,记录输出日志里每一个 EAR 和 MAR 的值。然后用一个小脚本统计清醒段和疲劳段的数值分布:
import numpy as np # 假设已经采集了清醒段和疲劳段各 500 帧的 EAR 值 alert_ears = np.array([...]) # 清醒段数据 drowsy_ears = np.array([...]) # 疲劳段数据 print("清醒段 EAR 均值: %.3f, 最小值: %.3f" % (alert_ears.mean(), alert_ears.min())) print("疲劳段 EAR 均值: %.3f, 最大值: %.3f" % (drowsy_ears.mean(), drowsy_ears.max()))这一步跑完,阈值就不是猜的了。取清醒段最小值和疲劳段最大值之间的中点作为 EAR_THRESHOLD,再结合疲劳段的闭眼时长来定 CONSEC_FRAMES。比如疲劳段里单次闭眼最短持续了 15 帧,那 CONSEC_FRAMES 就设在 15 到 20 之间,既能捕捉到真实闭眼,又不会把 1 到 3 帧的眨眼算进去。PERCLOS 的验证同理,在统计窗口内人为制造一次持续闭眼,确认报警延迟和触发时机符合预期。
把验证过程整理成数据表格,答辩时直接展示“参数是基于实测标定的”,比说“我试了几个数发现这个效果最好”要让人信服得多。这套标定方法也适用于后续扩展,比如把 EAR/MAR 特征收集起来,接入一个简单的分类模型,就能从规则判断升级成基于状态序列的疲劳分级。我从那以后每跑一个 OpenCV 相关的毕设项目,都会强制自己先走一遍视频标定再上摄像头,这个习惯帮我避开了不少现场演示的翻车环节。希望帮到你。
本文还有配套的精品资源,点击获取