作者/圖片來源:CAVEDU 教育團隊
MediaPipe是一款由Google於2019年開發並開源處理機器學習應用框架專案,提供了跨平台的相關應用,我們之前已經介紹了 Mediapipe 豐富的範例,並於 Raspberry pi 和 Jetson Nano 單板電腦上執行Mediapipe的例子,有興趣的讀者歡迎看看與分享喔!
本篇應用的是 Mediapipe 的 Hand API,由下圖可看到手部各點的定義。
程式會在手掌上標記21個點,本篇是將大拇指和食指的標記抓出來,也就是第4點和第8點,並計算兩點的距離,進而控制「Arduino首次接觸就上手」套件的LED燈,使LED燈產生呼吸燈的效果。
本次專案程式主要是來自 Murtaza 這位 Youtuber,只要在他的網站上CVZONE中註冊就可以免費得到程式碼。建議大家可以觀看 Murtaza的手部追蹤和手勢控制的影片。本範例的實際執行影片如下
本文分成以下步驟:
- 電腦虛擬環境安裝。
- 手勢控制程式套件安裝。
- 「首次接觸就上手」的硬體接線。
- 「首次接觸就上手」的程式燒錄。
- 電腦端執行手勢控制程式。
第1步 電腦虛擬環境安裝
在執行手勢控制的程式前,需先在電腦中安裝Anaconda軟體並在其軟體中再安裝虛擬環境,安裝步驟請參考本文:【AI人工智慧-神經運算】環境建置:安裝Anaconda、Tensorflow、Keras與openCV(Windows篇)
在此所建立的虛擬環境名為 AI_7697,您可以隨意命名。
第2步 手勢控制程式套件安裝
2-1安裝Mediapipe套件0.8.7.1版:
pip install Mediapipe==0.8.7.1
install mediapipe
p.s.筆者試過,若Mediapipe安裝最新版,會無法執行!
2-2 安裝pyserial套件
此套件是為了讓電腦透過 USB 序列埠與「Arduino首次接觸就上手」套件溝通。
pip install pyserial
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 編號,再輸入指令:
python MediaPipeHandPose.py --video 2 --com 3
run python script
執行成果如本文開頭的影片
重要程式段落說明
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 首次接觸就上手全系列教學影片喔!
- 【CAVEDU講堂】NVIDIA Jetson AI Lab 大解密!範例與系統需求介紹 - 2024/10/08
- 【CAVEDU講堂】Google DeepMind使用大語言模型LLM提示詞來產生你的機器人操作程式碼 - 2024/07/30
- 【CAVEDU講堂】《Arduino首次接觸就上手》新手村教學:LED燈閃爍 - 2024/04/18
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!