从特征到应用:用 dlib+OpenCV 实现实时疲劳检测(基于眼睛纵横比)

在前两篇内容中,我们完成了人脸检测、68 点关键点定位与面部特征可视化。本文将基于这些技术,聚焦一个实用场景 ------实时疲劳检测,通过计算眼睛纵横比(EAR)判断用户是否闭眼,并在持续闭眼时触发警报,可应用于驾驶安全、学习专注度监控等场景。

一、核心原理:眼睛纵横比(EAR)如何判断闭眼?

疲劳检测的核心逻辑是通过眼睛开合状态判断是否困倦,而眼睛纵横比(Eye Aspect Ratio,简称 EAR)是衡量眼睛开合程度的经典指标,由研究者 Tereza Soukupová 在 2016 年提出。

1. EAR 的计算逻辑

眼睛的 6 个关键点(如右眼 36-41、左眼 42-47)分布如下图所示,EAR 通过计算 "垂直方向距离之和" 与 "水平方向距离" 的比值,量化眼睛的开合程度:

复制代码
          1    2
           \  /
0 ----------+---------- 3
           /  \
          5    4
  • 垂直距离:关键点 1 与 5 的距离(A)、关键点 2 与 4 的距离(B);
  • 水平距离:关键点 0 与 3 的距离(C);
  • EAR 公式EAR = (A + B) / (2 * C)

2. EAR 的判断标准

  • 睁眼状态:EAR 值通常在 0.25~0.35 之间(具体数值需根据摄像头距离、人脸大小微调);
  • 闭眼状态:EAR 值会急剧下降至 0.2 以下,甚至接近 0;
  • 疲劳判断:若 EAR 持续低于阈值(如 0.3)超过一定帧数(如 50 帧,约 1 秒,因摄像头通常为 30~60 帧 / 秒),则判定为疲劳状态。

二、完整代码实现与逐模块解析

1. 完整代码

复制代码
import numpy as np
import dlib
import cv2
from sklearn.metrics.pairwise import euclidean_distances  # 计算欧式距离
from PIL import Image, ImageDraw, ImageFont  # 处理中文显示

# 模块1:计算眼睛纵横比(EAR)
def eye_aspect_ratio(eye):
    # 计算垂直方向两个距离:A(1-5)、B(2-4)
    A = euclidean_distances(eye[1].reshape(1, 2), eye[5].reshape(1, 2))
    B = euclidean_distances(eye[2].reshape(1, 2), eye[4].reshape(1, 2))
    # 计算水平方向距离:C(0-3)
    C = euclidean_distances(eye[0].reshape(1, 2), eye[3].reshape(1, 2))
    # 计算EAR(避免除以0,加微小值)
    ear = ((A + B) / 2.0) / (C + 1e-6)
    return ear

# 模块2:OpenCV添加中文(解决cv2.putText不支持中文的问题)
def cv2AddChineseText(img, text, position, textColor=(255, 0, 0), textSize=50):
    if isinstance(img, np.ndarray):  # 若为OpenCV的numpy数组格式,转换为PIL图像
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(img)  # 创建绘图对象
    # 加载中文字体(需确保系统有simsun.ttc字体,Windows默认有,Linux/mac需手动安装)
    fontStyle = ImageFont.truetype("simsun.ttc", textSize, encoding="utf-8")
    draw.text(position, text, textColor, font=fontStyle)  # 绘制中文
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)  # 转回OpenCV格式

# 模块3:绘制眼睛凸包(可视化眼睛区域)
def drawEye(eye, frame):
    eyeHull = cv2.convexHull(eye)  # 计算眼睛关键点的凸包
    cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), -1)  # 填充式绘制凸包(绿色)

