简介面向计算机视觉初学者和希望快速搭建实时眼部状态检测原型的开发者这套代码包围绕 Python 与 OpenCV 展开给出人眼定位、眨眼判定和闭眼识别的完整实现并配套 Ubuntu 环境下的依赖安装、代码运行与原理讲解教程。实现上以 Haar 级联分类器检测眼部区域结合连续帧差异、光流或霍夫变换分析眼睛变化同时提供 68 点人脸关键点模型与 imutils 辅助模块方便读者理解从人脸对齐、眼部定位到状态判定的处理链路也便于按模块拆解复现。包内共 61 个文件以 37 个 py 源码为主辅以 12 个 png、4 个 jpg 示例图以及 dat 模型、pdf 教程和 md 说明等压缩包约 75.34MB源码、素材与文档分层清楚。已有 4369 人学习适合对照教程跑通摄像头实时流程并在此基础上调整阈值、复用检测脚本或完成课程实践与视觉入门项目。1. 从摄像头到眨眼计数这个 Python 项目到底解决了什么问题对着笔记本摄像头屏幕上实时画出人脸框和眼部轮廓你眨一下眼右上角的计数器加一你把眼睛闭上不动屏幕开始累计闭眼时长——这就是这套 Python OpenCV 人眼识别源码最直观的效果。它不是人脸识别的玩具 demo核心落点在两个可以量化的指标上眨眼检测的次数以及闭眼检测的持续时长。这两个数字往上走一步就是疲劳监测、驾驶分心提醒、在线考试的注意力分析。适合谁手上有一份源码但不知道从哪下嘴的新手照着跑通也适合想把眨眼判定接到自己业务里的熟手直接拿状态机去改。这套代码的技术主线是 dlib 的 68 点人脸关键点 EAR 眼纵横比后面的章节会一层层拆开。2. 技术选型与工程结构为什么是 dlib 68 点加 EAR而不是 Haar 级联很多人第一次做眼睛检测第一反应是 OpenCV 自带的haarcascade_eye.xml。能跑但你会发现它只告诉你「这块矩形里大概有只眼睛」闭眼时经常直接漏检——因为 Haar 特征训练时用的正样本基本都是睁眼图。这套源码走的是另一条路先用 dlib 的人脸检测器框出人脸再用 68 点关键点模型把眼角的六个点定位出来最后用 EAR 眼纵横比做几何判定。睁眼时 EAR 在 0.28~0.35 之间闭眼时塌到 0.15 以下这个数值变化是连续的、可观测的比分类器的二值输出好调太多。2.1 依赖清单与版本踩点跑这套代码需要的东西不多但版本上确实有玄学尤其是 dlib 在 Windows 上的编译。依赖包作用版本建议Python运行环境3.7 ~ 3.10不要上 3.12opencv-python摄像头读取、绘图、图像预处理4.5.x 及以上dlib人脸检测 68 点关键点回归19.22 ~ 19.24numpy关键点坐标的向量运算与 opencv 匹配的版本即可imutils坐标转换、尺寸缩放的辅助封装0.5.4opencv-python和numpy直接 pip 装二进制轮子就行没什么坑。麻烦的是dlib它底层依赖 C 编译工具链Windows 上直接pip install dlib大概率卡在 CMake 那一关报一堆Cannot find cmake或者 Visual Studio 版本不匹配。我的血泪经验是先装cmake和Visual Studio Build Tools勾选 C 生成工具再装dlib。如果实在不想折腾编译找对应 Python 版本的预编译 whl 文件离线安装比对着源码硬编省一个小时。macOS 上brew install cmake之后基本一次过Linux 上还需要libx11-dev之类的图形库依赖。提示装完 dlib 之后先跑一句import dlib; print(dlib.__version__)能打印出版本号再往下走别等到跑主程序时才暴露环境问题。2.2 工程目录与模块职责源码包的结构一般不会太复杂但拆分得比较清楚能看出作者是按职责分层的。典型目录长这样eye_blink_detect/ ├── models/ │ └── shape_predictor_68_face_landmarks.dat # dlib 68 点关键点模型 ├── src/ │ ├── detector.py # 人脸检测 关键点定位封装 │ ├── ear.py # 眼纵横比计算 │ ├── blink_logic.py # 眨眼状态机、计数、闭眼计时 │ └── utils.py # 坐标转换、画图辅助 ├── main.py # 主循环入口 ├── config.py # 阈值、帧数、摄像头索引等参数 └── requirements.txtmodels/里那个.dat文件是关键点回归模型的权重大约 95MB是整个项目最不能少的一块。有些下载来的包里模型缺失或者被替换成了别的版本跑起来会在shape_predictor初始化那一行直接抛异常。模型文件需要从 dlib 官方模型仓库获取文件名必须是这个路径要对得上config.py里写的相对路径。detector.py负责的是「从一帧图像里拿到 68 个点」这件事它内部维护两个对象dlib.get_frontal_face_detector()和dlib.shape_predictor()。这两个对象初始化开销不小必须在主循环外建好循环里只做推理。ear.py是个纯函数模块输入六个点的坐标输出一个浮点数不依赖任何全局状态所以特别好测。blink_logic.py是真正有「业务」的地方——它要处理连续帧之间的状态迁移判断这一帧的闭眼到底是不是一次眨眼的开始。2.3 EAR 值到底在算什么EAR 的公式看着简单但理解它为什么有效比抄公式重要。眼睛的六个关键点按顺时针排列编号 36 到 41 是左眼42 到 47 是右眼。公式是EAR (|p2 − p6| |p3 − p5|) / (2 × |p1 − p4|)分子是两条竖直方向的眼皮距离分母是水平方向的内外眼角距离。睁眼时上下眼睑拉开分子大EAR 就大闭眼时上下眼睑贴合分子趋近于零EAR 就小。分母做归一化保证远近距离、不同脸型的人之间有一定的可比性——这也是这套方法比直接看像素距离靠谱的原因。import numpy as np def eye_aspect_ratio(eye_points): eye_points: shape(6, 2) 的 numpy 数组顺序为 p1..p6顺时针p1 为外眼角 返回: 该只眼睛的 EAR 浮点值 # 两条竖直眼皮距离 vertical_a np.linalg.norm(eye_points[1] - eye_points[5]) vertical_b np.linalg.norm(eye_points[2] - eye_points[4]) # 水平方向的内外眼角距离做归一化用 horizontal np.linalg.norm(eye_points[0] - eye_points[3]) if horizontal 1e-6: # 防除零极端侧脸时会出现 return 0.0 return (vertical_a vertical_b) / (2.0 * horizontal)np.linalg.norm算的是欧氏距离两行就够。中间那个除零保护是必须加的否则侧脸到 90 度的时候horizontal会接近 0EAR 直接爆成inf后面的阈值判断全部失效。参数上唯一要调的是「用哪只眼」实际工程里常见做法是左右眼各算一次再取平均比只看单眼稳因为侧脸时被遮挡的那只眼误差大平均能把它稀释掉。3. 核心代码拆解一帧画面如何变成一次眨眼事件这一章是整套代码的骨架。从摄像头读进来一帧 BGR 图像到最后计数器加一并画到屏幕上中间要经过检测、定位、计算、状态判定四道关。任何一关的参数没对上出来的结果就是「一直显示睁眼」或者「疯狂计数」。3.1 人脸检测与 68 点关键点的坐标系dlib 的人脸检测器有两种HOG 和 CNN。源码里用的是 HOG 版本也就是get_frontal_face_detector()。它速度快CPU 上实时跑 30 帧没压力缺点是侧脸和远距离小脸容易漏。CNN 版本准确率高但需要 GPU 或者慢得离谱做实时眨眼检测不划算。import cv2 import dlib # 两个对象必须在循环外初始化反复 new 会拖慢帧率 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ok, frame cap.read() if not ok: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 第二个参数是上采样次数1 表示放大一倍再检测小脸场景可以调到 2 rects detector(gray, 1) for rect in rects: shape predictor(gray, rect) # 68 点全部落在 rect 坐标系内原点与 frame 左上角一致 pts [(p.x, p.y) for p in shape.parts()]这里有两个容易翻车的点。第一detector和predictor一定要在循环外面建放进循环里每帧重新加载模型帧率会从 30 掉到 2很多人以为是电脑不行其实是自己把初始化写错了位置。第二detector(gray, 1)的第二个参数是上采样倍数传 1 意味着图像先放大一倍再检测能提升小脸召回率但耗时翻倍传 0 是原图直接检测速度快但远处的人脸容易丢。摄像头分辨率 640×480、人坐得离屏幕半米左右用 1 比较稳。shape.parts()返回的 68 个点与输入frame共享同一套像素坐标系不需要额外做偏移换算这一点 dlib 处理得比很多自己训模型的方案省事。3.2 眼部关键点抽取与 EAR 计算拿到 68 点之后索引 36~47 就是两眼。左眼 36-41右眼 42-47这是 dlib 模型固定的编号约定换成别的模型比如 MediaPipe 的 468 点索引就完全不同代码不能直接搬。import numpy as np LEFT_EYE_IDX list(range(36, 42)) RIGHT_EYE_IDX list(range(42, 48)) def get_eye_points(pts, idx): 从 68 点列表里抽出指定索引的 6 个点返回 (6, 2) 的 ndarray return np.array([pts[i] for i in idx], dtypenp.float64) def compute_ear(pts): 一次算出左右眼 EAR 并取均值侧脸时更稳 left eye_aspect_ratio(get_eye_points(pts, LEFT_EYE_IDX)) right eye_aspect_ratio(get_eye_points(pts, RIGHT_EYE_IDX)) return (left right) / 2.0, left, rightcompute_ear返回三个值是有讲究的均值用来做判定单独的左右眼值用来做调试。如果你发现某个人左眼 EAR 一直是 0.1、右眼 0.3那大概率不是他闭着眼而是侧脸导致左侧关键点定位漂了这时候应该看右眼的值做决策。我在调试阶段会把三个值都打印到画面上比盯着一个平均值猜原因高效得多。眼部索引的顺序也不能乱。eye_aspect_ratio里 p1 和 p4 用来算水平距离如果顺序错了比如把 p1 和 p3 当成眼角算出来的就不是水平距离EAR 曲线会完全失真。dlib 的 36~41 是从左眼外眼角开始顺时针绕一圈这个顺序直接用就行自己重新排序反而容易出错。3.3 眨眼状态机把连续帧的 EAR 变成一次事件这一步是整套代码里最需要「想清楚」的地方。EAR 每帧都在变你不能简单地判断「EAR 阈值就计数 1」否则闭眼 20 帧就会计 20 次。真正的眨眼是这样一个过程睁眼EAR 高→ 闭眼EAR 低→ 睁眼EAR 回升只有完整走完这一圈才算一次。class BlinkCounter: def __init__(self, ear_thresh0.21, consec_frames3, release_frames2): # ear_thresh: 判定为闭眼的 EAR 阈值不同人差异较大0.18~0.25 之间调 self.ear_thresh ear_thresh # consec_frames: 连续多少帧低于阈值才确认为闭眼滤掉单帧噪声 self.consec_frames consec_frames # release_frames: 连续多少帧高于阈值才确认为重新睁眼防止抖动重复计数 self.release_frames release_frames self.closed_count 0 # 连续闭眼帧计数 self.open_count 0 # 连续睁眼帧计数 self.is_closed False # 当前是否处于闭眼状态 self.blink_total 0 # 眨眼总次数 self.closed_frames 0 # 累计闭眼帧数用来算闭眼时长 def update(self, ear): 每帧调用一次返回本帧是否发生了一次完整眨眼 blink_event False if ear self.ear_thresh: self.closed_count 1 self.open_count 0 if self.closed_count self.consec_frames: self.is_closed True self.closed_frames 1 # 只统计确认闭眼之后的帧 else: self.open_count 1 self.closed_count 0 if self.is_closed and self.open_count self.release_frames: # 从闭到开且睁眼状态稳定住了才算一次眨眼 self.is_closed False self.blink_total 1 blink_event True return blink_event三个参数是这套状态机的全部可调项。ear_thresh决定多低算闭眼consec_frames决定要连续低多少帧才确认release_frames决定要连续高多少帧才确认重新睁开。consec_frames和release_frames的存在就是为了抗噪——摄像头噪点、关键点抖动、轻微眯眼都可能让某一帧 EAR 突然掉下去加了连续帧确认之后单帧毛刺就进不了状态机。closed_frames的累加位置放在「确认闭眼之后」而不是「EAR 低于阈值就加」这一点很关键。如果把累加放在阈值判断的第一层那么所有单帧噪声都会被计入闭眼时长最后算出的「平均闭眼时长」会明显偏大。这个细节我在第一次写的时候也翻过车测出来的眨眼时长是 0.4 秒正常眨眼才 0.1~0.4 秒明显不对。3.4 视频主循环与画面叠加主循环负责把上面几块串起来并且把状态实时画到画面上。可视化不是装饰是调试手段——没有画面反馈你根本不知道是检测丢了还是阈值错了。counter BlinkCounter(ear_thresh0.21, consec_frames3, release_frames2) FPS cap.get(cv2.CAP_PROP_FPS) or 30.0 while True: ok, frame cap.read() if not ok: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) rects detector(gray, 1) ear_value 0.0 for rect in rects: shape predictor(gray, rect) pts [(p.x, p.y) for p in shape.parts()] ear_value, left_ear, right_ear compute_ear(pts) # 画人脸框和双眼轮廓方便肉眼确认检测是否跟上了 cv2.rectangle(frame, (rect.left(), rect.top()), (rect.right(), rect.bottom()), (0, 255, 0), 1) for idx in LEFT_EYE_IDX RIGHT_EYE_IDX: cv2.circle(frame, pts[idx], 2, (0, 0, 255), -1) if counter.update(ear_value): cv2.putText(frame, BLINK!, (30, 120), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 255), 2) # 把闭眼帧数折算成秒帧率不准时可以改成用 time.time() 计时 closed_seconds counter.closed_frames / FPS cv2.putText(frame, fEAR: {ear_value:.3f}, (30, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 0), 2) cv2.putText(frame, fBlinks: {counter.blink_total} Closed: {closed_seconds:.1f}s, (30, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(Eye Blink Detect, frame) if cv2.waitKey(1) 0xFF 27: # ESC 退出 break cap.release() cv2.destroyAllWindows()closed_seconds用帧数除以帧率来换算时长简单但有坑CAP_PROP_FPS在很多摄像头上返回 0 或者虚标比如标 30 实际 24除出来的时长会漂。更稳的做法是在循环里记录time.time()用真实时间差来累加闭眼时长代码多三行但结果可信。我在正式项目里一律用后者帧率这个值太不靠谱了。cv2.waitKey(1)的返回值要 0xFF这是 OpenCV 在部分平台上的老问题不做掩码的话按 ESC 可能没反应。另外摄像头索引 0 是默认设备笔记本外接USB摄像头可能是 1 或 2跑起来黑屏或者报错先检查这个。4. 从零跑通环境搭建、模型准备与首跑参数代码看懂和跑起来是两回事。这一章按顺序把环境、模型、摄像头、参数四件事走一遍每一步都给出「做完了应该看到什么」对不上就说明这一步没成。4.1 环境搭建与模型文件准备先建虚拟环境别在系统 Python 里装 dlib装崩了很难收拾。python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install numpy opencv-python imutils pip install cmake pip install dlibcmake要单独先装dlib 的安装脚本会调它顺序反了会失败。装 dlib 的过程可能持续几分钟如果卡在Building wheel for dlib超过十分钟基本就是缺 C 编译器别等了去补 Visual Studio Build Tools。模型文件shape_predictor_68_face_landmarks.dat需要单独准备把它放到models/目录下然后验证一下文件能被正确加载import dlib, os model_path models/shape_predictor_68_face_landmarks.dat print(exists:, os.path.exists(model_path)) print(size(MB):, round(os.path.getsize(model_path) / 1024 / 1024, 1)) predictor dlib.shape_predictor(model_path) print(model loaded ok)正常输出应该是exists: True、size(MB): 95.x、model loaded ok。如果 size 只有几 MB 或者几十 KB说明下载的是网页而不是模型文件如果shape_predictor抛RuntimeError说明文件损坏或者版本不匹配重新下一份。注意模型文件的路径写的是相对路径一定要在项目根目录下运行python main.py在src/里跑会找不到文件报Unable to open shape_predictor。4.2 首跑参数与摄像头调试第一次跑不要直接上业务场景先坐着正对摄像头室内正常光照跑个十几秒看看数值。# config.py 建议先这么配跑通之后再调 CAMERA_INDEX 0 FRAME_WIDTH 640 FRAME_HEIGHT 480 DETECT_UPSAMPLE 1 EAR_THRESH 0.21 CONSEC_FRAMES 3 RELEASE_FRAMES 2EAR_THRESH 0.21是个通用起点但不同人眼型差异很大眼睛本身比较大的人睁眼 EAR 能到 0.35闭眼 0.1眼睛偏细长的人睁眼可能只有 0.24闭眼 0.12阈值设 0.21 就几乎判不出闭眼。所以正确的做法是先看数值再定阈值——跑起来之后盯着画面上的 EAR 值睁眼时记一个数闭眼时记一个数把阈值取在两者中间偏下的位置。摄像头打不开的排查顺序先ls /dev/video*Linux或打开系统相机应用确认硬件正常再改CAMERA_INDEX试 1、2最后检查是不是被别的程序占用了。Windows 上被微信、腾讯会议占着摄像头的情况很常见关掉那些程序再跑。4.3 效果验证怎么确认检测是对的跑起来之后要验证三件事缺一个都说明代码有问题。第一件静止睁眼时 EAR 是否稳定。正常应该在 0.26~0.34 之间小幅波动如果一直在 0.05~0.1 徘徊说明关键点定位错了或者眼部索引取错了。第二件正常眨眼计数器是否 1。连续眨十次计数应该是 10如果变成 20 说明release_frames设太小一次眨眼被判成了两次如果是 5说明consec_frames设太大眨眼太快没达到连续帧数。第三件闭眼不动时长是否正确。闭眼三秒Closed应该显示约 3.0s偏差超过 0.5s 就要检查帧率换算。用一个固定的测试流程来验证比随手眨几下靠谱睁眼 2 秒 → 快速眨眼 5 次 → 闭眼 3 秒 → 睁眼 2 秒整套做三遍看计数是否稳定在 15 次、闭眼总时长是否稳定在 9 秒左右。数字对不上再去调参数。5. 避坑与常见问题排查那些让我重跑一下午的现场这套代码逻辑不复杂但出问题的位置很集中。下面这几条都是实际会遇到的按「现象 → 原因 → 解决」记下来遇到时能省不少时间。5.1 摄像头与帧读取类问题现象一程序跑起来窗口是全黑的或者第一帧之后卡住。原因是摄像头没成功打开cap.read()一直返回False但很多示例代码没检查返回值就拿着空帧往下走。解决方法是把ok判断提到最前面ok为False时直接打印提示并break不要让它静默失败。另外要确认CAMERA_INDEX与实际设备号对得上笔记本内置摄像头一般是 0外接 USB 摄像头经常是 1 或 2。现象二帧率明显偏低画面一顿一顿EAR 值也在跳。原因是循环内反复创建了dlib.shape_predictor或者在一个循环里同时做了检测和绘图拖慢了速度。predictor的加载开销在几百毫秒级别放进循环会让每帧耗时暴涨。解决方法是把detector、predictor都移到循环外循环里只做推理如果还慢把检测上采样参数从 1 调成 0或者把帧缩放到 320×240 再检测坐标最后按比例放大回来。5.2 检测与阈值判定类问题现象三眼睛明明是睁着的EAR 却一直在 0.1 左右计数器不涨。原因通常是关键点定位失准或者左右眼索引取反了。侧脸超过 30 度时被遮挡那只眼的关键点会挤在一起EAR 天然偏低。解决方法是先只取右眼或只取左眼的值看是否正常再决定是否用双眼平均同时让人正对摄像头测试排除角度因素。如果正脸也一样低检查LEFT_EYE_IDX range(36, 42)这一段有没有被改过。现象四眨一次眼计数器涨了两三次。原因是状态机缺了「回弹确认」闭眼瞬间 EAR 在阈值附近抖动一帧低一帧高就被当成了好几次眨眼。解决方法是把release_frames从 1 调到 2 或 3让重新睁开的判定也需要连续多帧确认。这个参数调大了会让极快的眨眼漏检所以不能盲目往大调2~3 是常用的平衡点。现象五闭眼时长明显偏大闭两秒显示五六秒。原因有两个可能一是closed_frames的累加放在阈值判断的第一层把所有噪声帧都计进去了二是用CAP_PROP_FPS换算时长而摄像头实际帧率远低于标称值。前者的解决方法是只在「确认闭眼」之后才累加帧数后者是改用time.time()记录进入闭眼状态的时间戳退出时用真实时间差计算。提示调试阶段把left_ear、right_ear、ear_thresh三个值都画到画面上比打印到控制台直观一眼就能看出是定位问题还是阈值问题。6. 二次开发从眨眼计数到闭眼时长预警源码跑通之后真正有价值的部分才开始。眨眼计数本身没什么业务含义能落地的指标是「单位时间内的眨眼频率」和「单次闭眼持续时长」——前者低于 10 次/分钟通常意味着注意力下降后者超过 1.5 秒已经接近微睡眠。这两个指标都能在现有状态机上改出来不需要推翻重写。6.1 用时间戳替掉帧计数第一个改造点是把所有基于帧数的统计换成基于时间的统计。原因是帧率不稳定帧计数在低帧率摄像头下会被严重低估。import time class BlinkTimer: def __init__(self, ear_thresh0.21, consec3, release2): self.ear_thresh ear_thresh self.consec consec self.release release self.closed_cnt 0 self.open_cnt 0 self.is_closed False self.enter_closed_ts 0.0 # 进入闭眼状态的时间戳 self.total_closed_sec 0.0 # 累计闭眼总时长 self.blink_times [] # 每次眨眼的结束时间戳 self.long_closure_alert False def update(self, ear, long_thresh1.5): now time.time() if ear self.ear_thresh: self.closed_cnt 1 self.open_cnt 0 if self.closed_cnt self.consec and not self.is_closed: self.is_closed True self.enter_closed_ts now else: self.open_cnt 1 self.closed_cnt 0 if self.is_closed and self.open_cnt self.release: duration now - self.enter_closed_ts self.is_closed False self.total_closed_sec duration self.blink_times.append(now) # 单次闭眼超过阈值置位预警标志 self.long_closure_alert duration long_thresh return duration return 0.0enter_closed_ts只在从睁到闭的那一刻记录一次退出闭眼时用now - enter_closed_ts得到真实持续秒数这段时长不受帧率影响。long_closure_alert是一个一次性标志可以由上层逻辑在下一次读取后重置。用blink_times这个列表可以算出最近 60 秒内的眨眼次数先裁掉早于now - 60的时间戳再取len()就是当前眨眼频率。6.2 阈值自适应与多人脸场景第二个改造点是阈值。EAR_THRESH 0.21对某些眼型直接失效一个可行的做法是启动时先采集 3 秒的睁眼数据取中位数乘 0.7 作为个人化阈值# 启动时调用采集 3 秒稳定睁眼状态下的 EAR 样本 def calibrate(cap, detector, predictor, compute_ear, seconds3.0): samples [] end_ts time.time() seconds while time.time() end_ts: ok, frame cap.read() if not ok: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) for rect in detector(gray, 1): pts [(p.x, p.y) for p in predictor(gray, rect).parts()] ear, _, _ compute_ear(pts) if ear 0.15: # 粗略过滤掉明显不是睁眼的样本 samples.append(ear) if not samples: return 0.21 # 采集失败就退回默认值 samples.sort() median samples[len(samples) // 2] return round(median * 0.7, 3) # 经验系数0.7 附近比较稳median * 0.7里的 0.7 是我试出来的经验值用中位数而不是均值是因为要抗偶发的闭眼帧。标定出来的阈值一般在 0.18~0.24 之间比固定值靠谱得多尤其是戴眼镜的人。多人脸场景的处理思路是给每张脸维护一个独立的BlinkTimer实例用rect的中心点做简单的位置关联前后两帧中心距离小于 80 像素就算同一张脸而不是只用一个全局计数器。脸多了之后帧率会掉这时候要把检测上采样参数调成 0并且把非主脸的绘图省掉只保留数据统计。改造完之后一定要用同一套验证流程回归正常眨眼 10 次、闭眼 2 秒、快速眨眼 5 次看计数和时长是否还对得上。我后来形成了一个习惯每次动状态机里的参数或逻辑都强制走一遍这套固定动作不看画面只看数字对不上就回滚。这套代码的价值不在检测本身而在它提供了一个干净可改的判定骨架。状态机、时间戳、标定这三个地方改完就能接到实际的疲劳提醒、注意力分析场景里去。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Agent 技术实现全解析:从架构设计到工程落地 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 9:01:20
CoreDNS 1.8.0 离线部署实战:从镜像导入到三层验证 简介:coredns_v1.8.0.tar.gz 面向在 Kubernetes 集群中部署与维护 DNS 服务的运维及开发人员,提供与 k8s v1.21.2 环境适配的 CoreDNS 镜像文件,可用于离线环境导入、集群 DNS 组件升级或故障排查时替换验证,适合具备一定容器与集… · 2026/11/6 9:01:20
稚晖君这台开源桌面机器人ElectronBot,藏着具身智能的预演 〇、先立时间锚点:一个“捡鼠标垫配电脑”的故事ElectronBot 发布于 2022 年 3 月,是稚晖君在华为"天才少年"时期的开源个人项目。作者在原视频里戏称项目动机是“捡到一个鼠标垫,想配一台电脑”——他淘到一块圆形 LCD 屏… · 2026/11/6 9:00:18
HBase与Neo4j集成架构:大规模图数据处理实战 1. HBase与Neo4j集成方案概述 在大规模图数据处理领域,HBase和Neo4j的结合堪称"天作之合"。我曾在某社交网络分析项目中采用这种架构,成功处理了超过10亿节点和50亿边的图数据。HBase负责存储原始数据和高吞吐量的读写操作,而Neo4j… · 2026/11/6 12:51:10
金蝶云星空V7.3生产模块:基础数据与系统参数的协同控制逻辑 简介:本资源是金蝶云星空V7.3制造领域生产管理模块的官方培训课件,面向制造业企业ERP实施顾问、生产计划员、车间管理人员及金蝶系统运维人员,聚焦解决生产订单全周期管控、物料齐套分析、领退补料协同、在制过程追溯等核心业务痛点。课件为单… · 2026/11/6 12:51:10
Qt QTableWidget十万行数据卡顿假死?模型视图优化与排查指南 简介:这份资源面向使用 Qt 进行 GUI 开发、需要解决 QTableWidget 大数据量展示卡顿问题的开发者,核心围绕惰性加载(Lazy Loading)思路展开。包内共 12 个文件,以 5 个 cpp 源文件与 4 个 h 头文件为主体,另… · 2026/11/6 12:51:10
CSP-J初赛选择题训练2021:分块刷题与错题复盘 很多人第一次认真做CSPJ初赛选择题训练2021的时候,都会经历一个很别扭的阶段:十套卷子刷完,正确率纹丝不动,错的地方还是那几处——补码的边界、二叉树的叶子数、快排的最坏情况、代码段里循环变量到底跑了几轮。对完答案… · 2026/11/6 12:49:05
R²高不等于预测准:回归分析避坑指南 简介:《基于回归分析的人口数量预测》是一份面向统计学、经济分析及数据建模初学者的课程实验论文文档,围绕人口数量与经济指标的关联预测展开,适合用作回归分析课程作业参考或建模入门范例。资源包内共1个doc文件,大小约442KB&am… · 2026/11/6 12:47:00
WOA优化VMD参数K与α:包络熵适应度及Matlab实战避坑 简介:面向计算机、电子信息工程、数学等专业学生及信号处理初学者,这份PDF资料围绕Matlab环境下的WOA-VMD展开,重点解决VMD中模态数K与惩罚因子alpha难以人工选取的问题。内容以鲸鱼优化算法自动寻优为主线,结合边际谱、频率图、收… · 2026/11/6 12:47:00
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
ASIC主模式RTL设计:状态机与三态驱动的流片级实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 10:29:23