news 2026/9/28 13:38:16

YOLOV5+dlib驾驶员疲劳检测:从环境配置到EAR/MAR阈值标定实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOV5+dlib驾驶员疲劳检测:从环境配置到EAR/MAR阈值标定实战

简介:这份资源是面向计算机视觉学习者与驾驶安全方向研究者的驾驶员疲劳检测实战项目包,基于YOLOv5与Dlib构建,可识别眨眼、打哈欠、抽烟、喝水、玩手机等行为,并检测水瓶、手机、香烟等目标,适合课程设计、毕业设计或算法入门练手。压缩包共115个文件,约274.19MB,以31个py源码、24个yaml配置、14个pyc缓存、2个pt权重及dat人脸关键点模型为主,另含mp4演示、md说明与Dockerfile等部署文件,覆盖数据预处理、模型构建、训练、目标检测与疲劳判定全流程。已有531人学习下载。读者可获得完整可运行源码、训练好的YOLOv5与Dlib模型,以及讲解项目背景、代码结构、模型说明与结果分析的使用教程,便于快速复现并迁移到实际驾驶安全场景。

1. 驾驶员疲劳检测为什么总在真实车里翻车:从 YOLOV5 加 dlib 的组合说起

高速上跑两小时,眼皮开始打架,但车还在 120 巡航——这是疲劳驾驶最危险的时刻。基于 YOLOV5 加 dlib 实现驾驶员疲劳检测,本质是把两个成熟工具拼成一条流水线:YOLOV5 负责在画面里框出人脸,dlib 负责在人脸上定位 68 个关键点,再从眼睛和嘴巴的几何变化里算出疲劳指标。它解决的不是"能不能检测到人脸",而是"在车内光照忽明忽暗、驾驶员低头抬头、戴不戴眼镜都变的条件下,稳定判断出这个人是不是快睡着了"。适合谁?有 Python 基础、想跑通一个完整视觉项目、或者要拿它做课程设计/毕业设计的人。热搜里 yolov5 训练自己的数据集、yolov5 环境配置、yolov5 部署这几个词,恰好对应这条流水线最容易卡住的三段。下面按"先立住原理、再动手复现、最后讲坑"的顺序拆开讲,每一步都落到能抄的命令和参数上。

2. 拆开这条流水线:YOLOV5 找人脸、dlib 找眼睛,各自负责什么

2.1 为什么不是"一个模型端到端"就完事

很多人第一反应是:既然 YOLOV5 这么强,直接训练一个"疲劳/清醒"二分类模型不就行了?我一开始也这么想,后来发现翻车点在于数据。端到端分类需要大量标注好的"疲劳状态"样本,而疲劳是一个渐变过程,标注边界极其主观——同一个打哈欠的动作,有人标疲劳有人标清醒。而拆成"人脸检测 + 关键点 + 几何判据"这条链路,每一段都有成熟方案:人脸检测用 YOLOV5 或现成的人脸模型,关键点用 dlib 的 shape_predictor_68,疲劳判据用眼睛纵横比(EAR)和嘴巴纵横比(MAR)。好处是每一段可单独替换、单独调参,坏处是误差会逐级累积——人脸框偏一点,关键点就偏,EAR 就抖。所以这条流水线的核心不是"模型多强",而是"每一级的稳定性"。

YOLOV5 在这里的角色是"粗定位"。它输出人脸框的坐标,把后续 dlib 的搜索范围从整张图缩小到一个框里。这一步很关键:dlib 的 68 点检测器在整图上跑又慢又容易受背景干扰,裁剪到人脸框后,速度和准确率都上来了。常见做法是用 YOLOV5 训练一个单类别(face)检测器,或者直接用人脸检测权重,输入尺寸 640,置信度阈值 0.5 起步。

dlib 的角色是"精定位"。它拿到人脸框后,用 68 点模型输出关键点索引:36-41 是左眼,42-47 是右眼,48-67 是嘴巴。EAR 的计算就是取眼睛上下左右几组点的距离比值,MAR 同理取嘴巴的垂直和水平距离比值。这两个比值是疲劳判据的物理基础,跟模型无关,纯几何。

2.2 环境配置:conda 建环境 + 装依赖的完整命令

