Python人臉檢測實戰之疲勞檢測

今天我們實現疲勞檢測。 如果眼睛已經閉上瞭一段時間,我們會認為他們開始打瞌睡並發出警報來喚醒他們並引起他們的註意。我們測試一段視頻來展示效果。同時代碼中保留開啟攝像頭的的代碼,取消註釋即可使用。

使用 OpenCV 構建犯困檢測器

要開始我們的實現,打開一個新文件,將其命名為 detect_drowsiness.py ,並插入以下代碼:

# import the necessary packages
from scipy.spatial import distance as dist
from imutils.video import VideoStream
from imutils import face_utils
from threading import Thread
import numpy as np
import playsound
import argparse
import imutils
import time
import dlib
import cv2

導入們所需的 Python 包。

我們還需要 imutils 包,我的一系列計算機視覺和圖像處理功能,以便更輕松地使用 OpenCV。

如果您的系統上還沒有安裝 imutils,您可以通過以下方式安裝/升級 imutils:

pip install --upgrade imutils

還將導入 Thread 類,以便我們可以在與主線程不同的線程中播放我們的警報,以確保我們的腳本不會在警報響起時暫停執行。

為瞭真正播放我們的 WAV/MP3 鬧鐘,我們需要 playsound 庫,這是一個純 Python 的跨平臺實現,用於播放簡單的聲音。

playsound 庫可以通過 pip 方便地安裝:

pip install playsound

但是,如果您使用的是 macOS(就像我為這個項目所做的那樣),您還需要安裝 pyobjc,否則當您實際嘗試播放聲音時,您將收到與 AppKit 相關的錯誤:

pip install pyobjc

接下來,我們需要定義 sound_alarm 函數,該函數播放音頻文件:

def sound_alarm(path):
	# play an alarm sound
	playsound.playsound(path)

定義 eye_aspect_ratio 函數,該函數用於計算垂直眼睛界標之間的距離與水平眼睛界標之間的距離之比:

def eye_aspect_ratio(eye):
	# compute the euclidean distances between the two sets of
	# vertical eye landmarks (x, y)-coordinates
	A = dist.euclidean(eye[1], eye[5])
	B = dist.euclidean(eye[2], eye[4])
	# compute the euclidean distance between the horizontal
	# eye landmark (x, y)-coordinates
	C = dist.euclidean(eye[0], eye[3])
	# compute the eye aspect ratio
	ear = (A + B) / (2.0 * C)
	# return the eye aspect ratio
	return ear

由於OpenCV不能直接繪制中文,我們還需定義繪制中文的方法:

def cv2ImgAddText(img, text, left, top, textColor=(0, 255, 0), textSize=20):
    if (isinstance(img, np.ndarray)):  # 判斷是否OpenCV圖片類型
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    # 創建一個可以在給定圖像上繪圖的對象
    draw = ImageDraw.Draw(img)
    # 字體的格式
    fontStyle = ImageFont.truetype(
        "font/simsun.ttc", textSize, encoding="utf-8")
    # 繪制文本
    draw.text((left, top), text, textColor, font=fontStyle,stroke_width=2)
    # 轉換回OpenCV格式
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

接下來,定義命令行參數:

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--shape-predictor", required=True,
	help="path to facial landmark predictor")
ap.add_argument("-v", "--video", type=str, default="",
                help="path to input video file")
ap.add_argument("-a", "--alarm", type=str, default="",
	help="path alarm .WAV file")
ap.add_argument("-w", "--webcam", type=int, default=0,
	help="index of webcam on system")
args = vars(ap.parse_args())

犯困檢測器需要一個命令行參數,後跟兩個可選參數,每個參數的詳細信息如下:

–shape-predictor :這是 dlib 的預訓練面部標志檢測器的路徑。 您可以使用本博文底部的“下載”部分將檢測器和本教程的源代碼一起下載。

–video:視頻文件。本文用視頻文件測試。

–alarm :您可以在此處選擇指定要用作警報的輸入音頻文件的路徑。

