[手勢辨識應用] Google Mediapipe 手勢控制LED呼吸燈-Arduino首次接觸就上手

前言

MediaPipe是一款由Google於2019年開發並開源處理機器學習應用框架專案,提供了跨平台的相關應用,我們之前已經介紹了 Mediapipe 豐富的範例,並於 Raspberry pi Jetson Nano 單板電腦上執行Mediapipe的例子,有興趣的讀者歡迎看看與分享喔!

本篇應用的是 Mediapipe 的 Hand API,由下圖可看到手部各點的定義。

程式會在手掌上標記21個點,本篇是將大拇指和食指的標記抓出來,也就是第4點和第8點,並計算兩點的距離,進而控制「Arduino首次接觸就上手」套件的LED燈,使LED燈產生呼吸燈的效果。

撰寫/攝影 許鈺莨
前情提要
時間 30分鐘 材料表 Arduino首次接觸就上手教學套件 x 1

 


本次專案程式主要是來自 Murtaza 這位 Youtuber ,只要在他的網站上CVZONE中註冊就可以免費得到程式碼。建議大家可以觀看 Murtaza的手部追蹤 手勢控制 的影片。本範例的實際執行影片如下

本文分成以下步驟:
1. 電腦虛擬環境安裝。
2. 手勢控制程式套件安裝。
3. 「首次接觸就上手」的硬體接線。
4. 「首次接觸就上手」的程式燒錄。
5. 電腦端執行手勢控制程式。

第1步 電腦虛擬環境安裝

在執行手勢控制的程式前,需先在電腦中安裝Anaconda軟體並在其軟體中再安裝虛擬環境,安裝步驟請參考本文:AI人工智慧-神經運算】環境建置:安裝Anaconda、Tensorflow、Keras與openCV(Windows篇)

在此所建立的虛擬環境名為 AI_7697,您可以隨意命名。

第2步 手勢控制程式套件安裝

2-1安裝Mediapipe套件0.8.7.1版:

install mediapipe
pip install Mediapipe==0.8.7.1

p.s.筆者試過,若Mediapipe安裝最新版,會無法執行!

2-2 安裝pyserial套件

此套件是為了讓電腦透過 USB 序列埠與「Arduino首次接觸就上手」套件溝通。

install pyserial
pip install pyserial

第3步 「Arduino首次接觸就上手」的硬體接線

請將「首次接觸就上手」的LED燈,用Grove的連接線另外接到Arduino開發板的D3,因為D3腳位才支援PWM 控制 (預設的D4 無法 PWM)。當然也可以另外找一顆LED來接。

p.s. PWM腳位除可以接D3外,還有D5、D6、D9、D10、D11,但Arduino程式要改腳位。

第4步 「Arduino首次接觸就上手」的程式燒錄

4-1 下載程式碼

請由本連結中下載相關程式,解壓縮後在 Arduino_code 資料夾中找到 Arduino_LED.ino

4-2  燒錄程式

請用 Hangeekduino 壓縮檔中的Arduino IDE 1.8.5上傳Arduino_LED.ino程式。Hangeekduino 軟體請點我下下載,教學請參考:【Arduino首次接觸就上手】快速執行AI圖像辨識

第5步 電腦端執行手勢控制程式

下載程式後,在Python_code資料夾中找到 MediaPipeHandPose.py

執行前請先用 micro USB 傳輸線連接「首次接觸就上手」套件接上電腦,並確定 Arduino USB Com Port 編號,再輸入指令:

run python script
python MediaPipeHandPose.py --video 0 --com 3

執行成果如本文開頭的影片

重要程式段落說明

Python程式

#主要程式來源來自:https://www.youtube.com/c/MurtazasWorkshopRoboticsandAI/featured

import serial
import argparse
import cv2
import time
import numpy as np
import math
import mediapipe as mp

