基于 mediapipe 做實(shí)時(shí)手部追蹤
隨著越來越多的資源和框架針對(duì)各種任務(wù)進(jìn)行定制,開始計(jì)算機(jī)視覺應(yīng)用從未如此簡單。其中一個(gè)任務(wù)就是手部追蹤,它在虛擬現(xiàn)實(shí)、手語翻譯以及許多其他人機(jī)交互相關(guān)應(yīng)用中有著廣泛的用途。在本文中,我將向你展示如何使用Python和網(wǎng)絡(luò)攝像頭輕松開始手部追蹤算法,所有操作都在你的計(jì)算機(jī)本地運(yùn)行。我們將直接使用mediapipe手部追蹤解決方案,并了解其基本工作原理。
項(xiàng)目設(shè)置
首先創(chuàng)建一個(gè)空的項(xiàng)目目錄。我強(qiáng)烈建議你使用虛擬環(huán)境管理器,例如Miniconda,以分離不同的Python項(xiàng)目。我喜歡通過在項(xiàng)目目錄中創(chuàng)建本地環(huán)境來設(shè)置我的環(huán)境,以避免弄亂我的全局Conda環(huán)境。
conda create -p ./env python=3.12
conda activate ./env
安裝Mediapipe
接下來我們需要安裝mediapipe pip包。這非常簡單,它會(huì)自動(dòng)安裝所有所需的依賴項(xiàng)。
pip install mediapipe
代碼
現(xiàn)在我們可以開始創(chuàng)建一個(gè)main.py文件。首先導(dǎo)入所需的包,我們將使用opencv-python進(jìn)行網(wǎng)絡(luò)攝像頭視頻幀捕獲,以及mediapipe手部解決方案及其繪圖工具。
import cv2
import mediapipe.python.solutions.hands as mp_hands
import mediapipe.python.solutions.drawing_utils as mp_drawing
import mediapipe.python.solutions.drawing_styles as mp_drawing_styles
接下來我們需要設(shè)置網(wǎng)絡(luò)攝像頭的VideoCapture。通過指定索引0,我們獲取第一個(gè)可用的網(wǎng)絡(luò)攝像頭。
cap = cv2.VideoCapture(index=0)
現(xiàn)在我們需要?jiǎng)?chuàng)建一個(gè)手部追蹤對(duì)象。通過使用with語句,我們?cè)趲东@循環(huán)周圍創(chuàng)建一個(gè)上下文,以便可以使用hands對(duì)象。這確保在with語句的上下文結(jié)束時(shí),所有與追蹤相關(guān)的資源都能正確清理。
with mp_hands.Hands(
model_complexity=0,
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
) as hands:
# TODO: video Frame loop
這里我們有幾個(gè)配置選項(xiàng)。model_complexity定義是使用簡單模型(0)還是更復(fù)雜的模型(1)。復(fù)雜模型在檢測中通常具有更高的準(zhǔn)確性,但會(huì)犧牲延遲。max_num_hands指定將檢測到的手部數(shù)量的上限,例如,如果我們只想識(shí)別一只手,可以將其降低到1。min_detection_confidence和min_tracking_confidence分別指模型在首次檢測手部時(shí)以及在保持追蹤時(shí)的置信度。
為了創(chuàng)建視頻幀循環(huán),我們使用一個(gè)while循環(huán),只要視頻捕獲打開,它就會(huì)運(yùn)行。我們從視頻捕獲中讀取最新的幀,如果成功,我們將其顯示出來。請(qǐng)注意,我們水平翻轉(zhuǎn)圖像以獲得類似鏡子/自拍的效果。我們還會(huì)檢查每一幀是否按下了鍵q,然后退出循環(huán)。最后在循環(huán)結(jié)束后,我們通過釋放視頻捕獲資源來清理它。
with mp_hands.Hands(
model_complexity=0,
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
) as hands:
while cap.isOpened():
success, frame = cap.read()
if not success:
print("Ignoring empty camera frame...")
continue
# TODO: check frame for hands
# TODO: draw detected hand landmarks on frame
cv2.imshow("Hand Tracking", cv2.flip(frame, 1))
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
現(xiàn)在唯一剩下要做的就是實(shí)現(xiàn)手部關(guān)鍵點(diǎn)檢測及其可視化。唯一需要考慮的是,OpenCV以BGR(藍(lán)、綠、紅顏色通道順序)格式加載圖像,而模型是在RGB上訓(xùn)練的,因此為了獲得最佳結(jié)果,我們也應(yīng)該將幀轉(zhuǎn)換為RGB。
注意:你也可以嘗試在不轉(zhuǎn)換為RGB的情況下運(yùn)行檢測。在我的情況下,它仍然有效,但檢測的準(zhǔn)確性要低得多。
# Check the frame for hands
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(frame_rgb)
為了檢查這是否有效,我們可以在檢測到手部時(shí)添加一個(gè)打印語句:
if results.multi_hand_landmarks is None:
print("No hands detected")
else:
print(f"Number of hands detected: {len(results.multi_hand_landmarks)}")
最后,我們可以使用mediapipe提供的繪圖工具在幀上注釋,以可視化幀中的手部。通過指定HAND_CONNECTIONS常量,手部關(guān)鍵點(diǎn)之間的連接以及關(guān)鍵點(diǎn)本身將被繪制出來。此外,指定的默認(rèn)繪圖規(guī)范允許對(duì)不同手指進(jìn)行明顯的著色。
# Draw the hand annotations on the image
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(
image=frame,
landmark_list=hand_landmarks,
connections=mp_hands.HAND_CONNECTIONS,
landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style(),
connection_drawing_spec=mp_drawing_styles.get_default_hand_connections_style(),
)
mediapipe 工作原理
手部追蹤的架構(gòu)主要由兩個(gè)階段組成,一個(gè)是手掌檢測,粗略地檢測手部的位置,然后是手部關(guān)鍵點(diǎn)檢測,更精確地定位手部和手指的不同部分。
第一階段的手掌檢測模型基于SSD。因此,該模型將完整圖像的像素值作為輸入,并輸出描述圖像中手掌可能位置的邊界框以及每個(gè)框的置信度分?jǐn)?shù)。這里使用了一些技巧,例如將錨框(在網(wǎng)絡(luò)中創(chuàng)建的分類前的提議)限制為正方形圖像。
第二部分,手部關(guān)鍵點(diǎn)模型,是一個(gè)回歸模型,它將手掌檢測的邊界框中的裁剪圖像作為輸入,并返回手部所有21個(gè)關(guān)鍵點(diǎn)的3D坐標(biāo)。
參考資料:
- mediapipe解決方案:https://mediapipe.readthedocs.io/en/latest/solutions/hands.html
- SSD論文:https://arxiv.org/abs/1512.02325
- 完整代碼:https://github.com/trflorian/hand-tracker