热搜里 yolov5 环境配置、conda yolov5 出现频率很高,说明这一步劝退了不少人。我一般用 conda 建独立环境,避免和系统 Python 打架。下面这套命令在 Windows 和 Linux 上都验证过,PyTorch 版本按自己显卡选,没有 GPU 就用 CPU 版。

# 建一个 Python 3.8 的环境,名字叫 fatigue conda create -n fatigue python=3.8 -y conda activate fatigue # 装 PyTorch,有 NVIDIA 显卡走 cu118,没有就走 cpu # 具体命令去 PyTorch 官网生成,这里给一个常见组合 pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu118 # 装 YOLOV5 依赖和 dlib pip install -r requirements.txt # YOLOV5 仓库根目录下的依赖清单 pip install dlib==19.24.0 pip install opencv-python numpy scipy

逻辑说明:conda 环境隔离是为了防止 dlib 编译时找不到正确的 Python 头文件,这是血泪经验——系统里多个 Python 版本时,dlib 经常装到一半报 CMake 错误。dlib 用 pip 装预编译 wheel 最省事,19.24.0 这个版本在 Python 3.8 上有现成 wheel,不用自己编译。如果 pip 装 dlib 失败,再考虑 conda install -c conda-forge dlib,但 conda 源的版本可能偏旧。

参数说明:torch 版本要和 torchvision 匹配,1.13.1 配 0.14.1 是官方对应关系,乱配会报 undefined symbol。opencv-python 用默认最新即可,但注意有些老教程用 opencv-contrib-python,两者不要同时装,会冲突。

2.3 人脸检测这一级:YOLOV5 推理代码与置信度阈值

YOLOV5 推理有两种方式:命令行 detect.py 和 Python 里加载模型。做疲劳检测要接后续逻辑,必须用 Python 方式,把检测结果拿到手里。

import torch import cv2 # 加载 YOLOV5 模型,weights 换成你自己训练的人脸权重或官方权重 model = torch.hub.load('ultralytics/yolov5', 'custom', path='weights/face_best.pt') model.conf = 0.5 # 置信度阈值,低于这个的框丢掉 model.iou = 0.45 # NMS 的 IoU 阈值,人脸重叠时调这个 model.classes = [0] # 只保留 face 这一类,避免检测到别的物体 def detect_face(frame): # YOLOV5 接受 RGB,OpenCV 读进来是 BGR,要转 img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = model(img_rgb, size=640) # results.xyxy[0] 是 [x1, y1, x2, y2, conf, cls] boxes = results.xyxy[0].cpu().numpy() return boxes

逻辑说明:torch.hub.load 第一次运行会去拉 YOLOV5 仓库代码,网络不通就提前把仓库 clone 到本地,把路径换成本地目录。model.conf 是置信度阈值,人脸检测场景下 0.5 是个稳妥起点,漏检多就降到 0.3,误检多就升到 0.6。model.classes 限定类别很重要,如果你用的是 80 类 COCO 权重,不加这个会检测出一堆无关物体。

参数说明:size=640 是推理输入尺寸,和训练时一致。显存不够就降到 416 或 320,但小脸会漏。results.xyxy[0] 里的坐标是原图尺度,直接能用。如果一帧里有多个人脸,boxes 会有多行,疲劳检测一般只取面积最大的那个——驾驶员离摄像头最近。

3. 从 68 个点到疲劳判据:EAR 和 MAR 怎么算、阈值怎么定

3.1 dlib 关键点检测与 EAR/MAR 计算公式

拿到人脸框后,裁剪出来送进 dlib。dlib 的 68 点模型输出的是 68 个 (x, y) 坐标,索引固定。EAR 的经典公式是:取眼睛的 6 个点,算垂直方向三组距离之和,除以水平方向距离的两倍。MAR 类似,取嘴巴上下和左右的距离比值。

import dlib import numpy as np detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') def eye_aspect_ratio(eye_points): # eye_points 是 6 个点的数组,顺序:左角、上左、上右、右角、下右、下左 # 垂直距离:上左-下左、上右-下右、上中-下中(这里用两组近似) A = np.linalg.norm(eye_points[1] - eye_points[5]) B = np.linalg.norm(eye_points[2] - eye_points[4]) C = np.linalg.norm(eye_points[0] - eye_points[3]) ear = (A + B) / (2.0 * C) return ear def mouth_aspect_ratio(mouth_points): # 嘴巴 20 个点,取上下唇中点和左右嘴角 A = np.linalg.norm(mouth_points[13] - mouth_points[19]) # 上下唇中点 B = np.linalg.norm(mouth_points[14] - mouth_points[18]) C = np.linalg.norm(mouth_points[12] - mouth_points[16]) # 左右嘴角 mar = (A + B) / (2.0 * C) return mar def get_landmarks(frame, box): x1, y1, x2, y2 = map(int, box[:4]) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # dlib 用矩形检测,这里直接用人脸框构造 rect,跳过它自己的人脸检测 rect = dlib.rectangle(x1, y1, x2, y2) shape = predictor(gray, rect) points = np.array([[p.x, p.y] for p in shape.parts()]) return points

逻辑说明:这里有个关键优化——dlib 自带的人脸检测器(get_frontal_face_detector)很慢,既然 YOLOV5 已经给了框,就直接用 dlib.rectangle 构造矩形,跳过 dlib 的检测步骤,只跑关键点预测。这一步能把单帧耗时砍掉一半以上。EAR 公式里 A、B 是两组垂直距离,C 是水平距离,正常睁眼时 EAR 在 0.25-0.35 之间,闭眼会掉到 0.15 以下。

参数说明:shape_predictor_68_face_landmarks.dat 是 dlib 官方模型文件,约 100MB,需要单独下载。eye_points 的索引顺序要对:左眼是 36-41,右眼是 42-47,嘴巴是 48-67。索引错一位,EAR 就完全没意义,这是新手最容易犯的错。

3.2 阈值不是拍脑袋:EAR/MAR 的标定方法与滑动窗口

EAR 阈值不能直接抄网上的 0.2,因为每个人的眼睛形状不同,摄像头角度也不同。我一般让使用者先正常睁眼录 3 秒,取 EAR 均值作为基准,闭眼阈值设成基准的 60%。MAR 同理,打哈欠时嘴巴张开,MAR 会明显上升,阈值一般设在基准的 1.5 倍以上。

光有单帧阈值还不够,眨眼是正常的,闭眼超过一定帧数才算疲劳。这就用到滑动窗口:维护一个长度为 N 的队列,统计窗口内闭眼帧的比例。热搜里的滑动窗口滤波模型,在这里就是干这个的。

from collections import deque class FatigueDetector: def __init__(self, ear_thresh=0.2, mar_thresh=0.6, window=30, eye_ratio=0.7, mouth_ratio=0.3): self.ear_thresh = ear_thresh self.mar_thresh = mar_thresh self.window = window self.eye_ratio = eye_ratio # 窗口内闭眼帧占比超过这个值判疲劳 self.mouth_ratio = mouth_ratio self.eye_queue = deque(maxlen=window) self.mouth_queue = deque(maxlen=window) def update(self, ear, mar): self.eye_queue.append(1 if ear < self.ear_thresh else 0) self.mouth_queue.append(1 if mar > self.mar_thresh else 0) eye_score = sum(self.eye_queue) / len(self.eye_queue) mouth_score = sum(self.mouth_queue) / len(self.mouth_queue) # 两个指标任一超标就报警,也可以加权 if eye_score > self.eye_ratio or mouth_score > self.mouth_ratio: return True return False

逻辑说明:window=30 表示看最近 30 帧,按 30fps 算就是 1 秒。eye_ratio=0.7 表示这 1 秒里 70% 的帧都是闭眼,才判疲劳——这样能过滤掉正常眨眼(眨眼一般只占几帧)。mouth_ratio=0.3 是打哈欠的判据,打哈欠持续时间长,占比容易超。两个指标用 or 连接偏保守,容易误报;用 and 偏宽松,容易漏报。实际项目里我一般用加权:eye_score * 0.7 + mouth_score * 0.3 > 0.5。

参数说明:window 太小会抖动,太大反应迟钝,30 是 30fps 下的经验值。如果摄像头只有 15fps,window 要相应减半。ear_thresh 和 mar_thresh 必须按 3.2 节的方法标定,不要用默认值直接上。

4. 避坑与排查:这条流水线最容易翻车的 5 个地方

4.1 现象:dlib 装不上,报 CMake 或 boost 错误