–webcam :此整數控制內置網絡攝像頭/USB 攝像頭的索引。

定義瞭命令行參數,我們還需要定義幾個重要的變量:

# define two constants, one for the eye aspect ratio to indicate
# blink and then a second constant for the number of consecutive
# frames the eye must be below the threshold for to set off the
# alarm
EYE_AR_THRESH = 0.3
EYE_AR_CONSEC_FRAMES = 48
# initialize the frame counter as well as a boolean used to
# indicate if the alarm is going off
COUNTER = 0
ALARM_ON = False

定義瞭 EYE_AR_THRESH。如果眼睛縱橫比低於此閾值,我們將開始計算人閉上眼睛的幀數。

如果該人閉上眼睛的幀數超過 EYE_AR_CONSEC_FRAMES,我們將發出警報。

在實驗中,我發現 0.3 的 EYE_AR_THRESH 在各種情況下都能很好地工作(盡管您可能需要為自己的應用程序自己調整它)。

我還將 EYE_AR_CONSEC_FRAMES 設置為 48 ,這意味著如果一個人連續閉眼 48 幀,我們將播放警報聲。

您可以通過降低 EYE_AR_CONSEC_FRAMES 來使疲勞檢測器更敏感——同樣,您可以通過增加它來降低疲勞檢測器的敏感度。

定義瞭 COUNTER,即眼睛縱橫比低於 EYE_AR_THRESH 的連續幀的總數。

如果 COUNTER 超過 EYE_AR_CONSEC_FRAMES ,那麼我們將更新佈爾值 ALARM_ON。

dlib 庫附帶瞭一個基於定向梯度的人臉檢測器的直方圖以及一個人臉地標預測器——我們在以下代碼塊中實例化瞭這兩個:

# initialize dlib's face detector (HOG-based) and then create
# the facial landmark predictor
print("[INFO] loading facial landmark predictor...")
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(args["shape_predictor"])

dlib 產生的面部標志是一個可索引的列表,見下圖:

因此,要從一組面部標志中提取眼睛區域,我們隻需要知道正確的數組切片索引:

# grab the indexes of the facial landmarks for the left and
# right eye, respectively
(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]

使用這些索引,我們將能夠通過數組切片輕松提取眼睛區域。

我們現在準備啟動我們的睡意檢測器的核心:

# start the video stream thread
print("[INFO] starting video stream thread...")
vs = VideoStream(src=args["webcam"]).start()
time.sleep(1.0)
# loop over frames from the video stream
while True:
	# grab the frame from the threaded video file stream, resize
	# it, and convert it to grayscale
	# channels)
	frame = vs.read()
	frame = imutils.resize(frame, width=450)
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
	# detect faces in the grayscale frame
	rects = detector(gray, 0)

實例化 VideoStream。

暫停一秒鐘,讓相機傳感器預熱。

開始遍歷視頻流中的幀。

讀取下一幀,然後我們通過將其大小調整為 450 像素的寬度並將其轉換為灰度進行預處理。

應用 dlib 的人臉檢測器來查找和定位圖像中的人臉。

下一步是應用面部標志檢測來定位面部的每個重要區域:

	# loop over the face detections
	for rect in rects:
		# determine the facial landmarks for the face region, then
		# convert the facial landmark (x, y)-coordinates to a NumPy
		# array
		shape = predictor(gray, rect)
		shape = face_utils.shape_to_np(shape)
		# extract the left and right eye coordinates, then use the
		# coordinates to compute the eye aspect ratio for both eyes
		leftEye = shape[lStart:lEnd]
		rightEye = shape[rStart:rEnd]
		leftEAR = eye_aspect_ratio(leftEye)
		rightEAR = eye_aspect_ratio(rightEye)
		# average the eye aspect ratio together for both eyes
		ear = (leftEAR + rightEAR) / 2.0

循環遍歷檢測到的每個人臉——在我們的實現中(特別與司機睡意有關),我們假設隻有一張臉——司機——但我把這個 for 循環留在這裡以防萬一你想應用多張臉視頻的技術。

