ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python+Dlib的疲劳驾驶检测:人脸关键点与眨眼哈欠识别

基于Python+Dlib的疲劳驾驶检测:人脸关键点与眨眼哈欠识别 简介基于Python与Dlib的驾驶员疲劳检测完整毕设资源面向计算机相关专业学生、毕业设计开发者及人工智能方向初学者。项目以人脸关键点检测为基础综合眼睛开合度、眨眼频率、嘴部张合度、点头频率等数据实时判断驾驶员的注意力集中程度对打哈欠、频繁眨眼、瞌睡点头三类典型疲劳状态进行识别并即时安全提示。压缩包共17个文件包含6个Python脚本用于核心检测与界面逻辑2个ipynb交互式示例便于分步调试另有界面设计文件、测试视频、预训练模型shape_predictor_68_face_landmarks.dat以及可执行安装包整体约85.92MB目录结构清晰便于按模块阅读和二次开发。已有232人学习下载代码经测试运行成功答辩评审平均分达到96分既适合作为毕业设计、课程设计的完整演示也适合新手逐步理解疲劳检测的工程实现。资源内附README说明、可视化界面运行效果图及操作演示可直接运行也支持在此基础上扩展更多功能。1. 基于 Python Dlib 的疲劳检测这个毕设源码包里到底有什么开车犯困这件事比喝酒更隐蔽也更危险。打哈欠、频繁眨眼、不自觉点头这三类面部信号 Dlib 的 68 点人脸关键点模型都能精确捕捉。这套基于 Python Dlib 的驾驶员疲劳检测源码把眨眼、打哈欠、瞌睡点头三个检测模块拆成独立脚本还带可视化界面、模型文件、测试视频和文档说明做毕设、课程设计或者只想快速跑通一个实时检测 demo 的人都能直接用。它的核心逻辑并不玄先用 Dlib 定位人脸 68 个关键点再分别算眼睛开合度、嘴巴开合度和头部姿态变化超过阈值就触发疲劳报警。源码我拆过一遍实测能跑通生成界面用的 wxFormBuilder 安装包和 .fbp 工程文件都打包在内跟着 README 走一遍就能看到效果下面按模块逐个说。2. Dlib 68 点模型与三类疲劳特征的计算原理2.1 模型文件与 68 点坐标分布shape_predictor_68_face_landmarks.dat 是整套检测的地基。这个模型来自 dlib 官方训练好的 HOG 线性分类器级联检测器基于 iBUG-300W 数据集训练模型大小约 99MB能稳定输出人脸的 68 个关键点坐标。资源里 model 目录下带的就是它不需要自己训练下载即用。68 个点的分布是理解后面所有代码的前提0-16 是脸颊轮廓17-26 是眉毛27-35 是鼻梁和鼻翼36-41 是右眼六个点42-47 是左眼六个点48-67 是嘴巴和嘴唇轮廓。关键是眼睛 6 个点和嘴巴轮廓点疲劳检测的三个算法全部围绕这些坐标做几何计算。初次接触的人容易误以为 Dlib 输出的是人脸朝向角度或者瞳孔位置实际上它只输出二维关键点坐标。眼睛开合度、打哈欠张嘴幅度、点头动作都需要自己用坐标差去算。理解了这一层后面看代码就不会觉得逻辑跳。2.2 眨眼检测EAR 眼纵横比的几何原理代码里 eye_detecting.py 的核心是 EAREye Aspect Ratio眼纵横比。这算是对平面几何距离比值的经典应用人眼区域的 6 个关键点垂直方向取两组点算欧氏距离水平方向取一组点算距离两者作比值。from scipy.spatial import distance as dist def eye_aspect_ratio(eye): # eye 是 6 个关键点的坐标列表 # A: 眼角外侧到上眼皮的垂直距离 A dist.euclidean(eye[1], eye[5]) # B: 眼角内侧到下眼皮的垂直距离 B dist.euclidean(eye[2], eye[4]) # C: 水平瞳孔两端的距离 C dist.euclidean(eye[0], eye[3]) # EAR 垂直距离的平均值 / 水平距离 return (A B) / (2.0 * C)这个比值的聪明之处在于它天然抗人脸缩放。摄像头离人远或近水平距离 C 和垂直距离 A、B 会同比例变化比值基本不变。正常睁眼时 EAR 大约在 0.25 到 0.3 之间闭眼时会掉到 0.1 以下。只算单帧 EAR 还不够判断眨眼要结合连续帧。常见做法是设定一个 EAR 阈值比如 0.2连续 N 帧低于阈值记一次眨眼再把眨眼频率和时间分布作为疲劳判断依据。眨眼次数多了、闭眼时长拉长都是疲劳信号。2.3 打哈欠检测MAR 嘴纵横比打哈欠和眨眼是同一个思路换了个部位。代码里 mouth_detecting.py 复用距离比值原理计算 MARMouth Aspect Ratio嘴纵横比。嘴部关键点是 48-67外嘴唇轮廓加内嘴唇轮廓MAR 用的是外部六个点的坐标。def mouth_aspect_ratio(mouth): # mouth 是嘴部 6 个关键点的坐标列表 # A: 上嘴唇外侧到下巴外侧的垂直距离 A dist.euclidean(mouth[2], mouth[10]) # B: 上嘴唇内侧到下嘴唇内侧的垂直距离 B dist.euclidean(mouth[4], mouth[8]) # C: 左右嘴角的水平距离 C dist.euclidean(mouth[0], mouth[6]) return (A B) / (2.0 * C)这里有个容易踩的坑嘴部关键点的索引顺序要对着论文原图核对不同版本的 dlib 关键点定义一致但代码实现里 mouth 列表的截取方式可能不同。我一般在调用前先打印轮廓坐标确认索引对得上再跑检测。打哈欠的判定和眨眼不同它的特征是开口幅度大且持续时间长。普通说话 MAR 会短时间波动哈欠则是 MAR 高于阈值比如 0.5并保持约 1 秒以上。因此代码里一般会设计一个状态计数器MAR 超过阈值就累加帧数低于阈值就清零计数超过设定帧数才触发哈欠报警。2.4 瞌睡点头检测关键点位移与头部姿态点头检测是三个模块里相对复杂的。node_detecting.py 用的是鼻尖关键点相对肩线或耳朵基准线的位移变化来判断低头动作。Dlib 的 68 点中关键点 30 是鼻尖58 到 30 的头颈位置变化能反映俯仰角。一种常见实现是取左右眼距离和鼻尖位置估算俯仰角变化另一种更直接的做法是用头部矩形框的纵向位移。我用过相对稳定的方案是算下颌关键点8 号点相对两个外眼角连线中点的偏移量低头时这个偏移显著增大连续多帧偏移超过阈值且随后恢复就记一次点头。点头判定还有一个时间维度的特征疲劳瞌睡时的点头通常伴随快速低头和快速回正整个过程在 0.5 到 1.5 秒内完成。和持续低头不同它是脉冲式的。代码里一般用滑动窗口统计一段时间内的点头次数超过设定频次比如每分钟 5 次判定为瞌睡。2.5 三类特征融合的综合预警逻辑单独看任何一个指标都可能有误报所以项目的 main.py 会把三个检测模块的结果汇总。EAR 连续低帧数触发眨眼警报MAR 高且持续触发哈欠警报点头计数高频触发瞌睡警报三者独立输出文本提示。3. 源码结构拆解检测模块到可视化界面的调用链3.1 文件职责一览拿到压缩包先别急着跑先认文件。下面是我按职责重新梳理后的清单文件职责main.py主入口组合三个检测模块加载模型和视频源eye_detecting.py眨眼检测模块EAR 计算与眨眼计数mouth_detecting.py打哈欠检测模块MAR 计算与哈欠判定node_detecting.py点头检测模块头部姿态估算与点头计数main_UI.pywxPython 可视化主界面绑定检测逻辑noname.pywxFormBuilder 生成的界面代码111.fbpwxFormBuilder 界面工程源文件UIdemo.ipynb / Test.ipynbJupyter Notebook 演示与测试脚本README.md项目说明与运行指引model/存放 shape_predictor_68_face_landmarks.datimages/ test.mp4 camera.png 123.ico界面图标、测试视频与辅助素材这些文件之间有清晰的依赖三个检测模块eye/mouth/node不依赖 main_UImain.py 调用检测模块main_UI.py 调用检测模块并负责界面展示。想只做算法验证就绕过 UI直接跑 Test.ipynb想完整演示就进 UIdemo.ipynb。3.2 主入口 main.py 的检测循环main.py 的典型流程是实例化检测器读取视频流循环取帧逐帧执行三个检测画标注框显示结果。import cv2 import dlib from eye_detecting import EyeDetector from mouth_detecting import MouthDetector from node_detecting import NodeDetector # 初始化人脸检测器和关键点检测模型 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(model/shape_predictor_68_face_landmarks.dat) # 三个独立检测器 eye_det EyeDetector(predictor) mouth_det MouthDetector(predictor) node_det NodeDetector(predictor) # 打开视频源0 代表摄像头也可以传视频文件路径 cap cv2.VideoCapture(test.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: eye_det.run(frame, face) mouth_det.run(frame, face) node_det.run(frame, face) cv2.imshow(Fatigue Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里的关键点是三个检测器共享同一个 predictor 实例避免重复加载模型造成内存浪费。detector(gray, 0) 的第二个参数是金字塔上采样次数值越大检测越慢但更能发现小尺寸人脸实时场景下用 0 或 1 更稳妥。3.3 main_UI.py 界面与检测逻辑的绑定wxFormBuilder 生成的 noname.py 只负责界面布局里面的按钮事件是空的。项目作者在 main_UI.py 里做了二次开发把检测线程挂到了按钮回调上。界面逻辑一般是这样组织的摄像头开启按钮绑定视频流启动函数检测结果通过 wx 控件的 SetLabel 方法刷新到文本区域。核心问题在 Python 的 GIL 和 wxPython 的 UI 线程安全——视频检测必须在独立线程里跑否则画面会卡死。import threading import wx import cv2 from noname import MyFrame # wxFormBuilder 生成的界面框架 class FatigueApp(MyFrame): def __init__(self): super().__init__(None) self.running False self.cap None # 绑定按钮事件 self.btn_start.Bind(wx.EVT_BUTTON, self.on_start) self.btn_stop.Bind(wx.EVT_BUTTON, self.on_stop) def on_start(self, event): if not self.running: self.running True self.cap cv2.VideoCapture(0) # 检测循环放子线程避免阻塞 UI t threading.Thread(targetself.detect_loop, daemonTrue) t.start() def detect_loop(self): while self.running: ret, frame self.cap.read() # 检测代码省略结果用 wx.CallAfter 回到主线程刷新 wx.CallAfter(self.label_status.SetLabel, 检测结果...)一个重要的细节是 wx.CallAfter。子线程不能直接操作 wx 控件必须通过 CallAfter 把刷新动作调度到主线程执行否则程序会随机崩溃这是 wxPython 开发的常规坑。3.4 可视化界面的设计源文件111.fbp 是 wxFormBuilder 的工程文件你可以用项目附带的 wxFormBuilder_v3.6.2.exe 打开它拖拽调整界面布局然后重新生成 noname.py。这对想改界面文字、调整按钮位置的人来说是关键文件。界面上的元素覆盖了三个检测模块的展示位摄像头画面区域、检测状态文本、疲劳报警提示、启动停止按钮。界面设计上使用了 123.ico 作为程序图标camera.png 是摄像头占位图images 目录放着其它展示素材。4. 把 demo 跑起来从测试视频到摄像头实时检测4.1 环境准备与安装顺序先装 Python建议 3.7 到 3.9 之间的版本Dlib 对高版本 Python 支持有滞后。装 Dlib 是一个常见翻车点Windows 上优先用 pip 直接装预编译包不要自己从源码编译。# 基础依赖 pip install numpy opencv-python scipy # Windows 建议直接装预编译的 dlib pip install dlib # 界面依赖 pip install wxPython装完后验证一下模型文件是否存在shape_predictor_68_face_landmarks.dat 必须在代码运行时的相对路径下能找到或者把路径改成绝对路径。4.2 两种运行入口的选择这个资源给了两条运行路径作用不同入口适合场景特点Test.ipynb快速验证算法在 Jupyter 里逐格执行能看到中间过程方便改参数UIdemo.ipynb完整演示走可视化界面展示效果好main.py命令行运行不加界面直接跑视频检测main_UI.py毕设演示带 wxPython 界面最接近成品我一般建议第一次跑先用 test.mp4 验证算法是否正常再切摄像头。直接上摄像头遇到光线差、人脸过近过远、摄像头被占用等问题难以判断是环境问题还是代码问题。4.3 摄像头源与视频源的切换参数代码中用 cv2.VideoCapture 的索引或路径控制输入源。传 0 表示第一个摄像头传 test.mp4 表示读视频文件传 IP 摄像头地址就走网络流。# 修改 main_UI.py 或 main.py 中的视频源 # 摄像头模式 cap cv2.VideoCapture(0) # 测试视频模式 cap cv2.VideoCapture(test.mp4)如果要用自己的视频数据集注意帧率与真实场景匹配。摄像头采集的帧率通常为 25 到 30 fps而测试视频如果是网络下载的 15fps 动画同样的 EAR 连续帧阈值在时间尺度上会差近一倍。5. 避坑记录疲劳检测最常见的五个翻车现场5.1 dlib 安装失败导致代码启动即崩现象pip install dlib 报错提示 CMake 或 Visual Studio Build Tools 找不到代码运行到 import dlib 直接抛 ImportError。原因Windows 上如果 pip 找不到对应 Python 版本的预编译 wheel会回退到源码编译此时需要本机装 C 编译环境。很多人装的是精简版 Python缺少开发头文件编译必然失败。解决换 Python 版本重装 dlib。我试过 Python 3.8 dlib 19.23.1 组合在 Windows 上最容易成功。如果仍然失败检查 Python 是 32 位还是 64 位务必与系统位数一致。也可以直接去 dlib 的 wheel 仓库下载对应版本离线安装。5.2 模型文件路径写死导致换机器就黑屏现象代码在自己电脑上正常把压缩包拷到另一台电脑后运行界面打开但检测永远不触发控制台报文件找不到的错误。原因模型加载用了相对路径但默认的工作目录不是项目目录。直接双击运行 main_UI.py 时当前工作目录可能是用户主目录model/shape_predictor_68_face_landmarks.dat 自然找不到。解决把模型路径改为基于项目文件位置的绝对路径常见做法是用 os.path.dirname(file) 拼接路径代码里加上路径检查逻辑确保模型存在后再启动检测线程。5.3 中文路径导致视频读取失败现象压缩包解压到带中文的文件夹比如桌面/毕业设计后cv2.VideoCapture 读取 test.mp4 返回 False摄像头模式却正常。原因OpenCV 的 VideoCapture 在 Windows 上对非 ASCII 路径处理不完善中文路径下的文件读取会静默失败。解决把项目移动到纯英文路径下运行。对毕设答辩文件管理来说养成项目路径全英文的习惯少很多问题。5.4 戴眼镜和墨镜让眨眼检测失效现象戴眼镜的测试者坐在摄像头前眨眼次数统计异常少闭眼状态识别不出来。原因眼镜框反光和镜片遮挡会影响关键点定位精度。墨镜情况下 68 点坐标完全错误。这不是这套代码的问题而是基于 2D 关键点的检测方案的天然局限。解决摘下墨镜测试。反光严重的眼镜调整光源角度让脸正面受光均匀。如果必须支持墨镜场景需要考虑换用基于深度学习的关键点模型但会牺牲实时性。5.5 wxFormBuilder 改完界面后 main_UI.py 不识别新控件现象用 wxFormBuilder 打开 111.fbp加了新按钮重新生成 noname.py运行 main_UI.py 报属性不存在的错误新按钮怎么点都没反应。原因main_UI.py 是作者基于旧版 noname.py 写的重新生成的 noname.py 中的控件变量名或类结构变了main_UI.py 里 self.btn_start 这样的引用就对不上了。解决改界面时保留原有控件命名规则新增控件后去 main_UI.py 里手动补事件绑定。如果 main_UI.py 和 noname.py 的耦合太深我一般建议把界面代码合并进 main_UI.py手动改布局后期维护成本更低。5.6 摄像头被占用导致程序启动即退出现象程序运行后摄像头画面区域是黑屏检测无响应任务管理器显示进程还在但界面假死。关闭微信、腾讯会议等使用了摄像头的软件后恢复正常。原因Windows 上摄像头被其他进程独占OpenCV 无法打开摄像头。解决代码里对 cap.isOpened() 做判断打开失败时弹出提示而不是静默退出。运行前确认微信、钉钉、视频会议软件没有占用摄像头。6. 进阶技巧用 EMA 平滑和状态机让检测信号不再乱跳直接读单个 EAR 值的序列时会发现哪怕人安静坐着关键点定位的小幅抖动也会让 EAR 在阈值边缘来回穿越导致眨眼次数误统计。解决这个问题的一个有效手段是对 EAR 做指数移动平均EMA平滑同时按帧数状态机把单次误触发抑制掉。class SmoothEyeDetector: def __init__(self, ear_threshold0.2, consecutive_frames2, alpha0.3): self.ear_threshold ear_threshold self.consecutive_frames consecutive_frames self.frame_count 0 self.closed_count 0 self.ema_ear None self.alpha alpha # EMA 平滑系数越小越稳定但延迟越高 def update(self, raw_ear): if self.ema_ear is None: self.ema_ear raw_ear else: self.ema_ear self.alpha * raw_ear (1 - self.alpha) * self.ema_ear # 低于阈值时进入快照计数 if self.ema_ear self.ear_threshold: self.closed_count 1 else: if self.closed_count self.consecutive_frames: # 算一次眨眼 self.frame_count 1 self.closed_count 0 return self.ema_ear平滑系数 alpha 决定了对原始信号的响应速度。设得小信号特别平滑但会漏掉快速眨眼设得大抖动抑制不足。我的经验值是 0.3 到 0.4既能滤掉高频抖动又不会把真实的快速眨眼抹平。配合状态机的思路眨眼判定要满足闭眼帧数达到阈值后又睁开这个完整循环才算一次眨眼而不是单帧低于阈值就计数。同理打哈欠检测可以用 MAR 的滑动窗口均值代替单帧值点头检测则对鼻尖位移做差分后再阈值化。这套组合改完后测试视频上的 false positive 明显变少了。从那以后我每次做基于关键点的行为检测都强制先跑一遍平滑预处理再讲阈值先让信号可信再谈算法精度。希望帮到你。本文还有配套的精品资源点击获取
返回列表