iOS 手部地標偵測指南

MediaPipe 地標工具工作可讓你偵測圖片中手部的地標。 以下操作說明將說明如何搭配 iOS 應用程式使用手持地標工具。如需這些操作說明中提及的程式碼範例,請前往 GitHub

如要進一步瞭解這項工作的功能、模型和設定選項,請參閱總覽

程式碼範例

MediaPipe Tasks 範例程式碼是 iOS 版「手機地標」應用程式的基本實作。範例使用 iOS 實體裝置的相機,偵測連續影片串流中的手跡點。應用程式也可以從裝置圖片庫偵測圖片和影片中的手部地標。

您可以將該應用程式做為開發 iOS 應用程式的起點,也可以在修改現有應用程式時參照。實作地標程式碼可在 GitHub 上代管。

下載程式碼

以下操作說明說明如何使用 git 指令列工具建立範例程式碼的本機副本。

如要下載範例程式碼,請按照下列步驟操作:

  1. 使用下列指令複製 Git 存放區:

    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. 或者,您也可以設定 git 執行個體使用稀疏結帳功能,如此一來,您只會有「Handmarker」範例應用程式的檔案:

    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/hand_landmarker/ios/
    

建立範例程式碼的本機版本後,可以安裝 MediaPipe 工作程式庫,使用 Xcode 開啟專案並執行應用程式。如需操作說明,請參閱 iOS 設定指南

重要元件

以下檔案包含「Handmarker」應用程式的重要程式碼:

設定

本節說明使用手地標工具設定開發環境和程式碼專案的重要步驟。如需瞭解如何設定開發環境以使用 MediaPipe 工作 (包括平台版本需求),請參閱「iOS 設定指南」。

依附元件

手持地標使用 MediaPipeTasksVision 程式庫,你必須使用 CocoaPods 來安裝這個程式庫。這個程式庫與 Swift 和 Objective-C 應用程式相容,不需要任何其他語言的特定設定。

如需在 macOS 上安裝 CocoaPods 的操作說明,請參閱 CocoaPods 安裝指南。 如要瞭解如何建立具有應用程式必要 Pod 的 Podfile,請參閱「使用 CocoaPods」。

使用下列程式碼,在 Podfile 中新增 MediaPipeTasksVision Pod:

target 'MyHandLandmarkerApp' do
  use_frameworks!
  pod 'MediaPipeTasksVision'
end

如果您的應用程式含有單元測試目標,請參閱 iOS 設定指南,進一步瞭解如何設定 Podfile

型號

MediaPipe Kinger 工作需要經過訓練且與這項工作相容的模型。如要進一步瞭解工地地標的可用已訓練模型,請參閱工作總覽「模型」一節

選取並下載模型,然後使用 Xcode 將其新增至專案目錄。 如需將檔案新增至 Xcode 專案的操作說明,請參閱「管理 Xcode 專案中的檔案和資料夾」。

使用 BaseOptions.modelAssetPath 屬性指定應用程式套件中的模型路徑。如需程式碼範例,請參閱下一節。

建立工作

如要建立「手持地標」工作,請呼叫其中一個初始化器。HandLandmarker(options:) 初始化器會接受設定選項的值。

如果不需要使用自訂設定選項初始化的手地標,可以使用 HandLandmarker(modelPath:) 初始化器建立含有預設選項的手地標。如要進一步瞭解設定選項,請參閱「設定總覽」。

「Handmarker」工作支援 3 種輸入資料類型:靜態圖片、影片檔案和直播影片串流。根據預設,HandLandmarker(modelPath:) 會初始化靜態圖片的工作。如果想將工作初始化,以便處理影片檔案或直播影片串流,請使用 HandLandmarker(options:) 指定影片或直播執行模式。直播模式也需要額外的 handLandmarkerLiveStreamDelegate 設定選項,讓手定位地標能夠以非同步方式將手部地標結果傳送給委派代表。

請選擇與執行模式對應的分頁,瞭解如何建立工作並執行推論。

Swift

圖片

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "hand_landmarker",
                                      ofType: "task")

let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .image
options.minHandDetectionConfidence = minHandDetectionConfidence
options.minHandPresenceConfidence = minHandPresenceConfidence
options.minTrackingConfidence = minHandTrackingConfidence
options.numHands = numHands

let handLandmarker = try HandLandmarker(options: options)
    

影片

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "hand_landmarker",
                                      ofType: "task")

let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .video
options.minHandDetectionConfidence = minHandDetectionConfidence
options.minHandPresenceConfidence = minHandPresenceConfidence
options.minTrackingConfidence = minHandTrackingConfidence
options.numHands = numHands

let handLandmarker = try HandLandmarker(options: options)
    

直播