原因:pip 在找不到预编译 wheel 时会尝试从源码编译 dlib,而 dlib 依赖 CMake 和 C++ 编译环境,Windows 上还依赖 Visual Studio Build Tools。Python 版本太新(比如 3.11+)时,wheel 往往还没发布。

解决:优先用 Python 3.8 或 3.9,这两个版本 wheel 最全。确认 pip 版本够新(pip install --upgrade pip),然后 pip install dlib 让它直接下 wheel。如果还是编译,Windows 装 Visual Studio Build Tools 并勾选 C++ 桌面开发,Linux 装 cmake 和 build-essential。实在不行用 conda install -c conda-forge dlib,conda 源的包是预编译的。

4.2 现象:YOLOV5 检测框在,但 dlib 关键点乱飞

原因:YOLOV5 给的框可能偏大或偏小,dlib 的 predictor 对框的位置敏感。框偏了,68 点会整体偏移,EAR 直接失真。另一个原因是灰度图转换时用了错误的色彩空间。

解决:把 YOLOV5 的框往里收 10%-20% 再送给 dlib,给关键点检测留点余量。检查 cv2.cvtColor 用的是 COLOR_BGR2GRAY,不是 RGB2GRAY。如果关键点还是乱,打印出来可视化一下,看 68 点是不是落在脸上。

4.3 现象:白天正常,晚上或逆光时 EAR 剧烈抖动

原因:dlib 的关键点检测依赖图像梯度,光照不足时梯度弱,关键点定位精度下降。逆光时人脸变成剪影,YOLOV5 都可能漏检。

解决:加一个简单的图像预处理——直方图均衡化(cv2.equalizeHist)或者 CLAHE,提升暗部对比度。红外摄像头是更彻底的方案,但成本高。另外把 EAR 的滑动窗口拉长到 45 帧,用时间换稳定。

4.4 现象:戴眼镜的人 EAR 一直偏低,频繁误报

原因:镜框和镜片反光会干扰眼睛关键点,dlib 把镜框边缘当成眼睑,导致 EAR 计算偏小。

解决:对戴眼镜场景,EAR 阈值要单独标定,通常比不戴眼镜低 0.03-0.05。或者在关键点检测前做一次眼镜区域掩膜,但这会引入新误差。更稳的做法是降低 eye_ratio,比如从 0.7 降到 0.5,让判据更宽松,代价是反应变慢。

4.5 现象:CPU 上跑不动,一帧要几百毫秒

原因:YOLOV5 和 dlib 都是计算密集型,CPU 推理时两者叠加,帧率掉到个位数。

解决:YOLOV5 换 yolov5n(nano 版),输入尺寸降到 320,能快 3-5 倍。dlib 那边,把关键点检测的频率降下来——不用每帧都跑,每 3 帧跑一次,中间帧复用上一次的关键点。或者上 GPU,YOLOV5 和 dlib 都能吃 CUDA,帧率能回到 30fps 以上。树莓派 5 上部署自己训练的 yolov5 模型这个热搜场景,基本必须用 nano 版加降频策略。

5. 让疲劳检测真正可用:从单帧判据到状态机与验证方法

前面讲的都是单帧或短窗口的判据,但真实驾驶里,疲劳是一个持续状态,不是某一帧的瞬时判断。我后来把判据升级成了一个简单的状态机:清醒 → 疑似疲劳 → 疲劳 → 严重疲劳,每个状态有进入和退出条件,避免在阈值附近反复横跳。疑似疲劳是 EAR 窗口超标但还没持续够时间,疲劳是持续超标 3 秒以上,严重疲劳是叠加了打哈欠或头部下垂。状态机的好处是报警有层次,不会一超标就尖叫,也不会漏掉渐进式疲劳。

验证方法上,别只看准确率。我一般分三步:第一步用录制好的视频回放,人工标注每个疲劳片段,看报警时间点和标注的偏差;第二步找 3-5 个人做真实测试,每人正常驾驶 10 分钟,统计误报次数——误报比漏报更烦人,一次误报就让人想关掉系统;第三步做边界测试,戴眼镜、戴口罩、夜间、侧脸,看哪些场景会崩。这套流程跑下来,才能说这个方案"能用"。

