Day08 追蹤手部位置及動作並顯示魔法特效


🟡 先看看程式的效果

今天的程式是使用 OpenCV 和 MediaPipe 進行即時手部偵測,並在偵測到手部張開時,顯示旋轉的魔法陣特效。

Today’s program uses OpenCV and MediaPipe for real-time hand detection, and when it detects an open hand, it displays a rotating magic circle effect.


🟡 學習目標

在畫面貼上圖片於指定位置並使它不停旋轉。


🟡 程式碼

請先下載 “追蹤手部位置及動作並顯示魔法特效.py” 及 “magic.png”
請按此下載

'''

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 numpy as np
import math

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

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

def draw_glowing_line(frame, start_point, end_point, color=(148, 97, 189), thickness=4):
    # 畫出一條看起來會發光的線條
    cv2.line(frame, start_point, end_point, color, thickness)
    cv2.line(frame, start_point, end_point, (255, 255, 255), thickness - 3)

# 載入需要顯示的圖片
magic_image = cv2.imread('magic.png', cv2.IMREAD_UNCHANGED)

# 檢查圖片是否讀取成功
if magic_image is None:
    print("Error: Unable to load image. Please check the file path and name.")
    exit()

# 調整圖片大小,例如將寬度和高度調整為 300x300
new_width = 300
new_height = 300
magic_image = cv2.resize(magic_image, (new_width, new_height), interpolation=cv2.INTER_AREA)

'''
# 載入需要顯示的圖片
magic2_image = cv2.imread('magic2.png', cv2.IMREAD_UNCHANGED)

# 檢查圖片是否讀取成功
if magic_image is None:
    print("Error: Unable to load image. Please check the file path and name.")
    exit()

# 調整圖片大小,例如將寬度和高度調整為 300x300
new_width = 300
new_height = 300
magic2_image = cv2.resize(magic2_image, (new_width, new_height), interpolation=cv2.INTER_AREA)
'''


def is_hand_open(hand_landmarks):
    # 判斷手掌是否張開,這裡簡單使用大拇指和食指的距離作為判斷依據
    thumb_tip = hand_landmarks.landmark[4]
    index_finger_tip = hand_landmarks.landmark[8]
    distance = math.sqrt((thumb_tip.x - index_finger_tip.x) ** 2 + (thumb_tip.y - index_finger_tip.y) ** 2)
    return distance > 0.1  # 這個閾值可以根據實際情況調整

def overlay_image_alpha(img, img_overlay, pos, alpha_mask):
    """Overlay `img_overlay` on top of `img` at the position specified by
    `pos` and blend using `alpha_mask`.
    """
    x, y = pos

    # Image ranges
    y1, y2 = max(0, y), min(img.shape[0], y + img_overlay.shape[0])
    x1, x2 = max(0, x), min(img.shape[1], x + img_overlay.shape[1])

    # Overlay ranges
    y1o, y2o = max(0, -y), min(img_overlay.shape[0], img.shape[0] - y)
    x1o, x2o = max(0, -x), min(img_overlay.shape[1], img.shape[1] - x)

    # Exit if nothing to do
    if y1 >= y2 or x1 >= x2 or y1o >= y2o or x1o >= x2o:
        return

    # Blend overlay within the determined ranges
    img_crop = img[y1:y2, x1:x2]
    img_overlay_crop = img_overlay[y1o:y2o, x1o:x2o]

    alpha = alpha_mask[y1o:y2o, x1o:x2o, np.newaxis]
    alpha_inv = 1.0 - alpha

    img_crop[:] = alpha * img_overlay_crop + alpha_inv * img_crop

angle = 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:
            #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 is_hand_open(hand_landmarks):
                # 計算手心的位置
                palm_x = int(hand_landmarks.landmark[9].x * frame.shape[1])
                palm_y = int(hand_landmarks.landmark[9].y * frame.shape[0])

                # 旋轉圖片
                angle += 5  # 每幀旋轉 5 度
                rotation_matrix = cv2.getRotationMatrix2D((magic_image.shape[1] / 2, magic_image.shape[0] / 2), angle, 1)
                rotated_image = cv2.warpAffine(magic_image, rotation_matrix, (magic_image.shape[1], magic_image.shape[0]))

                # 取得 alpha 通道作為透明度遮罩
                alpha_mask = rotated_image[:, :, 3] / 255.0
                rotated_image = rotated_image[:, :, :3]

                # 疊加圖片
                overlay_image_alpha(frame, rotated_image, (palm_x - rotated_image.shape[1] // 2, palm_y - rotated_image.shape[0] // 2), alpha_mask)


            # 定義需要畫線的關鍵點對
            landmark_pairs = [(0, 4), (0, 8), (0, 12), (0, 16), (0, 20), (4, 8), (4, 12), (4, 16), (4, 20)]

            # 畫出橙色的線條
            for start, end in landmark_pairs:
                start_point = (int(hand_landmarks.landmark[start].x * frame.shape[1]),
                               int(hand_landmarks.landmark[start].y * frame.shape[0]))
                end_point = (int(hand_landmarks.landmark[end].x * frame.shape[1]),
                             int(hand_landmarks.landmark[end].y * frame.shape[0]))
                draw_glowing_line(frame, start_point, end_point)

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

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

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

🟡 小小挑戰一下

大家可以嘗試進行一些修改或改良喔! 例如:
✌️轉用其他圖檔作特效。
✌️根據不同的手勢 (例如: 🤘👌✌️) 顯示不同的特效。

😁 明天見!

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