########## 手部追蹤偵測 #############
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,
self.detectionCon, 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)
# print(results.multi_hand_landmarks)

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):
# print(id, lm)
h, w, c = img.shape
cx, cy = int(lm.x * w), int(lm.y * h)
# print(id, cx, cy)
lmList.append( [id, cx, cy])
if draw:
cv2.circle(img, (cx, cy), 15, (255, 0, 255), cv2.FILLED)

return lmList

def main():

############## 各參數設定 ##################
pTime = 0
minPwm = 0
maxPwm = 255
briArd = 0
briBar = 400
briPer = 0

############## 指定WEBCAM和Arduino Serial Port編號的指令 ##################

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=True)
args = parser.parse_args()

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

args = parser.parse_args()


############## WEBCAM相關參數定義 ##################
wCam, hCam = 640, 480
cap = cv2.VideoCapture(args.video) # 攝影機編號預設為0,也可以輸入其他編號!
cap.set(3, wCam)
cap.set(4, hCam)
detector = handDetector(detectionCon=0.7)

try:
while True:
success, img = cap.read()
img = detector.findHands(img)
lmList = detector.findPosition(img, draw=False)
#print(lmList)


if len(lmList) != 0:
x1, y1 = lmList[4][1], lmList[4][2]
x2, y2 = lmList[8][1], lmList[8][2]
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2

cv2.circle(img, (x1, y1), 15, (255, 0, 255), cv2.FILLED)
cv2.circle(img, (x2, y2), 15, (255, 0, 255), cv2.FILLED)

#計算大拇指和食指的直線中點距離
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 255), 3)
cv2.circle(img, (cx, cy), 15, (255, 0, 255), cv2.FILLED)

#計算大拇指和食指的直線距離
length = math.hypot(x2 - x1, y2 - y1)
#print(length)

#將大拇指和食指的直線距離換算成0~255,Arduino PWM控制數值亦為0~255
brightness = np.interp(length, [50, 300], [minPwm, maxPwm])

briBar = np.interp(length, [50, 300], [400, 150])
briArd = np.around(brightness,2)


if length < 50:
cv2.circle(img, (cx, cy), 15, (0, 255, 0), cv2.FILLED)

#畫出直方圖
cv2.rectangle(img,(50,150),(85,400),(255, 0, 255),3)
cv2.rectangle(img,(50, int(briBar)),(85,400),(255, 0, 255),cv2.FILLED)
cv2.putText(img, f'brightness: {int(briArd)}', (15, 140), cv2.FONT_HERSHEY_COMPLEX, 1, (255, 0, 255), 3)

#送出數值給Arduino
ser.write(str(briArd).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)

#顯示畫面
cv2.imshow("HandDetector", img)


#按q停止程式
if cv2.waitKey(10) & 0xFF == ord('q'):
break

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

if __name__ == '__main__' :
main()
  • 12~50:手部偵測動作,偵測手勢並標記手部的節點,一次可以偵測到兩隻手。
  • 64~76:執行程式時所指定的Arduino Serial Com Port 編號,和攝影機編號。若沒指定攝影機編號,則預設為0。
  • 94~123 抓出大拇指和食指的節點位置後,算出兩指節點的距離,將距離轉換成數值0~255,再將數值傳送至Arduino,並用直方圖顯示。

 

Arduino程式

//Arduno D3腳位
int LEDPin = 3;
String number = "" ;
int i = 0 ;
long 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
analogWrite(LEDPin, (pwm_val));
delay(100);
}
  • 2:指定 LED 接在 LED D3 腳位,若要換其他PWM腳位,請在此行程式修改。
  • 20~38:接收從電腦傳來的數值,但由於 Arduino 會以 ASCII 來處理,所以要減去 48 才是正確數字。最後由於這時的”數字”其實還是字串型態,所以還需要將其轉成整數型態,才可以當作控制LED的PWM數值。

本篇到此結束,也歡迎參考阿吉老師的 Arduino 首次接觸就上手全系列教學影片喔!

 

 

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *