简介基于Python的疲劳驾驶检测系统课程设计项目面向计算机、人工智能、自动化等专业的在校学生也可作为毕业设计或课程设计的完整参考。系统通过摄像头或视频输入利用OpenCV与dlib库对人脸关键点实时定位综合眼睛纵横比、嘴部张合程度和头部姿态等多维特征判断驾驶员的疲劳状态并借助PyQt5搭建可视化界面实现检测结果实时显示与异常声音提醒。资源包共29个文件整体大小约125MB主要包含可直接运行的Python源码.py、dlib人脸关键点模型.dat、界面截图.png、多段效果演示视频.mp4以及说明文档.md/.txt其中演示视频便于直观了解系统运行效果模型用于特征点定位文档辅助理解项目结构与运行方法。已有265人浏览学习。项目中附带了完整文档、测试通过的代码和运行示例既可直接作为高分课程设计交付也可在其基础上二次开发用于学习实时人脸检测、状态识别和PyQt5界面开发等关键技术还可扩展生成驾驶员安全提醒类应用。1. 用python把疲劳驾驶检测系统做成一个“看得见”的界面工程疲劳驾驶检测是课程设计里的常青树题目但大部分提交版本只是命令行里输出一行“您已疲劳”的字符串。这类项目拿高分的分水岭通常不在于用了多高级的神经网络而在于两件事一是能否用python把检测逻辑完整跑通二是能否用一个不卡顿的ui界面把闭眼过程实时演示出来。你不需要激光雷达不需要脑电设备只需要一个普通USB摄像头和一台能运行Dlib或MediaPipe的电脑就能搭建一套可演示、可答辩、可扩展的检测系统。这里的核心思路是用摄像头逐帧捕获人脸提取眼周关键点坐标计算一个叫做眼睛纵横比EAR的数值再通过连续低值帧数判断闭眼持续时长最后把状态映射到PyQt5界面上。整条链路里python既承担算法实现也负责界面线程调度、数值标定和打包分发。写这套系统时最值得投入精力的不是孤立的闭眼判断而是“视频流、检测线程、UI绘制”三者之间的协作方式。理解了这一点哪怕只拿到一份初级源码也能顺着自己的需求把功能补全。2. python疲劳驾驶检测的关键点提取与指标实现2.1 用眼睛纵横比把“疲劳”变成数值疲劳驾驶检测的第一步不是训练模型而是把“眼睛闭着”这件事翻译成一个可计算的数值。常见做法是采用Dlib的68点人脸关键点模型其中左眼和右眼分别由6个关键点描述通过计算垂直方向距离与水平方向距离的比值就得到眼睛纵横比EAR。import cv2 import dlib import numpy as np def eye_aspect_ratio(eye): # eye是一个包含6个坐标点的numpy数组 vertical_1 np.linalg.norm(eye[1] - eye[5]) vertical_2 np.linalg.norm(eye[2] - eye[4]) horizontal np.linalg.norm(eye[0] - eye[3]) return (vertical_1 vertical_2) / (2.0 * horizontal) LEFT_EYE_POINTS list(range(36, 42)) RIGHT_EYE_POINTS list(range(42, 48))这段代码先把眼睛区域的两条垂直边缘距离取平均再除以眼裂宽度。当人正常睁眼时EAR通常稳定在0.25到0.35之间闭眼瞬间上下眼睑距离接近零EAR会掉到0.2以下甚至逼近0.05。这个比例是相对距离所以摄像头装得远近不会彻底改变数值区间但不同分辨率、不同人脸形状会产生整体偏移这也是为什么后面必须要做阈值标定。注意EAR只是单帧数值不能拿一次低于阈值就判定疲劳。需要用滑动窗口统计“EAR低于阈值的持续时长”否则眨眼瞬间就会误报一次疲劳。2.2 选Dlib还是MediaPipe课程设计场景下的取舍很多初学者会在Dlib和MediaPipe之间犹豫。两者都能提取眼睛关键点但侧重点不同。如果在课程设计里需要解释原理、展示源代码Dlib的68点模型反而更友好因为眼周索引固定、资料多、中间步骤容易可视化MediaPipe的关键点更密集但模型封装程度高想画中间结果需要额外理解人脸网格坐标体系。对比项Dlib 68点MediaPipe FaceMesh模型体积约60MB需单独下载权重文件随pip包分发无需单独下载安装难度需要CMake和C编译环境pip直接安装但旧版Python兼容性要确认关键点数量68468CPU占用中等普通笔记本可流畅运行推理耗时略高低端CPU掉帧明显适合场景课堂答辩、源码讲解、老机器部署需要更精细嘴部动作的项目我一般建议以Dlib作为主实现原因是答辩老师大概率会问“68个点是怎么定位的”这时你可以直接说明每个序号对应的面部位置而不是抛出一个人脸网格。MediaPipe则更适合当作后期优化方向在文档里留一个对比说明反而显得工作量更饱满。2.3 最小实现摄像头实时采集加疲劳指标输出在写UI之前先用命令行程序把检测链路打通这是最稳妥的顺序。下面这段python源码实现了完整的“采集—检测—计算EAR—判断闭眼”流程还没有任何界面代码便于单独验证算法是否正常。import cv2 import dlib import numpy as np # 加载检测器和关键点模型 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) def get_ear(shape, point_indices): points np.array([[shape.part(i).x, shape.part(i).y] for i in point_indices]) return eye_aspect_ratio(points) cap cv2.VideoCapture(0) close_counter 0 while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) if len(faces) 0: # 没检测到人脸时不累计闭眼帧 close_counter 0 cv2.putText(frame, No Face, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) else: shape predictor(gray, faces[0]) left_ear get_ear(shape, LEFT_EYE_POINTS) right_ear get_ear(shape, RIGHT_EYE_POINTS) ear (left_ear right_ear) / 2.0 if ear 0.20: close_counter 1 else: close_counter 0 # 连续20帧低于阈值约等于闭眼0.7秒 state CLOSED if close_counter 20 else OPEN cv2.putText(frame, fEAR: {ear:.3f} State: {state}, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(Driver Monitor, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里的模型文件需要先和脚本放在同一个目录。detector返回的是人脸矩形框列表predictor接收灰度图和人脸框后返回关键点对象再用shape.part(i).x和.y读取坐标。close_counter本质上是一个帧数计数器用来替代单帧判定。这里面有三个参数最容易影响后续UI效果cv2.VideoCapture(0)的摄像头编号多摄像头机器上常需要改成1或2detector(gray, 0)的第二个参数表示图像金字塔上采样次数想要更灵敏检测远处小脸可以调整为1但CPU消耗会上升close_counter 20假设摄像头帧率约为30fps如果实际只有15fps判定时长就变成了1.3秒需要按真实帧率缩放。提示运行这个脚本前先确认shape_predictor_68_face_landmarks.dat文件已下载。这份模型文件比较大很多课程设计源码包只给了代码没给权重导致运行时直接报错。建议把路径写成一个独立变量方便后续打包时统一处理。3. 用PyQt5搭建疲劳驾驶检测的ui界面与实时刷帧3.1 先划分线程边界再画界面PyQt5界面的常见布局是左侧占据较大区域显示摄像头实时画面右侧放一个状态面板包含当前EAR数值、闭眼帧数、疲劳等级和一句文字提示。看起来简单但真正让界面卡顿的往往不是绘制本身而是把图像处理和模型推理直接塞进了UI主线程。注意UI主线程负责事件循环和控件重绘一旦在里面执行cv2.imread、Dlib人脸检测这类耗时操作界面就会表现为拖不动、最小化后恢复慢、视频画面一帧卡好几秒。这是“ui界面卡顿”最常见的成因和PyQt版本没有直接关系。正确的做法是让检测逻辑跑在一个独立的QThread里线程内部完成摄像头读取、人脸检测、EAR计算然后通过信号把处理完的画面和状态信息传回主线程主线程只负责更新QLabel和文字控件。3.2 用QThread刷帧的完整骨架下面整理的是一个可独立运行的线程类骨架直接套接在PyQt5窗口里即可。这里把检测频率限制在约30ms一帧避免摄像头缓冲堆积。from PyQt5.QtCore import QThread, pyqtSignal import cv2 import dlib import numpy as np class DetectThread(QThread): # 信号参数画面帧、状态字典 frame_ready pyqtSignal(object, dict) def __init__(self, predictor_path, parentNone): super().__init__(parent) self.detector dlib.get_frontal_face_detector() self.predictor dlib.shape_predictor(predictor_path) self.running True def run(self): cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) close_counter 0 while self.running: ret, frame cap.read() if not ret: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces self.detector(gray, 0) state_info {ear: 0.0, state: NO_FACE, close_counter: 0} if len(faces) 0: shape self.predictor(gray, faces[0]) left_ear self.eye_aspect_ratio(shape, list(range(36, 42))) right_ear self.eye_aspect_ratio(shape, list(range(42, 48))) ear (left_ear right_ear) / 2.0 if ear 0.20: close_counter 1 else: close_counter 0 state_info { ear: round(ear, 3), state: CLOSED if close_counter 20 else OPEN, close_counter: close_counter } self.frame_ready.emit(frame, state_info) self.msleep(30) # 约33fps的检测节奏 cap.release() def stop(self): self.running False self.wait()这段代码的逻辑说明frame_ready信号携带两个参数frame是OpenCV的BGR图像state_info是包含各项指标的字典。主线程槽函数收到信号后先把BGR转RGB再显示到QLabel上。msleep(30)是这个设计里的关键参数它决定了检测上限频率也决定了CPU占用。如果去掉这一句线程会无节制读取摄像头反而让系统负载升高并产生画面延迟。在主窗口里关联信号的写法参考如下def update_ui(self, frame, state_info): rgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qt_image QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qt_image).scaled( self.video_label.width(), self.video_label.height(), Qt.KeepAspectRatio)) self.ear_label.setText(fEAR: {state_info[ear]:.3f}) self.counter_label.setText(f闭眼连续帧数: {state_info[close_counter]})这里反复强调线程分离是因为课程设计现场演示时最怕出现“画面卡住”和“界面无响应”两个问题。只要后台线程不直接碰控件主线程不被阻塞PyQt5的ui界面即使连续运行半小时也不会有明显卡顿。3.3 状态映射与告警参数对照表疲劳等级不能只看EAR一个值需要结合连续闭眼帧数和时长联合判断。这里整理了一张参数对照表代码实现时直接按这张表映射状态。状态文本EAR范围连续闭眼帧数(约30fps)UI颜色建议动作正常驾驶EAR 0.25无要求绿色无轻度疲劳0.18 EAR 0.25连续30帧橙色提示“注意休息”中度疲劳EAR 0.18连续45帧红色播放高频提示音闭眼异常EAR 0.18连续90帧深红连续蜂鸣界面闪烁这个表的逻辑是单次眨眼也会造成EAR瞬间下降但正常眨眼持续0.2到0.4秒大约6到12帧不足以触发30帧阈值真正的疲劳闭眼通常会持续1秒以上也就是30帧往上。所以连续帧数比EAR本身更能反映疲劳状态这也是UI界面里“闭眼连续帧数”这个数值存在的意义。4. 疲劳阈值标定、环境适配与课程设计文档落地4.1 阈值不能照搬论文用日志标定你自己的摄像头网上流传的EAR阈值0.20其实来自论文并不保证适配你的摄像头、你的坐姿、你的脸部比例。直接照搬的结果往往是眼睛明明睁着系统却判定闭眼或者闭着眼睛系统毫无反应。更可靠的做法是在运行检测时把每一帧的EAR和状态记录到CSV文件里自己观察数值分布。import csv csv_file open(ear_log.csv, w, newlineTrue) writer csv.writer(csv_file) writer.writerow([frame, ear, state]) frame_id 0 # 将这个写入逻辑插入到检测循环体内 writer.writerow([frame_id, ear, state]) frame_id 1把这个写入逻辑插进第2章的检测循环中然后按三个步骤录制标定数据睁大眼睛直视摄像头保持3秒闭眼2秒再睁开连续眨5次眼。录完后用Excel或pandas打开CSV观察睁眼时段的EAR最小值与闭眼时段的EAR最大值取两者的中间值作为阈值。例如睁眼最低是0.23闭眼最高是0.11那么阈值可以设成0.17。除此之外连续闭眼帧数也要根据实际帧率调整。比较稳妥的做法是不用帧数表示而是记录闭眼起始时间戳用时间差判断闭眼时长这样换摄像头后不需要重新改代码。python标准库time.time()就能实现不需要额外依赖。提示很多课程设计源码包里直接硬编码了“if ear 0.25 and close_counter 48”这类参数遇到演示机帧率不稳定就会误判。把阈值和帧数提成文件开头的大写变量既是好习惯也能在答辩时展示你对参数体系的理解。4.2 光照、侧脸与低帧率三个高频踩坑点人脸检测对光照非常敏感。教室或实验室的顶灯通常是从上往下的硬光会在眼镜框下方产生阴影Dlib提取到的关键点会出现毫米级抖动EAR值随之上下跳动。处理办法是检测前对灰度图做直方图均衡化或者把摄像头稍微抬高避免直射光反射进镜头。代价是均衡化操作会增加约2到3毫秒耗时对于30fps的系统来说可以接受。侧脸属于另一个常见问题。Dlib的前置人脸检测器对正脸和轻微转头识别较好但当人低头看手机或大幅侧头时检测器直接返回空列表。这时不能把它当作疲劳信号而要在UI里显示“请正对摄像头”。很多同学把“人脸消失”错误地处理成“眼睑闭合”导致系统疯狂报警答辩时非常尴尬。建议在状态字典里单独设计一个NO_FACE状态和CLOSED区分开。低帧率问题在USB摄像头上尤其明显。普通摄像头在光线充足时能保持30fps但光线变暗后帧率会掉到15fps甚至10fps闭眼1秒只拍到10帧原本设定好的30帧连续阈值就形同虚设。解决办法是把EAR计算和帧率做松耦合——每次进入检测循环时记录当前时间戳用真实间隔累加闭眼时长而不是每帧只加1。import time last_time time.time() close_duration 0.0 # 在检测循环内部 now time.time() delta now - last_time last_time now if ear threshold: close_duration delta else: close_duration 0.0这段代码用delta充当时间权重帧率高时累计快帧率低时累计慢最终判断的是真实闭眼秒数。这个改进非常贴合真实驾驶场景也是答辩时能拉开差距的技术亮点因为它避开了“帧率不同导致参数失效”这一经典追问。4.3 课程设计文档说明要写哪些部分文档通常不是写得越多越好而是要让答辩老师快速找到“你做了什么”和“你怎么做的”。一套能被认可的文档结构包含五个部分需求分析、系统设计、核心实现、测试结果、总结与展望。重点放在核心实现和测试结果上前者给出关键代码片段和参数设计理由后者展示小规模测试数据例如“测试了3位同学共20组睁眼闭眼动作正确识别19组”。测试结果部分建议截取三张图正常状态界面、闭眼触发告警状态界面、无检测到人脸的状态界面。这三张图分别对应表格里最重要的三种状态比贴出一长串代码有说服力得多。文档里还可以附一张参数表说明EAR阈值为什么取0.18、连续闭眼时长为多少秒以及这些值是如何通过CSV日志标定出来的。另外一个容易被忽略的点是运行环境说明。python版本、OpenCV版本、Dlib版本、PyQt5版本、操作系统类型都要写清楚这能避免老师换一台机器演示时出现“无法定位程序输入点”之类的启动报错。项目里建议附带一个requirements.txt答辩前在同一台机器上从零安装验证一次确保没有遗漏依赖。5. 效果演示、打包与跨机复现的验收技巧效果演示的核心目标是让数据动起来让老师直观看到EAR数值随眼睛开合而变化而不是全程对着黑屏终端。现场演示时建议先用一个短脚本把摄像头画面和EAR数值同时展示在屏幕上调整好坐姿后再启动完整UI。一个稳妥的演示顺序是正对摄像头保持正常睁眼嘴上同步解说“现在EAR是0.28”然后闭眼2秒等界面状态从绿色变为橙色再变为红色最后连续眨眼三次展示眨眼计数不误报。这组动作能覆盖正常、闭眼、眨眼三个核心检测能力。如果闭眼2秒没有触发报警不要当场去改代码先检查控制台打印的EAR值是否低于阈值多数情况是摄像头角度和光照把EAR抬高了。打包成可执行文件时需要注意两点一是使用PyInstaller时Dlib的模型文件不会自动打包进exe需要显式设置--add-data参数二是打包后要在没有python环境的机器上测试一次防止缺少VC运行库导致DLL加载失败。建议调试阶段不要加--windowed参数改成控制台模式运行这样能看到报错堆栈很多打包后程序秒退的问题都能通过这条命令暴露出来。如果希望增强演示效果可以在检测线程里增加一个“眨眼次数统计”字段每次EAR从低于阈值恢复到正常值就加1。这个功能不需要额外模型只依靠状态机转换即可实现但会让系统看起来更完整。代码里注意把模型文件和UI资源文件的路径统一改成相对路径这样把项目文件夹复制到任何电脑上都不会出现路径找不到的问题。到这里这套基于python的疲劳驾驶检测系统就同时具备了算法可用性、界面易用性和演示可复现性。本文还有配套的精品资源点击获取