天天看點

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

目錄

    • 目的
    • 技術背景
  • 正文
    • (1)環境搭建
    • (2)下載下傳開源資料集
    • (3)Head Pose Estimation 如何了解?
    • (4)主要代碼思路
    • (5)運作效果

目的

經查閱相關文獻,疲勞在人體面部表情中表現出大緻三個類型:

打哈欠

(嘴巴張大且相對較長時間保持這一狀态)、

眨眼

(或眼睛微閉,此時眨眼次數增多,且眨眼速度變慢)、

點頭

(瞌睡點頭)。本實驗從人臉朝向、位置、瞳孔朝向、眼睛開合度、眨眼頻率、瞳孔收縮率等資料入手,并通過這些資料,實時地計算出駕駛員的注意力集中程度,分析駕駛員是否疲勞駕駛和及時作出安全提示。

技術背景

環境:

Win10、Python3.7、anaconda3、JupyterNotebook

技術:

  • Opencv:圖像處理
  • Dlib:一個很經典的用于圖像處理的開源庫,shape_predictor_68_face_landmarks.dat是一個用于人臉68個關鍵點檢測的dat模型庫,使用這個模型庫可以很友善地進行人臉檢測,并進行簡單的應用。
  • Numpy:基于Python的n維數值計算擴充。
  • Imutils :一系列使得opencv 便利的功能,包括圖像旋轉、縮放、平移,骨架化、邊緣檢測、顯示
  • matplotlib 圖像(imutils.opencv2matplotlib(image)。

正文

(1)環境搭建

安裝配置環境:

anaconda3、JupyterNotebook

,搭建TensorFlow和dlib環境

(2)下載下傳開源資料集

shape_predictor_68_face_landmarks.dat

官方下載下傳位址:https://pypi.org/simple/dlib/

碼雲下載下傳:https://gitee.com/cungudafa/fatigue_detecting

安裝教程參考前一文:基于dlib庫人臉特征提取【建構自己的人臉識别資料集】Dlib模型人臉特征檢測原理及demo

(3)Head Pose Estimation 如何了解?

一種比較經典的 Head Pose Estimation 算法的步驟一般為:2D人臉關鍵點檢測;3D人臉模型比對;求解3D點和對應2D點的轉換關系;根據旋轉矩陣求解歐拉角。

衆所周知一個物體相對于相機的姿态可以使用旋轉矩陣和平移矩陣來表示。

  • 平移矩陣:物體相對于相機的空間位置關系矩陣,用T表示;
  • 旋轉矩陣:物體相對于相機的空間姿态關系矩陣,用R表示。

如此看來必然少不了坐标系轉換。講點人性,繼續上圖

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

于是

世界坐标系(UVW)、相機坐标系(XYZ)、圖像中心坐标系(uv)和像素坐标系(xy)

四兄弟閃亮登場。如果相機完美無瑕,老三可以回家洗洗睡覺,關系也相對簡單。

世界坐标系到相機坐标系:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

相機坐标系到像素坐标系:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

是以像素坐标系和世界坐标系的關系如下:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

上式的求解可用

DLT(Direct Linear Transform)算法

結合

最小二乘

進行疊代求解,最小二乘的目标函數可為

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

帶^的變量為預測值,其餘為測量值。

可是

相機

也很無奈,她不完美,總有點瑕疵,比如徑向和切向

畸變

,那關系就要稍微複雜一些,叫醒阿三繼續推導:

相機坐标系要先轉換到圖像中心坐标系:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

然後再被折磨一番(計算考慮畸變):

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

最後圖像中心坐标系到像素坐标系:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

看來隻要知道世界坐标系内點的位置、像素坐标位置和相機參數就可以搞定旋轉和平移矩陣,可上面的關系分明是非線性的,這可怎麼解啊?其實OpenCV已經給我們提供了求解PnP問題的函數solvePnp(),一步輕松到位。

得到旋轉矩陣後,就可以開心地去見歐拉角了:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

世界坐标系中點的位置怎麼得到呢?

總不能每時每刻都要測一下人臉各個點在空間的位置

從各種論文中發現,原來大牛們在算法裡面

内置了一個3D人臉模型

,把關鍵點的空間位置都标出來,就充當真實臉的空間位置;可是大牛又覺得這樣不太合理,一個3D人臉模型不能表示所有人的臉,對所有人采用一個模型得到的精度肯定不好,于是便有了3DMM(3D Morphable Model),對不同人可以拟合出對應的3D臉模型,這樣關鍵點的空間位置就比較準确了,Head Pose Estimation 的精度提上去了。
Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文
解析參考原連結:重磅!頭部姿态估計「原理詳解 + 實戰代碼」來啦!
Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

如何确定疲勞?

思路一:可利用姿态估計結果(如Pitch的讀數)來

判斷是否點頭及點頭幅度

思路二:或用

鼻尖處30号點的前後移動值

(或是方差,方差表示一個機關時間資料的偏離程度,程度越大,則表示發生點頭動作的機率越大、點頭幅度越大)

(4)主要代碼思路

  • 第一步:2D人臉關鍵點檢測;
  • 第二步:3D人臉模型比對;
  • 第三步:求解3D點和對應2D點的轉換關系;
  • 第四步:根據旋轉矩陣求解歐拉角。

主要代碼參考:https://github.com/lincolnhard/head-pose-estimation

這裡隻做了思路梳理。

# 參考https://github.com/lincolnhard/head-pose-estimation
import cv2
import dlib
import numpy as np
from imutils import face_utils
"""
思路:
    第一步:2D人臉關鍵點檢測;第二步:3D人臉模型比對;
    第三步:求解3D點和對應2D點的轉換關系;第四步:根據旋轉矩陣求解歐拉角。
"""

# 加載人臉檢測和姿勢估計模型(dlib)
face_landmark_path = 'D:/myworkspace/JupyterNotebook/fatigue_detecting/model/shape_predictor_68_face_landmarks.dat'

"""
隻要知道世界坐标系内點的位置、像素坐标位置和相機參數就可以搞定旋轉和平移矩陣(OpenCV自帶函數solvePnp())
"""

# 世界坐标系(UVW):填寫3D參考點,該模型參考http://aifi.isr.uc.pt/Downloads/OpenGL/glAnthropometric3DModel.cpp
object_pts = np.float32([[6.825897, 6.760612, 4.402142],  #33左眉左上角
                         [1.330353, 7.122144, 6.903745],  #29左眉右角
                         [-1.330353, 7.122144, 6.903745], #34右眉左角
                         [-6.825897, 6.760612, 4.402142], #38右眉右上角
                         [5.311432, 5.485328, 3.987654],  #13左眼左上角
                         [1.789930, 5.393625, 4.413414],  #17左眼右上角
                         [-1.789930, 5.393625, 4.413414], #25右眼左上角
                         [-5.311432, 5.485328, 3.987654], #21右眼右上角
                         [2.005628, 1.409845, 6.165652],  #55鼻子左上角
                         [-2.005628, 1.409845, 6.165652], #49鼻子右上角
                         [2.774015, -2.080775, 5.048531], #43嘴左上角
                         [-2.774015, -2.080775, 5.048531],#39嘴右上角
                         [0.000000, -3.116408, 6.097667], #45嘴中央下角
                         [0.000000, -7.415691, 4.070434]])#6下巴角

# 相機坐标系(XYZ):添加相機内參
K = [6.5308391993466671e+002, 0.0, 3.1950000000000000e+002,
     0.0, 6.5308391993466671e+002, 2.3950000000000000e+002,
     0.0, 0.0, 1.0]# 等價于矩陣[fx, 0, cx; 0, fy, cy; 0, 0, 1]
# 圖像中心坐标系(uv):相機畸變參數[k1, k2, p1, p2, k3]
D = [7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000]

# 像素坐标系(xy):填寫凸輪的本征和畸變系數
cam_matrix = np.array(K).reshape(3, 3).astype(np.float32)
dist_coeffs = np.array(D).reshape(5, 1).astype(np.float32)



# 重新投影3D點的世界坐标軸以驗證結果姿勢
reprojectsrc = np.float32([[10.0, 10.0, 10.0],
                           [10.0, 10.0, -10.0],
                           [10.0, -10.0, -10.0],
                           [10.0, -10.0, 10.0],
                           [-10.0, 10.0, 10.0],
                           [-10.0, 10.0, -10.0],
                           [-10.0, -10.0, -10.0],
                           [-10.0, -10.0, 10.0]])
# 繪制正方體12軸
line_pairs = [[0, 1], [1, 2], [2, 3], [3, 0],
              [4, 5], [5, 6], [6, 7], [7, 4],
              [0, 4], [1, 5], [2, 6], [3, 7]]

def get_head_pose(shape):
    # 填寫2D參考點,注釋遵循https://ibug.doc.ic.ac.uk/resources/300-W/
    """
      17左眉左上角/21左眉右角/22右眉左上角/26右眉右上角/36左眼左上角/39左眼右上角/42右眼左上角/
      45右眼右上角/31鼻子左上角/35鼻子右上角/48左上角/54嘴右上角/57嘴中央下角/8下巴角
    """
    # 像素坐标集合
    image_pts = np.float32([shape[17], shape[21], shape[22], shape[26], shape[36],
                            shape[39], shape[42], shape[45], shape[31], shape[35],
                            shape[48], shape[54], shape[57], shape[8]])
    """
    用solvepnp或sovlepnpRansac,輸入3d點、2d點、相機内參、相機畸變,輸出r、t之後
    用projectPoints,輸入3d點、相機内參、相機畸變、r、t,輸出重投影2d點
    計算原2d點和重投影2d點的距離作為重投影誤差
    """
    # solvePnP計算姿勢——求解旋轉和平移矩陣:
    # rotation_vec表示旋轉矩陣,translation_vec表示平移矩陣,cam_matrix與K矩陣對應,dist_coeffs與D矩陣對應。
    _, rotation_vec, translation_vec = cv2.solvePnP(object_pts, image_pts, cam_matrix, dist_coeffs)
    # projectPoints重新投影誤差
    reprojectdst, _ = cv2.projectPoints(reprojectsrc, rotation_vec, translation_vec, cam_matrix,dist_coeffs)

    reprojectdst = tuple(map(tuple, reprojectdst.reshape(8, 2)))# 以8行2列顯示

    # 計算歐拉角calc euler angle
    # 參考https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#decomposeprojectionmatrix
    rotation_mat, _ = cv2.Rodrigues(rotation_vec)#羅德裡格斯公式(将旋轉矩陣轉換為旋轉向量)
    pose_mat = cv2.hconcat((rotation_mat, translation_vec))# 水準拼接,vconcat垂直拼接
    # eulerAngles –可選的三元素矢量,包含三個以度為機關的歐拉旋轉角度
    _, _, _, _, _, _, euler_angle = cv2.decomposeProjectionMatrix(pose_mat)# 将投影矩陣分解為旋轉矩陣和相機矩陣

    return reprojectdst, euler_angle


def main():
    # return
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print("Unable to connect to camera.")
        return
    # 檢測人臉
    detector = dlib.get_frontal_face_detector()
    # 檢測第一個人臉的關鍵點
    predictor = dlib.shape_predictor(face_landmark_path)

    while cap.isOpened():
        ret, frame = cap.read()
        if ret:
            face_rects = detector(frame, 0)

            if len(face_rects) > 0:
                # 循環臉部位置資訊,使用predictor(gray, rect)獲得臉部特征位置的資訊
                shape = predictor(frame, face_rects[0])
                # 将臉部特征資訊轉換為數組array的格式
                shape = face_utils.shape_to_np(shape)
                # 擷取頭部姿态
                reprojectdst, euler_angle = get_head_pose(shape)
                pitch = format(euler_angle[0, 0])
                yaw = format(euler_angle[1, 0])
                roll = format(euler_angle[2, 0])
                print('pitch:{}, yaw:{}, roll:{}'.format(pitch, yaw, roll))
                
                # 标出68個特征點
                for (x, y) in shape:
                    cv2.circle(frame, (x, y), 1, (0, 0, 255), -1)
                    
                # 繪制正方體12軸
                for start, end in line_pairs:
                    cv2.line(frame, reprojectdst[start], reprojectdst[end], (0, 0, 255))
                # 顯示角度結果
                cv2.putText(frame, "X: " + "{:7.2f}".format(euler_angle[0, 0]), (20, 20), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0, 0, 255), thickness=2)
                cv2.putText(frame, "Y: " + "{:7.2f}".format(euler_angle[1, 0]), (20, 50), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0, 0, 255), thickness=2)
                cv2.putText(frame, "Z: " + "{:7.2f}".format(euler_angle[2, 0]), (20, 80), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0, 0, 255), thickness=2)    
    
            # 按q退出提示
            cv2.putText(frame, "Press 'q': Quit", (20, 450),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (84, 255, 159), 2)
            # 視窗顯示 show with opencv
            cv2.imshow("Head_Posture", frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    # 釋放攝像頭 release camera
    cap.release()
    # do a bit of cleanup
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

           

頭部姿态顯示效果:

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文

點頭如何判斷是否是瞌睡?

主要參數:

歐拉角

頭部姿态判斷打瞌睡得到

實時頭部姿态的旋轉角度

過後,為頭部旋轉角度的3個參數

Yaw

,

Pitch

Roll

的示意圖,駕駛員在打瞌睡時,顯然頭部會做類似于點頭和傾斜的動作.而根據一般人的打瞌睡時表現出來的頭部姿态,顯然很少會在Yaw上有動作,而主要集中在Pitch和Roll的行為.設定參數門檻值為

0.3

,在一個時間段内10 s内,當I PitchI≥20°或者|Rolll≥20°的時間比例超過0.3時,就認為駕駛員處于打瞌睡的狀态,發出預警。

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文
參考連結:旋轉矩陣、歐拉角、四元數理論及其轉換關系

結合之前的疲勞駕駛,彙總代碼:

# -*- coding: utf-8 -*-
# import the necessary packages
from scipy.spatial import distance as dist
from imutils.video import FileVideoStream
from imutils.video import VideoStream
from imutils import face_utils
import numpy as np # 資料處理的庫 numpy
import argparse
import imutils
import time
import dlib
import cv2
import math
import time
from threading import Thread,
 
# 世界坐标系(UVW):填寫3D參考點,該模型參考http://aifi.isr.uc.pt/Downloads/OpenGL/glAnthropometric3DModel.cpp
object_pts = np.float32([[6.825897, 6.760612, 4.402142],  #33左眉左上角
                         [1.330353, 7.122144, 6.903745],  #29左眉右角
                         [-1.330353, 7.122144, 6.903745], #34右眉左角
                         [-6.825897, 6.760612, 4.402142], #38右眉右上角
                         [5.311432, 5.485328, 3.987654],  #13左眼左上角
                         [1.789930, 5.393625, 4.413414],  #17左眼右上角
                         [-1.789930, 5.393625, 4.413414], #25右眼左上角
                         [-5.311432, 5.485328, 3.987654], #21右眼右上角
                         [2.005628, 1.409845, 6.165652],  #55鼻子左上角
                         [-2.005628, 1.409845, 6.165652], #49鼻子右上角
                         [2.774015, -2.080775, 5.048531], #43嘴左上角
                         [-2.774015, -2.080775, 5.048531],#39嘴右上角
                         [0.000000, -3.116408, 6.097667], #45嘴中央下角
                         [0.000000, -7.415691, 4.070434]])#6下巴角

# 相機坐标系(XYZ):添加相機内參
K = [6.5308391993466671e+002, 0.0, 3.1950000000000000e+002,
     0.0, 6.5308391993466671e+002, 2.3950000000000000e+002,
     0.0, 0.0, 1.0]# 等價于矩陣[fx, 0, cx; 0, fy, cy; 0, 0, 1]
# 圖像中心坐标系(uv):相機畸變參數[k1, k2, p1, p2, k3]
D = [7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000]

# 像素坐标系(xy):填寫凸輪的本征和畸變系數
cam_matrix = np.array(K).reshape(3, 3).astype(np.float32)
dist_coeffs = np.array(D).reshape(5, 1).astype(np.float32)



# 重新投影3D點的世界坐标軸以驗證結果姿勢
reprojectsrc = np.float32([[10.0, 10.0, 10.0],
                           [10.0, 10.0, -10.0],
                           [10.0, -10.0, -10.0],
                           [10.0, -10.0, 10.0],
                           [-10.0, 10.0, 10.0],
                           [-10.0, 10.0, -10.0],
                           [-10.0, -10.0, -10.0],
                           [-10.0, -10.0, 10.0]])
# 繪制正方體12軸
line_pairs = [[0, 1], [1, 2], [2, 3], [3, 0],
              [4, 5], [5, 6], [6, 7], [7, 4],
              [0, 4], [1, 5], [2, 6], [3, 7]]

def get_head_pose(shape):# 頭部姿态估計
    # (像素坐标集合)填寫2D參考點,注釋遵循https://ibug.doc.ic.ac.uk/resources/300-W/
    # 17左眉左上角/21左眉右角/22右眉左上角/26右眉右上角/36左眼左上角/39左眼右上角/42右眼左上角/
    # 45右眼右上角/31鼻子左上角/35鼻子右上角/48左上角/54嘴右上角/57嘴中央下角/8下巴角
    image_pts = np.float32([shape[17], shape[21], shape[22], shape[26], shape[36],
                            shape[39], shape[42], shape[45], shape[31], shape[35],
                            shape[48], shape[54], shape[57], shape[8]])
    # solvePnP計算姿勢——求解旋轉和平移矩陣:
    # rotation_vec表示旋轉矩陣,translation_vec表示平移矩陣,cam_matrix與K矩陣對應,dist_coeffs與D矩陣對應。
    _, rotation_vec, translation_vec = cv2.solvePnP(object_pts, image_pts, cam_matrix, dist_coeffs)
    # projectPoints重新投影誤差:原2d點和重投影2d點的距離(輸入3d點、相機内參、相機畸變、r、t,輸出重投影2d點)
    reprojectdst, _ = cv2.projectPoints(reprojectsrc, rotation_vec, translation_vec, cam_matrix,dist_coeffs)
    reprojectdst = tuple(map(tuple, reprojectdst.reshape(8, 2)))# 以8行2列顯示

    # 計算歐拉角calc euler angle
    # 參考https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#decomposeprojectionmatrix
    rotation_mat, _ = cv2.Rodrigues(rotation_vec)#羅德裡格斯公式(将旋轉矩陣轉換為旋轉向量)
    pose_mat = cv2.hconcat((rotation_mat, translation_vec))# 水準拼接,vconcat垂直拼接
    # decomposeProjectionMatrix将投影矩陣分解為旋轉矩陣和相機矩陣
    _, _, _, _, _, _, euler_angle = cv2.decomposeProjectionMatrix(pose_mat)
    
    pitch, yaw, roll = [math.radians(_) for _ in euler_angle]
 
 
    pitch = math.degrees(math.asin(math.sin(pitch)))
    roll = -math.degrees(math.asin(math.sin(roll)))
    yaw = math.degrees(math.asin(math.sin(yaw)))
    print('pitch:{}, yaw:{}, roll:{}'.format(pitch, yaw, roll))

    return reprojectdst, euler_angle# 投影誤差,歐拉角

def eye_aspect_ratio(eye):
    # 垂直眼标志(X,Y)坐标
    A = dist.euclidean(eye[1], eye[5])# 計算兩個集合之間的歐式距離
    B = dist.euclidean(eye[2], eye[4])
    # 計算水準之間的歐幾裡得距離
    # 水準眼标志(X,Y)坐标
    C = dist.euclidean(eye[0], eye[3])
    # 眼睛長寬比的計算
    ear = (A + B) / (2.0 * C)
    # 傳回眼睛的長寬比
    return ear
 
def mouth_aspect_ratio(mouth):# 嘴部
    A = np.linalg.norm(mouth[2] - mouth[9])  # 51, 59
    B = np.linalg.norm(mouth[4] - mouth[7])  # 53, 57
    C = np.linalg.norm(mouth[0] - mouth[6])  # 49, 55
    mar = (A + B) / (2.0 * C)
    return mar

# 定義常數
# 眼睛長寬比
# 閃爍門檻值
EYE_AR_THRESH = 0.2
EYE_AR_CONSEC_FRAMES = 3
# 打哈欠長寬比
# 閃爍門檻值
MAR_THRESH = 0.5
MOUTH_AR_CONSEC_FRAMES = 3
# 瞌睡點頭
HAR_THRESH = 0.3
NOD_AR_CONSEC_FRAMES = 3
# 初始化幀計數器和眨眼總數
COUNTER = 0
TOTAL = 0
# 初始化幀計數器和打哈欠總數
mCOUNTER = 0
mTOTAL = 0
# 初始化幀計數器和點頭總數
hCOUNTER = 0
hTOTAL = 0

# 初始化DLIB的人臉檢測器(HOG),然後建立面部标志物預測
print("[INFO] loading facial landmark predictor...")
# 第一步:使用dlib.get_frontal_face_detector() 獲得臉部位置檢測器
detector = dlib.get_frontal_face_detector()
# 第二步:使用dlib.shape_predictor獲得臉部特征位置檢測器
predictor = dlib.shape_predictor('D:/myworkspace/JupyterNotebook/fatigue_detecting/model/shape_predictor_68_face_landmarks.dat')
 
# 第三步:分别擷取左右眼面部标志的索引
(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
(mStart, mEnd) = face_utils.FACIAL_LANDMARKS_IDXS["mouth"]

# 第四步:打開cv2 本地攝像頭
cap = cv2.VideoCapture(0)
 
# 從視訊流循環幀
while True:
    # 第五步:進行循環,讀取圖檔,并對圖檔做次元擴大,并進灰階化
    ret, frame = cap.read()
    frame = imutils.resize(frame, width=720)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 第六步:使用detector(gray, 0) 進行臉部位置檢測
    rects = detector(gray, 0)
    
    # 第七步:循環臉部位置資訊,使用predictor(gray, rect)獲得臉部特征位置的資訊
    for rect in rects:
        shape = predictor(gray, rect)
        
        # 第八步:将臉部特征資訊轉換為數組array的格式
        shape = face_utils.shape_to_np(shape)
        
        # 第九步:提取左眼和右眼坐标
        leftEye = shape[lStart:lEnd]
        rightEye = shape[rStart:rEnd]
        # 嘴巴坐标
        mouth = shape[mStart:mEnd]        
        
        # 第十步:構造函數計算左右眼的EAR值,使用平均值作為最終的EAR
        leftEAR = eye_aspect_ratio(leftEye)
        rightEAR = eye_aspect_ratio(rightEye)
        ear = (leftEAR + rightEAR) / 2.0
        # 打哈欠
        mar = mouth_aspect_ratio(mouth)
 
        # 第十一步:使用cv2.convexHull獲得凸包位置,使用drawContours畫出輪廓位置進行畫圖操作
        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)
        mouthHull = cv2.convexHull(mouth)
        cv2.drawContours(frame, [mouthHull], -1, (0, 255, 0), 1)
 
        # 第十二步:進行畫圖操作,用矩形框标注人臉
        left = rect.left()
        top = rect.top()
        right = rect.right()
        bottom = rect.bottom()
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 1)    
 
        '''
            分别計算左眼和右眼的評分求平均作為最終的評分,如果小于門檻值,則加1,如果連續3次都小于門檻值,則表示進行了一次眨眼活動
        '''
        # 第十三步:循環,滿足條件的,眨眼次數+1
        if ear < EYE_AR_THRESH:# 眼睛長寬比:0.2
            COUNTER += 1
           
        else:
            # 如果連續3次都小于門檻值,則表示進行了一次眨眼活動
            if COUNTER >= EYE_AR_CONSEC_FRAMES:# 門檻值:3
                TOTAL += 1
            # 重置眼幀計數器
            COUNTER = 0
            
        # 第十四步:進行畫圖操作,同時使用cv2.putText将眨眼次數進行顯示
        cv2.putText(frame, "Faces: {}".format(len(rects)), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)     
        cv2.putText(frame, "COUNTER: {}".format(COUNTER), (150, 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.putText(frame, "Blinks: {}".format(TOTAL), (450, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2)
        
        '''
            計算張嘴評分,如果小于門檻值,則加1,如果連續3次都小于門檻值,則表示打了一次哈欠,同一次哈欠大約在3幀
        '''
        # 同理,判斷是否打哈欠    
        if mar > MAR_THRESH:# 張嘴門檻值0.5
            mCOUNTER += 1
            cv2.putText(frame, "Yawning!", (10, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
        else:
            # 如果連續3次都小于門檻值,則表示打了一次哈欠
            if mCOUNTER >= MOUTH_AR_CONSEC_FRAMES:# 門檻值:3
                mTOTAL += 1
            # 重置嘴幀計數器
            mCOUNTER = 0
        cv2.putText(frame, "COUNTER: {}".format(mCOUNTER), (150, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) 
        cv2.putText(frame, "MAR: {:.2f}".format(mar), (300, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
        cv2.putText(frame, "Yawning: {}".format(mTOTAL), (450, 60),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2)
        """
        瞌睡點頭
        """
        # 第十五步:擷取頭部姿态
        reprojectdst, euler_angle = get_head_pose(shape)
        
        har = euler_angle[0, 0]# 取pitch旋轉角度
        if har > HAR_THRESH:# 點頭門檻值0.3
            hCOUNTER += 1
        else:
            # 如果連續3次都小于門檻值,則表示瞌睡點頭一次
            if hCOUNTER >= NOD_AR_CONSEC_FRAMES:# 門檻值:3
                hTOTAL += 1
            # 重置點頭幀計數器
            hCOUNTER = 0
        
        # 繪制正方體12軸
        for start, end in line_pairs:
            cv2.line(frame, reprojectdst[start], reprojectdst[end], (0, 0, 255))
        # 顯示角度結果
        cv2.putText(frame, "X: " + "{:7.2f}".format(euler_angle[0, 0]), (10, 90), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0, 255, 0), thickness=2)# GREEN
        cv2.putText(frame, "Y: " + "{:7.2f}".format(euler_angle[1, 0]), (150, 90), cv2.FONT_HERSHEY_SIMPLEX,0.75, (255, 0, 0), thickness=2)# BLUE
        cv2.putText(frame, "Z: " + "{:7.2f}".format(euler_angle[2, 0]), (300, 90), cv2.FONT_HERSHEY_SIMPLEX,0.75, (0, 0, 255), thickness=2)# RED    
        cv2.putText(frame, "Nod: {}".format(hTOTAL), (450, 90),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,0), 2)
        
            
        # 第十六步:進行畫圖操作,68個特征點辨別
        for (x, y) in shape:
            cv2.circle(frame, (x, y), 1, (0, 0, 255), -1)

    print('嘴巴實時長寬比:{:.2f} '.format(mar)+"\t是否張嘴:"+str([False,True][mar > MAR_THRESH]))
    print('眼睛實時長寬比:{:.2f} '.format(ear)+"\t是否眨眼:"+str([False,True][COUNTER>=1]))
    
    # 确定疲勞提示:眨眼50次,打哈欠15次,瞌睡點頭15次
    if TOTAL >= 50 or mTOTAL>=15 or hTOTAL>=15:
        cv2.putText(frame, "SLEEP!!!", (100, 200),cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 3)
        
    # 按q退出
    cv2.putText(frame, "Press 'q': Quit", (20, 500),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (84, 255, 159), 2)
    # 視窗顯示 show with opencv
    cv2.imshow("Frame", frame)
    
    # if the `q` key was pressed, break from the loop
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
        
# 釋放攝像頭 release camera
cap.release()
# do a bit of cleanup
cv2.destroyAllWindows()

           

(5)運作效果

疲勞認定标準:

眨眼:連續3幀内,眼睛長寬比為 0.2

打哈欠:嘴部長寬比為 0.5

瞌睡點頭:pitch(x)旋轉角為 0.3

Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文
Dlib模型之駕駛員疲勞檢測三(瞌睡點頭)序正文
完整項目,碼雲下載下傳連結:https://gitee.com/cungudafa/fatigue_detecting