🔥 新作首發 🎮 天堂私服 YOLOv8 物件偵測實戰 — 從資料蒐集、模型訓練到即時偵測 立即閱讀 →
熱門系列
Like Share Discussion Bookmark Smile

J.J. Huang   2026-04-17   Python OpenCV 08.專案實作篇   瀏覽次數:次   DMCA.com Protection Status

Python | OpenCV 專案:MediaPipe 手勢控制應用

📚 前言

在上一篇 小型圖片分類專案 中,我們完成了端對端的圖片分類系統。

這一篇介紹 MediaPipe 手勢控制應用。MediaPipe 是 Google 開發的跨平台機器學習管線框架,其中的 Hands 模組可以即時偵測手部的 21 個關鍵點,不需要 GPU,精度遠高於傳統輪廓法。

🎯 專案目標

  • 使用 MediaPipe Hands 偵測手部關鍵點
  • 實作手指計數(0 ~ 5)
  • 自訂手勢辨識(OK、Rock、拳頭等)
  • 整合互動應用:手勢控制螢幕畫筆

🛠️ 套件安裝

1
pip install mediapipe

🔎 MediaPipe Hands 關鍵點說明

MediaPipe Hands 會回傳手部 21 個關鍵點(Landmark),每個點有 xy(相對比例)與 z(深度):

1
2
3
4
5
6
7
8
9
    8   12  16  20
| | | |
7 11 15 19
4 6 10 14 18
| 5 9 13 17
3 |
2 0(手腕)
|
1
關鍵點 位置
0 手腕
1~4 大拇指(4 = 指尖)
5~8 食指(8 = 指尖)
9~12 中指(12 = 指尖)
13~16 無名指(16 = 指尖)
17~20 小指(20 = 指尖)

💻 基本手部偵測

使用 MediaPipe Hands 偵測手部 21 個關鍵點,即時繪製骨架連線並顯示於攝影機畫面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# detect_hands.py
import cv2
import mediapipe as mp

mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils

hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.5,
)

