MediaPipe 手勢辨識工作可讓您即時辨識手勢、提供已辨識的手勢結果,以及偵測到的手部的手勢。以下操作說明將說明如何在 iOS 應用程式中使用手勢辨識工具。
如要查看這項工作的實際運作情形,請參閱網路示範。如要進一步瞭解這項工作的功能、模型和設定選項,請參閱總覽。
程式碼範例
MediaPipe Tasks 範例程式碼是 iOS 版手勢辨識應用程式的基本實作方式。這個範例使用實體 iOS 裝置上的相機持續偵測手勢,也能使用裝置圖片庫中的圖片和影片,以靜態方式偵測手勢。
您可以將該應用程式做為開發 iOS 應用程式的起點,也可以在修改現有應用程式時參照。手勢辨識程式範例程式碼由 GitHub 代管。
下載程式碼
以下操作說明說明如何使用 git 指令列工具建立範例程式碼的本機副本。
如要下載範例程式碼,請按照下列步驟操作:
使用下列指令複製 Git 存放區:
git clone https://github.com/google-ai-edge/mediapipe-samples
您可以選擇設定 git 執行個體以使用稀疏檢查,如此一來,您只有手勢辨識工具範例應用程式的檔案:
cd mediapipe git sparse-checkout init --cone git sparse-checkout set examples/gesture_recognizer/ios/
建立範例程式碼的本機版本後,可以安裝 MediaPipe 工作程式庫,使用 Xcode 開啟專案並執行應用程式。如需操作說明,請參閱 iOS 設定指南。
重要元件
下列檔案包含手勢辨識工具範例應用程式的重要程式碼:
- GestureRecognizerService.swift:初始化手勢辨識工具、處理所選模型,並針對輸入資料執行推論。
- CameraViewController.swift:實作攝影機即時影像輸入模式的使用者介面,並以視覺化方式呈現結果。
- MediaLibraryViewController.swift:實作靜態圖片和影片檔案輸入模式的使用者介面,並以視覺化方式呈現結果。
設定
本節說明如何設定開發環境,以及使用手勢辨識工具的程式碼專案。如需瞭解如何設定開發環境以使用 MediaPipe 工作 (包括平台版本需求),請參閱「iOS 設定指南」。
依附元件
手勢辨識工具使用 MediaPipeTasksVision
程式庫,你必須使用 CocoaPods 安裝這個程式庫。這個程式庫與 Swift 和 Objective-C 應用程式相容,不需要任何其他語言的特定設定。
如需在 macOS 上安裝 CocoaPods 的操作說明,請參閱 CocoaPods 安裝指南。
如要瞭解如何建立具有應用程式必要 Pod 的 Podfile
,請參閱「使用 CocoaPods」。
使用下列程式碼,在 Podfile
中新增 MediaPipeTasksVision Pod:
target 'MyGestureRecognizerApp' do
use_frameworks!
pod 'MediaPipeTasksVision'
end
如果您的應用程式含有單元測試目標,請參閱 iOS 設定指南,進一步瞭解如何設定 Podfile
。
型號
MediaPipe 手勢辨識工作需要使用與這項工作相容的已訓練模型。如要進一步瞭解手勢辨識工具可用的已訓練模型,請參閱工作總覽一節。
選取並下載模型,然後使用 Xcode 將其新增至專案目錄。 如需將檔案新增至 Xcode 專案的操作說明,請參閱「管理 Xcode 專案中的檔案和資料夾」。
使用 BaseOptions.modelAssetPath
屬性指定應用程式套件中的模型路徑。如需程式碼範例,請參閱下一節。
建立工作
如要建立手勢辨識工作,請呼叫其中一個初始化器。GestureRecognizer(options:)
初始化器會接受設定選項的值。
如果不需要使用自訂設定選項初始化的手勢辨識工具,可以使用 GestureRecognizer(modelPath:)
初始化工具,透過預設選項建立手勢辨識工具。如要進一步瞭解設定選項,請參閱「設定總覽」。
手勢辨識工作支援 3 種輸入資料類型:靜態圖片、影片檔案和即時影片串流。根據預設,GestureRecognizer(modelPath:)
會初始化靜態圖片的工作。如果想將工作初始化,以便處理影片檔案或直播影片串流,請使用 GestureRecognizer(options:)
指定影片或直播執行模式。直播模式也需要額外的 gestureRecognizerLiveStreamDelegate
設定選項,讓手勢辨識工具能夠以非同步方式將手勢辨識結果提供給委派代表。
請選擇與執行模式對應的分頁,瞭解如何建立工作並執行推論。
Swift
圖片
import MediaPipeTasksVision let modelPath = Bundle.main.path(forResource: "gesture_recognizer", ofType: "task") let options = GestureRecognizerOptions() options.baseOptions.modelAssetPath = modelPath options.runningMode = .image options.minHandDetectionConfidence = minHandDetectionConfidence options.minHandPresenceConfidence = minHandPresenceConfidence options.minTrackingConfidence = minHandTrackingConfidence options.numHands = numHands let gestureRecognizer = try GestureRecognizer(options: options)
影片
import MediaPipeTasksVision let modelPath = Bundle.main.path(forResource: "gesture_recognizer", ofType: "task") let options = GestureRecognizerOptions() options.baseOptions.modelAssetPath = modelPath options.runningMode = .video options.minHandDetectionConfidence = minHandDetectionConfidence options.minHandPresenceConfidence = minHandPresenceConfidence options.minTrackingConfidence = minHandTrackingConfidence options.numHands = numHands let gestureRecognizer = try GestureRecognizer(options: options)
直播
import MediaPipeTasksVision // Class that conforms to the `GestureRecognizerLiveStreamDelegate` protocol and // implements the method that the gesture recognizer calls once it finishes // performing recognizing hand gestures in each input frame. class GestureRecognizerResultProcessor: NSObject, GestureRecognizerLiveStreamDelegate { func gestureRecognizer( _ gestureRecognizer: GestureRecognizer, didFinishRecognition result: GestureRecognizerResult?, timestampInMilliseconds: Int, error: Error?) { // Process the gesture recognizer result or errors here. } } let modelPath = Bundle.main.path( forResource: "gesture_recognizer", ofType: "task") let options = GestureRecognizerOptions() options.baseOptions.modelAssetPath = modelPath options.runningMode = .liveStream options.minHandDetectionConfidence = minHandDetectionConfidence options.minHandPresenceConfidence = minHandPresenceConfidence options.minTrackingConfidence = minHandTrackingConfidence options.numHands = numHands // Assign an object of the class to the `gestureRecognizerLiveStreamDelegate` // property. let processor = GestureRecognizerResultProcessor() options.gestureRecognizerLiveStreamDelegate = processor let gestureRecognizer = try GestureRecognizer(options: options)
Objective-C
圖片
@import MediaPipeTasksVision; NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"gesture_recognizer" ofType:@"task"]; MPPGestureRecognizerOptions *options = [[MPPGestureRecognizerOptions alloc] init]; options.baseOptions.modelAssetPath = modelPath; options.runningMode = MPPRunningModeImage; options.minHandDetectionConfidence = minHandDetectionConfidence options.minHandPresenceConfidence = minHandPresenceConfidence options.minTrackingConfidence = minHandTrackingConfidence options.numHands = numHands MPPGestureRecognizer *gestureRecognizer = [[MPPGestureRecognizer alloc] initWithOptions:options error:nil];
影片
@import MediaPipeTasksVision; NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"gesture_recognizer" ofType:@"task"]; MPPGestureRecognizerOptions *options = [[MPPGestureRecognizerOptions alloc] init]; options.baseOptions.modelAssetPath = modelPath; options.runningMode = MPPRunningModeVideo; options.minHandDetectionConfidence = minHandDetectionConfidence options.minHandPresenceConfidence = minHandPresenceConfidence options.minTrackingConfidence = minHandTrackingConfidence options.numHands = numHands MPPGestureRecognizer *gestureRecognizer = [[MPPGestureRecognizer alloc] initWithOptions:options error:nil];
直播
@import MediaPipeTasksVision; // Class that conforms to the `MPPGestureRecognizerLiveStreamDelegate` protocol // and implements the method that the gesture recognizer calls once it finishes // performing gesture recognition on each input frame. @interface APPGestureRecognizerResultProcessor : NSObject@end @implementation APPGestureRecognizerResultProcessor - (void)gestureRecognizer:(MPPGestureRecognizer *)gestureRecognizer didFinishRecognitionWithResult:(MPPGestureRecognizerResult *)gestureRecognizerResult timestampInMilliseconds:(NSInteger)timestampInMilliseconds error:(NSError *)error { // Process the gesture recognizer result or errors here. } @end NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"gesture_recognizer" ofType:@"task"]; MPPGestureRecognizerOptions *options = [[MPPGestureRecognizerOptions alloc] init]; options.baseOptions.modelAssetPath = modelPath; options.runningMode = MPPRunningModeLiveStream; options.minHandDetectionConfidence = minHandDetectionConfidence options.minHandPresenceConfidence = minHandPresenceConfidence options.minTrackingConfidence = minHandTrackingConfidence options.numHands = numHands // Assign an object of the class to the `gestureRecognizerLiveStreamDelegate` // property. APPGestureRecognizerResultProcessor *processor = [APPGestureRecognizerResultProcessor new]; options.gestureRecognizerLiveStreamDelegate = processor; MPPGestureRecognizer *gestureRecognizer = [[MPPGestureRecognizer alloc] initWithOptions:options error:nil];
設定選項
這項工作的 iOS 應用程式設定選項如下:
選項名稱 | 說明 | 值範圍 | 預設值 | |
---|---|---|---|---|
runningMode |
設定工作的執行模式。共有三種模式: IMAGE:單一圖片輸入的模式。 影片:影片已解碼影格的模式。 LIVE_STREAM:輸入資料串流 (例如攝影機) 的直播模式。在這個模式下,必須呼叫 resultListener 才能設定事件監聽器,以非同步方式接收結果。 在這個模式下, gestureRecognizerLiveStreamDelegate 必須設為實作 GestureRecognizerLiveStreamDelegate 的類別例項,才能以非同步的方式接收執行手勢辨識的結果。 |
{RunningMode.image, RunningMode.video, RunningMode.liveStream } |
RunningMode.image |
|
num_hands |
GestureRecognizer 可以偵測出手的數量上限。 |
Any integer > 0 |
1 |
|
min_hand_detection_confidence |
在手掌偵測模型中,系統判定手部偵測成功的最低信心分數。 | 0.0 - 1.0 |
0.5 |
|
min_hand_presence_confidence |
在手部地標偵測模型中,手持狀態分數的最低可信度分數。在影片模式和手勢辨識的直播模式中,如果手部地標模型的手持信心分數低於這個門檻,就會觸發手掌偵測模型。否則,系統會使用輕量的手追蹤演算法判斷手部位置,以便進行後續地標偵測。 | 0.0 - 1.0 |
0.5 |
|
min_tracking_confidence |
系統判定手機追蹤成功時的最低可信度分數。這是目前影格和最後一個影格之間的定界框 IoU 門檻。在影片模式和手勢辨識的串流模式下,如果追蹤失敗,手勢辨識工具會觸發手部偵測。否則系統會略過手部偵測。 | 0.0 - 1.0 |
0.5 |
|
canned_gestures_classifier_options |
設定罐頭手勢分類器行為的選項。罐頭手勢為 ["None", "Closed_Fist", "Open_Palm", "Pointing_Up", "Thumb_Down", "Thumb_Up", "Victory", "ILoveYou"] |
|
|
|
custom_gestures_classifier_options |
設定自訂手勢分類器行為的選項。 |
|
|
|
result_listener |
設定結果事件監聽器,在手勢辨識工具處於直播模式時,以非同步方式接收分類結果。只能在執行模式設為 LIVE_STREAM 時使用 |
ResultListener |
不適用 | 不適用 |
執行模式設為直播時,手勢辨識工具需要額外的 gestureRecognizerLiveStreamDelegate
設定選項,讓手勢辨識工具能夠以非同步方式提供手勢辨識結果。委派項目必須實作 gestureRecognizer(_:didFinishRecognition:timestampInMilliseconds:error:)
方法,手勢辨識工具在處理每個影格的手勢辨識結果後,就會呼叫此方法。
選項名稱 | 說明 | 值範圍 | 預設值 |
---|---|---|---|
gestureRecognizerLiveStreamDelegate |
啟用手勢辨識工具,在直播模式下以非同步方式接收手勢辨識結果。執行個體設為此屬性的類別必須實作 gestureRecognizer(_:didFinishRecognition:timestampInMilliseconds:error:) 方法。 |
不適用 | 未設定 |
準備資料
您必須先將輸入圖片或框架轉換為 MPImage
物件,才能將其傳遞至手勢辨識工具。MPImage
支援不同類型的 iOS 圖片格式,且可在任何執行模式下用於推論。如要進一步瞭解 MPImage
,請參閱 MPImage API。
依據您的用途和應用程式所需的執行模式選擇 iOS 圖片格式。MPImage
接受 UIImage
、CVPixelBuffer
和 CMSampleBuffer
iOS 圖片格式。
UIImage
UIImage
格式非常適合下列執行模式:
圖片:應用程式套件、使用者圖片庫或檔案系統格式的圖片,可以轉換為
MPImage
物件。UIImage
影片:使用 AVAssetImageGenerator 將影片影格擷取為 CGImage 格式,然後轉換為
UIImage
圖片。
Swift
// Load an image on the user's device as an iOS `UIImage` object. // Convert the `UIImage` object to a MediaPipe's Image object having the default // orientation `UIImage.Orientation.up`. let image = try MPImage(uiImage: image)
Objective-C
// Load an image on the user's device as an iOS `UIImage` object. // Convert the `UIImage` object to a MediaPipe's Image object having the default // orientation `UIImageOrientationUp`. MPImage *image = [[MPPImage alloc] initWithUIImage:image error:nil];
這個範例會使用預設的 UIImage.Orientation.Up 方向初始化 MPImage
。您可以使用任何支援的 UIImage.Orientation 值初始化 MPImage
。手勢辨識工具不支援鏡像方向,例如 .upMirrored
、.downMirrored
、.leftMirrored
、.rightMirrored
。
如要進一步瞭解 UIImage
,請參閱「UIImage Apple Developer 說明文件」。
CVPixelBuffer
CVPixelBuffer
格式非常適合產生影格以及使用 iOS CoreImage 架構處理的應用程式。
CVPixelBuffer
格式非常適合下列執行模式:
圖片:在使用 iOS 的
CoreImage
架構進行某些處理後產生CVPixelBuffer
圖片的應用程式,可以傳送至圖片執行模式下的手勢辨識工具。影片:您可以將影片影格轉換為
CVPixelBuffer
格式進行處理,然後在影片模式中傳送至手勢辨識工具。直播:使用 iOS 相機產生影格的應用程式可能會先轉換為
CVPixelBuffer
格式進行處理,再傳送至直播模式下的手勢辨識工具。
Swift
// Obtain a CVPixelBuffer. // Convert the `CVPixelBuffer` object to a MediaPipe's Image object having the default // orientation `UIImage.Orientation.up`. let image = try MPImage(pixelBuffer: pixelBuffer)
Objective-C
// Obtain a CVPixelBuffer. // Convert the `CVPixelBuffer` object to a MediaPipe's Image object having the // default orientation `UIImageOrientationUp`. MPImage *image = [[MPPImage alloc] initWithUIImage:image error:nil];
如要進一步瞭解 CVPixelBuffer
,請參閱 CVPixelBuffer Apple 開發人員說明文件。
CMSampleBuffer
CMSampleBuffer
格式可儲存統一媒體類型的媒體樣本,非常適合用於直播執行模式。iOS 相機的即時影格會由 iOS AVCaptureVideoDataOutput 以 CMSampleBuffer
格式非同步傳送。
Swift
// Obtain a CMSampleBuffer. // Convert the `CMSampleBuffer` object to a MediaPipe's Image object having the default // orientation `UIImage.Orientation.up`. let image = try MPImage(sampleBuffer: sampleBuffer)
Objective-C
// Obtain a `CMSampleBuffer`. // Convert the `CMSampleBuffer` object to a MediaPipe's Image object having the // default orientation `UIImageOrientationUp`. MPImage *image = [[MPPImage alloc] initWithSampleBuffer:sampleBuffer error:nil];
如要進一步瞭解 CMSampleBuffer
,請參閱 CMSampleBuffer Apple 開發人員說明文件。
執行工作
如要執行手勢辨識工具,請使用已指派的執行模式專屬的 recognize()
方法:
- 靜態圖片:
recognize(image:)
- 影片:《
recognize(videoFrame:timestampInMilliseconds:)
》 - 直播:
recognizeAsync(image:timestampInMilliseconds:)
以下程式碼範例顯示如何在這些不同的執行模式下,執行手勢辨識工具的基本範例:
Swift
圖片
let result = try gestureRecognizer.recognize(image: image)
影片
let result = try gestureRecognizer.recognize( videoFrame: image, timestampInMilliseconds: timestamp)
直播
try gestureRecognizer.recognizeAsync( image: image, timestampInMilliseconds: timestamp)
Objective-C
圖片
MPPGestureRecognizerResult *result = [gestureRecognizer recognizeImage:mppImage error:nil];
影片
MPPGestureRecognizerResult *result = [gestureRecognizer recognizeVideoFrame:image timestampInMilliseconds:timestamp error:nil];
直播
BOOL success = [gestureRecognizer recognizeAsyncImage:image timestampInMilliseconds:timestamp error:nil];
範例程式碼可讓使用者切換不同的處理模式,但您的用途可能不需要這些模式。
注意事項:
在影片模式或直播模式中執行時,您必須在手勢辨識工作中提供輸入影格的時間戳記。
在圖片或影片模式中執行時,手勢辨識工作會封鎖目前的執行緒,直到完成輸入圖片或影格的處理為止。為避免封鎖目前的執行緒,請使用 iOS Dispatch 或 NSOperation 架構在背景執行緒中執行處理作業。
以直播模式執行時,手勢辨識工作會立即回傳,且不會封鎖目前的執行緒。會在處理每個輸入框後,使用手勢辨識結果叫用
gestureRecognizer(_:didFinishRecognition:timestampInMilliseconds:error:)
方法。手勢辨識器會在專屬的序列調度佇列上,以非同步方式叫用這個方法。如要在使用者介面上顯示結果,請在處理結果後將結果分派到主佇列。如果手勢辨識工作正在忙於處理其他影格時呼叫recognizeAsync
函式,手勢辨識工具會忽略新的輸入影格。
處理並顯示結果
執行推論時,手勢辨識工作會傳回 GestureRecognizerResult
,其中包含圖片座標中的手標地標、世界座標的手標標記、慣用手(左/右手),以及偵測到的手部手勢類別。
以下為這項工作的輸出資料範例:
產生的 GestureRecognizerResult
包含四個元件,每個元件都是陣列,其中每個元素都包含偵測到的一隻手的結果。
慣用手設計
慣用手是指偵測到的手是左手還是右手。
手勢
偵測到的手所辨識的手勢類別。
地標
有 21 個手部地標,每個地標由
x
、y
和z
座標組成。x
和y
座標會分別根據圖片寬度和高度正規化為 [0.0, 1.0]。z
座標代表地標深度,手腕的深度為起點。值越小,地標與相機越近。z
的規模與x
大致相同。世界著名地標
21 隻手的地標也會顯示在世界座標中。每個地標都由
x
、y
和z
組成,代表實際的 3D 座標 (以公尺為單位),其起點位於手的幾何中心。
GestureRecognizerResult:
Handedness:
Categories #0:
index : 0
score : 0.98396
categoryName : Left
Gestures:
Categories #0:
score : 0.76893
categoryName : Thumb_Up
Landmarks:
Landmark #0:
x : 0.638852
y : 0.671197
z : -3.41E-7
Landmark #1:
x : 0.634599
y : 0.536441
z : -0.06984
... (21 landmarks for a hand)
WorldLandmarks:
Landmark #0:
x : 0.067485
y : 0.031084
z : 0.055223
Landmark #1:
x : 0.063209
y : -0.00382
z : 0.020920
... (21 world landmarks for a hand)
下圖以視覺化方式呈現工作輸出: