[关闭]
@Perfect-Demo 2018-12-17T01:32:18.000000Z 字数 2936 阅读 1336

(11)眨眼检测

opencv+dlib


我们使用opencv+dlib库进行眨眼检测。
其中参考一篇论文:Real-Time Eye Blink Detection using Facial Landmarks
作者在这篇文章中提出了一个眼睛纵横比(eye aspect ratio (EAR))的概念,通过计算这个EAR的数值,我们可以判断眼睛是张开还是闭合,从而检测眨眼动作

首先我们要用到68个特征点检测。之前我们已经实现过。其实68个特征点检测是非常复杂的。但是我们可以用dilb库较容易得实现。
在得到的68个点中,左眼和右眼各由6个点包围,我们之后用到的算法就是基于这6个点的。如图:
此处输入图片的描述

上图中的6个特征点p1、p2、p3、p4、p5、p6是人脸特征点中对应眼睛的6个特征点。
我们想关注的重点是:这些点在眼睛睁开和闭合时,彼此坐标之间的关系。
如图中直线所示,我们可以看出,长宽比在眼睛睁开和闭合时会有所不同。
我们可以导出EAR的方程:


分子中计算的是眼睛的特征点在垂直方向上的距离,分母计算的是眼睛的特征点在水平方向上的距离。由于水平点只有一组,而垂直点有两组,所以分母乘上了2,以保证两组特征点的权重相同。
接下来看看,上面的那个表格。我们不难发现,EAR在眼睛睁开时是基本保持不变的,在小范围内会上下浮动,然而,当眼睛闭合时,EAR会迅速下降。这也就是我们进行眨眼检测的原理,十分简单。更详细的内容还是请查阅论文。

首先我们来写一个直接用阈值判断的程序

  1. import dlib
  2. import cv2
  3. import os
  4. import sys
  5. from scipy.spatial import distance
  6. from imutils import face_utils
  7. def eye_aspect_ratio(eye):
  8. # print(eye)
  9. A = distance.euclidean(eye[1], eye[5])
  10. B = distance.euclidean(eye[2], eye[4])
  11. C = distance.euclidean(eye[0], eye[3])
  12. ear = (A + B) / (2.0 * C)
  13. return ear
  14. pwd = os.getcwd()# 获取当前路径
  15. model_path = os.path.join(pwd, 'model')# 模型文件夹路径
  16. shape_detector_path = os.path.join(model_path, 'shape_predictor_68_face_landmarks.dat')# 人脸特征点检测模型路径
  17. detector = dlib.get_frontal_face_detector()# 人脸检测器
  18. predictor = dlib.shape_predictor(shape_detector_path)# 人脸特征点检测器
  19. EYE_AR_THRESH = 0.2# EAR阈值
  20. EYE_AR_CONSEC_FRAMES = 2zd# 当EAR小于阈值时,接连多少帧一定发生眨眼动作
  21. # 对应特征点的序号
  22. RIGHT_EYE_START = 37 - 1
  23. RIGHT_EYE_END = 42 - 1
  24. LEFT_EYE_START = 43 - 1
  25. LEFT_EYE_END = 48 - 1
  26. frame_counter = 0# 连续帧计数
  27. blink_counter = 0# 眨眼计数
  28. cap = cv2.VideoCapture(0)
  29. while(1):
  30. ret, img = cap.read()# 读取视频流的一帧
  31. if ret is True:
  32. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  33. else:
  34. break
  35. rects = detector(gray, 0)# 人脸检测
  36. for rect in rects:# 遍历每一个人脸
  37. print('-'*20)
  38. shape = predictor(gray, rect)# 检测特征点
  39. points = face_utils.shape_to_np(shape)# convert the facial landmark (x, y)-coordinates to a NumPy array
  40. leftEye = points[LEFT_EYE_START:LEFT_EYE_END + 1]# 取出左眼对应的特征点
  41. rightEye = points[RIGHT_EYE_START:RIGHT_EYE_END + 1]# 取出右眼对应的特征点
  42. leftEAR = eye_aspect_ratio(leftEye)# 计算左眼EAR
  43. rightEAR = eye_aspect_ratio(rightEye)# 计算右眼EAR
  44. print('leftEAR = {0}'.format(leftEAR))
  45. print('rightEAR = {0}'.format(rightEAR))
  46. ear = (leftEAR + rightEAR) / 2.0# 求左右眼EAR的均值
  47. leftEyeHull = cv2.convexHull(leftEye)# 寻找左眼轮廓
  48. rightEyeHull = cv2.convexHull(rightEye)# 寻找右眼轮廓
  49. cv2.drawContours(img, [leftEyeHull], -1, (0, 255, 0), 1)# 绘制左眼轮廓
  50. cv2.drawContours(img, [rightEyeHull], -1, (0, 255, 0), 1)# 绘制右眼轮廓
  51. # 如果EAR小于阈值,开始计算连续帧,只有连续帧计数超过EYE_AR_CONSEC_FRAMES时,才会计做一次眨眼
  52. if ear < EYE_AR_THRESH:
  53. frame_counter += 1
  54. cv2.putText(img, "Detect the blinks",(200, 30) , cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
  55. else:
  56. if frame_counter >= EYE_AR_CONSEC_FRAMES:
  57. blink_counter += 1
  58. frame_counter = 0
  59. # 在图像上显示出眨眼次数blink_counter和EAR
  60. cv2.putText(img, "Blinks:{0}".format(blink_counter), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
  61. cv2.putText(img, "EAR:{:.2f}".format(ear), (500, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
  62. cv2.imshow("Frame", img)
  63. if cv2.waitKey(1) & 0xFF == ord("q"):
  64. break
  65. cap.release()
  66. cv2.destroyAllWindows()

另外我发现,戴上眼镜的话,阈值可能偏大一点效果更好。(但是这并没有lilun理论支持,只是我的个人感觉)
现在我是用的是0.2,戴上眼镜的话,或许0.3更好(也可能我眼睛小吧。)

本文是我学习博客https://blog.csdn.net/hongbin_xu/article/details/79033116后的学习笔记。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注