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

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

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

サンプルコード

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

このアプリは、独自の iOS アプリの出発点として使用できます。また、既存のアプリを変更するときにアプリを参照することもできます。Hand Landscapeer のサンプルコードは、GitHub でホストされています。

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

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

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

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

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

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

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

主要コンポーネント

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

セットアップ

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

依存関係

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

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

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

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

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

モデル

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

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

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

タスクを作成する

手のランドマーク タスクを作成するには、いずれかのイニシャライザを呼び出します。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 しきい値です。手のマーカーの動画モードとストリーム モードでは、トラッキングが失敗すると、手のマーカーが手の検出をトリガーします。それ以外の場合は、手の検出はスキップされます。 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 形式は、次の実行モードに適しています。

  • 画像: UIImage 画像としてフォーマットされた App Bundle、ユーザー ギャラリー、ファイル システムの画像を 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 形式に変換された後、動画モードで手のランドマーク ツールに送信できます。

  • livestream: iOS カメラを使用してフレームを生成するアプリは、ライブストリーム モードで Hand Landscapeer に送信される前に、処理のために 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 デベロッパー向けドキュメントをご覧ください。

タスクを実行する

手のランドマーク ツールを実行するには、割り当てられた実行モードに固有の 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 フレームワークを使用して、バックグラウンド スレッドで処理を実行します。

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

結果を処理して表示する

推論を実行すると、手のランドマーク タスクは 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)

次の図は、タスク出力を可視化したものです。