OpenCV之人脸疲劳检测

1.dlib

dlib 是一个用 C++ 编写的开源机器学习库,同时也提供了 Python 接口,它被广泛应用于计算机视觉和机器学习领域:

1.​人脸识别与生物特征分析

dlib 的人脸检测和关键点定位功能是其最突出的应用方向。通过预训练模型(如 HOG + SVM 或深度学习模型),dlib 可实现高效的人脸检测、68 个面部关键点定位(如眼睛、嘴唇、下巴等)

。例如:

  • 安全系统​:用于门禁、身份验证,结合活体检测技术(如眨眼识别)防止照片或视频伪造
  • 社交媒体​:支持美颜滤镜、表情分析(如情绪识别)和虚拟试妆

2.计算机视觉任务​

dlib 提供多种图像处理算法,支持以下场景:

  • 物体检测与跟踪:利用 HOG 特征和 SVM 分类器检测车辆、行人等目标,适用于智能交通系统中的车牌识别或违规行为监控,如 OpenCV之目标追踪
  • 图像增强与分割​:包括降噪、边缘检测和图像对齐功能,提升医学影像分析(如肿瘤检测)的准确性

3.机器学习算法集成​

dlib 实现了多种经典和现代算法,如:

  • 分类与回归​:支持向量机(SVM)、决策树、线性回归等,适用于数据分类和预测任务
  • 深度学习​:结合预训练模型处理复杂任务(如姿态估计),兼容实时计算需求

4.实时系统开发​

其优化的内存管理和高效算法使其适合嵌入式设备和实时处理:

  • 摄像头监控​:实时人脸跟踪、驾驶员疲劳检测(通过眼部闭合频率判断)
  • 交互式应用​:如虚拟现实中的面部动作捕捉

5.跨领域创新应用​

  • 医疗保健​:辅助康复训练监测(如关节活动分析)、医疗影像中的器官识别
  • 智能交通​:交通流量监控、违法行为记录(如未系安全带检测)

6.​跨平台与多语言支持​

dlib 兼容 Windows、Linux、macOS 等系统,并提供 Python 接口,便于集成到不同项目中

7.预训练模型

模型 用途 适用场景
mmod_human_face_detector.dat 高精度 CNN 人脸检测 安防、监控
shape_predictor_68_face_landmarks.dat 68 点关键点检测 美颜、AR、疲劳检测
shape_predictor_5_face_landmarks.dat 5 点关键点检测 人脸对齐、移动端
dlib_face_recognition_resnet_model_v1.dat 人脸特征提取 人脸识别、身份验证
hand_landmark_detector.dat 手部关键点检测 手势交互

2.人脸关键点检测

如何用shape_predictor_68_face_landmarks.dat来做人脸检测,并可视化人脸的关键部位,如眼睛、鼻子、嘴巴等。

1.人脸关键点

定义了68个关键点,如下分布:

按关键点索引可分成以下7类:

1
2
3
4
5
6
7
8
9
facial_landmarks = OrderedDict([
("mouth", (48, 68)), # 嘴
("right_eyebrow", (17, 22)), # 右眉毛
("left_eyebrow", (22, 27)), # 左眉毛
("right_eye", (36, 42)), # 右眼
("left_eye", (42, 48)), # 左眼
("nose", (27, 36)), # 鼻子
("jaw", (0, 17)) # 下巴
])

为何代码中的左右和图片中的左右是相反的?
当我们说右眉毛,我们通常指的是图片中人的右眉毛(也就是图像左边的眉毛),但在数据结构中,“right_eyebrow” 指的是我们从屏幕看的左边,这也是左右的定义在视觉上和数据上的差异。

2.shape to np

将 dlib 的 shape 转换成 numpy 数组

1
2
3
4
5
def _shape_to_np(shape, dtype="int"):
coors = np.zeros((shape.num_parts, 2), dtype=dtype)
for i in range(0, shape.num_parts):
coors[i] = (shape.part(i).x, shape.part(i).y)
return coors

dlib.full_object_detection 类型的 shape,转成一个 (68, 2) 的 numpy 数组,方便后续处理。

3.主流程函数

使用 dlib 进行人脸检测与关键点定位,使用 OpenCV 对每个面部区域进行可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def detect_face_part(image_path):  
# 1.加载人脸检测与关键点定位
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

# 2.读取输入数据,预处理
image = cv2.imread(image_path)
(h, w) = image.shape[:2]
width = 500
r = width / float(w)
dim = (width, int(h * r))
image = cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
# 图像统一缩放到宽500px,并转成灰度图以便处理
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 3.人脸检测
rects = detector(gray, 1) # rects是一个包含所有检测到人脸的列表

# 4.遍历检测到的框
for (_, rect) in enumerate(rects):
# 对人脸框进行关键点定位
shape = predictor(gray, rect)
shape = _shape_to_np(shape) # 转换成ndarray

