
简介这是一份基于Python的驾驶员疲劳检测系统完整源码面向毕业设计、课程实践及嵌入式视觉入门学习者解决疲劳驾驶实时监测中的面部关键点定位、眼睛闭合判断与报警联动等典型问题。资源共45个文件压缩包约135.75MB其中20个xml用于工程配置与界面布局10个py为主要算法与逻辑代码另有模型参数dat、提示音频mp3及说明文档md/txt结构清晰便于按模块阅读。已有61人学习下载适合作为计算机视觉结合单片机的综合项目参考。源码涵盖OpenCV视频流捕获与预处理、Dlib人脸特征点检测、疲劳阈值判定逻辑并预留单片机串行通信接口可对接外部报警硬件同时提供项目配置文件与说明文档能帮助理解从图像采集到状态输出的完整流程对掌握Python编程、OpenCV/Dlib应用、实时系统设计及软硬件协同开发均有直接参考价值。1. 基于 Python 的驾驶员面部特征疲劳检测这套毕设源码到底在做什么这套“基于Python的驾驶员面部特征的疲劳检测系统”源码把 OpenCV 视频采集、Dlib 关键点定位、EAR 眼部开合度计算串成一条完整链路是计算机视觉方向毕业设计的典型样本。它解决的具体问题很直接用普通摄像头实时判断驾驶员是否闭眼、低头在危险发生前触发报警。适合正在做毕设、想快速跑通检测流程的学生也适合想了解疲劳检测落地边界的算法新人。源码里给的并不是训练脚本而是一个拿现成模型和 OpenCV 函数组合出来的实时检测系统重点不在模型怎么训练而在怎么把采集、检测、判定、报警四个环节稳定跑起来。我第一次拆它时最意外的是检测逻辑本身并不复杂真正的坑全集中在环境依赖和阈值参数上。后面就按这个顺序展开——先讲原理与源码结构再给环境搭建和运行步骤最后把复现路上的坑一次列全。2. 检测原理与源码结构从 OpenCV 视频流到 EAR 疲劳判定2.1 OpenCV 与 Dlib 的分工视频采集、HOG 检测与 68 点定位这个项目第一眼看上去会觉得复杂拆开看其实只有两条主线OpenCV 负责“看”Dlib 负责“认”。“看”的部分包括用 VideoCapture 从摄像头读帧、把彩色帧转成灰度图、做直方图均衡化来缓解光照波动“认”的部分才是检测的关键。Dlib 的 get_frontal_face_detector() 是一个基于 HOG方向梯度直方图的检测器它先在灰度图上提取梯度特征再用内置分类器判断窗口里是不是人脸返回一个包含坐标和大小的矩形框。拿到人脸框之后shape_predictor 加载一个训练好的 68 特征点模型把人脸轮廓细化为 68 个二维坐标点。选 HOG 加 68 点模型而不是直接上深度学习是这套源码适合做毕设的核心原因。HOG 特征不依赖 GPU纯 CPU 上就能跑到实时帧率68 点模型是现成的省掉了训练数据收集这个最耗时的大头。也就是说你拿到源码后不需要碰模型训练只需要把注意力放在视频流读取、关键点坐标提取和阈值逻辑上这三个环节都能直接观测和调试改一处就能看到一处效果。68 点模型里每个编号对应脸上的固定位置。眼睛相关的是左眼 36~41、右眼 42~47鼻尖是 30下巴是 8。我一般会把眼睛那 12 个点先单独打印出来确认顺序再进入后续计算。这里有个常见误解68 点模型给出的是“关键点”不是“眼睛轮廓”它没法直接画出椭圆但可以用这些点的相对位置算几何比例下一节的 EAR 公式就是这么来的。2.2 疲劳判定的核心EAR 公式与持续帧计数怎么把 12 个眼部关键点压缩成一个能判断疲劳的标量工程上最常用的是 EAREye Aspect Ratio眼睛纵横比。先看代码def eye_aspect_ratio(eye): # eye 是传入的 6 个关键点顺序按 dlib 输出p1 到 p6 # 垂直方向距离p2-p6、p3-p5 A np.linalg.norm(eye[1] - eye[5]) B np.linalg.norm(eye[2] - eye[4]) # 水平方向距离p1-p4即内外眼角 C np.linalg.norm(eye[0] - eye[3]) # EAR 平均垂直距离 / 水平距离睁眼约 0.25~0.35闭眼趋近于 0 ear (A B) / (2.0 * C) return ear逻辑与参数说明这段代码是整套系统的度量核心。eye 参数是长度为 6 的 numpy 数组每个元素是一个二维坐标顺序必须严格对应 dlib 输出的眼部关键点编号。EAR 的物理意义是眼睛开合程度的归一化比值睁开眼睛时上下眼皮的垂直距离相对稳定EAR 通常在 0.25 以上闭眼瞬间垂直距离被压缩到接近 0EAR 会掉到 0.1 以下。由于它是垂直距离和水平距离的比值人脸靠近或远离摄像头时两个方向会同比缩放所以 EAR 对物距不敏感戴眼镜时只要不遮瞳孔这个值也能维持稳定。单帧的 EAR 有抖动所以疲劳判定不能只看一帧。常见做法是在主循环里做持续帧计数EAR 连续低于阈值的帧数超过设定值才触发报警。while True: ret, frame cap.read() # 每一帧先转灰度图给 dlib 的 HOG 检测器降低计算量 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) # 第二个参数 0 表示不做金字塔缩放 for face in faces: landmarks predictor(gray, face) # 左眼 36~41右眼 42~47按 dlib 的 68 点固定编号取坐标 left_eye np.array([[landmarks.part(i).x, landmarks.part(i).y] for i in range(36, 42)]) right_eye np.array([[landmarks.part(i).x, landmarks.part(i).y] for i in range(42, 48)]) ear (eye_aspect_ratio(left_eye) eye_aspect_ratio(right_eye)) / 2.0 # 低于阈值则累加帧数否则清零 if ear EAR_THRESHOLD: frame_count 1 else: frame_count 0 # 帧数阈值达到后进入报警分支 if frame_count FRAME_THRESHOLD: alarm_trigger()参数含义detector(gray, 0) 里的 0 是图像金字塔层数表示只在原始分辨率下检测如果摄像头离人远、脸在画面里很小可以提高到 1 或 2但计算时间会成倍增加。EAR_THRESHOLD 的常见起点是 0.2FRAME_THRESHOLD 的取值要和摄像头帧率联动来估算实际时间30FPS 下取 20大约代表闭眼持续 0.67 秒后报警取 30 则是 1 秒。这两个值建议先按默认跑通再用录制视频做回归调整第 5 章会单独讲。除眼睛闭合外源码里通常还带一个头部姿态分支利用鼻尖 30 点和下巴 8 点的位置变化判断驾驶员是否低头。我的经验是这个分支只作为辅助证据不参与主报警逻辑复现时先注释掉能避免双路判定互相干扰。2.3 源码目录与主循环流程模型文件、入口脚本与启动顺序解压 zip 之后目录里能看到几类东西。.idea 是 PyCharm 的工程配置记录解释器路径和运行参数直接用 PyCharm 打开时能自动还原环境Fatigue-detection-system-branch 是主源码目录偶尔还会出现“新建文本文档.txt”这类随手建的临时文件内容一般是环境安装备注或思路记录值得先打开看一眼。目录/文件常见作用.idea/PyCharm 工程配置记录解释器与运行环境Fatigue-detection-system-branch/主源码目录入口脚本和依赖清单所在models/shape_predictor_68_face_landmarks.dat68 点模型文件需自行放入requirements.txt依赖清单固定 opencv-python、dlib、numpy 等版本启动顺序建议拆成三步验证第一步单独加载模型并读一张图确认 68 点能画出来第二步打开摄像头连续检测观察人脸框是否稳定第三步再加上报警分支。这样做的好处是让每一步的失败边界清晰——先是文件路径错误再是摄像头索引问题最后才是阈值误报三个问题不会搅在一起排查。主循环的结构就是上面那段代码初始化检测器和预测器打开摄像头读帧、转灰度、检测人脸、提取关键点、算 EAR、计数、触发报警。整个过程只依赖 OpenCV 和 Dlib不涉及其他框架所以入口脚本用命令行就能直接跑不需要在 IDE 里做额外配置。3. 本地复现与参数调优环境搭建、运行与阈值设置3.1 环境搭建Python 版本选择与依赖安装顺序这套源码对 Python 版本和依赖版本都比较敏感。dlib 在 Python 3.9 以上时Windows 上经常没有预编译的 wheelpip 会尝试从源码编译一旦缺 Visual Studio 生成工具就报 C 错误所以我一般建议用 Python 3.8 建独立虚拟环境。先建环境再装依赖顺序固定为先 numpy再 opencv-python最后 dlib。# 创建独立虚拟环境并激活 python -m venv fatigue_env # Windows 激活命令Linux/macOS 用 source fatigue_env/bin/activate fatigue_env\Scripts\activate # 固定版本安装避免新版 dlib 无预编译 wheel 的情况 pip install numpy1.21.6 pip install opencv-python4.5.5.64 pip install dlib19.22.0 # 如果要做串口报警再装一个轻量串口库 pip install pyserial3.5参数说明numpy 1.21.6 对应 Python 3.8 的稳定兼容区间opencv-python 4.5.5 是实测摄像头调用比较稳定的版本4.8 之后某些摄像头型号在 VideoCapture 里会出现解码异常所以不建议一上来就装最新版。dlib 19.22.0 是和 68 点模型配套最常用的版本。先装 numpy 再装 dlib是为了让 dlib 安装时能检测到 numpy 的 include 路径省掉一长串头文件报错。提示dlib 的 68 点模型不在下载包里需要单独获取 shape_predictor_68_face_landmarks.dat 并放到 models 目录或入口脚本同级。模型约 96 MB体积大是很多“解压后跑不起来”案例的头号原因。3.2 入口脚本运行摄像头索引、模型路径与阈值参数跑起来之前先确认三件事摄像头索引、模型路径和 EAR 阈值。笔记本自带摄像头一般是 0外接 USB 摄像头常常变成 1甚至被虚拟摄像头软件占成 2。我习惯写一个 3 行的小循环把可用索引直接试出来import cv2 # 从 0 到 2 逐个试探打印每个索引的打开状态 for idx in range(3): cap cv2.VideoCapture(idx) ret, frame cap.read() # ret 为 True 说明能读到帧这个索引就是可用的摄像头 print(fcamera {idx}: isOpened{cap.isOpened()}, read{ret}) cap.release()逻辑说明cap.read() 返回的 ret 比 isOpened() 更可靠因为有些索引能打开但取不到帧。找到可用的索引后把它改到源码入口的摄像头配置变量里。如果源码入口本身没有参数解析我在处理这类毕设源码时习惯给它加一个 argparse 入口配置把阈值也暴露成参数省得每次调参都翻代码。常见参数长这样# 常见做法是提供四个命令行参数方便不打开代码直接调值 parser argparse.ArgumentParser() parser.add_argument(--camera, typeint, default0, help摄像头索引外接摄像头通常改为 1) parser.add_argument(--shape-predictor, defaultmodels/shape_predictor_68_face_landmarks.dat, helpdlib 68 点模型文件路径) parser.add_argument(--ear-threshold, typefloat, default0.2, helpEAR 低于该值视为闭眼) parser.add_argument(--frames-threshold, typeint, default20, help连续闭眼帧数30FPS 下 20 帧约 0.67 秒) args parser.parse_args()参数说明camera 对应摄像头设备号shape-predictor 指向模型文件路径建议用相对路径避免换机器后绝对路径失效ear-threshold 是闭眼判定阈值默认 0.2frames-threshold 是疲劳判定的持续时间。这两个值一定要结合摄像头实际帧率去调脱离帧率只调帧数调出来的是没有意义的数字。跑起来之后画面里会画出人脸框并在眼睛周围显示关键点一旦判定疲劳常见做法是用红色文字叠加提示或者调用系统 beep 声音。报警分支里我建议加一个去抖同一人 2 秒内只触发一次防止连续低头动作刷爆报警。3.3 可选硬件扩展串口报警与单片机联动如果毕设要求体现软硬结合而不是只在屏幕上画框最常见的方式是加一个串口报警分支疲劳标志置位时通过串口向 STM32 或 51 单片机发送报警帧由单片机驱动蜂鸣器或震动模块。Python 端只需 pyserialimport serial # 串口号以设备管理器为准Linux 下是 /dev/ttyUSB0 ser serial.Serial(COM3, 9600, timeout1) # 主循环报警分支触发时执行 if fatigue_flag: # 带换行结尾方便单片机按行解析协议 ser.write(bALARM\r\n) fatigue_flag False # 复位防止重复触发参数说明波特率 9600 是单片机项目里最常用的默认值数据位 8、无校验、停止位 1 是 serial.Serial 的默认配置不用显式写。写入的内容必须是字节流所以要加 b 前缀或 encode(utf-8)。单片机端按行读取收到 ALARM 就拉高一个 GPIO想做成两级报警可以把协议扩展成 LEVEL1 和 LEVEL2分别对应闭眼 0.67 秒和 2 秒的疲劳等级。这个扩展对 CPU 负载几乎没影响因为串口写入是异步的重点是控制触发频率。我一般会在报警后加 5 秒冷却再把疲劳标志复位这样蜂鸣器不会在持续疲劳状态下被反复拉响。4. 避坑指南毕设复现中的五个翻车现场4.1 摄像头打不开或画面黑屏现象终端没有报错但 OpenCV 窗口黑屏或者 cap.isOpened() 返回 False程序直接跳到退出分支。原因摄像头索引固定写死。笔记本刚开机摄像头驱动还没就绪索引 0 失效外接 USB 摄像头接在扩展坞上Windows 分配的索引不固定可能是 1 或 2。部分远程控制软件、会议软件也会占用摄像头导致画面黑屏。解决先在 0~2 三个索引间循环试探找到能读到帧的索引再写回配置。黑屏的话检查系统隐私设置里摄像头的调用权限关掉后台会议软件后重跑。4.2 dlib 编译失败或模型文件加载崩溃现象pip install dlib 刷大片 C error以 error C2039、fatal error C1083 这类 MSVC 报错为主另一类是程序跑到 predictor 相关行时直接退出终端没有 Python 异常栈。原因Python 3.9 找不到 dlib 的预编译 wheelpip 自动走源码编译但系统里没有 Visual Studio Build Tools 和 CMake。模型文件缺失或路径写错时dlib 的 C 层加载失败不会抛 Python 异常而是直接退出。解决按 3.1 用 Python 3.8 避坑编译问题坚持高版本就先装 VS Build Tools 的 C 生成工具和 CMake。模型文件统一放在入口脚本同级目录或 models 子目录用相对路径引用。4.3 EAR 阈值适应不了光照变化现象白天在实验室正常晚上或地下车库未开灯时误报暴增有时人明明睁着眼系统判定闭眼。原因EAR 虽然是几何比例但嘴型动作和眼镜反光会干扰关键点收敛低照度下 6 个关键点整体偏移垂直方向的 4 个点抖动被比例放大。戴墨镜时镜框边缘会被误识别成眼皮。解决预处理阶段把普通灰度图替换成 CLAHE 局部直方图均衡对逆光和阴影场景有改善。阈值不要全局固定分别记录清醒和闭眼状态下的 EAR取两段数据的中位作为新起点。墨镜场景建议直接放弃这是物理限制不是阈值能解决的。4.4 视频路径含中文导致读取失败现象用 cv2.VideoCapture 打开“桌面\毕设资料”目录下的测试视频ret 恒为 False把文件挪到英文路径下就正常。原因OpenCV 旧版 VideoCapture 在 Windows 上用窄字符读文件中文路径转码失败。dlib 的模型文件同样存在这个隐患。解决项目根目录用纯英文比如 C:\fatigue-system。需要读入外部视频时先复制到源码目录下不含中文的子目录里再作为参数传入绕开编码问题。4.5 串口报警不响应或误触发现象终端打印“报警已发送”单片机这边没有反应有时偶尔响一下像接触不良。另一种是蜂鸣器持续响停不下来。原因串口号填成虚拟 COM 口实际设备是另一个号波特率不匹配单片机写 115200Python 端配 9600TX/RX 两头接反。持续响的原因是 Python 端没有复位疲劳标志和设置冷却时间。解决在设备管理器里确认 COM 号两边波特率统一连线按 TX 接 RX、RX 接 TX、GND 共地。程序端在触发后先清疲劳标志再延时 2~5 秒单片机才能收到完整的报警节奏。5. 进阶验证用录制视频回归测试疲劳检测阈值5.1 准备测试片段与最小标注调参最怕用自己当测试对象对着摄像头睁眼闭眼凭感觉调换个环境就翻车。后来我把验证改成录制视频回归找人用驾驶座视角录 3~5 分钟视频前半段正常驾驶动作后半段做点头和闭眼 2 秒的动作。录完按 1 秒间隔给每一段打标签“0”表示清醒“1”表示疲劳存成一个 CSV。这一步不用写复杂标注工具直接在回放视频时计时打点即可关键是标签要和帧号对齐。5.2 批量回放 EAR 序列用阈值曲线选最优参数把入口脚本改成 video 模式核心改动只有两处把 VideoCapture(0) 换成视频文件路径把每帧算出的 EAR 写进列表结束后落盘成 CSV。# 从摄像头源改成视频源逐帧记录 EAR 轨迹 cap cv2.VideoCapture(test_videos/driver_01.mp4) ear_list [] while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: landmarks predictor(gray, face) left_eye np.array([[landmarks.part(i).x, landmarks.part(i).y] for i in range(36, 42)]) right_eye np.array([[landmarks.part(i).x, landmarks.part(i).y] for i in range(42, 48)]) ear (eye_aspect_ratio(left_eye) eye_aspect_ratio(right_eye)) / 2.0 # 用帧号做时间轴写进 CSV 后可以在 pandas 里直接回溯 ear_list.append((cap.get(cv2.CAP_PROP_POS_FRAMES), ear)) with open(ear_trace.csv, w) as f: f.write(frame,ear\n) for frame_id, ear in ear_list: f.write(f{frame_id},{ear:.4f}\n)代码说明cap.get(cv2.CAP_PROP_POS_FRAMES) 返回当前帧号用它作为时间轴。ear 落盘后阈值扫描可以不依赖 OpenCV在 pandas 里对 ear 做滑动平均再令 EAR_THRESHOLD 从 0.15 到 0.25 按 0.01 步进对比 CSV 标签算检出率和误报次数。下面是一段 3 分钟样本跑出来的参考结果实际数值会随摄像头角度、光照和动作幅度变化EAR 阈值连续帧数检出率误报次数0.181591%50.201588%30.202586%10.222579%0从这组数字能看出一个直接权衡阈值拉高、帧数放宽误报数量下降但检出率也相应走低阈值放低、帧数收窄检出率上来了误报又难以接受。真实布置时我会用这段数据画一条 F1 曲线取最优点如果只是毕设演示取检出率和误报率的交叉处也就是表格里 0.20/25 这一档效果最划算。提示EAR 阈值扫描不依赖 OpenCV把 ear_trace.csv 导入 pandas 后可直接做批量回溯快速定位具体是哪几帧误报再回到视频对应位置看究竟是人脸检测漂了还是闭眼动作太快。从那以后我每换一次环境或调一版阈值都强制走一遍这条录制视频回归流程十分钟就能看出参数合不合理再也不会在答辩前夜被摄像头角度和光线打败。希望帮到你。本文还有配套的精品资源点击获取