前言
我們針對
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)
還沒有留言,來當第一個。