【机器视觉案例】(11) 眨眼计数器,人脸关键点检测,附python完整代码
各位同學好,今天和大家分享一下如何使用 mediapipe+opencv 實現眨眼計數器。先放張圖看效果。
下圖左側為視頻圖像,右側為平滑后的人眼開合比曲線。以左眼為例,若眼眶上下邊界的距離與左右邊界的距離的比值小于26%,就認為是眨眼。當眨眼成功計數一次后,接下來的10幀不再計算眨眼次數,防止重復。
不眨眼時:
眨眼時:
1. 安裝工具包
pip install opencv_python==4.2.0.34 # 安裝opencv
pip install mediapipe # 安裝mediapipe
# pip install mediapipe --user #有user報錯的話試試這個
pip install cvzone # 安裝cvzone# 導入工具包
import cv2
import cvzone
from cvzone.FaceMeshModule import FaceMeshDetector # 導入臉部關鍵點檢測方法
from cvzone.PlotModule import LivePlot # 導入實時繪圖模塊
2. 臉部關鍵點檢測
(1)cvzone.FaceMeshModule.FaceMeshDetector()? 人臉關鍵點檢測方法
參數:
staticMode:?默認為 False,將輸入圖像視為視頻流。它將嘗試在第一個輸入圖像中檢測人臉,并在成功檢測后進一步定位468個關鍵點的坐標。在隨后的圖像中,一旦檢測到所有 maxFaces 張臉并定位了相應的關鍵點的坐標,它就會跟蹤這些坐標,而不會調用另一個檢測,直到它失去對任何一張臉的跟蹤。這減少了延遲,非常適合處理視頻幀。如果設置為 True,則在每個輸入圖像上運行臉部檢測,用于處理一批靜態的、可能不相關的圖像。
maxFaces:?最多檢測幾張臉,默認為 2
minDetectionCon=0.5:?臉部關鍵點檢測模型的最小置信值(0-1之間),超過閾值則檢測成功。默認為 0.5
minTrackCon=0.5:?關鍵點坐標跟蹤模型的最小置信值 (0-1之間),用于將手部坐標視為成功跟蹤,不成功則在下一個輸入圖像上自動調用手部檢測。將其設置為更高的值可以提高解決方案的穩健性,但代價是更高的延遲。如果?mode 為 True,則忽略這個參數,手部檢測將在每個圖像上運行。默認為 0.5
(2)cvzone.FaceMeshModule.FaceMeshDetector.findFaceMesh()? 人臉關鍵點檢測方法
參數:
img:?需要檢測關鍵點的幀圖像,格式為BGR
draw:?是否需要在原圖像上繪制關鍵點及連線
返回值:
img:?返回繪制了關鍵點及連線后的圖像
faces:?檢測到的臉部信息,三維列表,包含每張臉的468個關鍵點。
代碼如下:
import cv2
import cvzone
from cvzone.FaceMeshModule import FaceMeshDetector # 導入臉部關鍵點檢測方法#(1)讀取視頻文件
filepath = 'D:/deeplearning/video/eyes.mp4'
cap = cv2.VideoCapture(filepath)#(2)配置
# 接收臉部檢測方法,設置參數
detector = FaceMeshDetector(maxFaces=1) # 最多只檢測一張臉#(3)圖像處理
while True:# 原視頻較短,循環播放if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT): # 如果當前幀等于總幀數,即視頻播放到了結尾cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # 讓當前幀為0,重置視頻從頭開始# 返回幀圖像是否讀取成功success,讀取的幀圖像imgsuccess, img = cap.read()#(4)關鍵點檢測,繪制人臉網狀檢測結果img, faces = detector.findFaceMesh(img) # img為繪制關鍵點后的圖像,faces為關鍵點坐標# 查看結果print(faces) # faces是三維數組,包含每張臉所有關鍵點的坐標[[[mark1],[mark2],..,[markn]], [face2], [face3]...]#(5)顯示圖像cv2.imshow('img', img) # 傳入窗口名和幀圖像# 每幀圖像滯留10毫秒后消失,按下鍵盤上的ESC鍵退出程序if cv2.waitKey(10) & 0xFF == 27:break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
效果圖如下,只檢測一張人臉的468個關鍵點,并在輸出欄中打印各個關鍵點的坐標。
2. 判斷是否眨眼
(1)計算人眼開合比
首先考慮到人臉距離攝像機的遠近對眼眶上下及左右邊界的距離有影響,人臉靠攝像機越近,眼眶間的距離就越小。因此,不能單單靠計算眼眶上下邊界的距離來判斷是否眨眼。
以左眼的開閉來判斷是否眨眼,先根據人臉關鍵點坐標編號找出左眼關鍵點的編號。運用?cvzone.FaceMeshModule.FaceMeshDetector.findDistance() 來計算兩個關鍵點之間的距離,等同于勾股定理計算兩點間的距離。參數,兩個點的坐標;返回值,兩點之間的線段長度 length,線段信息 info 包括(兩個端點的坐標,以及連線中點的坐標)
計算得到左眼上下框之間的距離?lengthver,以及左眼左右框之間的距離?lengthhor。計算上下框距離除以左右框的距離,得到開合比?ratio
(2)實時繪圖
cvzone.PlotModule LivePlot()? 實時繪圖方法
參數:
w:繪圖框的寬,默認為 640
h:繪圖框的高,默認為 480
yLimit:y坐標的刻度范圍,默認為 [0, 100]
interval:圖像上每個點的間隔,默認為 0.001
invert:曲線圖上下翻轉,默認為 False
cvzone.PlotModule LivePlot.update()? 在圖像上實時添加新的坐標點
參數:
y:新的坐標點的y軸坐標(x軸坐標為實時的時間點)
color:圖像上的坐標點的顏色,默認是(255, 0, 255)
返回值:繪制好坐標點的當前幀的圖像
cvzone.stackImages()? 合并兩張圖像
參數:
imgList:列表形式,需要合并的圖片,如:[img1, img2, ...]
cols:列方向排序,圖片合并后排幾列
scale:改變合并后的圖像的size,圖像大小是原來的幾倍。
返回值:合并后的圖像
接著上面的代碼補充:
import cv2
import cvzone
from cvzone.FaceMeshModule import FaceMeshDetector # 導入臉部關鍵點檢測方法
from cvzone.PlotModule import LivePlot # 導入實時繪圖模塊#(1)讀取視頻文件
filepath = 'D:/deeplearning/video/eyes1.mp4'
cap = cv2.VideoCapture(filepath)#(2)配置
# 接收臉部檢測方法,設置參數
detector = FaceMeshDetector(maxFaces=1) # 最多只檢測一張臉# 人眼關鍵點所在的索引
idList = [22, 23, 24, 26, 110, 130, 157, 158, 159, 160, 161, 243] # 左眼 # 接收實時繪圖方法,圖寬640高360,y軸刻度范圍,間隔默認0.01,上下翻轉默認False
plotY = LivePlot(640, 360, [20,40], invert=True)#(3)圖像處理
while True:# 原視頻較短,循環播放if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT): # 如果當前幀等于總幀數,即視頻播放到了結尾cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # 讓當前幀為0,重置視頻從頭開始# 返回幀圖像是否讀取成功success,讀取的幀圖像imgsuccess, img = cap.read()#(4)關鍵點檢測,不繪制關鍵點及連線img, faces = detector.findFaceMesh(img, draw=False) # img為繪制關鍵點后的圖像,faces為關鍵點坐標# 如果檢測到關鍵點了就接下去處理if faces:face = faces[0] # face接收一張臉的所有關鍵點信息,且faces是三維列表# 遍歷所有的眼部關鍵點for id in idList:cv2.circle(img, tuple(face[id]), 4, (0,255,255), cv2.FILLED) # 以關鍵點為圓心半徑為5畫圓# 由于人臉距離攝像機的距離的遠近會影響眼部上下邊界之間的距離,因此通過開合比例來判斷是否眨眼leftUp = tuple(face[159]) # 左眼最上邊界的關鍵點坐標leftDown = tuple(face[23]) # 左眼最下邊界的關鍵點坐標leftLeft = tuple(face[130]) # 左眼最左邊界的關鍵點坐標leftRight = tuple(face[243]) # 左眼最右邊界的關鍵點坐標# 計算上下邊界以及左右邊界的距離,函數返回距離length,以及線段兩端點和中點坐標,這里用不到lengthver, __ = detector.findDistance(leftUp, leftDown) # 上下邊界距離lengthhor, __ = detector.findDistance(leftLeft, leftRight) # 左右邊界距離# 在上下及左右關鍵點之間各畫一條線段,端點坐標是元組cv2.line(img, leftUp, leftDown, (255,0,255), 2)cv2.line(img, leftLeft, leftRight, (255,0,255), 2)# 計算豎直距離與水平距離的比值ratio = 100*lengthver/lengthhorprint('水平距離:', lengthhor, '垂直距離:', lengthver, '百分比%:', ratio)# 以時間為x軸,百分比為y軸,繪制實時變化曲線imgPlot = plotY.update(ratio, (0,0,255)) # 參數:每個時刻的y值,線條顏色#(5)眨眼計數器# 重塑圖像的寬和高,保證圖像size和曲線圖size一致img = cv2.resize(img, (640,360)) # 將變化曲線和原圖像組合起來,2代表排成兩列,最后一個1代表比例不變imgStack = cvzone.stackImages([img, imgPlot], 2, 1)#(6)顯示圖像cv2.imshow('img', imgStack) # 傳入窗口名和幀圖像# 每幀圖像滯留20毫秒后消失,按下鍵盤上的ESC鍵退出程序if cv2.waitKey(20) & 0xFF == 27:break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
效果圖如下,右側為每一幀圖像的人眼開合百分比,下側輸出欄打印每一幀的眼眶的水平、豎直距離,及比值。
3. 眨眼計數器
(1)平滑曲線
由于每一幀計算一次人眼開合比,曲線變化幅度較大,不宜判斷。因此對曲線進行平滑操作,如果幀數小于10幀,更新的坐標是每幀的人眼閉合比;如果幀數大于10幀,那就每10幀求一次均值,將這個均值更新到圖像上。例如,第11幀圖像的人眼閉合比為,第2至第11幀的人眼閉合比的均值。
如下面代碼中的第(5)步,變量 ratioList 中總是存放10幀的人眼閉合比值。如,計算第12幀圖像的閉合比時,就將列表中第2幀的閉合比值刪除,計算第3幀到第12幀的均值
(2)避免重復計數
如下面代碼中的第(6)步,如果判斷了一次眨眼之后,那么接下來的10幀都是屬于該次眨眼的過程,只有10幀過后才能進行下次一眨眼計數。
代碼如下:
import cv2
import cvzone
from cvzone.FaceMeshModule import FaceMeshDetector # 導入臉部關鍵點檢測方法
from cvzone.PlotModule import LivePlot # 導入實時繪圖模塊#(1)讀取視頻文件
filepath = 'D:/deeplearning/video/eyes1.mp4'
cap = cv2.VideoCapture(filepath)#(2)配置
# 接收臉部檢測方法,設置參數
detector = FaceMeshDetector(maxFaces=1) # 最多只檢測一張臉# 人眼關鍵點所在的索引
idList = [22, 23, 24, 26, 110, 130, 157, 158, 159, 160, 161, 243] # 左眼 # 接收實時繪圖方法,圖寬640高360,y軸刻度范圍,間隔默認0.01,上下翻轉默認False
plotY = LivePlot(640, 360, [20,40], invert=True)ratioList = [] # 存放實時的人眼開合百分比blinkCounter = 0 # 眨眼計數器默認=0counter = 0 # 代表當前幀沒計算過眨眼次數colorblink = (255,0,255) # 不眨眼時計數器的顏色#(3)圖像處理
while True:# 原視頻較短,循環播放if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT): # 如果當前幀等于總幀數,即視頻播放到了結尾cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # 讓當前幀為0,重置視頻從頭開始# 返回幀圖像是否讀取成功success,讀取的幀圖像imgsuccess, img = cap.read()#(4)關鍵點檢測,不繪制關鍵點及連線img, faces = detector.findFaceMesh(img, draw=False) # img為繪制關鍵點后的圖像,faces為關鍵點坐標# 如果檢測到關鍵點了就接下去處理if faces:face = faces[0] # face接收一張臉的所有關鍵點信息,且faces是三維列表# 遍歷所有的眼部關鍵點for id in idList:cv2.circle(img, tuple(face[id]), 4, (0,255,255), cv2.FILLED) # 以關鍵點為圓心半徑為5畫圓# 由于人臉距離攝像機的距離的遠近會影響眼部上下邊界之間的距離,因此通過開合比例來判斷是否眨眼leftUp = tuple(face[159]) # 左眼最上邊界的關鍵點坐標leftDown = tuple(face[23]) # 左眼最下邊界的關鍵點坐標leftLeft = tuple(face[130]) # 左眼最左邊界的關鍵點坐標leftRight = tuple(face[243]) # 左眼最右邊界的關鍵點坐標# 計算上下邊界以及左右邊界的距離,函數返回距離length,以及線段兩端點和中點坐標,這里用不到lengthver, __ = detector.findDistance(leftUp, leftDown) # 上下邊界距離lengthhor, __ = detector.findDistance(leftLeft, leftRight) # 左右邊界距離# 在上下及左右關鍵點之間各畫一條線段,端點坐標是元組cv2.line(img, leftUp, leftDown, colorblink, 2)cv2.line(img, leftLeft, leftRight, colorblink, 2)# 計算豎直距離與水平距離的比值ratio = 100*lengthver/lengthhorratioList.append(ratio) # 存放每幀圖像的人眼開合比#(5)平滑實時變化曲線# 每10幀圖像的人眼開合百分比取平均值,在圖像上更新一個值if len(ratioList) > 10:ratioList.pop(0) # 刪除最前面一個元素ratioAvg = sum(ratioList)/len(ratioList) # 超過10幀后,保證每10個元素取平均print(ratioAvg)#(6)眨眼計數器# 當開合比低于26%并且距前一次計算眨眼已超過10幀時,認為是眨眼if ratioAvg < 26 and counter == 0:blinkCounter += 1 # 眨眼次數加1colorblink = (0,255,0) # 眨眼時改變當前幀的顏色counter = 1 # 當前幀計算了一次眨眼# 保證在一次眨眼期間只在10幀中計算一次,不再多余的計算眨眼次數if counter != 0: # 代表前面某10幀已經計算過一次眨眼counter += 1 # 累計不計算眨眼次數的幀數+1if counter > 10: # 如果距前一次計算眨眼次數超過10幀了,那么下一次可以計算眨眼counter = 0colorblink = (255,0,255) # 不眨眼時計數器顏色回到原顏色# 以時間為x軸,百分比為y軸,繪制實時變化曲線imgPlot = plotY.update(ratioAvg, colorblink) # 參數:每個時刻的y值,線條顏色# 將計數顯示在圖上cvzone.putTextRect(img, f'BlinkCount:{blinkCounter}', # 顯示內容是字符串類型(850,80), 4, 3, colorR=colorblink) # 設置文本顯示位置、顏色、線條# 重塑圖像的寬和高,保證圖像size和曲線圖size一致img = cv2.resize(img, (640,360)) # 將變化曲線和原圖像組合起來,2代表排成兩列,最后一個1代表比例不變imgStack = cvzone.stackImages([img, imgPlot], 2, 1)#(7)顯示圖像cv2.imshow('img', imgStack) # 傳入窗口名和幀圖像# 每幀圖像滯留20毫秒后消失,按下鍵盤上的ESC鍵退出程序if cv2.waitKey(20) & 0xFF == 27:break# 釋放視頻資源
cap.release()
cv2.destroyAllWindows()
效果圖如下:眨眼時,曲線和計數器變成綠色
總結
以上是生活随笔為你收集整理的【机器视觉案例】(11) 眨眼计数器,人脸关键点检测,附python完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【seaborn】(1) 数据可视化,绘
- 下一篇: 【神经网络】(11) 轻量化网络Mobi