工程筆記本 / Python

Python

手指點球遊戲結合 Arduino - Google Mediapipe

CAVEDU 阿吉 - 雜工📅 2023-12-22👁 30

前言

我們針對 Google Mediapipe 已經發表過多篇教學文章,這套裝置端的機器學習工具目前已支援視覺(分類、偵測與分割)、文字與聲音(分類)。本文要使用手部 api 做一個簡易的點球遊戲,畫面上會隨機出現不同顏色的球,點擊三次之後球消失,並撥放對應音效。並且可透過 USB 傳輸線來控制 Arduino 的 LED 亮起。 目前CAVEUE已發表的 Mediapipe 專題包含:  

本文

本專題分成兩端:Python 與 Arduino,依序說明如下:

python程式碼

您需要先安裝 mediapipe 與 pypserial 函式庫,請在終端機中輸入以下指令來安裝: 注意:如果您沒有 Arduino,只要把以下程式碼中的 ser 相關都註解掉就可以執行了,例如:
ser = serial.Serial(COM_PORT, BAUD_RATES)
[pastacode lang="bash" manual="" message="pip install mediapipe" highlight="" provider="manual"/] 完整Python端程式碼如下,註解都在程式碼中,相當好理解。您可以參考 #164 開始的計算食指指尖位置與圓心之間距離 d,如果 d < r (圓半徑),代表碰到球了。 每次觸碰球就會修改球的透明度 ( alpha -= 0.33),並在碰觸到三次之後播放音效並發送指定給 Arduino ( ser.write(str(light).encode()) )
import serial
import argparse
import cv2
import time
import numpy as np
import math
import mediapipe as mp
import random
import pygame

############## 手部追蹤偵測 ###################
class handDetector():
    def __init__(self, mode=False, maxHands=2, detectionCon=0.5, trackCon=0.5):
        self.mode = mode
        self.maxHands = maxHands
        self.detectionCon = detectionCon
        self.trackCon = trackCon

        self.mpHands = mp.solutions.hands
        self.hands = self.mpHands.Hands(
            self.mode,
            self.maxHands,
            min_detection_confidence=self.detectionCon,
            min_tracking_confidence=self.trackCon
        )
        self.mpDraw = mp.solutions.drawing_utils

    def findHands(self, img, draw=True):
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        self.results = self.hands.process(imgRGB)

        if self.results.multi_hand_landmarks:
            for handLms in self.results.multi_hand_landmarks:
                if draw:
                    self.mpDraw.draw_landmarks(
                        img, handLms, self.mpHands.HAND_CONNECTIONS
                    )
        return img

    def findPosition(self, img, handNo=0, draw=True):
        lmList = []
        if self.results.multi_hand_landmarks:
            myHand = self.results.multi_hand_landmarks[handNo]
            for id, lm in enumerate(myHand.landmark):
                h, w, c = img.shape
                cx, cy = int(lm.x * w), int(lm.y * h)
                lmList.append([id, cx, cy])
                if draw:
                    cv2.circle(img, (cx, cy), 15, (255, 0, 255), cv2.FILLED)
        return lmList

def play_background_music():
    pygame.mixer.music.load("bgm.mp3")
    pygame.mixer.music.play(-1)

def play_good_sound():
    good_sound = pygame.mixer.Sound("good.mp3")
    good_sound.play()

def play_bad_sound():
    bad_sound = pygame.mixer.Sound("bad.mp3")
    bad_sound.play()

