简介:基于OpenCV和Dlib实现的疲劳驾驶检测系统完整工程,面向计算机视觉入门者、智能座舱开发者以及安全驾驶相关课题研究者,用于实时监测驾驶员的眨眼、闭眼和头部姿态,及时发出疲劳预警。系统以Python编写,将OpenCV的图像处理能力与Dlib的人脸关键点检测能力相结合,形成从摄像头采集、人脸检测、眼睛纵横比计算到疲劳判断的完整流程。压缩包体积约93.53MB,共包含21个文件,类型覆盖Python源码、预训练人脸关键点模型(.dat)、人脸检测配置(.xml)、可视化检测页面(.html)、测试图像(.png/.jpg)以及演示动画(.gif)等,基本涵盖模型、代码、数据与展示四个层面。资源包中的文件按代码、模型、图片等分类组织,便于直接定位使用。目前该资源已有422人浏览学习,对于希望快速跑通一个疲劳检测项目的读者而言,获得整套工程后可以直接运行调试,结合源码理解算法原理,也可替换模型或修改参数进行二次开发,适合课程设计、毕业设计和技术验证。
1. 疲劳驾驶检测系统拆解:OpenCV + dlib 能做哪些事
凌晨两点,长途司机连续开了四个小时,眼睛开始不自觉地往下掉。装在驾驶室的摄像头拍到闭眼过程时,如果系统能在眼睛闭合的那一秒做出判断,提前报警,就可能避免一次事故。这套基于 OpenCV 和 dlib 的疲劳驾驶检测系统,就是用普通摄像头抓帧,dlib 检测 68 个人脸关键点,OpenCV 做画面处理,最后用眼睛纵横比和嘴巴纵横比判断打瞌睡、打哈欠。它不依赖眼球追踪仪,也不依赖脑电手环,一台笔记本加一个 USB 摄像头就能搭出实时原型。适合正在入门 OpenCV、需要完成毕业设计,或者想给公司做低成本安全监控方案的工程师。下面按我拆这套项目的顺序,先解决环境,再谈关键点计算、疲劳判定和几个翻车点。
2. 环境搭建:OpenCV 与 dlib 的版本搭配和安装顺序
先说结论:整个项目最花时间的地方不是算法,而是把 dlib 装进你的 Python 环境。OpenCV 一条 pip 命令基本能搞定,dlib 在 Windows 上经常要现场编译,一条命令走不完。我的习惯是先建虚拟环境,再装 OpenCV,最后装 dlib,顺序反了可能出现 OpenCV 和 dlib 各自依赖的 Python 版本冲突。
2.1 先建虚拟环境:Python 3.8 是当前兼容性最稳的选择
用 conda 或 venv 都行。这套代码里主要用的是 OpenCV 的 VideoCapture、cvtColor、imshow,还有 dlib 的正脸检测器和 68 点预测器。这些库对新版本 Python 的支持有滞后性,特别是 dlib 的预编译 wheel 并不是所有 Python 版本都覆盖。常见做法是建一个 Python 3.8 的环境,原因很朴素:OpenCV 对 3.8 的二进制包最全,dlib 的社区轮子也优先打 3.8 和 3.9。
conda create -n fatigue_detect python=3.8 -y conda activate fatigue_detect建环境的动作不复杂,但我见过不少人跳过这一步,直接用系统 Python 装一堆依赖,结果 opencv-python 和另一个项目里的 opencv-contrib-python 互相覆盖,连 import cv2 都可能拿到旧的版本号。虚拟环境相当于后悔药,等系统环境被搞坏再回头重建,成本高得多。python=3.8 不是绝对的,如果你手头平台只有 3.9,也可以继续,后面安装 dlib 时多留意报错即可。
环境激活后,先用一条命令确认 pip 指向的是当前环境:
python -m pip --version如果显示的路径不是 fatigue_detect 里的 Python,多数情况是 IDE 里解释器没切。这个细节后面避坑章还会提到,现在先把这个习惯落实。
2.2 安装 OpenCV:opencv-python 和 opencv-contrib-python 的区别
正常情况下,装一个 opencv-python 就够了。项目用到的函数都在主包里。如果你还需要 SIFT、ORB 这些在 opencv_contrib 里的扩展功能,才需要装 opencv-contrib-python,但疲劳驾驶检测用不到,所以别图省事同时装两个,很容易出现符号冲突。
pip install opencv-python==4.8.1.78为什么固定版本号?因为 4.8.x 是当前比较稳的稳定版,OpenCV 5.x 还没有正式发布,贸然升到最新版,部分函数行为有变化,代码本身能跑,但一些小参数会让你莫名其妙。固定版本也能保证团队里其他同事复现时看到的是同一个行为。
安装完验证:
python -c "import cv2; print(cv2.__version__)"如果输出了类似 4.8.1,说明没问题。如果这里报 ModuleNotFoundError: No module named 'cv2',先检查你当前是不是在疲劳检测的虚拟环境里,不要在全局环境里验证。另一个常被忽视的点:Linux 服务器上如果没有图形界面,import cv2 之后一旦调用 cv2.imshow 就会崩溃,这时应该用 opencv-python-headless,并把代码里所有 imshow、waitKey 相关分支去掉,改成写文件输出。做远程测试时我一般直接不显示窗口,把每帧检测结果打印到控制台。
2.3 dlib 安装:一条 pip 命令为什么在 Windows 上不够用
dlib 不是一个纯 Python 库,它的核心是 C++ 模板库,pip 安装时会尝试本地编译。在 Linux 上编译前提是 build-essential 和 cmake,在 macOS 上需要 Xcode 命令行工具,在 Windows 上需要 Visual Studio Build Tools 里的“使用 C++ 的桌面开发”工作负载加 CMake。任何一个缺失,都会在编译到一半时抛出一堆看不懂的红字,最常见的两类错误是 CMake 版本不满足、Boost 找不到。
我的建议分两步走:先在环境里试pip install dlib,如果报编译错,立刻换 conda 预编译包,不要在同一个坑里硬磨半小时。
pip install dlib如果失败,执行:
conda install -c conda-forge dlibconda-forge 里通常有对应平台编译好的 dlib 二进制,省去本地编译。装完执行:
python -c "import dlib; print(dlib.__version__)"确认能 import。这里有个细节:输出正常后,不要急着往下走,先运行一个简单的人脸检测,确保没有缺少 libX11 之类的依赖。否则后面加载模型时才暴露问题更难受。
2.4 准备 68 点模型文件:shape_predictor_68_face_landmarks.dat
算法部分依赖 dlib 官方训练好的 68 点人脸关键点模型,文件名是shape_predictor_68_face_landmarks.dat。这个文件大概一百多兆,从 dlib 官网模型列表里能下载。下载后不要放在中文路径下,Windows 下 Python 对中文路径的支持偶尔会抽风,常见做法是放在项目的models/目录里,路径用相对路径。
import dlib # 加载模型,路径里不要出现中文 predictor = dlib.shape_predictor("models/shape_predictor_68_face_landmarks.dat") print("predictor loaded")代码逻辑很简单,但它的加载过程会校验文件头。如果文件下载不完整,这里会抛异常或者加载成功后预测出来的点全是 (0,0)。所以在正式编写数据流程之前,先单独加载一次模型并打印一行确认信息,能省掉后面排查的大量时间。我一般会顺手打印predictor对象本身,确认它不是空引用。
到这里,环境侧的工作已经闭环:OpenCV 提供图像读写和显示,dlib 提供人脸检测与关键点预测。接下来一章节讲的是整个系统真正的数据入口:怎么拿到一帧画面里人脸的 68 个点。
3. 人脸检测与 68 点关键点:从 HOG 到 dlib 坐标解析
3.1 dlib 正脸检测器:为什么不用 OpenCV 的 Haar Cascade
对人脸检测,OpenCV 自带 Haar Cascade,也能框住正脸。但 dlib 的get_frontal_face_detector()基于 HOG 特征加线性 SVM,对侧脸和光照变化的容忍度比 Haar 好,而且和后面的shape_predictor是同一套生态,直接喂检测框,省了不同库之间的坐标系转换。疲劳驾驶场景里光线不是理想环境,我一般直接选 dlib 检测器,OpenCV 只负责摄像头读取和图像处理。
代码里先把检测器、预测器、摄像头初始化:
import cv2 import dlib detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("models/shape_predictor_68_face_landmarks.dat") cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) 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: shape = predictor(gray, faces[0]) for i in range(68): x = shape.part(i).x y = shape.part(i).y cv2.circle(frame, (x, y), 2, (0, 255, 0), -1) cv2.imshow("face landmarks", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:读到的 frame 通常是 BGR,dlib 的检测器接受 8bit 灰度图,所以先用 cvtColor 转灰。detector 返回的是dlib.rectangles,里面每个矩形是人脸框。把它直接传给 predictor,predictor 返回dlib.full_object_detection,里面 68 个关键点的坐标通过part(i).x和.y访问。这里detector(gray, 0)的第二个参数是图像上采样倍数,0 表示不做放大,速度快;调成 1 会在检测前放大一倍,小脸更容易被找到,但速度会掉一大截。做疲劳检测时司机脸部通常离摄像头不太远,设置为 0 更合适。
另外,cv2.CAP_DSHOW只在 Windows 下生效,它的作用是让 VideoCapture 走 DirectShow 后端,解决笔记本摄像头打开慢、索引不稳定问题。Linux/macOS 上这个参数会被忽略,保留也不影响编译。
3.2 68 点索引表:眼睛、眉毛、嘴巴的编号和坐标验证
dlib 的 68 点模型遵循固定的编号排序:脸部轮廓 0-16,眉毛 17-26,鼻子 27-35,眼睛 36-47,外嘴唇 48-59,内嘴唇 60-67。这里的左右眼是面向摄像头时候的左右,和你从屏幕看到的左右刚好相反,这是第一个容易错的地方。
| 区域 | 索引范围 | 用途 |
|---|---|---|
| 右眼(画面左侧) | 36–41 | 计算右眼纵横比 |
| 左眼(画面右侧) | 42–47 | 计算左眼纵横比 |
| 外嘴唇 | 48–59 | 计算嘴巴纵横比 |
| 内嘴唇 | 60–67 | 可做更精细的哈欠判断 |
注意表格里右眼指的是人的右眼,出现在画面左边;左眼是人的左眼,出现在画面右边。很多人在写代码时把 36-41 当作左眼用,结果 EAR 曲线完全反着走。
拿到 shape 之后,我一般会把每只眼的六个点单独打印出来,确认是不是紧贴着眼眶。代码可以这样写:
def get_eye_points(shape, indices): points = [] for i in indices: x, y = shape.part(i).x, shape.part(i).y points.append((x, y)) return points left_eye_indices = list(range(42, 48)) right_eye_indices = list(range(36, 42)) left_eye = get_eye_points(shape, left_eye_indices) right_eye = get_eye_points(shape, right_eye_indices)这段代码做的事情是把编号转成坐标列表。为什么用 list(range(42,48)) 而不是写死六个数字?因为后面计算 EAR 时需要按顺序访问 p0-p5,而 dlib 的索引本身就是从眼角外侧开始顺时针排列,用 range 生成可以少记六个数字。逻辑上,左眼 42-47 是以人脸的左眼为准,如果你看到画面上右边那只眼的连接线画得奇怪,就把 36-41 和 42-47 对调。
3.3 一个容易被忽略的边界:检测不到人脸时怎么办
在驾驶场景里,低头、侧头、光线过暗都会让正脸检测器返回空列表。如果直接跑faces[0]会抛 IndexError,整个视频循环崩掉。上面代码里已经用if len(faces) > 0挡了一下,但更好的做法是维护一个“上一帧关键点存在”的状态。当这一帧没有检测到人脸,就沿用上一帧的 EAR 值或直接判定为闭眼状态。从工程上讲,你不能让人脸偶尔丢失导致整个报警系统闪烁。
实际代码里可以这样:
last_ear = 0.25 if len(faces) > 0: shape = predictor(gray, faces[0]) # 这里计算最新 EAR 并刷新 last_ear else: # 使用 last_ear 继续跑阈值判断,避免闭眼计数清零 pass这个保存历史值的模式,在第四节实现时很有用。因为疲劳判断是“连续若干帧闭眼”,中间突然丢一帧人脸,不应该让闭眼计数清零。我一般把 last_ear 放在循环外,每次检测到人脸才更新。这种细节决定了系统在真实驾驶室里能不能扛过偶尔的人脸丢失,而不是在演示时表现良好。
4. 疲劳指标计算:从 EAR 到 PERCLOS 的阈值与判定逻辑
4.1 EAR 不是玄学:六个点算出的眼睛开合度
眼睛纵横比(EAR)的概念来自 Soukupová 的论文,它的计算方式是取眼睛周围六个关键点,分别计算竖直距离与水平距离的比值。当眼睛睁开时,竖直距离 A、B 占水平距离 C 的比例约在 0.25-0.35;闭眼时比值会掉到 0.1 以下。用距离比值而不是绝对像素,好处是摄像头远近变化时不会被像素值骗到。
import numpy as np def euclidean_dist(point1, point2): return np.linalg.norm(np.array([point1.x, point1.y]) - np.array([point2.x, point2.y])) def eye_aspect_ratio(shape, eye_indices): # 按顺时针顺序取出六个点 p = [shape.part(i) for i in eye_indices] A = euclidean_dist(p[1], p[5]) B = euclidean_dist(p[2], p[4]) C = euclidean_dist(p[0], p[3]) return (A + B) / (2.0 * C)参数说明:eye_indices 传 42-47 或 36-41,顺序必须保持顺时针。A 是上下两点(1和5)的垂直距离,B 是第二组上下点(2和4)的垂直距离,C 是内眼角到外眼角的宽度。EAR 是垂直整体除以水平宽度,眼睛越闭越小。判断闭眼时不能单看一只眼,我会把左右眼的 EAR 分别算出来,取较小值,因为人有习惯性单眼眯着的情况,左边睁着右边闭的场景,平均值会丢掉右眼闭眼信息。
4.2 嘴巴纵横比 MAR:区分打哈欠与说话
嘴部区域在第 48-67 号点,外嘴唇 48-59 号点围成一圈。打哈欠时嘴会张得很大,张嘴说话时也有开度,但持续时间很短。用 48 和 54 两点作宽度,50 和 58 两点作高度,高度/宽度就是简单的嘴部纵横比。
def mouth_aspect_ratio(shape): p48 = shape.part(48) p50 = shape.part(50) p54 = shape.part(54) p58 = shape.part(58) width = euclidean_dist(p48, p54) height = euclidean_dist(p50, p58) return height / width逻辑说明:正常闭着嘴时 width 远大于 height,比值大概在 0.2 以下;打哈欠时 height 变大,比值可能超过 0.5。说话时也会冲到 0.5,但不会持续太长时间。所以哈欠报警在代码里需要加一个“连续 N 帧 MAR 大于阈值”的时间窗口,我一般取 0.5 和 5 帧,避免把一句“哦”误判成哈欠。
4.3 疲劳判定的时间窗口:连续闭眼帧数与 PERCLOS
单帧 EAR 低于阈值不能报警,因为正常眨眼也会让 EAR 短时间掉到阈值以下。正常人每分钟眨眼 10-15 次,一次闭眼约 100-150ms。疲劳的典型表现不是眨眼次数减少,而是单次闭眼时间变长,闭眼持续时间超过 0.3-0.5 秒就需要报警。如果摄像头是 25fps,0.4 秒大约对应 10 帧。
EAR_THRESHOLD = 0.25 CLOSED_FRAME_LIMIT = 8 closed_frames = 0 while True: # 读取帧,检测人脸,计算左右眼 EAR ear = min(left_ear, right_ear) if ear < EAR_THRESHOLD: closed_frames += 1 if closed_frames >= CLOSED_FRAME_LIMIT: alarm = True else: # 眼睛睁开,重置闭眼计数 closed_frames = 0参数说明:EAR_THRESHOLD 是静态闭眼阈值,CLOSED_FRAME_LIMIT 是连续闭眼多少帧报警。这两个值一定要实测微调。戴眼镜、眯眯眼、光照差都会让 EAR 整体偏高或偏低,打印实时 EAR 值观察后再定。代码里把 alarm 置为 True 后,通常还要再接一段声音播放或保存证据帧的逻辑。报警之后不要在同一帧立刻复位 closed_frames,否则系统会一直是报警-复位-报警的循环,停不下来。
PERCLOS 是另一个常用指标,含义是单位时间内眼睛闭合时间占比。比如统计 30 秒里 EAR 低于阈值的帧数除以总帧数,超过 20% 就认为疲劳。在驾驶场景里,我会把 PERCLOS 做成一个滚动窗口,每秒计算一次,而不是从头累计到 30 秒。窗口太长响应慢,窗口太短容易受单次眨眼干扰。实测里 10 秒窗口、20% 阈值比较合理。
from collections import deque closed_ratio_buffer = deque(maxlen=250) # 每检测一帧: closed_ratio_buffer.append(1 if ear < EAR_THRESHOLD else 0) if sum(closed_ratio_buffer) / len(closed_ratio_buffer) > 0.2: fatigue_level = "high"逻辑说明:deque(maxlen=250) 在 25fps 下恰好是 10 秒,最老的帧自动出去,新的进来,队列里 1 的占比就是 10 秒内闭眼比例。这样疲劳状态不是一条简单的持续上升计数器,而是能反映最近一段时间的行为状态。用这个窗口值配合连续闭眼帧数,误报比单独用固定帧数少很多。
4.4 报警输出与画面叠加
报警提示要直观,我一般直接在帧上用红色字写 FATIGUE,同时把实时 EAR、MAR、fps 打在画面左上角。这样在调试阶段能一眼看到阈值选得是否合适。
cv2.putText(frame, f"EAR: {ear:.2f} MAR: {mar:.2f}", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) if alarm: cv2.putText(frame, "FATIGUE ALERT", (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3)参数说明:putText 的坐标系以画面左上角为原点,字体、颜色、粗细都可以直接调。显示实时值不是为了好看,而是后面避坑章里标定阈值的重要工具。报警逻辑可以做在同一个循环里,也可以用多线程单独跑语音文件,避免阻塞视频帧读取。
5. 避坑指南:五个常见失败现场与排查顺序
5.1 dlib 安装失败:CMake 和 Boost 报错
现象:pip install dlib 在 Windows 上编译到 80% 时报错 “Could NOT find Boost” 或者 “CMake error at CMakeLists.txt”。原因:系统缺少 Visual Studio Build Tools 的 “使用 C++ 的桌面开发” 工作负载,dlib 的 CMake 配置找不到 Boost。解决:打开 Visual Studio Installer,添加该工作负载后重启电脑,再重新 pip 安装。如果前后配置没把握,直接改走 conda-forge:
conda install -c conda-forge dlib我遇到过一个更隐蔽的情况:电脑里装了 Visual Studio 但是没有勾选 C++ 工作负载,pip 报错时显示找不到编译器。先不要把锅甩给库,用where cl检查命令是否存在。另外 CMake 版本也别太低,老版本无法识别 dlib 的 CMakeLists 中的新写法,直接升级到最新随身版即可。
5.2 闭眼 EAR 却不降:关键点画到了眼皮外面
现象:眼睛闭着,EAR 还是 0.3 甚至 0.4,报警永远不触发。原因:左右眼索引写反了。dlib 模型的 36-41 是人的右眼,42-47 是人的左眼。如果把 42-47 当画面左眼处理,画出来的六个点连成一个歪的六边形,EAR 当然不对。解决:把关键点可视化出来,确认每个编号落在眼眶边缘。我现在的习惯是拿到任何关键点模型,先运行一次“画点+连线”的脚本,绝不想当然地相信索引表:
# 把所有眼睛相关点都画出来 for i in range(36, 48): x, y = shape.part(i).x, shape.part(i).y cv2.circle(frame, (x, y), 2, (0, 255, 0), -1)运行后看哪只眼出现在画面的左/右,再对照你的左右眼变量名。还有一个连带现象:单只眼 EAR 正常,另一只眼 EAR 恒为 0.5 左右,多半是眼皮上的点被误判到了眉毛上,这时要检查是不是戴了粗框眼镜导致检测点偏移。
5.3 帧率掉到 10fps:人脸检测参数与连续检测陷阱
现象:系统能跑,但画面明显卡顿,fps 只有 10 左右。原因:detector(gray, 1)的上采样放大倍数设为 1,人脸检测的计算量几乎是 0 的好几倍;另外每一帧重新做整图检测,本身也比关键点预测耗时。解决:把第二个参数改成 0;如果还卡,采取“每 10 帧重新检测一次人脸,中间帧用上一次的人脸框”,或者改用 dlib.correlation_tracker 跟踪上一次位置。注意 tracker 和 predictor 的搭配:先 track 再 predict,因为 predict 需要准确的人脸矩形。实测 30fps 摄像头上,这个改动能让系统回到 25fps 以上。
# 每 10 帧做一次完整检测 if frame_count % 10 == 0: faces = detector(gray, 0) if len(faces) > 0: tracker.start_track(gray, faces[0])tracker.start_track 只能初始化一次,后续要调用 tracker.update(gray)。我一般把 frame_count 放在视频循环里,第一帧先 start_track,中间帧 update,这样既保留了检测精度,又避免每帧都跑完整人脸检测。
5.4 摄像头打不开或画面是黑的:索引和 DirectShow 后端
现象:代码在 A 机器上能开摄像头,换到 B 机器上VideoCapture(0)返回 True 但画面漆黑,或者 cap.isOpened() 一直 False。原因:笔记本有内置和外接两个摄像头,索引 0 被占用为其他虚拟设备;Windows 下 OpenCV 默认的 VFW 后端兼容性差。解决:用一个循环探测 0-5 号索引,选第一个能打开且返回有效帧的设备;Windows 下使用cv2.CAP_DSHOW后端:
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)如果画面还是黑的,可能是摄像头被别的软件占用,关闭微信、浏览器摄像头权限后重试。在 Linux 上还要检查 /dev/video0 的权限,常见做法是把当前用户加入 video 组。
5.5 模型文件加载异常或预测点全是 0
现象:加载shape_predictor_68_face_landmarks.dat时不抛错,但预测出来的 68 个点全是 (0,0);或者加载时抛 “Invalid shape predictor file”。原因:模型文件损坏,下载不完整;路径包含中文让 ifstream 读出了错。解决:检查文件大小是否为 99.7MB 左右,把模型放到纯英文路径,重新下载后覆盖旧文件。任何一次 load 后先打印shape.part(30).x验证是否为非 0。这条在之前的章节里提过,但它是最容易在交付现场出现的问题,值得单独记一条:拿到新环境,先加载模型再跑流程。如果检查完都没问题但预测点仍然偏移,再看分辨率,过小的输入图像会导致关键点回归失败,我一般把帧缩放到宽度 640 再送给 predictor。
6. 让系统在实车上跑:实时平滑与阈值标定技巧
6.1 用 EMA 平滑 EAR,别让单帧抖动干扰判断
EAR 是坐标像素算出来的,摄像头噪点、面部肌肉轻微拉动都会让单帧值上下跳。直接拿原始值与阈值比较,报警可能在边界值附近反复横跳。我一般在进判定之前做一次指数滑动平均,alpha 取 0.3 左右。
smooth_ear = 0.0 # 每帧更新 smooth_ear = alpha * ear + (1 - alpha) * smooth_earalpha 越小,曲线越平滑但反应越迟钝;太大又骗不了噪声。0.3 是我实测驾驶场景下比较折中的值。
6.2 验证技巧:把关键点、EAR、FPS 画在同一帧画面上
拿到任何摄像头项目,我的习惯是先开一屏三显:画面左上角打印 fps,左下方打印实时 EAR 和眼睛状态,同时把所有关键点画出来。然后对着摄像头做几个动作:睁眼、闭眼、打哈欠,记录睁眼时的 EAR 范围和闭眼时的范围,用这两个范围去定阈值,而不是直接抄别人代码里的 0.25 和 0.5。
cv2.putText(frame, f"fps: {fps:.1f} ear: {smooth_ear:.2f} mar: {mar:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)逻辑说明:fps 用最近 30 帧的平均耗时算,直接向前端展示。阈值标定之后,再验证两件事:连续眨眼 10 次不应该触发报警;人为闭眼超过 0.5 秒一定要触发报警。这套资源里算法部分已经包好,拿到手最重要的是把自己的摄像头环境调通,别在没做完可视化验证前就上真车。
那次我在实验室里调误报,眼看着屏幕上 EAR 在 0.22 到 0.28 之间来回动,阈值怎么改都拦不住误报。后来把关键点和实时值打印出来才发现,摄像头自动曝光把亮度拉得太低,六个关键点有两三个跑到眼皮外面去了。从那以后,我每次接手摄像头检测项目,都强制先走一遍“画关键点、打 fps、打实时指标”的流程,再谈阈值和报警。希望帮到你。
本文还有配套的精品资源,点击获取