cap = cv2.VideoCapture(0)
print("MediaPipe 手部偵測,按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
break

frame = cv2.flip(frame, 1) # 水平翻轉,讓畫面如鏡子
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
result = hands.process(rgb)

if result.multi_hand_landmarks:
for hand_lm in result.multi_hand_landmarks:
# 繪製 21 個關鍵點與連線
mp_drawing.draw_landmarks(
frame, hand_lm, mp_hands.HAND_CONNECTIONS
)

cv2.imshow("Hands", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break

cap.release()
cv2.destroyAllWindows()

💻 手指計數

比較各手指指尖與第二關節的座標判斷每根手指是否伸直,回傳伸出的手指數量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# demo_count_fingers.py
def count_fingers(hand_lm, handedness):
"""
回傳伸出的手指數量(0~5)
handedness: "Left" 或 "Right"(MediaPipe 回傳的是鏡像後的手)
"""
lm = hand_lm.landmark

# 指尖與第二關節的 ID
tip_ids = [4, 8, 12, 16, 20]
pip_ids = [3, 6, 10, 14, 18]

fingers = 0

# 大拇指:比較 x 軸(左右手方向不同)
if handedness == "Right":
if lm[tip_ids[0]].x < lm[pip_ids[0]].x:
fingers += 1
else:
if lm[tip_ids[0]].x > lm[pip_ids[0]].x:
fingers += 1

# 其他四指:比較 y 軸(指尖 y < 第二關節 y 表示伸直)
for i in range(1, 5):
if lm[tip_ids[i]].y < lm[pip_ids[i]].y:
fingers += 1

return fingers

💻 手勢辨識

根據關鍵點幾何關係辨識拳頭、讚、剪刀、OK、五指張開等特定手勢並回傳對應名稱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# demo_recognize_gesture.py
def recognize_gesture(hand_lm):
"""辨識特定手勢,回傳手勢名稱"""
lm = hand_lm.landmark

tip_ids = [4, 8, 12, 16, 20]
pip_ids = [3, 6, 10, 14, 18]

# 各手指是否伸直
fingers = [lm[tip_ids[i]].y < lm[pip_ids[i]].y for i in range(1, 5)]
# 大拇指(簡化:只看是否向側邊伸出)
thumb_open = abs(lm[4].x - lm[3].x) > 0.04

# 拳頭:所有手指收起
if not any(fingers) and not thumb_open:
return "✊ 拳頭"

# 讚:只有大拇指伸出,其他收起
if thumb_open and not any(fingers):
return "👍 讚"

# 剪刀:食指與中指伸出
if fingers[0] and fingers[1] and not fingers[2] and not fingers[3]:
return "✌️ 剪刀"

# OK:食指與大拇指靠近(其他手指伸出)
dist = ((lm[4].x - lm[8].x)**2 + (lm[4].y - lm[8].y)**2) ** 0.5
if dist < 0.06 and fingers[1] and fingers[2] and fingers[3]:
return "👌 OK"

# 五指全開
if all(fingers) and thumb_open:
return "🖐️ 五指張開"

return ""

💻 完整互動應用:手勢畫筆

伸出食指時在畫布上繪製軌跡,握拳時清除畫布,將手勢繪圖疊加在攝影機畫面上顯示

用手勢控制螢幕畫筆,食指繪圖、拳頭清除畫布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# gesture_painter.py
import cv2
import mediapipe as mp
import numpy as np

mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils

hands = mp_hands.Hands(max_num_hands=1,
min_detection_confidence=0.7,
min_tracking_confidence=0.5)
cap = cv2.VideoCapture(0)
canvas = None
prev_x, prev_y = None, None
color = (0, 255, 0)

print("食指繪圖,拳頭清除,按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
break

frame = cv2.flip(frame, 1)
h, w = frame.shape[:2]

if canvas is None:
canvas = np.zeros_like(frame)

rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
result = hands.process(rgb)

if result.multi_hand_landmarks:
hand_lm = result.multi_hand_landmarks[0]
lm = hand_lm.landmark

# 食指指尖位置
ix = int(lm[8].x * w)
iy = int(lm[8].y * h)

# 手指計數判斷手勢
tip_ids = [8, 12, 16, 20]
pip_ids = [6, 10, 14, 18]
finger_count = sum(
lm[tip_ids[i]].y < lm[pip_ids[i]].y for i in range(4)
)

if finger_count == 1:
# 只伸出食指:繪圖模式
if prev_x is not None:
cv2.line(canvas, (prev_x, prev_y), (ix, iy), color, 5)
prev_x, prev_y = ix, iy
elif finger_count == 0:
# 拳頭:清除畫布
canvas = np.zeros_like(frame)
prev_x, prev_y = None, None
else:
prev_x, prev_y = None, None

mp_drawing.draw_landmarks(frame, hand_lm, mp_hands.HAND_CONNECTIONS)

# 疊加畫布
output = cv2.addWeighted(frame, 0.7, canvas, 0.3, 0)
cv2.putText(output, "食指:畫 拳頭:清除 q:離開", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)

cv2.imshow("Gesture Painter", output)
if cv2.waitKey(1) & 0xFF == ord("q"):
break

cap.release()
cv2.destroyAllWindows()

⚠️ 注意事項

  • cv2.flip(frame, 1) 水平翻轉很重要:不翻轉的話,MediaPipe 回傳的 Left/Right 與畫面方向相反,大拇指方向判斷會錯誤。
  • MediaPipe 的 handedness 是鏡像判斷:對著攝影機時,MediaPipe 判斷的「右手」實際上是你的左手(因為是鏡像)。翻轉畫面後就正確了。
  • z 值可用於判斷手指深度:若需要更精細的手勢(例如區分手掌朝前/朝後),可利用 z 值輔助判斷。
  • 在黑暗環境效果較差:MediaPipe Hands 依賴 RGB 畫面,光線不足時偵測率會下降。

🎯 結語

MediaPipe Hands 提供了高精度、低延遲的手部關鍵點偵測,讓複雜的手勢辨識只需要幾何計算就能完成,不需要另外訓練模型。
下一個專案是 天堂私服 YOLOv8 物件偵測實戰,把 YOLOv8 訓練技術應用在遊戲場景中。

📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀,掌握完整的章節目錄,方便快速找到你需要的內容。

註:以上參考了
MediaPipe Hands 官方文件
MediaPipe Python Solutions