
简介一份面向Python与OpenCV初学者及计算机视觉开发者的完整工程包聚焦实时人眼识别、眨眼检测与闭眼检测提供在Ubuntu环境下的源代码、模型文件与图文教程。工程以OpenCV的Haar级联分类器实现人眼定位结合人脸关键点模型辅助判断睁闭眼状态可应用于疲劳驾驶预警、注意力监测、表情分析等场景适合作为计算机视觉方向的入门练手项目。包内共六十一个文件包含三十七个Python源码文件检测主程序、预处理与辅助脚本、十二张演示图片、四张示例图片、一个人脸关键点模型另有PDF图文教程和Markdown说明文档压缩包整体约七十五MB目录结构清晰便于按模块阅读或提取复用。目前已有四千二百九十一人学习浏览资源附有详细的运行与编译指导能够帮助读者快速跑通实时检测Demo并理解关键原理在此基础上可自行扩展连续帧眨眼计数、闭眼时长统计等进阶功能。1. 项目整体思路与方案设计1.1 为什么做这个项目眼睛状态的实时分析在计算机视觉领域是一个非常经典且实用的切入点。我们开车打瞌睡时眼皮会不自觉地变沉上课走神时目光会涣散疲劳驾驶预警系统、网课注意力检测、甚至部分无障碍交互系统核心都需要精确识别眼睛的开合状态。这个项目以 PythonOpenCV 为底座加入 dlib 人脸关键点检测模型实现实时的人眼识别、眨眼检测与闭眼检测。你可以直接用摄像头现场跑起来也可以把输入源换成视频文件用于批量分析。代码量不大但涉及人脸检测、关键点定位、几何特征计算与状态机判定这一整套流程非常适合作为计算机视觉入门的综合实践项目。1.2 技术方案选型与权衡在实现人眼检测时最直接的方案是用 OpenCV 自带的 Haar Cascade 级联分类器里面预置了haarcascade_eye.xml眼睛检测模型几行代码就能画出眼睛框。但这个方案有个天生缺陷它只能输出眼睛的矩形框无法提供眼睛轮廓的精确坐标也就没法计算更高级的指标——比如眼睛开合程度。所以我选择用 dlib 的 68 点人脸关键点模型。它能一次性定位人脸 68 个特征点其中左右眼各占 6 个关键点。基于这 6 个点计算人眼纵横比EAREye Aspect Ratio就能精确量化眼睛的开合程度眨眼和闭眼本质上就是 EAR 值随时间变化的波形。方案对比起来很简单方案人眼定位精度支持开合度计算速度依赖复杂度Haar Cascade 眼睛检测矩形框精度一般不支持快低dlib 68点关键点像素级轮廓坐标支持中中需安装dlib深度学习关键点模型像素级轮廓坐标支持依赖GPU高我最终选了 dlib 68 点方案原因是它在普通 CPU 上就能跑到实时帧率而且 68 点模型文件约 60MB下载一次之后就能离线使用。如果你后续想换 MediaPipe 或 OpenCV 的深度学习人脸检测器代码框架也不用大改只需要替换关键点提取那一段即可。1.3 眨眼检测的原理推导先明确一个核心概念EAREye Aspect Ratio眼睛纵横比。具体来说dlib 68 点模型中右眼关键点索引是 36~41左眼是 42~47。以右眼为例6 个点的分布大致是p136和 p439是眼睛左右两端p2、p337、38是上眼皮p5、p640、41是下眼皮。EAR 的计算公式EAR (||p2 - p6|| ||p3 - p5||) / (2 * ||p1 - p4||)分子是两条垂直距离之和分母是水平距离的两倍。正常人睁眼时 EAR 大约在 0.25~0.35 之间闭眼时垂直距离趋近于 0EAR 会掉到 0.05 以下。这个比值非常稳定因为它只关注眼睛自身的几何比例所以人脸在画面中远近变化、稍微倾斜都不会对结果造成明显干扰——这正是它比固定像素阈值检测更靠谱的原因。2. 环境准备与基础库安装2.1 Python 环境与 OpenCV 安装这个项目的底子就是 Python 环境建议直接用 Anaconda 创建独立虚拟环境避免把系统 Python 搞乱。Python 版本建议选择 3.8~3.10太高或太低都可能遇到预编译包缺失的问题。安装 OpenCV 这条命令就能搞定pip install opencv-python这里装的是opencv-python它已经包含cv2模块。如果你想用 OpenCV 内置的 SIFT、SURF 这些专利算法还需要装opencv-contrib-python但本项目用不到。这里有个常见的坑如果你在 PyCharm 里写import cv2报ModuleNotFoundError: No module named cv2绝大多数情况是解释器选错了。在 PyCharm 右下角点解释器选到你创建虚拟环境对应的 Python而不是系统的全局解释器。判断解释器对不对最简单的方法是直接在 PyCharm 的 Terminal 里执行python --version和pip list确认python和pip指向同一个环境。2.2 dlib 库安装与编译问题解决dlib 是这项目的另一个关键依赖但它不像 OpenCV 那样一条pip install dlib就一定能成功。Linux 和 macOS 上通常还好Windows 上大概率会报错因为它需要编译 C 源码要求系统里有 CMake 和 Visual Studio Build Tools。安装命令pip install dlib如果报错先装两个底层依赖pip install cmake然后去官网下载并安装 Visual Studio Build Tools勾选“使用 C 的桌面开发”再重新执行pip install dlib。提示如果编译时间太长也可以直接下载预编译的 dlib wheel 包搜索dlib whl python 3.10用pip install 下载好的.whl安装基本能省掉 5~10 分钟的编译等待时间。另外一个轻量级工具库imutils也要装上它能让视频处理代码更简洁pip install imutils如果懒得装这个库也可以自己写 resize 函数但 imutils 的imutils.resize在保留宽高比方面确实方便。2.3 需要下载的模型文件dlib 说完还要下载人脸关键点检测模型shape_predictor_68_face_landmarks.dat。这个文件大概 60.7MB可以到 dlib 的官方模型库下载搜索shape_predictor_68_face_landmarks.bz2解压得到 .dat 文件放到项目目录的models子目录即可。模型文件是二进制的不能直接当文本打开。项目运行时会读取它加载模型加载成功后会有几秒钟的初始化耗时这是正常现象并不代表程序卡住了。3. 核心代码实现与逐段讲解3.1 完整可运行源码直接贴出完整代码我加了详细注释你可以先复制跑通再对着后面的解析加深理解。import cv2 import dlib import imutils from scipy.spatial import distance as dist def eye_aspect_ratio(eye): # 计算上下眼皮的垂直欧氏距离 p2_p6 dist.euclidean(eye[1], eye[5]) p3_p5 dist.euclidean(eye[2], eye[4]) # 计算眼睛水平欧氏距离 p1_p4 dist.euclidean(eye[0], eye[3]) # 眼睛纵横比 ear (p2_p6 p3_p5) / (2.0 * p1_p4) return ear # 初始化 dlib 的人脸检测器与关键点检测器 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) # 关键点索引 LEFT_EYE_START 42 LEFT_EYE_END 48 RIGHT_EYE_START 36 RIGHT_EYE_END 42 # 打开摄像头 cap cv2.VideoCapture(0) if not cap.isOpened(): print(无法打开摄像头请检查设备是否被占用) exit() print(按 q 退出程序) while True: ret, frame cap.read() if not ret: print(获取视频帧失败) break # 缩小帧尺寸提高检测速度 frame imutils.resize(frame, width640) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: # 获取人脸区域的关键点坐标 landmarks predictor(gray, face) # 提取左右眼坐标 left_eye [] right_eye [] for i in range(LEFT_EYE_START, LEFT_EYE_END): x landmarks.part(i).x y landmarks.part(i).y left_eye.append((x, y)) for i in range(RIGHT_EYE_START, RIGHT_EYE_END): x landmarks.part(i).x y landmarks.part(i).y right_eye.append((x, y)) # 计算双眼 EAR left_ear eye_aspect_ratio(left_eye) right_ear eye_aspect_ratio(right_eye) ear (left_ear right_ear) / 2.0 # 在图上画出眼睛关键点 for point in left_eye: cv2.circle(frame, point, 2, (0, 255, 0), -1) for point in right_eye: cv2.circle(frame, point, 2, (0, 255, 0), -1) # 显示 EAR 值 cv2.putText( frame, fEAR: {ear:.2f}, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, ) # 阈值判断 if ear 0.2: cv2.putText( frame, BLINK / CLOSED, (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2, ) cv2.imshow(Eye Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码的核心处理流程就三步检测人脸、提取关键点、计算 EAR。你看代码里没有复杂的滑动窗口或卷积操作本质就是几何计算。为什么能稳定工作全靠 dlib 的 68 点关键点定位在正脸前提下非常准确。3.2 代码逻辑逐段解析先看eye_aspect_ratio函数。参数eye是一个包含 6 个(x, y)坐标的列表dist.euclidean来自scipy.spatial用来计算两点间的欧氏距离。如果你不想额外装 scipy也可以自己写((x1-x2)**2 (y1-y2)**2) ** 0.5结果完全一样。再看循环里的detector(gray, 0)。第二个参数0表示不做图像金字塔上采样也就是不主动放大图像去找更小的人脸速度更快但远处的小脸可能检不到。如果摄像头距离人比较远可以改成1甚至2能提高小脸召回率代价是每帧处理时间增加。关键点提取部分landmarks.part(i).x和.y返回的就是第 i 个特征点的坐标。注意左右眼的索引范围不要写反36 是右眼画面中你的左边42 是左眼。两边 EAR 求平均是为了减少单眼误差——实际测试中如果人轻微侧脸单只眼的 EAR 波动偏大平均之后会更平滑。3.3 为什么阈值设置为 0.20.2 这个数值不是拍脑袋定的。正常睁眼时 EAR 通常在 0.25~0.35闭眼时会掉到 0.05 以下。取中间值 0.2 作为阈值可以保证正常睁眼时不误报闭眼或眨眼时能及时捕捉到。但在实际场景中这个阈值会因为摄像头高度、距离、人眼大小产生偏移。更稳的做法是增加一个“校准阶段”程序启动前 3 秒让人正对摄像头正常睁眼程序自动采集这段时间的 EAR 值取平均值的 80% 作为阈值。这样能适配不同的人比写死 0.2 更科学。我建议你在文章里看到 0.2 时先理解它是一个经验起点实际部署还要做个性化调整。4. 眨眼检测与闭眼状态判定4.1 眨眼检测的判定逻辑单纯判断EAR 0.2只能说“当前眼睛闭着”还不能说“眨了一次眼”。眨眼是一个过程睁开眼睛 → 闭眼 → 睁开眼睛。正常人每次眨眼持续约 100~150ms在 30FPS 的摄像头下大约是 3~5 帧。所以道的眨眼检测需要状态机当前帧 EAR 低于阈值且之前是睁眼状态则标记“可能正在眨眼”。继续检测后续帧如果看到 EAR 回到阈值以上确认这是一次眨眼计数器加 1。如果 EAR 持续低于阈值超过一定时间就不再是眨眼而是闭眼状态。这个方法在疲劳驾驶检测领域有一个更专业的名字PERCLOSPercentage of Eyelid Closure over the Pupil over Time即单位时间内眼睛闭合时间所占的比例。PERCLOS 超过 0.4 通常被认为是疲劳的标志。你可以在此基础上延伸用累计眨眼次数和闭眼时长综合评分判断疲劳程度。4.2 闭眼检测与疲劳预警闭眼检测和眨眼检测不同之处在于“持续时间”。一次普通的眨眼只有几十毫秒到一百多毫秒如果是疲劳导致的眼睛闭合持续时间往往超过 500ms甚至长达数秒。代码升级思路维护一个closed_frames计数器连续帧 EAR 低于阈值就加 1高于阈值就清零。摄像头帧率假设为 30FPS那么 15 帧以上的连续闭眼就代表闭眼时间超过 0.5 秒可以触发提醒。提醒方式可以是画面上的文字提示也可以加声音告警或通过串口发送信号给外部设备。4.3 升级版代码框架我在完整代码里加上眨眼计数和闭眼时长判断# 初始化状态变量 EAR_THRESH 0.2 EAR_CONSEC_FRAMES 2 # 连续 2 帧低于阈值才认为可能眨眼 CLOSED_FRAME_LIMIT 15 # 连续 15 帧低于阈值判定为闭眼 frame_counter 0 blink_counter 0 eye_closed False while True: # ... 前面的检测代码省略 ... if ear EAR_THRESH: frame_counter 1 else: if frame_counter EAR_CONSEC_FRAMES: blink_counter 1 frame_counter 0 if frame_counter CLOSED_FRAME_LIMIT: eye_closed True else: eye_closed False # 在画面上输出 cv2.putText(frame, fBlinks: {blink_counter}, (30, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 0), 2) if eye_closed: cv2.putText(frame, DROWSINESS ALERT!, (30, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)注意这里frame_counter EAR_CONSEC_FRAMES的判定逻辑眨眼时眼睛闭合至少持续 2 帧单帧 EAR 抖动导致的误检就不会被当作眨眼。这个思路和按键去抖动是同一个道理。5. 常见问题与调试经验5.1 dlib 安装失败的多种解法几乎没有哪个初学者能一次装好 dlib报错种类也是花样百出。报错信息原因解决方案CMake must be installed缺少 CMakepip install cmakeError: Visual Studio not found缺少 C 编译工具链安装 Visual Studio Build Tools勾选“使用 C 的桌面开发”fatal error: dlib/serialize.h: No such file版本不兼容升级 dlib 到最新版ModuleNotFoundError: No module named dlib安装没成功检查 pip list确认是否真的装上了如果你用的是 Anaconda也可以尝试conda install -c conda-forge dlibconda 会自动把编译好的二进制包拉下来比 pip 省事很多。5.2 检测速度慢与卡顿优化dlib 的人脸检测在 CPU 上是能实时跑的但如果你用detector(gray, 1)开启图像金字塔速度会明显下降。我实测下来640x480 的帧尺寸、金字塔上采样为 0 时大约能跑 20~30FPS上采样改为 1帧率直接掉到 15FPS 以下。几个提速技巧缩小帧尺寸到 480 到 640 宽度即可不需要全高清原图。只对灰度图做检测不用彩色图。如果画面中只有一个人脸可以每 2 帧做一次人脸检测中间 1 帧用上一帧的人脸矩形区域直接提取关键点这样能明显提速。要求高的话换 MediaPipe它对 CPU 优化更好帧率能翻倍但需要额外安装mediapipe。5.3 现实场景中的精度问题戴眼镜的人会明显影响关键点定位尤其是镜框反光严重时dlib 会在镜框边缘打出奇怪的关键点。我实测下来无框眼镜影响最小粗黑框眼镜偶尔会让 EAR 值抖动。解决办法是启动时做一个“无眼镜校正”或者改用深度学习模型如 MediaPipe FaceMesh替代 dlib。侧脸时 68 点模型也会失效因为部分关键点被遮挡。这属于方法本身的局限不是参数能调的。如果你需要做多角度检测建议换成 FaceMesh 的 468 点模型它对人脸姿态的鲁棒性好很多。5.4 摄像头与光线环境调参经验我经历了无数次摄像头测试后发现最影响 EAR 稳定性的外部因素不是距离而是光线角度。顺光下 EAR 的波动范围大约在 0.03 以内逆光和顶光环境下人脸区域出现明显阴影和过曝EAR 波动能到 0.06这时候阈值 0.2 就可能出现睁眼误报。解决方法是让摄像头尽可能靠近窗户或台灯光源避免正上方顶光照射。如果条件不允许可以把 EAR 阈值上调到 0.22~0.25让误报率下降代价是轻微的闭眼状态可能检测得稍微迟钝。还有一点要注意摄像头帧率会直接影响闭眼时长判断的准确性。假如摄像头实际只有 15FPS那么 15 帧对应的闭眼时长是 1 秒而不是 0.5 秒。建议在日志中打印实际计算出的帧率然后动态调整CLOSED_FRAME_LIMIT公式是CLOSED_FRAME_LIMIT 目标检测秒数 * 实际FPS。6. 实际运行效果与经验总结跑通这个项目之后我对计算机视觉项目的认知又有了一层深化真正实用的人眼识别与眨眼检测不是靠复杂的算法堆出来的而是靠“几何特征 状态判断 工程调参”三者的配合。我个人的一个实测数据可以参考在普通笔记本自带摄像头、室内光线良好、距离约 60cm 的条件下EAR 在睁眼时稳定在 0.30 左右闭眼时降到 0.04两者差距非常大所以阈值取 0.2 非常安全。戴眼镜之后睁眼 EAR 会略微下降大约在 0.27 左右闭眼仍然低于 0.08所以不会导致完全失效。最后分享一个我在实际开发中非常喜欢的小技巧在调试阶段不要只在画面上输出“BLINK / CLOSED”这种状态文本可以把ear数值实时打印到终端或者画一个折线图。这样你就能直观看到 EAR 的波形——眨眼时的 V 字形波形、闭眼时的平台期波形一目了然。等以后做疲劳检测时你甚至可以直接分析这个波形的形态学特征识别异常眨眼模式那又是另一个深水区了。本文还有配套的精品资源点击获取