手のランドマーク検出ガイド(iOS)

MediaPipe Hand Landmarker タスクを使用すると、画像内の手のランドマークを検出できます。以下の手順では、iOS アプリでハンド ランドマークを使用する方法について説明します。この手順で説明するコードサンプルは GitHub で入手できます。

このタスクの機能、モデル、構成オプションの詳細については、概要をご覧ください。

サンプルコード

MediaPipe Tasks のサンプルコードは、iOS 向けの手のランドマーク アプリの基本的な実装です。この例では、物理的な iOS デバイスのカメラを使用して、連続した動画ストリームで手のランドマークを検出します。このアプリはデバイスのギャラリーから 画像や動画内の手のランドマークを検出することもできます

このアプリは、独自の iOS アプリの開始点として使用できます。また、既存のアプリを変更する際にも参照できます。ハンド ランドマークの例コードは GitHub でホストされています。

コードをダウンロードする

次の手順では、git コマンドライン ツールを使用してサンプルコードのローカルコピーを作成する方法を示します。

サンプルコードをダウンロードするには:

  1. 次のコマンドを使用して Git リポジトリのクローンを作成します。

    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. 必要に応じて、スパース チェックアウトを使用するように Git インスタンスを構成し、Hand Landmarker サンプルアプリのファイルのみが含まれるようにします。

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

ローカル バージョンのサンプルコードを作成したら、MediaPipe タスク ライブラリをインストールし、Xcode を使用してプロジェクトを開いてアプリを実行できます。手順については、iOS 用セットアップガイドをご覧ください。

主要コンポーネント

次のファイルには、ハンド ランドマーカー サンプル アプリケーション用の重要なコードが含まれています。

  • HandLandmarkerService.swift: ハンドランドマーカーを初期化し、モデル選択を処理して、入力データに対する推論を実行します。
  • CameraViewController.swift: ライブカメラ フィード入力モードの UI を実装し、結果を可視化します。
  • MediaLibraryViewController.swift: 静止画像ファイルと動画ファイル入力モードの UI を実装し、結果を可視化します。

セットアップ

このセクションでは、Hand Landmarker を使用するように開発環境とコード プロジェクトを設定する主な手順について説明します。プラットフォーム バージョンの要件など、MediaPipe タスクを使用する開発環境の設定に関する一般的な情報については、iOS 用セットアップ ガイドをご覧ください。

依存関係

Hand Landmarker は MediaPipeTasksVision ライブラリを使用します。このライブラリは CocoaPods を使用してインストールする必要があります。このライブラリは Swift アプリと Objective-C アプリの両方に対応しており、言語固有の追加設定は必要ありません。

macOS に CocoaPods をインストールする手順については、CocoaPods インストール ガイドをご覧ください。アプリに必要な Pod を使用して Podfile を作成する方法については、CocoaPods の使用をご覧ください。

次のコードを使用して、Podfile に MediaPipeTasksVision Pod を追加します。

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

アプリに単体テスト ターゲットが含まれている場合は、Podfile の設定について詳しくは、iOS 用セットアップ ガイドをご覧ください。

モデル

MediaPipe のハンドランドマーカー タスクには、このタスクと互換性のあるトレーニング済みモデルが必要です。ハンド ランドマークで利用可能なトレーニング済みモデルの詳細については、タスクの概要のモデル セクションをご覧ください。

モデルを選択してダウンロードし、Xcode を使用してプロジェクト ディレクトリに追加します。Xcode プロジェクトにファイルを追加する方法については、Xcode プロジェクト内のファイルとフォルダの管理をご覧ください。

BaseOptions.modelAssetPath プロパティを使用して、アプリバンドルのモデルのパスを指定します。コード例については、次のセクションをご覧ください。

タスクを作成する

Hand Landmarker タスクを作成するには、いずれかの初期化子を呼び出します。HandLandmarker(options:) イニシャライザは、構成オプションの値を受け入れます。

カスタマイズした設定オプションを使用して手のマーカーを初期化する必要がない場合は、HandLandmarker(modelPath:) イニシャライザを使用して、デフォルトのオプションを使用して手のマーカーを作成できます。構成オプションの詳細については、構成の概要をご覧ください。

ハンド ランディングマーカー タスクは、静止画像、動画ファイル、ライブ動画ストリームの 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 タスクの実行モードを設定します。モードには次の 3 つがあります。

IMAGE: 単一画像入力のモード。

VIDEO: 動画のデコードされたフレームのモード。

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 しきい値です。Hand Landmarker の動画モードとストリーミング モードでは、トラッキングに失敗すると、Hand Landmarker が手の検出をトリガーします。それ以外の場合は、手の検出をスキップします。 0.0 - 1.0 0.5
result_listener ハンド ランドマークがライブ配信モードのときに検出結果を非同期で受信するように結果リスナーを設定します。実行モードが LIVE_STREAM に設定されている場合にのみ適用されます。 なし なし

実行モードがライブ配信に設定されている場合、Hand Landmarker には handLandmarkerLiveStreamDelegate 構成オプションが必要です。これにより、Hand Landmarker は手のマーカー検出結果を非同期で提供できます。デリゲートには handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) メソッドを実装する必要があります。このメソッドは、Hand Landmarker が各フレームの手マーカー検出結果を処理した後に呼び出されます。

オプション名 説明 値の範囲 デフォルト値
handLandmarkerLiveStreamDelegate Hand Landmarker がライブ配信モードで手のアバターの検出結果を非同期的に受け取れるようにします。このプロパティにインスタンスが設定されているクラスは、handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) メソッドを実装する必要があります。 該当なし 未設定

データの準備

入力画像またはフレームを Hand Landmarker に渡す前に、MPImage オブジェクトに変換する必要があります。MPImage は、さまざまな種類の iOS 画像形式をサポートしており、任意の実行モードで推論に使用できます。MPImage について詳しくは、MPImage API をご覧ください。

ユースケースと、アプリに必要な実行モードに基づいて iOS イメージ形式を選択します。MPImage は、UIImageCVPixelBufferCMSampleBuffer の iOS イメージ形式を受け入れます。

UIImage

UIImage 形式は、次の実行モードに適しています。

  • 画像: アプリバンドル、ユーザー ギャラリー、または UIImage イメージとしてフォーマットされたファイル システムの画像は、MPImage オブジェクトに変換できます。

  • 動画: 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];
    

このサンプルでは、MPImage をデフォルトの UIImage.Orientation.Up の向きで初期化します。MPImage は、サポートされている UIImage.Orientation 値のいずれかで初期化できます。ハンド ランドマークは、.upMirrored.downMirrored.leftMirrored.rightMirrored などのミラーリングされた向きをサポートしていません。

UIImage の詳細については、UIImage Apple デベロッパー ドキュメントをご覧ください。

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 デベロッパー ドキュメントをご覧ください。

タスクを実行する

Hand Landmarker を実行するには、割り当てられた実行モードに固有の 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];
    

手のマーカーのコード例では、これらの各モードの実装の詳細を示しています。サンプルコードでは、ユーザーが処理モードを切り替えることができますが、ユースケースでは不要な場合があります。

次の点にご留意ください。

  • 動画モードまたはライブ ストリーム モードで実行する場合は、ハンド ランドマーク タスクに入力フレームのタイムスタンプも指定する必要があります。

  • 画像モードまたは動画モードで実行している場合、ハンド ランドマーカー タスクは、入力画像またはフレームの処理が完了するまで現在のスレッドをブロックします。現在のスレッドをブロックしないようにするには、iOS の Dispatch または NSOperation フレームワークを使用して、バックグラウンド スレッドで処理を実行します。

  • ライブ配信モードで実行すると、Hand Landmarker タスクはすぐに返され、現在のスレッドはブロックされません。各入力フレームを処理した後、ハンド ランドマークの結果を使用して handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) メソッドを呼び出します。Hand Landmarker は、専用のシリアル ディスパッチ キューでこのメソッドを非同期的に呼び出します。結果をユーザー インターフェースに表示するには、結果を処理した後に結果をメインキューにディスパッチします。Hand Landmarker タスクが別のフレームの処理でビジー状態になっているときに detectAsync 関数が呼び出されると、Hand Landmarker は新しい入力フレームを無視します。

結果を処理して表示する

推論を実行すると、Hand Landmarker タスクは、画像座標のハンド ランドマーク、ワールド座標のハンド ランドマーク、検出された手の利き手(左手/右手)を含む HandLandmarkerResult を返します。

このタスクの出力データの例を次に示します。

HandLandmarkerResult の出力には 3 つのコンポーネントが含まれます。各コンポーネントは配列で、各要素には検出された 1 つの手に関する次の結果が含まれます。

  • 利き手

    利き手は、検出された手が左手か右手かを表します。

  • ランドマーク

    21 個のハンド ランドマークがあり、それぞれ xyz 座標で構成されています。x 座標と y 座標は、それぞれ画像の幅と高さで [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)

次の画像は、タスク出力の可視化を示しています。