# 模块4:初始化与实时检测
if __name__ == "__main__":
    COUNTER = 0  # 闭眼持续帧数计数器
    EAR_THRESHOLD = 0.3  # EAR阈值(低于此值判定为闭眼)
    FATIGUE_FRAMES = 50  # 疲劳判定帧数(持续闭眼超过此帧数触发警报)

    # 初始化人脸检测器与关键点预测器
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

    # 初始化摄像头(0表示默认摄像头,外接摄像头可改为1)
    cap = cv2.VideoCapture(0)
    # 设置摄像头分辨率(可选,根据硬件调整)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

    while True:
        # 读取一帧图像
        ret, frame = cap.read()
        if not ret:  # 摄像头读取失败(如未连接),退出循环
            print("Error: 无法读取摄像头图像")
            break

        # 水平翻转图像(可选,解决摄像头镜像问题,更符合人眼习惯)
        frame = cv2.flip(frame, 1)

        # 1. 检测人脸
        faces = detector(frame, 0)  # 0表示不上采样,平衡速度与精度

        # 2. 遍历每个人脸,处理眼睛关键点
        for face in faces:
            # 2.1 预测68点关键点
            shape_dlib = predictor(frame, face)
            shape = np.array([[p.x, p.y] for p in shape_dlib.parts()])

            # 2.2 提取左右眼关键点(索引对应68点分布)
            right_eye = shape[36:42]  # 右眼:36-41(左闭右开)
            left_eye = shape[42:48]   # 左眼:42-47(左闭右开)

            # 2.3 计算左右眼EAR,并取平均值(减少单眼误差)
            right_ear = eye_aspect_ratio(right_eye)
            left_ear = eye_aspect_ratio(left_eye)
            avg_ear = (right_ear + left_ear) / 2.0  # 最终EAR值

            # 2.4 绘制眼睛凸包(可视化,便于观察检测效果)
            drawEye(right_eye, frame)
            drawEye(left_eye, frame)

            # 2.5 疲劳判断逻辑
            if avg_ear < EAR_THRESHOLD:
                COUNTER += 1  # 闭眼帧数+1
                # 持续闭眼超过FATIGUE_FRAMES,触发警报
                if COUNTER >= FATIGUE_FRAMES:
                    frame = cv2AddChineseText(frame, "!!!危险:请勿疲劳用眼!!!", 
                                             position=(200, 200), 
                                             textColor=(0, 0, 255),  # 红色警报文字
                                             textSize=40)
            else:
                COUNTER = 0  # 睁眼时,计数器清零

            # 2.6 在图像上显示当前EAR值(便于调试阈值)
            ear_text = f"EAR: {avg_ear[0][0]:.2f}"  # 格式化EAR值(保留2位小数)
            frame = cv2AddChineseText(frame, ear_text, position=(50, 50), textSize=30)

        # 3. 显示实时画面
        cv2.imshow("Real-Time Fatigue Detection", frame)

        # 4. 退出逻辑:按下ESC键(ASCII码27)关闭窗口
        if cv2.waitKey(1) & 0xFF == 27:
            break

    # 释放资源(摄像头+窗口)
    cap.release()
    cv2.destroyAllWindows()

2. 核心模块解析

(1)eye_aspect_ratio():EAR 计算函数

该函数是疲劳检测的 "核心算法",关键逻辑如下:

  • 欧式距离计算 :使用sklearn.metrics.pairwise.euclidean_distances计算两点间距离(也可手动用np.linalg.norm(eye[1]-eye[5])实现,效果一致);
  • 维度处理eye是形状为 (6,2) 的 numpy 数组(6 个关键点,每个点含 x/y 坐标),需用reshape(1,2)将单个点转换为 (1,2) 的二维数组,适配euclidean_distances的输入要求;
  • 防除零处理 :在分母C后加1e-6(极小值),避免因水平距离为 0 导致程序报错。
(2)cv2AddChineseText():中文显示函数

OpenCV 的cv2.putText()不支持中文字体,需通过 PIL 库转换图像格式实现中文显示,关键步骤:

  1. 格式转换:将 OpenCV 的 BGR 格式 numpy 数组,转为 PIL 的 RGB 格式图像;
  2. 字体加载 :使用系统自带的 "宋体" 字体(simsun.ttc),Windows 默认路径为C:/Windows/Fonts/simsun.ttc,Linux/mac 需手动安装该字体并指定路径(如/Library/Fonts/simsun.ttc);
  3. 格式转回:绘制中文后,再将 PIL 图像转回 OpenCV 的 BGR 格式,确保后续处理正常。