# 遍历每一个部分
for (name, (i, j)) in facial_landmarks.items():
clone = image.copy()
cv2.putText(clone, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

# 根据位置画点
for (x, y) in shape[i:j]:
cv2.circle(clone, (x, y), 3, (0, 0, 255), -1)

# 提取每个区域的 ROI(感兴趣区域)
(x, y, w, h) = cv2.boundingRect(np.array([shape[i:j]]))

roi = image[y:y + h, x:x + w]
(h, w) = roi.shape[:2]
width = 250
r = width / float(w)
dim = (width, int(h * r))
roi = cv2.resize(roi, dim, interpolation=cv2.INTER_AREA)

# 显示每一部分
cv2.imshow("ROI", roi)
cv2.imshow("Image", clone)
cv2.waitKey(0)

# 5.展示所有区域
output = _visualize_facial_landmarks(image, shape)
cv2.imshow("Image", output)
cv2.waitKey(0)

OpenCV可视化如下:

4.可视化面部区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def _visualize_facial_landmarks(image, shape, colors=None, alpha=0.75):  
# 用于绘制半透明的彩色区域
overlay = image.copy()
output = image.copy()
# 如果没有指定颜色,则使用预设的7种颜色
if colors is None:
colors = [(19, 199, 109), (79, 76, 240), (230, 159, 23), (168, 100, 168), (158, 163, 32), (163, 38, 32), (180, 42, 220)]
# 对每个部位进行处理
for (i, name) in enumerate(facial_landmarks.keys()):
(j, k) = facial_landmarks[name]
pts = shape[j:k]
# jaw(下巴轮廓),用线段依次连接
if name == "jaw":
for l in range(1, len(pts)):
ptA = tuple(pts[l - 1])
ptB = tuple(pts[l])
cv2.line(overlay, ptA, ptB, colors[i], 2)
# 其余对其关键点构建凸包(convex hull)并填充颜色
else:
hull = cv2.convexHull(pts)
cv2.drawContours(overlay, [hull], -1, colors[i], -1)
# 把绘制的overlay和原图按一定比例融合
cv2.addWeighted(overlay, alpha, output, 1 - alpha, 0, output)
return output

3.眨眼检测

原理:使用dlib进行人脸关键点识别,通过计算眼睛长宽比EAR(Eye Aspect Ratio)来判断是否闭眼,闭眼时间 > 1秒判定为疲劳。

1.计算眼睛长宽比(EAR)

1
2
3
4
5
6
def _eye_aspect_ratio(eye):
A = dist.euclidean(eye[1], eye[5])
B = dist.euclidean(eye[2], eye[4])
C = dist.euclidean(eye[0], eye[3])
ear = (A + B) / (2.0 * C)
return ear

EAR值越小,表示眼睛越闭合。当 EAR < 0.3 且持续几帧,就认为眨了一次眼。

2.主流程函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def detect_blinks(path):  
eye_ar_thresh = 0.3 # 低于0.3EAR值认为眼睛闭合
eye_ar_consec_frames = 3 # 连续闭合超过3帧,才算一次眨眼

counter = 0
total = 0

print("[INFO] loading facial landmark predictor...")
# 使用dlib加载人脸检测器和68个点的预测器模型
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

# 分别取两个眼睛区域
(lStart, lEnd) = facial_landmarks["left_eye"]
(rStart, rEnd) = facial_landmarks["right_eye"]

print("[INFO] starting video stream thread...")
# 读取视频
vs = cv2.VideoCapture(path)
time.sleep(1.0)

# 遍历每一帧
while True:
frame = vs.read()[1] # 读取帧
if frame is None:
break

(h, w) = frame.shape[:2]
width = 1200
r = width / float(w)
dim = (width, int(h * r))
frame = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 转成灰度

# 检测人脸
rects = detector(gray, 0)

# 遍历每一个检测到的人脸
for rect in rects:
# 获取坐标
shape = predictor(gray, rect)
shape = _shape_to_np(shape)

# 分别计算ear值
leftEye = shape[lStart:lEnd]
rightEye = shape[rStart:rEnd]
leftEAR = _eye_aspect_ratio(leftEye)
rightEAR = _eye_aspect_ratio(rightEye)

# 算一个平均的
ear = (leftEAR + rightEAR) / 2.0

# 绘制眼睛区域
leftEyeHull = cv2.convexHull(leftEye)
rightEyeHull = cv2.convexHull(rightEye)
cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)

# 检查是否满足阈值
if ear < eye_ar_thresh:
counter += 1

else:
# 如果连续几帧都是闭眼的,总数算一次
if counter >= eye_ar_consec_frames:
total += 1

# 重置
counter = 0

# 显示
cv2.putText(frame, "Blinks: {}".format(total), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.putText(frame, "EAR: {:.2f}".format(ear), (300, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

cv2.imshow("Frame", frame)
key = cv2.waitKey(10) & 0xFF

# 按下ESC键退出
if key == 27:
break

vs.release()
cv2.destroyAllWindows()

4.总结

1.人脸关键点检测

  • 使用 shape_predictor_68_face_landmarks.dat 实现 68 点人脸关键点定位
  • 分区域提取面部部位(如眼睛、嘴、鼻子等),并通过 OrderedDict 定义索引范围
  • 利用 OpenCV 可视化每个面部区域,包括点、轮廓和半透明区域高亮显示

2.眨眼检测原理

使用 EAR(Eye Aspect Ratio)判断眼睛是否闭合,当 EAR < 0.3 且连续超过 3 帧,视为一次眨眼

3.其它动作识别

除了眨眼识别,dlib还支持其它面部相关动作识别

动作 方法或指标 场景用途
打哈欠 MAR(嘴巴长宽比) 注意力检测、驾驶监控
张嘴说话 连续 MAR 变化 音视频同步、语音识别辅助
微笑识别 嘴角抬起 + 脸部肌肉张力 表情识别、用户反馈分析
头部点头/摇头 检测面部关键点相对移动轨迹 手势交互、虚拟现实
睁眼/闭眼识别 EAR 静态阈值 + 多帧验证 活体识别、疲劳监测

5.备注

环境

  • mac: 15.2
  • python: 3.12.4
  • numpy: 1.26.4
  • opencv-python: 4.11.0.86
  • dlib: 19.24.6

资源和代码

https://github.com/keychankc/dl_code_for_blog/tree/main/010_opencv_face_fatigue_detection