一、核心原理:眼睛纵横比(EAR)
要实现眼部疲劳检测,首先要明确一个核心指标:眼睛纵横比(EAR),这是判断眼睛是否闭合的关键依据。
1. 人脸关键点与眼部区域定位
Dlib 提供的shape_predictor_68_face_landmarks.dat模型能够精准检测人脸 68 个关键点,其中眼部区域的关键点分布如下:
右眼:36~41 号关键点(共 6 个)
左眼:42~47 号关键点(共 6 个)
2. EAR 计算公式
眼睛纵横比的核心思想是:
眼睛睁开时,纵横比保持稳定;
眼睛闭合时,纵向距离会急剧缩小,横向距离基本不变,最终纵横比趋近于 0。
具体计算逻辑(对应眼部 6 个关键点的分布):
# 1 2
# 0 3 <----眼睛的6个关键点
# 5 4
-
计算纵向两个距离:A(关键点 1 与 5 的欧氏距离)、B(关键点 2 与 4 的欧氏距离)
-
计算横向一个距离:C(关键点 0 与 3 的欧氏距离)
-
纵横比 EAR = (A + B) / (2 * C)(取纵向距离均值再除以横向距离,消除个体眼部尺寸差异)
当 EAR 值低于某个阈值(本文选用 0.3,可根据实际需要调整)时,判定为闭眼状态;若闭眼状态持续超过一定帧数(本文选用 50 帧,对应摄像头帧率约 1~2 秒),则判定为疲劳状态并触发警报。
二、代码实现
1. 导入所需库
import numpy as np
import dlib
import cv2
from sklearn.metrics.pairwise import euclidean_distances # 计算欧氏距离
from PIL import Image, ImageDraw, ImageFont # pip install pillow
2. 核心函数 1:计算眼睛纵横比(EAR)
def eye_aspect_ratio(eye):
'''-------------计算眼睛纵横比(EAR)-------------
# 1 2
# 0 3 <----这是眼睛的6个关键点
# 5 4
'''
# 计算纵向距离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 = ((A + B) / 2.0) / C
return ear
注意:
使用euclidean_distances计算欧氏距离,需将单个关键点坐标 reshape 为 (1,2) 格式,满足函数输入要求。
3.辅助函数 1:OpenCV 添加中文文本
def cv2AddChineseText(img, text, position, textColor=(255,0, 0), textSize=50):
"""向图片中添加中文(解决OpenCV不支持中文的问题)"""
if (isinstance(img, np.ndarray)): # 判断是否为OpenCV图片类型(np.ndarray)
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # BGR转RGB,转换为Pillow图像
draw = ImageDraw.Draw(img) # 创建Pillow绘图对象
# 加载中文字体(simsun.ttc为宋体,需确保环境中有该字体文件)
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) # RGB转BGR,转换回OpenCV图像格式
4. 辅助函数 2:绘制眼部凸包轮廓
def drawEye(eye): # 绘制眼眶凸包
eyeHull = cv2.convexHull(eye)
cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 1)
凸包:能够包围所有眼部关键点的最小凸多边形,清晰勾勒出眼睛的轮廓。
绿色细线条((0,255,0) 为绿色,线宽 1)
5. 主程序:视频流捕获与疲劳检测
# 全局变量:闭眼持续次数统计
COUNTER = 0
# 1. 初始化Dlib检测器与关键点预测器
detector = dlib.get_frontal_face_detector() # 人脸检测器
predictor = dlib.shape_predictor(r"shape_predictor_68_face_landmarks.dat") # 关键点预测器(替换为你的模型路径)
# 2. 初始化摄像头(0为默认摄像头)
cap = cv2.VideoCapture(0)
# 3. 循环读取视频帧,进行实时检测
while True:
ret, frame = cap.read()
if not ret: # 若无法读取帧,退出循环
break
# 4. 检测人脸
faces = detector(frame, 0)
# 5. 遍历每一张检测到的人脸
for face in faces:
# 6. 预测人脸关键点
shape = predictor(frame, face)
# 7. 将关键点转换为NumPy数组(坐标格式:(x,y))
shape = np.array([[p.x, p.y] for p in shape.parts()])
# 8. 提取左右眼关键点
rightEye = shape[36:42] # 右眼:36~41号关键点
leftEye = shape[42:48] # 左眼:42~47号关键点
# 9. 计算左右眼EAR值
rightEAR = eye_aspect_ratio(rightEye)
leftEAR = eye_aspect_ratio(leftEye)
# 10. 计算平均EAR值(消除单眼遮挡等影响)
ear = (leftEAR + rightEAR) / 2.0
# 11. 疲劳判断逻辑
if ear < 0.3: # 阈值0.3,判定为闭眼
COUNTER += 1 # 累计闭眼帧数
if COUNTER >= 50: # 持续闭眼50帧,触发疲劳警报
frame = cv2AddChineseText(frame, "!!!!!危险!!!!! ", (250, 250), textColor=(0,0,255))
else: # 眼睛睁开,重置闭眼计数器
COUNTER = 0
# 12. 绘制眼部凸包与EAR信息
drawEye(leftEye)
drawEye(rightEye)
info = "EAR: {:.2f}".format(ear[0][0])
frame = cv2AddChineseText(frame, info, (0, 30))
# 13. 显示视频帧
cv2.imshow("Frame", frame)
# 14. 按键退出:Esc键(ASCII码27)
key = cv2.waitKey(1)
if key == 27:
break
# 15. 释放资源
cv2.destroyAllWindows()
cap.release()
运行结果:


注意事项
-
模型路径:需正确配置
shape_predictor_68_face_landmarks.dat的文件路径,否则无法加载关键点模型。 -
字体文件:运行环境需存在
simsun.ttc宋体文件,否则无法显示中文。 -
环境光线:建议在光线充足的环境下运行,光线过暗会影响人脸检测与关键点提取的精度。
-
阈值调整:EAR 阈值(0.3)与闭眼帧数阈值(50)可根据个人眼部特征、摄像头帧率调整,优化检测效果。
-
遮挡问题:佩戴眼镜、头发遮挡眼部等情况,可能会导致关键点提取失败,影响检测精度。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_83998832/article/details/157652265



