基于Python?OpenCV和?dlib實(shí)現(xiàn)眨眼檢測(cè)
今天,我們使用面部標(biāo)記和 OpenCV 檢測(cè)視頻流中的眨眼次數(shù)。
為了構(gòu)建我們的眨眼檢測(cè)器,我們將計(jì)算一個(gè)稱為眼睛縱橫比 (EAR) 的指標(biāo),該指標(biāo)由 Soukupová 和 ?ech 在他們 2016 年的論文《使用面部標(biāo)記的實(shí)時(shí)眨眼檢測(cè)》中介紹。
與計(jì)算眨眼的傳統(tǒng)圖像處理方法不同,傳統(tǒng)的圖像處理方法通常涉及以下某些組合:
- 眼睛定位。
- 閾值以找到眼白。
- 確定眼睛的“白色”區(qū)域是否在一段時(shí)間內(nèi)消失(表示眨眼)。
- 眼睛縱橫比是一個(gè)更優(yōu)雅的解決方案,它涉及基于眼睛面部標(biāo)志之間距離比的非常簡(jiǎn)單的計(jì)算。
這種眨眼檢測(cè)方法要求快速、高效且易于實(shí)現(xiàn)。
今天我們通過(guò)四部分來(lái)實(shí)現(xiàn)眨眼檢測(cè):
第一部分,我們將討論眼睛縱橫比以及如何使用它來(lái)確定一個(gè)人在給定的視頻幀中是否在眨眼。
然后,我們將編寫(xiě) Python、OpenCV 和 dlib 代碼來(lái) (1) 執(zhí)行面部標(biāo)志檢測(cè)和 (2) 檢測(cè)視頻流中的眨眼。
基于此實(shí)現(xiàn),我們將應(yīng)用我們的方法來(lái)檢測(cè)示例網(wǎng)絡(luò)攝像頭流和視頻文件中的眨眼。
最后,我將通過(guò)討論改進(jìn)眨眼檢測(cè)器的方法來(lái)結(jié)束今天的博客文章。
了解“眼睛縱橫比”(EAR)
在眨眼檢測(cè)方面,我們只對(duì)兩組面部結(jié)構(gòu)感興趣——眼睛。每只眼睛由 6 個(gè) (x, y) 坐標(biāo)表示,從眼睛的左角開(kāi)始(就像您在看人一樣),然后圍繞該區(qū)域的其余部分順時(shí)針旋轉(zhuǎn):
基于這張圖片,我們應(yīng)該了解關(guān)鍵點(diǎn):
這些坐標(biāo)的寬度和高度之間存在關(guān)系。根據(jù) Soukupová 和 ?ech 在 2016 年發(fā)表的論文《使用面部標(biāo)志進(jìn)行實(shí)時(shí)眨眼檢測(cè)》中的工作,我們可以推導(dǎo)出反映這種關(guān)系的方程,稱為眼睛縱橫比 (EAR):
其中 p1, …, p6 是 2D 面部標(biāo)志位置。
該方程的分子計(jì)算垂直眼睛界標(biāo)之間的距離,而分母計(jì)算水平眼睛界標(biāo)之間的距離,由于只有一組水平點(diǎn)但有兩組垂直點(diǎn),因此對(duì)分母進(jìn)行適當(dāng)加權(quán)。
為什么這個(gè)方程如此有趣?
好吧,正如我們將發(fā)現(xiàn)的那樣,眼睛睜開(kāi)時(shí)眼睛的縱橫比大致恒定,但在眨眼時(shí)會(huì)迅速降至零。
使用這個(gè)簡(jiǎn)單的方程,我們可以避免使用圖像處理技術(shù),而只需依靠眼睛界標(biāo)距離的比率來(lái)確定一個(gè)人是否在眨眼。
為了更清楚地說(shuō)明這一點(diǎn),請(qǐng)考慮 Soukupová 和 ?ech 的下圖:
在左上角,我們有一個(gè)完全睜開(kāi)的眼睛——這里的眼睛縱橫比會(huì)很大(r)并且隨著時(shí)間的推移相對(duì)恒定。
然而,一旦人眨眼(右上),眼睛的縱橫比就會(huì)急劇下降,接近于零。
下圖繪制了視頻剪輯的眼睛縱橫比隨時(shí)間變化的圖表。 正如我們所看到的,眼睛縱橫比是恒定的,然后迅速下降到接近零,然后再次增加,表明發(fā)生了一次眨眼。
在下一節(jié)中,我們將學(xué)習(xí)如何使用面部標(biāo)志、OpenCV、Python 和 dlib 實(shí)現(xiàn)眨眼檢測(cè)的眼睛縱橫比。
使用面部標(biāo)志和 OpenCV 檢測(cè)眨眼
首先,打開(kāi)一個(gè)新文件并將其命名為 detect_blinks.py 。 從那里,插入以下代碼:
# 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 import argparse import imutils import time import dlib import cv2
導(dǎo)入必要的庫(kù)。
如果您的系統(tǒng)上沒(méi)有安裝 imutils(或者如果您使用的是舊版本),請(qǐng)確保使用以下命令安裝/升級(jí):
pip install --upgrade imutils
如果沒(méi)有安裝dlib,請(qǐng)參考文章
?接下來(lái),我們將定義我們的 eye_aspect_ratio 函數(shù):
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
此函數(shù)接受單個(gè)必需參數(shù),即給定眼睛的面部標(biāo)志的 (x, y) 坐標(biāo)。
計(jì)算兩組垂直眼睛界標(biāo)之間的距離,然后計(jì)算水平眼睛界標(biāo)之間的距離。
最后,結(jié)合了分子和分母以得出最終的眼睛縱橫比。
然后將眼睛縱橫比返回給調(diào)用函數(shù)。
讓我們繼續(xù)解析我們的命令行參數(shù):
# 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") args = vars(ap.parse_args())
我們的detect_blinks.py 腳本需要一個(gè)命令行參數(shù),然后是第二個(gè)可選參數(shù):
- –shape-predictor :這是 dlib 的預(yù)訓(xùn)練面部標(biāo)志檢測(cè)器的路徑。 您可以使用本博文底部的“下載”部分將檢測(cè)器以及源代碼 + 示例視頻下載到本教程中。
- –video :此可選開(kāi)關(guān)控制駐留在磁盤(pán)上的輸入視頻文件的路徑。 如果您想使用實(shí)時(shí)視頻流,只需在執(zhí)行腳本時(shí)省略此開(kāi)關(guān)即可。
我們現(xiàn)在需要設(shè)置兩個(gè)重要的常量,您可能需要為自己的實(shí)現(xiàn)進(jìn)行調(diào)整,同時(shí)初始化另外兩個(gè)重要的變量,所以一定要注意這個(gè)解釋:
# 定義兩個(gè)常量,一個(gè)為眼睛縱橫比來(lái)表示 # 閃爍然后第二個(gè)常量為連續(xù)的次數(shù) # 幀眼睛必須低于閾值 EYE_AR_THRESH = 0.3 EYE_AR_CONSEC_FRAMES = 3 # 初始化幀計(jì)數(shù)器和閃爍總數(shù) COUNTER = 0 TOTAL = 0
在確定視頻流中是否發(fā)生眨眼時(shí),我們需要計(jì)算眼睛縱橫比。
如果眼睛縱橫比低于某個(gè)閾值,然后又高于閾值,那么我們將注冊(cè)一個(gè)“眨眼”——EYE_AR_THRESH 就是這個(gè)閾值。我們默認(rèn)它的值為 0.3,因?yàn)檫@對(duì)我的應(yīng)用程序最有效,但您可能需要為自己的應(yīng)用程序調(diào)整它。
然后我們有一個(gè)重要的常量,EYE_AR_CONSEC_FRAME——這個(gè)值被設(shè)置為 3 以指示眼睛縱橫比小于 EYE_AR_THRESH 的三個(gè)連續(xù)幀必須發(fā)生,以便注冊(cè)眨眼。
同樣,根據(jù)管道的幀處理吞吐率,您可能需要為自己的實(shí)現(xiàn)提高或降低此數(shù)字。
第 44 和 45 行初始化兩個(gè)計(jì)數(shù)器。 COUNTER 是眼睛縱橫比小于 EYE_AR_THRESH 的連續(xù)幀的總數(shù),而 TOTAL 是腳本運(yùn)行時(shí)發(fā)生的眨眼總數(shù)。
現(xiàn)在我們的導(dǎo)入、命令行參數(shù)和常量都已經(jīng)處理好了,我們可以初始化 dlib 的人臉檢測(cè)器和面部標(biāo)記檢測(cè)器:
# 初始化dlib的人臉檢測(cè)器(基于HOG)然后創(chuàng)建 # 面部標(biāo)志預(yù)測(cè)器 print("[INFO] loading facial landmark predictor...") detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor(args["shape_predictor"])
初始化實(shí)際的面部標(biāo)志預(yù)測(cè)器。
dlib 生成的面部標(biāo)志遵循可索引的列表,如下:
因此,我們可以確定開(kāi)始和結(jié)束數(shù)組切片索引值,以便為下面的左眼和右眼提取 (x, y) 坐標(biāo):
# 獲取左側(cè)和面部標(biāo)志的索引 # 右眼,分別 (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"] (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
使用這些索引,我們將能夠毫不費(fèi)力地提取眼睛區(qū)域。
接下來(lái),我們需要決定是使用基于文件的視頻流還是實(shí)時(shí) USB/網(wǎng)絡(luò)攝像頭/Raspberry Pi 相機(jī)視頻流:
# start the video stream thread print("[INFO] starting video stream thread...") vs = FileVideoStream(args["video"]).start() fileStream = True # vs = VideoStream(src=0).start() # vs = VideoStream(usePiCamera=True).start() # fileStream = False time.sleep(1.0) fps = 30 #保存視頻的FPS,可以適當(dāng)調(diào)整 size=(450,800) videoWriter = cv2.VideoWriter('3.mp4',-1,fps,size)#最后一個(gè)是保存圖片的尺寸
如果您使用的是文件視頻流,則保留代碼原樣。
如果您想使用內(nèi)置網(wǎng)絡(luò)攝像頭或 USB 攝像頭,請(qǐng)取消注釋# vs = VideoStream(src=0).start()。
對(duì)于 Raspberry Pi 攝像頭模塊,取消注釋# vs = VideoStream(usePiCamera=True).start()。
定義幀數(shù)。
定義大小
定義視頻寫(xiě)入對(duì)象
最后,我們到達(dá)了腳本的主循環(huán):
# loop over frames from the video stream while True: # 如果這是一個(gè)文件視頻流,那么我們需要檢查是否 # 緩沖區(qū)中還有更多幀要處理 if fileStream and not vs.more(): break frame = vs.read() if frame is None: break frame = imutils.resize(frame, width=450) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 在灰度幀中檢測(cè)人臉 rects = detector(gray, 0)
遍歷視頻流中的幀。
如果我們正在訪問(wèn)一個(gè)視頻文件流并且視頻中沒(méi)有更多的幀,我們就會(huì)中斷循環(huán)。
從視頻流中讀取下一幀,然后調(diào)整其大小并將其轉(zhuǎn)換為灰度。
然后我們通過(guò) dlib 的內(nèi)置人臉檢測(cè)器檢測(cè)灰度幀中的人臉。
我們現(xiàn)在需要遍歷幀中的每個(gè)人臉,然后對(duì)每個(gè)人應(yīng)用面部標(biāo)志檢測(cè):
# loop over the face detections for rect in rects: # 確定面部區(qū)域的面部標(biāo)志,然后 # 將面部標(biāo)志 (x, y) 坐標(biāo)轉(zhuǎn)換為 NumPy數(shù)組 shape = predictor(gray, rect) shape = face_utils.shape_to_np(shape) # 提取左右眼坐標(biāo),然后使用 # 坐標(biāo)來(lái)計(jì)算雙眼的眼睛縱橫比 leftEye = shape[lStart:lEnd] rightEye = shape[rStart:rEnd] leftEAR = eye_aspect_ratio(leftEye) rightEAR = eye_aspect_ratio(rightEye) # 平均兩只眼睛的眼睛縱橫比 ear = (leftEAR + rightEAR) / 2.0
確定面部區(qū)域的面部標(biāo)志,將這些 (x, y) 坐標(biāo)轉(zhuǎn)換為 NumPy 數(shù)組。
使用本腳本前面的數(shù)組切片技術(shù),我們可以分別提取左眼和右眼的 (x, y) 坐標(biāo)。
然后,在第 96 和 97 行計(jì)算每只眼睛的眼睛縱橫比。
按照 Soukupová 和 ?ech 的建議,我們將兩只眼睛的縱橫比平均在一起以獲得更好的眨眼估計(jì)(當(dāng)然,假設(shè)一個(gè)人同時(shí)眨眼)。
我們的下一個(gè)代碼塊只是處理眼睛區(qū)域本身的面部標(biāo)志的可視化:
# 計(jì)算左眼和右眼的凸包,然后 # 可視化每只眼睛 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)
在這一點(diǎn)上,我們已經(jīng)計(jì)算了我們的(平均)眼睛縱橫比,但我們實(shí)際上還沒(méi)有確定是否發(fā)生了眨眼——這將在下一節(jié)中解決:
# 檢查眼睛的縱橫比是否低于眨眼 # 閾值,如果是,則增加閃爍幀計(jì)數(shù)器 if ear < EYE_AR_THRESH: COUNTER += 1 # 否則,眼睛縱橫比不低于眨眼 # 臨界點(diǎn) else: # 如果眼睛閉上足夠多的次數(shù) # 然后增加閃爍的總數(shù) if COUNTER >= EYE_AR_CONSEC_FRAMES: TOTAL += 1 # 重置眼框計(jì)數(shù)器 COUNTER = 0
檢查眼睛縱橫比是否低于我們的眨眼閾值——如果是增加指示正在發(fā)生眨眼的連續(xù)幀的數(shù)量。
否則,處理眼睛縱橫比不低于眨眼閾值的情況。
在這種情況下,再次檢查以查看是否有足夠數(shù)量的連續(xù)幀包含低于我們預(yù)定義閾值的眨眼率。
如果檢查通過(guò),我們?cè)黾娱W爍的總次數(shù)。
然后我們重置連續(xù)閃爍的次數(shù) COUNTER。
我們的最終代碼塊只是處理在我們的輸出幀上繪制眨眼次數(shù),以及顯示當(dāng)前眼睛縱橫比:
# 繪制幀上閃爍的總數(shù)以及 # 計(jì)算出的幀的眼睛縱橫比 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) # show the frame cv2.imshow("Frame", frame) videoWriter.write(frame) key = cv2.waitKey(1) & 0xFF # if the `q` key was pressed, break from the loop if key == ord("q"): break videoWriter.release() # do a bit of cleanup cv2.destroyAllWindows() vs.stop()
眨眼檢測(cè)結(jié)果
要將我們的眨眼檢測(cè)器應(yīng)用于示例視頻,只需執(zhí)行以下命令:
python detect_blinks.py --shape-predictor shape_predictor_68_face_landmarks.dat --video 11.mp4
測(cè)試結(jié)果:
測(cè)試視頻鏈接:
眨眼檢測(cè)
如果測(cè)試攝像頭則,如下操作:
#vs = FileVideoStream(args["video"]).start() #fileStream = True vs = VideoStream(src=0).start() # vs = VideoStream(usePiCamera=True).start() fileStream = False
注釋FileVideoStream,取消注釋VideoStream。
執(zhí)行命令:
python detect_blinks.py --shape-predictor shape_predictor_68_face_landmarks.dat
總結(jié)
在這篇博文中,我演示了如何使用 OpenCV、Python 和 dlib 構(gòu)建眨眼檢測(cè)器。
構(gòu)建眨眼檢測(cè)器的第一步是執(zhí)行面部標(biāo)志檢測(cè),以定位視頻流中給定幀中的眼睛。
一旦我們有了雙眼的面部標(biāo)志,我們就計(jì)算每只眼睛的眼睛縱橫比,這給了我們一個(gè)奇異值,將垂直眼睛標(biāo)志點(diǎn)之間的距離與水平標(biāo)志點(diǎn)之間的距離聯(lián)系起來(lái)。
一旦我們有了眼睛縱橫比,我們就可以確定一個(gè)人是否在眨眼——眼睛縱橫比在睜眼時(shí)將保持大致恒定,然后在眨眼時(shí)迅速接近零,然后隨著眼睛睜開(kāi)再次增加.
為了改進(jìn)我們的眨眼檢測(cè)器,Soukupová 和 ?ech 建議構(gòu)建一個(gè) 13 維的眼睛縱橫比特征向量(第 N 幀、N – 6 幀和 N + 6 幀),然后將該特征向量輸入線性 SVM分類。
通過(guò)這篇博文你還學(xué)會(huì)了如何保存視頻。
眨眼檢測(cè)的一個(gè)應(yīng)用場(chǎng)景是睡意檢測(cè)。
完整的代碼,提取碼:k653
以上就是基于Python OpenCV和 dlib實(shí)現(xiàn)眨眼檢測(cè)的詳細(xì)內(nèi)容,更多關(guān)于Python OpenCV dlib眨眼檢測(cè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python實(shí)現(xiàn)簡(jiǎn)單的貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)簡(jiǎn)單的貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03Python pandas 列轉(zhuǎn)行操作詳解(類似hive中explode方法)
這篇文章主要介紹了Python pandas 列轉(zhuǎn)行操作詳解(類似hive中explode方法),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05利用Python對(duì)中國(guó)500強(qiáng)排行榜數(shù)據(jù)進(jìn)行可視化分析
這篇文章主要介紹了利用Python對(duì)中國(guó)500強(qiáng)排行榜數(shù)據(jù)進(jìn)行可視化分析,從不同角度去對(duì)數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析,可視化展示,下文詳細(xì)內(nèi)容介紹需要的小伙伴可以參考一下2022-05-05Python實(shí)現(xiàn)隨機(jī)密碼生成器實(shí)例
這篇文章主要介紹了Python實(shí)現(xiàn)隨機(jī)密碼生成器實(shí)例,string.printable是string中的可打印字符,用strip函數(shù)首尾去掉空格,random模塊用來(lái)取字符,random.choice隨機(jī)取字符,將隨機(jī)取出的字符與password空字符串進(jìn)行拼接,最后用print輸出,需要的朋友可以參考下2023-09-09python numpy中setdiff1d的用法說(shuō)明
這篇文章主要介紹了python numpy中setdiff1d的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Python實(shí)現(xiàn)簡(jiǎn)單的圖書(shū)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)簡(jiǎn)單的圖書(shū)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03使用python讀取.text文件特定行的數(shù)據(jù)方法
今天小編就為大家分享一篇使用python讀取.text文件特定行的數(shù)據(jù)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01Pandas.DataFrame重置列的行名實(shí)現(xiàn)(set_index)
本文主要介紹了Pandas.DataFrame重置列的行名實(shí)現(xiàn)(set_index),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02