姿勢分類オプション

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]];