def main():
    pygame.init()

    pygame.mixer.music.set_volume(0.8)

    pTime = 0
    minPwm = 0
    maxPwm = 255
    briArd = 0
    briBar = 400
    briPer = 0

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--video', help='Video number',
                        required=False, type=int, default=0)
    parser.add_argument('--com', help='Number of UART prot.',
                        required=False)
    args = parser.parse_args()

    COM_PORT = 'COM' + str(args.com)
    BAUD_RATES = 9600
    ser = serial.Serial(COM_PORT, BAUD_RATES)

    height = 1080
    width = 1920
    wCam, hCam = height, width

    cap = cv2.VideoCapture(args.video)
    cap.set(3, wCam)
    cap.set(4, hCam)
    detector = handDetector(detectionCon=0.7)

    count = 0
    score = 0
    hp = 3

    circle_radius = 50
    circle_y, circle_x = 50, random.randint(circle_radius, int((width - circle_radius) * 0.6))
    circle_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    finger_radius = 20
    finger_touching = False
    alpha = 0.99

    play_background_music()

    try:
        while True:
            success, img = cap.read()
            img = cv2.flip(img, 1)

            img = detector.findHands(img)
            mask = np.zeros(img.shape, dtype=np.uint8)
            lmList = detector.findPosition(img, draw=False)

            light = 0

            circle_center = (circle_x, circle_y)
            cv2.circle(mask, circle_center, circle_radius, circle_color, -1)
            cv2.putText(mask, f'{count}', circle_center,
                        cv2.FONT_HERSHEY_COMPLEX, 3, (255, 255, 255), 3)

            circle_y += 3

            if circle_y >= int((height - circle_radius) * 0.7):
                circle_y, circle_x = 50, random.randint(circle_radius, int((width - circle_radius) * 0.6))
                circle_color = (random.randint(100, 200), random.randint(100, 200), random.randint(100, 200))
                count = 0
                alpha = 0.99
                hp -= 1
                if hp > 0:
                    play_bad_sound()

            cv2.putText(img, f'Score: {score}', (40, 100),
                        cv2.FONT_HERSHEY_COMPLEX, 1, (0, 153, 255), 3)
            cv2.putText(img, f'HP: {hp}', (40, 150),
                        cv2.FONT_HERSHEY_COMPLEX, 1, (0, 0, 255), 3)

            if len(lmList) != 0:
                x, y = lmList[8][1], lmList[8][2]
                finger_position = (x, y)
                cv2.circle(img, finger_position, finger_radius, 255, -1)

                distance = math.sqrt((finger_position[0] - circle_center[0]) ** 2 +
                                     (finger_position[1] - circle_center[1]) ** 2)

                if distance < circle_radius + finger_radius and not finger_touching:
                    alpha -= 0.33
                    count += 1
                    finger_touching = True

                elif distance >= (circle_radius + finger_radius) * 2 and finger_touching:
                    finger_touching = False

                if count == 3:
                    circle_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
                    circle_y, circle_x = 50, random.randint(circle_radius, int((width - circle_radius) * 0.6))
                    circle_center = (circle_x, circle_y)
                    count = 0
                    alpha = 0.99
                    score += 1
                    light = 1
                    play_good_sound()

                ser.write(str(light).encode())

            cTime = time.time()
            fps = 1 / (cTime - pTime)
            pTime = cTime
            cv2.putText(img, f'FPS: {int(fps)}', (40, 50),
                        cv2.FONT_HERSHEY_COMPLEX, 1, (255, 0, 0), 3)

            if hp <= 0:
                result = np.zeros(img.shape, dtype=np.uint8)
                cv2.putText(result, f'GAME OVER',
                            (150, int(height * 0.6 / 2)),
                            cv2.FONT_HERSHEY_COMPLEX, 5, (0, 0, 255), 3)
                cv2.putText(result, f'Score: {score}',
                            (540, int(height * 0.6 - 200)),
                            cv2.FONT_HERSHEY_COMPLEX, 1, (0, 153, 255), 3)
                pygame.mixer.music.stop()
            else:
                result = cv2.addWeighted(img, 1, mask, alpha, 0)

            cv2.imshow("HandDetector", result)

            if cv2.waitKey(10) & 0xFF == ord('q'):
                break

    except KeyboardInterrupt:
        ser.close()
        cap.release()
        cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

Arduino 端程式

Arduino 端程式就更簡單了,等候來自另一端的指令,進行 ASCII code 處理之後,用於控制 LED 亮暗。在此保留之後的彈性所以使用 analogWrite(LEDPin, 255); 語法,效果等同於 digitalWrite(LEDPin, HIGH);
// Arduno digital 腳位
int LEDPin = 7;
String number = "";
int i = 0;
int pwm_val; // 改為整數類型

void setup()
{
  // 各協定通訊初始化
  Serial.begin(9600);
  pinMode(LEDPin, OUTPUT);
}

void loop()
{
  // 執行 command 副函式
  command();
}

long command()
{
  while (Serial.available())
  {
    if (i == 0)
    {
      number = "";
    }

    // 扣除 ASCII 碼值
    number += Serial.read() - 48;
    i++;
  }

  // 字串轉換成整數值
  pwm_val = number.toInt();
  i = 0;

  Serial.println(pwm_val);

  // PWM 控制 LED
  if (pwm_val == 1)
  {
    analogWrite(LEDPin, 255);
    delay(1000);
    analogWrite(LEDPin, 0);
  }
}

執行

執行以下指令即可看到畫面,伸出您的手指來點點球吧!可在畫面左上角看到 FPS、Score 與 HP 三個遊戲參數。   執行下列程式 python mp_finger.py   如果要搭配 Arduino,則請先燒錄好 Arduino sketch 之後並修改以下指令的 COM 號: python mp_finger.py --com 3

相關文章 📎

留言 💬 (0)

還沒有留言,來當第一個。