Day05 手勢辨識 (過濾不雅手勢) Hand Posture Recognition


🟡 先看看程式的效果

今天的程式是利用 OpenCV 和 MediaPipe 進行即時的手部偵測, 並在偵測到使用者舉起中指時, 於畫面中對應的區域添加馬克賽效果。

Today’s program uses OpenCV and MediaPipe for real-time hand detection. When it detects the user raising their middle finger, it adds a mosaic effect to the corresponding area on the screen.


🟡 學習目標

了解如何在特定的節點上顯示馬克賽之類的東西。


🟡 程式碼

請先下載 “Hand Posture Recognition 手勢辨識 (過濾不雅手勢).py”
請按此下載

'''

Python + AI in 21 days
https://jasonworkshop.com/python

Designed by Jason Workshop

[請勿修改以上內容]

---

預備工作:

首先,請確保已安裝 opencv-python, mediapipe 模組
如不確定可直接在 Windows 的 cmd prompt 執行以下指定
pip install opencv-python mediapipe

'''

import cv2
import mediapipe as mp
import math

# 初始化 MediaPipe Hands 模組
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(static_image_mode=False, max_num_hands=1, min_detection_confidence=0.7)
# static_image_mode:是否靜態模式,若為 True 則每張圖像都會獨立處理
# max_num_hands:最多手部偵測數量
# min_detection_confidence:手部檢測的最低可信度
mp_drawing = mp.solutions.drawing_utils # 繪製工具

# 儲存手部關鍵點的 x 和 y 座標
x = [0 for _ in range(20)]
y = [0 for _ in range(20)]


# 對指定區域進行馬克賽處理
def mosaic(img, scale=30):
    h, w, _ = img.shape
    new = cv2.resize(img, (h // scale, w // scale)) # 縮小圖像
    return cv2.resize(new, (h, w), interpolation=cv2.INTER_NEAREST) # 重新放大圖像


# 計算兩個關鍵點之間的距離
def dist(a, b):
    return math.sqrt((x[a] - x[b]) ** 2 + (y[a] - y[b]) ** 2)


# 檢測手指的狀態並返回二進制表示
def detect(hand_landmarks):
    # 更新手部關鍵點的 x 和 y 座標
    for i in range(0, 20):
        x[i] = hand_landmarks.landmark[i].x
        y[i] = hand_landmarks.landmark[i].y

    # 初始化手指狀態計數
    finger1 = finger2 = finger3 = finger4 = finger5 = 0

    # 檢測各手指是否伸出
    if abs(dist(0, 1) + dist(1, 2) + dist(2, 3) + dist(3, 4) - dist(0, 4)) < 0.025:
        finger1 = 1 # 大拇指

    if abs(dist(5, 6) + dist(6, 7) + dist(7, 8) - dist(5, 8)) < 0.02:
        finger2 = 2 # 食指

    if abs(dist(0, 9) + dist(9, 10) + dist(10, 11) + dist(11, 12) - dist(0, 12)) < 0.02:
        finger3 = 4 # 中指

    if abs(dist(0, 13) + dist(13, 14) + dist(14, 15) + dist(15, 16) - dist(0, 16)) < 0.02:
        finger4 = 8 # 無名指

    if abs(dist(0, 17) + dist(17, 18) + dist(18, 19) - dist(0, 19)) < 0.02:
        finger5 = 16# 小指

    # 在畫面上顯示手指狀態
    cv2.putText(frame, f"{finger1} {finger2} {finger3} {finger4} {finger5}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255))
    cv2.putText(frame, str(finger1 + finger2 + finger3 + finger4 + finger5), (20, 80), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255))

    # 返回手指狀態的二進制表示
    return format(finger1 + finger2 + finger3 + finger4 + finger5, "05b")


# 開啟攝影機
cap = cv2.VideoCapture(0)

while cap.isOpened():
    ret, frame = cap.read() # 讀取攝影機畫面
    if not ret:
        print("無法讀取攝影機畫面")
        break

    # 轉換 BGR 圖像到 RGB
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # 偵測手部
    result = hands.process(rgb_frame)

    # 繪製手部關鍵點和連線
    if result.multi_hand_landmarks:
        for hand_landmarks in result.multi_hand_landmarks:
            finger = detect(hand_landmarks) # 將手指狀態以二進制方式表示
            mp_drawing.draw_landmarks(
                frame,
                hand_landmarks,
                mp_hands.HAND_CONNECTIONS,
                landmark_drawing_spec=mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),  # 關鍵點樣式
                connection_drawing_spec=mp_drawing.DrawingSpec(color=(255, 255, 255), thickness=1, circle_radius=2)  # 連線樣式
            )

            # 如果中指舉起,則添加馬克賽效果
            if finger == "00100":
                img_mosaic = frame.copy() # 複製當前畫面
                finger_tip = hand_landmarks.landmark[10] # 獲取中指尖的關鍵點
                # 計算馬克賽區域的邊界
                sx, ex = max(0, int(finger_tip.x * frame.shape[1] - 100)), min(frame.shape[1], int(finger_tip.x * frame.shape[1] + 100))
                sy, ey = max(0, int(finger_tip.y * frame.shape[0] - 100)), min(frame.shape[0], int(finger_tip.y * frame.shape[0] + 100))
                # 添加馬克賽效果
                img_mosaic[sy:ey, sx:ex] = mosaic(img_mosaic[sy:ey, sx:ex])
                frame = img_mosaic # 更新畫面

    # 顯示圖像
    cv2.imshow('Hand Landmarks Detection', frame)

    # 按下 ESC 鍵退出
    if cv2.waitKey(1) & 0xFF == 27: 
        break

# 釋放攝影機並關閉所有視窗
cap.release()
cv2.destroyAllWindows()

🟡 小小挑戰一下

大家可以嘗試進行一些修改或改良喔! 例如:
✌️大家知道這次的馬克賽是用了甚麼簡單而粗暴的方式去運算出來? 這樣的話能否逆轉? 還有沒有其他產生馬克賽效果的方法?

😁 明天見!

按這裏回到 Python + AI 的 21 天挑戰