(3)实时检测逻辑
  • 摄像头初始化cv2.VideoCapture(0)调用默认摄像头,cap.set()可调整分辨率(越高越清晰,但占用资源越多);
  • 镜像翻转cv2.flip(frame, 1)实现水平翻转,解决摄像头 "左右颠倒" 的问题,让操作更自然;
  • 疲劳判定COUNTER记录持续闭眼帧数,当COUNTER >= FATIGUE_FRAMES时,用红色中文显示警报信息,直观提醒用户。

三、参数调试与效果优化

1. 关键参数调整

代码中的两个核心参数需根据实际场景微调,以达到最佳检测效果:

参数名称 作用 调整建议
EAR_THRESHOLD EAR 阈值,低于此值判定为闭眼 - 若频繁误判 "睁眼为闭眼":适当降低阈值(如 0.28);- 若频繁漏判 "闭眼为睁眼":适当提高阈值(如 0.32);- 建议在光线充足环境下调试,先记录睁眼时的 EAR 值,取其 0.8~0.9 倍作为阈值。
FATIGUE_FRAMES 疲劳判定帧数,超过此帧数触发警报 - 摄像头为 30 帧 / 秒时:50 帧≈1.7 秒,适合驾驶场景(反应稍快);- 摄像头为 60 帧 / 秒时:100 帧≈1.7 秒,需按帧率等比例调整;- 若希望更灵敏:减少帧数(如 30 帧≈1 秒);若避免误报:增加帧数(如 80 帧≈2.7 秒)。

2. 环境优化建议

  • 光线条件:避免逆光或过暗环境,光线不足会导致关键点检测偏差,建议在室内正常照明或室外阴影处使用;
  • 人脸姿态:确保人脸正面朝向摄像头,侧脸或低头时眼睛关键点可能检测失败,可配合 "人脸跟踪" 功能优化(后续文章会介绍);
  • 硬件性能 :若画面卡顿,可降低摄像头分辨率(如 800x600),或减少detector的上采样次数(如保持detector(frame, 0),不改为 1)。

五、总结

本文从 "理论原理" 到 "代码实现",完整讲解了基于 EAR 的实时疲劳检测系统,核心是将眼睛关键点的几何特征(EAR)转化为实用的状态判断指标。相比传统的 "基于像素亮度的闭眼检测",该方法抗干扰能力更强,精度更高,且可移植到嵌入式设备(如树莓派),实现更广泛的应用。

至此,我们已完成从 "人脸检测→关键点定位→特征可视化→实用应用" 的完整技术链。后续文章将继续探索更复杂的计算机视觉场景,如人脸识别、表情分类等,敬请关注!

相关推荐
databook2 小时前
让YOLO飞起来:从CPU到GPU的配置指南
人工智能·python·图像识别
Leinwin2 小时前
微软 Azure AI 视频翻译服务助力 JowoAI 实现短剧高效出海
人工智能·microsoft·azure
黄啊码3 小时前
AI智能体落地失败的罪魁祸首除了大模型幻觉,还有它
人工智能·agent·mcp
数据堂官方账号3 小时前
版权数据集上新 | 覆盖大模型、多模态大模型、语音识别、语音合成及计算机视觉等多领域
人工智能·计算机视觉·大模型·数据集·语音识别·语音合成·多模态大模型
CV实验室3 小时前
IEEE TGRS 2025 | 突破小波U-Net局限,ASCNet实现更精准的红外去条纹!
人工智能·计算机视觉·论文
几两春秋梦_3 小时前
强化学习原理(二)
人工智能·机器学习
互联网之声3 小时前
兑吧集团受邀参加2025华康会·DaJK大健康“源头创新·链动未来”创新论坛
大数据·人工智能
倔强青铜三3 小时前
苦练Python第54天:比较运算魔术方法全解析,让你的对象“懂大小、能排序”!
人工智能·python·面试
倔强青铜三3 小时前
苦练Python第53天:数值运算魔术方法从入门到精通
人工智能·python·面试