對於每個檢測到的人臉,我們應用 dlib 的面部標志檢測器並將結果轉換為 NumPy 數組。

使用 NumPy 數組切片,我們可以分別提取左眼和右眼的 (x, y) 坐標。

給定雙眼的 (x, y) 坐標,我們然後計算它們的眼睛縱橫比。

Soukupová 和 Čech 建議將兩個眼睛的縱橫比平均在一起以獲得更好的估計。

然後,我們可以使用下面的 cv2.drawContours 函數可視化框架上的每個眼睛區域——這在我們嘗試調試腳本並希望確保正確檢測和定位眼睛時通常很有幫助:

		# compute the convex hull for the left and right eye, then
		# visualize each of the eyes
		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)

最後,我們現在準備檢查視頻流中的人是否出現犯困的癥狀:

		# check to see if the eye aspect ratio is below the blink
		# threshold, and if so, increment the blink frame counter
		if ear < EYE_AR_THRESH:
			COUNTER += 1
			# if the eyes were closed for a sufficient number of
			# then sound the alarm
			if COUNTER >= EYE_AR_CONSEC_FRAMES:
				# if the alarm is not on, turn it on
				if not ALARM_ON:
					ALARM_ON = True
					# check to see if an alarm file was supplied,
					# and if so, start a thread to have the alarm
					# sound played in the background
					if args["alarm"] != "":
						t = Thread(target=sound_alarm,
							args=(args["alarm"],))
						t.deamon = True
						t.start()
				# draw an alarm on the frame
                frame=cv2ImgAddText(frame,"醒醒,別睡!",10,30,(255, 0, 0),30)
		# otherwise, the eye aspect ratio is not below the blink
		# threshold, so reset the counter and alarm
		else:
			COUNTER = 0
			ALARM_ON = False

檢查眼睛縱橫比是否低於“眨眼/閉合”眼睛閾值 EYE_AR_THRESH 。

如果是,我們增加 COUNTER ,即該人閉上眼睛的連續幀總數。

如果 COUNTER 超過 EYE_AR_CONSEC_FRAMES,那麼我們假設此人開始打瞌睡。

進行瞭另一次檢查,以查看警報是否已打開——如果沒有,我們將其打開。

處理播放警報聲音,前提是在執行腳本時提供瞭 –alarm 路徑。我們特別註意創建一個單獨的線程來負責調用 sound_alarm 以確保我們的主程序在聲音播放完畢之前不會被阻塞。

繪制文本 DROWSINESS ALERT!在我們的框架上——同樣,這通常有助於調試,尤其是當您不使用 playsound 庫時。

最後,第 136-138 行處理眼睛縱橫比大於 EYE_AR_THRESH 的情況,表示眼睛是睜開的。如果眼睛是睜開的,我們重置計數器並確保警報關閉。

我們的睡意檢測器中的最後一個代碼塊處理將輸出幀顯示到我們的屏幕上:

		# draw the computed eye aspect ratio on the frame to help
		# with debugging and setting the correct eye aspect ratio
		# thresholds and frame counters
		cv2.putText(frame, "EAR: {:.2f}".format(ear), (300, 30),
			cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
 
	# show the frame
	cv2.imshow("Frame", frame)
	key = cv2.waitKey(1) & 0xFF
 
	# if the `q` key was pressed, break from the loop
	if key == ord("q"):
		break
# do a bit of cleanup
cv2.destroyAllWindows()
vs.stop()

到這裡編碼完成!!!

測試疲勞檢測器

運行指令:

python detect_drowsiness.py --shape-predictor shape_predictor_68_face_landmarks.dat --video 12.mp4  --alarm alarm.mp3

運行結果:

檢測到打瞌睡就會發出提示,並將提醒打印在視頻上面 

以上就是Python人臉檢測實戰之疲勞檢測的詳細內容,更多關於Python 人臉疲勞檢測的資料請關註WalkonNet其它相關文章!

推薦閱讀: