牟奕炫
MediaPipe 是一個以視覺算法為核心的機器學習工具庫,集成了包括人臉及關鍵點檢測、手勢和姿態的檢測與識別等多種模型的訓練數據,識別的運行速度非常快,尤其適用于實時監控和流媒體視頻畫面內容的檢測和識別。
以識別與檢測人的單只手掌為例,MediaPipe 中的訓練數據會識別出總共21 個手指關鍵點(標注為0-20),比如0 號點是WRIST( 腕關節),7 號點是INDEX_FINGER_DIP(食指下第一關節),16 號點是RING_FINGER_TIP(無名指指尖)(如圖1)。

1. 前期的準備工作
臺式機接好攝像頭或使用筆記本電腦;在CMD 窗口定位至Python3.8 的安裝路徑, 用“pip install mediapipe” 安裝MediaPipe。根據Python 環境安裝或升級matplotlib 等相關庫,注意確保提前安裝好OpenCV, 直至“Successfullyinstalled”的成功提示。
啟動PythonIDLE,新建文件開始程序代碼的編寫及運行測試。
2. 檢測單(雙)手并進行關鍵點的標注及連線
導入OpenCV 和MediaPipe 庫模塊; 定義變量camera, 并賦值為0,如果默認的攝像頭編號不是0則更改為1, 打開攝像頭; 建立變量hand_detector,用于創建檢測人手關鍵點的檢測對象,判斷并獲取視頻畫面中是否有符合“手”特征的信息,該參數保持為空時默認狀態是“model_complexity=0,min_detection_confidence=0.5,min_tracking_confidence=0.5”,分別表示動態畫面跟蹤速度增益、檢測及跟蹤的置信度。
在循環中, 通過語句“ret,img =camera.read()” 實現對攝像頭所拍攝畫面的數據讀??; 變量img_rgb, 用于將OpenCV 讀取的BGR 模式圖像轉換為常規的RGB 模式; 變量result, 通過檢測模型來提取變量img_rgb 所存儲的圖片信息(比如每幀畫面檢測出幾只手);每次在檢測到畫面中出現一只手時,就可以通過語句“print(result.multi_hand_landmarks)”來打印輸出對應的數據信息:一個內嵌有21 個字典數據(對應手的21個關鍵點)的列表,其中的每個字典均包含一組相對位置信息,形式為“landmark{x:0.8149930238723755,y:0.7734243273735046,z:-0.11069273203611374}”,其中的x、y 分別表示該點在視頻畫面中位置的百分比,z 表示該點距離攝像頭的遠近。在內嵌的“for handlms in result.multi_hand_landmarks:” 循環中, 語句“mp.solutions.drawing_utils.draw_landmarks(img,handlms,mp.solutions.hands.HAND_CONNECTIONS)”的作用是在img 圖片上進行“作畫”操作,其中的參數handlms 對應的是每個關鍵點(默認為紅色小圓點),參數mp.solutions.hands.HAND_CONNECTIONS 對應的是相鄰兩個關鍵點進行連線(默認為白色細線段)。
語句“cv2.imshow('Video',img)” 是Python 調用OpenCV 的常規操作,對應的功能是將攝像頭捕獲的畫面(包括使用MediaPipe 進行畫點和連線的內容)輸出顯示在電腦屏幕上;變量k 的值為“cv2.waitKey(1)”,等待鍵盤響應事件時間為1毫秒,如果檢測到用戶按下了q 鍵(“if k== ord('q'):”)則執行break 跳出“whileTrue:”循環;語句“camera.release()”和“cv2.destroyAllWindows()”的功能是分別對應釋放攝像頭資源和關閉Video 監控窗口。

程序保存為“[01]Get_Points_Draw_Lines.py”并運行:當手掌朝向攝像頭,在監控窗口中會即時標注有21 個關鍵點以及白色連接線;當手背朝向攝像頭或者同時伸出兩只手時,程序能否正常捕捉和標注;此時還會在PythonShell 窗口中不斷顯示有“Squeezed text(106 lines)”模塊信息,雙擊即可顯示其詳細數據內容,即對應21 個關鍵點的landmark 列表(如圖2)。
3. 檢測右手三個指尖關鍵點并標注為不同顏色的圓點
將“[01]Get_Points_Draw_Lines.py”復制粘貼并重命名為“[02]Draw_Three_Points.py”, 代碼段的開始庫文件的導入、變量camera和hand_detector 等初始化代碼,以及“while True:”循環體中的“cv2.waitKey(1)”等待鍵盤響應事件,還有最終攝像頭資源釋放、程序窗口關閉等不變?,F以檢測和標注右手三個指尖關鍵點為例,進行其他代碼的編寫:
語句“h,w,c = img.shape” 中的變量h 和w 對應攝像頭所捕獲畫面的高度與寬度(c 對應的是通道),這些數據是從img.shape 中獲取的;而在“while True:” 循環體嵌套的“forid,lm in enumerate(hand_lms):”中,通過語句“x,y = int(lm.x * w),int(lm.y * h)”來實現變量x、y 的動態賦值,也就是將視頻畫面中橫向與縱向的百分比值(lm.x 和lm.y)分別與寬度w、高度h 進行乘法運算,再通過int() 取整得出視頻畫面中某手指關鍵點的真實相對二維坐標值(x,y)。當攝像頭正常捕獲到監控畫面時(“if ret:”),對變量img 進行鏡像翻轉處理,賦值為“cv2.flip(img,1)”, 否則左手和右手的對應識別標志會有Right 和Left的“ 錯位”; 建立變量position, 賦值為“{'Left':{},'Right':{}}”, 用來保存兩只手各自的21個關鍵點坐標數據(此處可添加語句“print(position)”來測試); 在“for point in hands_data.multi_handedness:” 循環中,建立變量score, 賦值為“point.classification[0].score”,對應判斷是否為某關鍵點的置信度,如果該值超過80%(“if score >= 0.8:”),則為變量label 賦值“point.classification[0].label”,也就是從classification 分類信息中獲取其中的label值。
變量right_finger_4、right_finger_8 和right_finger_12 分別對應拇指、食指和中指的指尖位置共三個關鍵點,也就是MediaPipe 內手的訓練數據(21個關鍵點):4號THUMB_TIP、8號INDEX_FINGER_TIP 和12 號MIDDLE_FIGER_TIP; 為變量right_finger_4 賦值為“position['Right'].get(4,None)”,表示右手的4 號關鍵點( 拇指指尖),而“position['Right'].get(8,None)” 和“position['Right'].get(12,None)” 則分別表示右手8 號(食指指尖)和12 號(中指指尖)關鍵點。接著,在三個if 判斷語句(是否檢測到對應的三個指尖關鍵點)中,分別為三個關鍵點進行不同顏色的圓點區域填充操作——如果檢測到有右手拇指指尖出現,則調用OpenCV 中的circle 開始畫圓:“cv2.circle(img,(right_finger_4[0],right_finger_4[1]),10,(0,0,255),cv2.FILLED)”,注意其中的“(0,0,255)”顏色代碼是BGR模式,也就是表示純紅的顏色;同樣,為右手食指和中指的指尖分別繪制綠色和藍色圓點所對應的顏色代碼就是“(0,255,0)”和“(255,0,0)”;另外,其中的參數10 表示的是所繪制圓點的半徑為10 個像素大小。
測試時看是否能準確識別右手,掌心方向、掌背方向、側向(也包括各種手指彎曲狀態)時三個指尖的實時位置,并分別以紅、綠、藍顏色的圓點進行標注。
4. 左手食指指尖關鍵點的二維坐標值獲取及標注
再將“[02]Draw_Three_Points.py” 復制粘貼并重命名為“[03]Get_IndexFinger_TIP.py”,繼續進行代碼更新,以左手食指指尖關鍵點為例實現二維坐標值的獲取并繪制紅色圓點。
除了需要在開始部分導入時間庫(“import time”) 之外,“whileTrue:”循環體部分的代碼基本上保持不變, 用變量left_finger_8 來替代之前的right_finger_4、right_finger_8 和right_finger_12, 賦值為“position['Left'].get(8,None)”, 即左手食指指尖的關鍵點;如果檢測到該關鍵點出現(條件“ifleft_finger_8:” 為真), 則先執行語句“cv2.circle(img,(left_finger_8[0],left_finger_8[1] ) ,10 , ( 0 , 0 , 2 5 5 ) , c v 2 .FILLED)”, 在左手食指指尖上繪制半徑為10 個像素的紅色圓點; 再執行語句“print(left_finger_8[0],left_finger_8[1])”,作用是打印輸出該關鍵點的二維坐標值——左上角為坐標原點(0,0),右下角為(640,480),由于程序中在關鍵點坐標值計算時會進行小數位數精度取舍以及取整等運算,存在一定的誤差??紤]到截圖顯示左手食指指尖關鍵點二維坐標值,循環中加有延時語句“time.sleep(100)”,控制每次循環等待100毫秒。
程序保存之后按F5 運行測試:當右手出現在攝像頭前時,畫面中沒有對任何一個關鍵點進行標注;換為左手時,食指的指尖立刻會出現一個紅色圓點,同時會在程序界面不斷有一組數據輸出:在左上角區域時,顯示為“105 57”“103 58”等;在右上角區域時,顯示為“560 72”“56174”等;在左下角區域時,顯示為“206289”“202 290”等;在右下角區域時,顯示為“601 394”“598 383”等。
在Windows 中進行各種測試成功的基礎上,我們就可以到樹莓派中再結合開源硬件庫(像GPIOZero)進行許多創客項目的開發,比如使用左手的食指指尖來控制LED的亮度變化。
1. 前期的準備工作
實驗硬件包括樹莓派3B+主板一塊,古德微擴展板一塊,P5V04A SUNNY 定焦攝像頭(帶排線)一個,紅色LED燈一支。首先將樹莓派主板“CAMERA”卡槽接口黑色塑料鎖扣輕輕向上拉起,然后將攝像頭的“金手指”一面對準15根豎紋金屬接觸面,小心插入進去(銀色部分差不多都要插進白色塑料插孔);接著,扶住軟排數據線,將黑色塑料鎖扣小心向下壓緊、鎖好,使攝像頭與樹莓派主板緊密接觸;再將古德微擴展板安裝上去,注意要特別小心別夾斷攝像頭的軟排數據線,將其從側面縫隙中引出;最后,將紅色LED 燈按照“長腿正、短腿負”的原則,正確插入擴展板的5 號引腳處。
接下來,給樹莓派加電,啟動操作系統,通過Windows 的遠程桌面連接(IP:192.168.1.120)程序進入后,運行“LX終端”開始安裝MediaPipe,輸入命令:“sudo pip3 install mediapipe-rpi3”,其中的參數“-rpi3”表示安裝版本為樹莓派3,回車,直至最終出現“Successfullyinstalled”的成功提示即可。
2. 在Thonny 中進行Python 代碼編程
將“[03]Get_IndexFinger_TIP.py”復制粘貼并重命名為“[04]Control_LED_Bright.py”,開始代碼的修改——
首先, 要導入GPIOZero 中的PWMLED:“from gpiozero importPWMLED”,目的是以PWM 模式來控制LED 燈的亮度;接著,建立變量Red_LED 并賦值為“PWMLED(5)”,作用是初始化插入5 號引腳的紅色LED燈,并且通過語句“Red_LED.value = 0”將其默認狀態設置為“不發光”(亮度為0),其他的代碼基本不需要改變,在“whileTrue:”循環的“if left_finger_8:”識別左手食指指尖判斷中,除了在該關鍵點上繪制紅色圓點外,增加語句“Red_LED.value = left_finger_8[1]/500”,作用是為控制LED 亮度的變量Red_LED.value 重新賦值, 其中的“left_finger_8[1]”表示攝像頭捕獲畫面中左手食指指尖所處的豎向縱坐標值,由于其范圍為0-480,而GPIOZero庫中控制LED燈亮度的PWM 范圍是0-1,所以添加“除以500”的運算來匹配對應的數據區間(如圖3)。

3. 測試左手食指指尖操控LED燈的亮度
運行程序,左手出現在攝像頭的捕獲畫面中,識別并標注出左手食指的指尖關鍵點,紅色LED燈的亮度會隨之變化:當食指指尖處在屏幕靠上的區域時,LED燈的亮度比較低,因為此時該關鍵點的縱坐標值小;當食指指尖向屏幕下方滑動時,LED的亮度會越來越高,因為它的縱坐標值在逐漸變大(如圖4)。(源代碼可在“壹零社”公眾號下載)
