【CAVEDU講堂】 Google Mediapipe 手勢控制LED呼吸燈

作者/圖片來源:CAVEDU 教育團隊

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

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

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

本次專案程式主要是來自 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版:


1
pip install Mediapipe==0.8.7.1

install mediapipe

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

2-2 安裝pyserial套件

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


1
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 編號,再輸入指令:


1
python MediaPipeHandPose.py --video 2 --com 3

run python script

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

重要程式段落說明

Python程式


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#主要程式來源來自: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程式


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//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同意轉載,原文連結;責任編輯:謝涵如)

CAVEDU 教育團隊
CAVEDU 教育團隊

Author: CAVEDU 教育團隊

CAVEDU 教育團隊是由一群對教育充滿熱情的大孩子所組成的機器人科學教育團隊。致力推動國內機器人教育。

Share This Post On

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。