import MediaPipeTasksVision

// Class that conforms to the `HandLandmarkerLiveStreamDelegate` protocol and
// implements the method that the hand landmarker calls once it finishes
// performing landmarks detection in each input frame.
class HandLandmarkerResultProcessor: NSObject, HandLandmarkerLiveStreamDelegate {

  func handLandmarker(
    _ handLandmarker: HandLandmarker,
    didFinishDetection result: HandLandmarkerResult?,
    timestampInMilliseconds: Int,
    error: Error?) {

    // Process the hand landmarker result or errors here.

  }
}

let modelPath = Bundle.main.path(
  forResource: "hand_landmarker",
  ofType: "task")

let options = HandLandmarkerOptions()
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 `handLandmarkerLiveStreamDelegate`
// property.
let processor = HandLandmarkerResultProcessor()
options.handLandmarkerLiveStreamDelegate = processor

let handLandmarker = try HandLandmarker(options: options)
    

Objective-C

圖片

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"hand_landmarker"
                                                      ofType:@"task"];

MPPHandLandmarkerOptions *options = [[MPPHandLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeImage;
options.minHandDetectionConfidence = minHandDetectionConfidence;
options.minHandPresenceConfidence = minHandPresenceConfidence;
options.minTrackingConfidence = minHandTrackingConfidence;
options.numHands = numHands;

MPPHandLandmarker *handLandmarker =
  [[MPPHandLandmarker alloc] initWithOptions:options error:nil];
    

影片

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"hand_landmarker"
                                                      ofType:@"task"];

MPPHandLandmarkerOptions *options = [[MPPHandLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeVideo;
options.minHandDetectionConfidence = minHandDetectionConfidence;
options.minHandPresenceConfidence = minHandPresenceConfidence;
options.minTrackingConfidence = minHandTrackingConfidence;
options.numHands = numHands;

MPPHandLandmarker *handLandmarker =
  [[MPPHandLandmarker alloc] initWithOptions:options error:nil];
    

直播

@import MediaPipeTasksVision;

// Class that conforms to the `MPPHandLandmarkerLiveStreamDelegate` protocol
// and implements the method that the hand landmarker calls once it finishes
// performing landmarks detection in each input frame.

@interface APPHandLandmarkerResultProcessor : NSObject 

@end

@implementation APPHandLandmarkerResultProcessor

-   (void)handLandmarker:(MPPHandLandmarker *)handLandmarker
    didFinishDetectionWithResult:(MPPHandLandmarkerResult *)handLandmarkerResult
         timestampInMilliseconds:(NSInteger)timestampInMilliseconds
                           error:(NSError *)error {

    // Process the hand landmarker result or errors here.

}

@end

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"hand_landmarker"
                                                      ofType:@"task"];

MPPHandLandmarkerOptions *options = [[MPPHandLandmarkerOptions 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 `handLandmarkerLiveStreamDelegate`
// property.
APPHandLandmarkerResultProcessor *processor =
  [APPHandLandmarkerResultProcessor new];
options.handLandmarkerLiveStreamDelegate = processor;

MPPHandLandmarker *handLandmarker =
  [[MPPHandLandmarker alloc] initWithOptions:options error:nil];
    

設定選項

這項工作的 iOS 應用程式設定選項如下:

選項名稱 說明 值範圍 預設值
running_mode 設定工作的執行模式。共有三種模式:

IMAGE:單一圖片輸入的模式。

影片:影片已解碼影格的模式。

LIVE_STREAM:輸入資料串流 (例如攝影機) 的直播模式。在這個模式下,必須呼叫 resultListener 才能設定事件監聽器,以非同步方式接收結果。 在這個模式下,handLandmarkerLiveStreamDelegate 必須設為實作 HandLandmarkerLiveStreamDelegate 的類別例項,才能以非同步方式接收手部地標偵測結果。
{RunningMode.image, RunningMode.video, RunningMode.liveStream} RunningMode.image
numHands 手部地標偵測器偵測到的手數量上限。 Any integer > 0 1
minHandDetectionConfidence 在手掌偵測模型中,系統判定手部偵測成功的最低信心分數。 0.0 - 1.0 0.5
minHandPresenceConfidence 在手部地標偵測模型中,手持狀態分數的最低可信度分數。在影片模式和直播模式下,如果手持位置標記模型的信心分數低於這個門檻,手寫地標就會觸發手掌偵測模型。否則,為了後續的地標偵測,系統會透過輕量追蹤演算法判斷手部的位置。 0.0 - 1.0 0.5
minTrackingConfidence 系統判定手機追蹤成功時的最低可信度分數。這是目前影格和最後一個影格之間的定界框 IoU 門檻。在影片模式和手持地標的「串流」模式中,如果追蹤失敗,手地標就會觸發手部偵測。否則,會略過手部偵測。 0.0 - 1.0 0.5
result_listener 設定結果監聽器,在手標位置處於直播模式時,以非同步方式接收偵測結果。只有在執行模式設為 LIVE_STREAM 時適用 不適用 不適用

當跑步模式設為直播時,「手地標」需要額外的 handLandmarkerLiveStreamDelegate 設定選項,這樣手地標就能以非同步方式提供手部地標偵測結果。委派項目必須實作 handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) 方法,而手地標會在處理每個影格的手標標記偵測結果後呼叫此方法。

選項名稱 說明 值範圍 預設值
handLandmarkerLiveStreamDelegate 啟用「手寫地標」功能,即可在直播模式中,以非同步方式接收手部地標偵測結果。執行個體設為此屬性的類別必須實作 handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) 方法。 不適用 未設定

準備資料

您必須先將輸入圖片或框架轉換為 MPImage 物件,才能將其傳送至手遊地標。MPImage 支援不同類型的 iOS 圖片格式,且可在任何執行模式下用於推論。如要進一步瞭解 MPImage,請參閱 MPImage API

依據您的用途和應用程式所需的執行模式選擇 iOS 圖片格式。MPImage 接受 UIImageCVPixelBufferCMSampleBuffer 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 AVCaptureVideoDataOutputCMSampleBuffer 格式非同步傳送。

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 開發人員說明文件

執行工作

如要執行指針地標,請使用已指派的跑步模式專屬的 detect() 方法:

  • 靜態圖片:detect(image:)
  • 影片:《detect(videoFrame:timestampInMilliseconds:)
  • 直播:detectAsync(image:timestampInMilliseconds:)

Swift

圖片

let result = try handLandmarker.detect(image: image)
    

影片

let result = try handLandmarker.detect(
    videoFrame: image,
    timestampInMilliseconds: timestamp)
    

直播

try handLandmarker.detectAsync(
  image: image,
  timestampInMilliseconds: timestamp)
    

Objective-C

圖片

MPPHandLandmarkerResult *result =
  [handLandmarker detectInImage:image error:nil];
    

影片

MPPHandLandmarkerResult *result =
  [handLandmarker detectInVideoFrame:image
             timestampInMilliseconds:timestamp
                               error:nil];
    

直播

BOOL success =
  [handLandmarker detectAsyncInImage:image
             timestampInMilliseconds:timestamp
                               error:nil];
    

「Handmarker」程式碼範例會詳細說明這兩種模式的實作方式。範例程式碼可讓使用者切換不同處理模式,但您的用途可能並不需要該模式。

注意事項:

  • 以影片模式或直播模式執行時,你必須為手持地標工作提供輸入影格的時間戳記。

  • 在圖片或影片模式下執行時,「手鍊」工作會封鎖目前的執行緒,直到處理完成輸入圖片或影格為止。為避免封鎖目前的執行緒,請使用 iOS DispatchNSOperation 架構在背景執行緒中執行處理作業。

  • 以直播模式執行時,「手持地標」工作會立即返回,且不會封鎖目前的執行緒。這會在處理每個輸入影格後,利用手繪地標結果叫用 handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) 方法。手持地標程式會在專屬的序列調度佇列上,以非同步方式叫用這個方法。如要在使用者介面上顯示結果,請在處理結果後將結果分派到主佇列。如果手地標工作正在處理其他影格時呼叫 detectAsync 函式,則手地標會忽略新的輸入影格。

處理並顯示結果

執行推論時,手地標工作會傳回 HandLandmarkerResult,其中包含圖片座標中的手標地標、以世界座標表示的手部地標,以及偵測到的手部慣用手(左/右手)。

以下為這項工作的輸出資料範例:

HandLandmarkerResult 輸出內容包含三個元件。每個元件都是陣列,其中每個元素都包含下列偵測到的單一手結果:

  • 慣用手設計

    慣用手是指偵測到的手是左手還是右手。

  • 地標

    有 21 個手部地標,每個地標由 xyz 座標組成。xy 座標會分別根據圖片寬度和高度正規化為 [0.0, 1.0]。z 座標代表地標深度,手腕的深度為起點。值越小,地標與相機越近。z 的規模與 x 大致相同。

  • 世界著名地標

    21 隻手的地標也會顯示在世界座標中。每個地標都由 xyz 組成,代表實際的 3D 座標 (以公尺為單位),其起點位於手的幾何中心。

HandLandmarkerResult:
  Handedness:
    Categories #0:
      index        : 0
      score        : 0.98396
      categoryName : Left
  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)

下圖以視覺化方式呈現工作輸出內容: