姿勢の分類オプション

ML Kit Pose Detection API を使用すると、身体のさまざまな部位の相対位置を確認することで、ポーズの有意義な解釈を導き出すことができます。このページでは、いくつかの例をご紹介します。

k-NN アルゴリズムによる姿勢分類と繰り返しカウント

姿勢検出の最も一般的な用途の一つは、フィットネス トラッキングです。 特定のフィットネスのポーズを認識し、繰り返しをカウントするポーズの分類器を作成するのは、デベロッパーにとって困難な作業です。

このセクションでは、MediaPipe Colab を使用してカスタムポーズ分類器を作成する方法について説明し、ML Kit サンプルアプリで実際に機能する分類器のデモを行います。

Google Colaboratory を使い慣れていない方は、概要ガイドをご覧ください。

ポーズを認識するために、k 最近傍アルゴリズム(k-NN)を使用します。このアルゴリズムはシンプルで簡単に始めることができます。アルゴリズムは、トレーニング セット内の最も近いサンプルに基づいてオブジェクトのクラスを決定します。

認識ツールをビルドしてトレーニングする手順は次のとおりです。

1. 画像サンプルを収集する

さまざまなソースから対象エクササイズの画像サンプルを収集しました。腕立て伏せの「上」と「下」の位置など、エクササイズごとに数百の画像が選択されます。さまざまなカメラアングル、環境条件、身体形状、エクササイズのバリエーションをカバーするサンプルを収集することが重要です。

図 1. 上下の腕立て伏せのポーズの位置

2. サンプル画像に対して姿勢検出を実行する

これにより、トレーニングに使用するポーズ ランドマークのセットが生成されます。次のステップで独自のモデルをトレーニングするため、姿勢検出そのものには興味がありません。

カスタム姿勢分類に選択した k-NN アルゴリズムには、各サンプルの特徴ベクトル表現と、ポーズのサンプルに最も近いターゲットを見つけるために 2 つのベクトル間の距離を計算するための指標が必要です。つまり、取得したポーズのランドマークを変換する必要があります。

姿勢ランドマークを特徴ベクトルに変換するには、手首と肩の距離、足首と腰、左手首と右手首の距離など、ポーズの関節の事前定義リスト間のペア単位の距離を使用します。画像の規模はさまざまであるため、ランドマークを変換する前に、胴体のサイズと垂直の向きが同じになるようにポーズを正規化しました。

3. モデルをトレーニングして繰り返しをカウントする

MediaPipe Colab を使用して、分類器のコードにアクセスし、モデルをトレーニングしました。

繰り返しをカウントするために、別の Colab アルゴリズムを使用して、目標ポーズ位置の確率しきい値をモニタリングしました。例:

  • 「下」のポーズクラスの確率が初めて特定のしきい値を超えた場合、アルゴリズムは「下」のポーズクラスに入るとマークします。
  • 確率がしきい値を下回ると、アルゴリズムは「下」のポーズクラスが終了したとマークし、カウンタを増やします。
図 2. 繰り返しカウントの例

4. ML Kit クイックスタート アプリと統合する

上記の Colab では、すべてのポーズのサンプルを入力できる CSV ファイルが生成されます。このセクションでは、CSV ファイルを ML Kit Android クイックスタート アプリと統合して、カスタムポーズ分類をリアルタイムで確認する方法について説明します。

クイックスタート アプリにバンドルされているサンプルで姿勢分類を試す

  • GitHub から ML Kit Android クイックスタート アプリ プロジェクトを取得し、ビルドされ、正常に実行されることを確認します。
  • LivePreviewActivity に移動し、設定ページで姿勢検出 Run classification を有効にします。これで、腕立て伏せとスクワットを分類できるようになりました。

独自の CSV を追加

  • CSV ファイルをアプリのアセット フォルダに追加します。
  • PoseClassifierProcessor で、CSV ファイルと一致するように POSE_SAMPLES_FILE 変数と POSE_CLASSES 変数を更新し、サンプルをポーズします。
  • アプリをビルドして実行します。

サンプルが十分でない場合、分類がうまくいかないことがあります。一般に、ポーズクラスごとに約 100 個のサンプルが必要です。

詳細については、MediaPipe ColabMediaPipe 分類ガイドをご覧ください。

ランドマーク距離を計算してシンプルなジェスチャーを認識

2 つ以上のランドマークが互いに近い場合、それらを使用して操作を認識できます。たとえば、手の 1 本以上の指のランドマークが鼻のランドマークに近い場合、ユーザーが顔に触れる可能性が最も高いと推測できます。

図 3. ポーズの解釈

角度ヒューリスティックでヨガのポーズを認識する

さまざまな関節の角度を計算することで、ヨガのポーズを特定できます。たとえば、下の図 2 はヨガのポーズ「戦士 II」を示しています。このポーズを識別するおおよその角度は、次のように記述されます。

図 4. 複数の角度からポーズをとる

このポーズは、次のおおよその身体部位の角度の組み合わせとして説明できます。

  • 両肩に 90 度の角度
  • 両肘を 180 度に
  • 前脚とウエストを 90 度に調整する
  • 膝の後ろ 180 度
  • ウェスト 135 度

ポーズ ランドマークを使用してこれらの角度を計算できます。たとえば、右前脚と腰部の角度は、右肩から右腰までの線と右腰から右膝までの線との間の角度です。

ポーズの特定に必要な角度をすべて計算したら、一致するものがあるかどうかをチェックできます。一致するものがあれば、ポーズを認識したことになります。

以下のコード スニペットは、X 座標と Y 座標を使用して 2 つの部位間の角度を計算する方法を示しています。この分類アプローチにはいくつかの制限があります。X と Y だけを確認すると、被写体とカメラの間の角度に応じて算出された角度が変わります。正面から率直でわかりやすい画像にすると、最適な画像が得られます。また、Z 座標を使用して、このアルゴリズムを拡張し、自分のユースケースでのパフォーマンスが改善するかどうかを確認することもできます。

Android でランドマークの角度を計算する

次のメソッドでは、任意の 3 つのランドマーク間の角度を計算します。これにより、返される角度は 0 ~ 180 度になります。

Kotlin

fun getAngle(firstPoint: PoseLandmark, midPoint: PoseLandmark, lastPoint: PoseLandmark): Double {
        var result = Math.toDegrees(atan2(lastPoint.getPosition().y - midPoint.getPosition().y,
                lastPoint.getPosition().x - midPoint.getPosition().x)
                - atan2(firstPoint.getPosition().y - midPoint.getPosition().y,
                firstPoint.getPosition().x - midPoint.getPosition().x))
        result = Math.abs(result) // Angle should never be negative
        if (result > 180) {
            result = 360.0 - result // Always get the acute representation of the angle
        }
        return result
    }

Java

static double getAngle(PoseLandmark firstPoint, PoseLandmark midPoint, PoseLandmark lastPoint) {
  double result =
        Math.toDegrees(
            atan2(lastPoint.getPosition().y - midPoint.getPosition().y,
                      lastPoint.getPosition().x - midPoint.getPosition().x)
                - atan2(firstPoint.getPosition().y - midPoint.getPosition().y,
                      firstPoint.getPosition().x - midPoint.getPosition().x));
  result = Math.abs(result); // Angle should never be negative
  if (result > 180) {
      result = (360.0 - result); // Always get the acute representation of the angle
  }
  return result;
}

右腰の角度を計算する方法は次のとおりです。

Kotlin

val rightHipAngle = getAngle(
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE))

Java

double rightHipAngle = getAngle(
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE));

iOS でランドマークの角度を計算する

次のメソッドでは、任意の 3 つのランドマーク間の角度を計算します。これにより、返される角度は 0 ~ 180 度になります。

Swift

func angle(
      firstLandmark: PoseLandmark,
      midLandmark: PoseLandmark,
      lastLandmark: PoseLandmark
  ) -> CGFloat {
      let radians: CGFloat =
          atan2(lastLandmark.position.y - midLandmark.position.y,
                    lastLandmark.position.x - midLandmark.position.x) -
            atan2(firstLandmark.position.y - midLandmark.position.y,
                    firstLandmark.position.x - midLandmark.position.x)
      var degrees = radians * 180.0 / .pi
      degrees = abs(degrees) // Angle should never be negative
      if degrees > 180.0 {
          degrees = 360.0 - degrees // Always get the acute representation of the angle
      }
      return degrees
  }

Objective-C

(CGFloat)angleFromFirstLandmark:(MLKPoseLandmark *)firstLandmark
                      midLandmark:(MLKPoseLandmark *)midLandmark
                     lastLandmark:(MLKPoseLandmark *)lastLandmark {
    CGFloat radians = atan2(lastLandmark.position.y - midLandmark.position.y,
                            lastLandmark.position.x - midLandmark.position.x) -
                      atan2(firstLandmark.position.y - midLandmark.position.y,
                            firstLandmark.position.x - midLandmark.position.x);
    CGFloat degrees = radians * 180.0 / M_PI;
    degrees = fabs(degrees); // Angle should never be negative
    if (degrees > 180.0) {
        degrees = 360.0 - degrees; // Always get the acute representation of the angle
    }
    return degrees;
}

右腰の角度を計算する方法は次のとおりです。

Swift

let rightHipAngle = angle(
      firstLandmark: pose.landmark(ofType: .rightShoulder),
      midLandmark: pose.landmark(ofType: .rightHip),
      lastLandmark: pose.landmark(ofType: .rightKnee))

Objective-C

CGFloat rightHipAngle =
    [self angleFromFirstLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightShoulder]
                     midLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightHip]
                    lastLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightKnee]];