一个具体技巧:把 EAR 和 MAR 的原始值实时画成曲线叠加在视频上,调试时一眼就能看出阈值设得对不对。我习惯用 OpenCV 的 cv2.line 在画面上画两条水平线代表阈值,EAR 曲线用绿色,MAR 用蓝色,超标的部分标红。这个可视化花不了多少代码,但省下的调试时间是以小时计的。

# 在视频帧上叠加 EAR/MAR 曲线和阈值线 def draw_debug(frame, ear_history, mar_history, ear_thresh, mar_thresh): h, w = frame.shape[:2] base_y = h - 100 # 画阈值线 cv2.line(frame, (0, base_y - int(ear_thresh * 200)), (w, base_y - int(ear_thresh * 200)), (0, 255, 0), 1) cv2.line(frame, (0, base_y - int(mar_thresh * 200)), (w, base_y - int(mar_thresh * 200)), (255, 0, 0), 1) # 画历史曲线 for i in range(1, len(ear_history)): x1 = int((i - 1) / len(ear_history) * w) x2 = int(i / len(ear_history) * w) y1 = base_y - int(ear_history[i-1] * 200) y2 = base_y - int(ear_history[i] * 200) cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) return frame

逻辑说明:ear_history 和 mar_history 是长度固定的队列,存最近 N 帧的值。base_y 是曲线基准线,乘以 200 是把 0-1 的比值放大到像素尺度。阈值线画出来,曲线在阈值线以下就是闭眼/打哈欠。这个调试面板在项目初期帮我省了大量时间,强烈建议加上。

参数说明:200 是放大系数,EAR 一般在 0.2-0.4,乘 200 后是 40-80 像素,肉眼能看清。如果画面分辨率高,系数可以调大。曲线长度和滑动窗口保持一致,方便对照。

最后说个我自己的习惯:每次调完阈值,我都会把当天的测试视频和参数记在一个表格里,标注日期、光照、是否戴眼镜、误报次数。攒够十几条记录后,阈值该怎么设基本就有数了,比拍脑袋靠谱得多。这个项目不难,难的是把每一级的误差控制住,让整条流水线在真实场景里稳下来。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 13:38:16

电热综合能源系统日前经济调度模型与Matlab实现:促进可再生能源消纳

前几个月在做一个综合能源系统调度方向的课题&#xff0c;核心就是标题里这个模型&#xff1a;考虑可再生能源消纳的电热综合能源系统日前经济调度模型&#xff0c;并且用Matlab写了一套可跑的代码。这个课题的典型场景是冬季供暖期&#xff0c;热电联产机组为了保供热必须压着…

作者头像 李华
网站建设 2026/9/28 13:38:09

发布管道全解析:从设计到实战的CI/CD核心知识体系

作为一个在软件交付一线摸爬滚打多年的工程师&#xff0c;我越来越深刻地意识到&#xff1a;发布管道&#xff08;Release Pipeline&#xff09;不是一个停留在PPT上的概念&#xff0c;而是决定团队交付效率、线上稳定性和工程师幸福感的关键基础设施。很多团队不是不会写代码&…

作者头像 李华
网站建设 2026/9/28 13:36:42

VPC2187B/VPC2188B超宽压启动电路设计避坑指南

1. 这不是普通PWM芯片——VPC2187B/VPC2188B的“宽压启动”本质是什么&#xff1f;你手头拿到的VPC2187B或VPC2188B数据手册第一页就写着“4–100V宽范围启动电压”&#xff0c;但翻到第12页的典型应用电路时&#xff0c;却发现启动电阻标得模棱两可&#xff0c;Rstart旁边只写…

作者头像 李华
网站建设 2026/9/28 13:35:58

MLP做虚假新闻检测:从TF-IDF特征工程到可调试二分类实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 13:35:10

Sqoop分片机制深挖:分片键选型与边界计算才是并行导入提速关键

用 Sqoop 做数据导入&#xff0c;只要数据量一上来&#xff0c;你早晚会遇到一个场景&#xff1a;命令行里明明写了-m 10&#xff0c;任务也起了 10 个 Map&#xff0c;但导入速度就是提不上去&#xff0c;甚至有几个 Map 要跑别人的两倍时间。问题大概率出在 Sqoop 分片机制上…

作者头像 李华