姿勢分類選項

透過 ML Kit Pose Detection API,您可以檢查各種身體部位的相對位置,取得有意義的姿勢解讀。本頁提供一些例子。

使用 k-NN 演算法計算姿勢分類與重複計數

姿勢偵測最常見的應用程式之一就是健身追蹤。要建構一個能辨識特定健身姿勢和重複姿勢的 姿勢分類器,對開發人員來說是一大挑戰。

本節將說明如何使用 MediaPipe Colab 建立自訂 姿勢分類器,以及在機器學習套件範例應用程式中示範工作分類器。

如果您不熟悉 Google Colaboratory,請參閱簡介指南

為辨識姿勢,我們使用 k-nearest 相鄰演算法 (k-NN),因為這是簡單且易於使用的入門。演算法會根據訓練集中最相近的範例來判定物件類別。

請按照下列步驟建構及訓練辨識器:

1. 收集圖片樣本

我們從多個來源收集了有關運動的影像範例。我們會為每個練習選擇數百張圖片,例如「向上」和「向下」的位置。請務必收集涵蓋不同相機角度、環境條件、身體形狀和運動變化版本的樣本。

圖 1.上推下移姿勢位置

2. 對範例圖片執行姿勢偵測

這會產生一組用於訓練的姿勢地標。我們可能不會對姿勢偵測本身感興趣,因為我們會在下一個步驟中訓練自己的模型。

我們為自訂姿勢分類所選的 k-NN 演算法需要每個樣本的特徵向量表示法,以及計算兩個向量之間的距離之間的距離,以找出最接近的 pose 範例目標。因此,我們必須轉換剛剛取得的姿勢地標。

如要將姿勢地標轉換為特徵向量,我們會使用預先定義的姿勢清單之間的成對距離,例如手腕與肩部、腳踝和臀部以及左右手腕之間的距離。由於圖片的尺寸可能不同,因此我們在轉換地標之前,會將姿勢正規化為軀幹大小和垂直方向。

3. 訓練模型及計算重複次數

我們使用 MediaPipe Colab 存取分類器的程式碼並訓練模型。

為了計算重複次數,我們會使用其他 Colab 演算法來監控目標姿勢位置的機率門檻。例如:

  • 當「下移」姿勢首次傳遞指定的門檻時,演算法會標示「向下」姿勢輸入。
  • 當機率降至門檻以下時,演算法會標示「向下」pose 已離開,並增加計數器。
圖 2.重複計數範例

4. 與 ML Kit 快速入門導覽課程應用程式整合

上述 Colab 會產生 CSV 檔案,您可以填入所有姿勢範例。本節將說明如何將 CSV 檔案與 ML Kit Android 快速入門導覽課程應用程式整合,以便即時查看自訂姿勢分類。

嘗試在快速入門導覽課程應用程式中納入範例的姿勢分類

新增自己的 CSV 檔案

  • 將 CSV 檔案加入應用程式的素材資源資料夾。
  • PoseClassifierProcessor 中,更新 POSE_SAMPLES_FILEPOSE_CLASSES 變數,以符合您的 CSV 檔案和姿勢範例。
  • 建構並執行應用程式。

請注意,如果樣本數量不足,分類功能可能無法正常運作。 一般來說,每個 pose 類別需要約 100 個樣本。

如要瞭解詳情及自行嘗試,請參閱 MediaPipe ColabMediaPipe 分類指南

計算地標距離,辨識簡單的手勢

如果兩個以上的地標相鄰,可以用來辨識手勢。舉例來說,如果一隻手的手指放在鼻子的地標附近,可以推斷使用者最有可能觸碰對方的臉。

圖 3.解讀姿勢

以角度經驗法則稱作瑜珈姿勢

計算各種關節的角度,就能找出瑜珈姿勢。例如,下方的圖 2 顯示了《勇士隊二號》的姿勢。用來辨識此姿勢的概略角度如下:

圖 4.以一個角度打破姿勢

姿勢可描述為下列主體部分角度的組合:

  • 兩個角度的 90 度角
  • 雙肘 180 度
  • 前腿和腰部有 90 度角
  • 膝蓋的 180 度角
  • 腰圍為 135 度角

您可以使用姿勢地標計算這些角度。例如,右前腿和腰部的角度,從右肩到右臀部以及從右臀部到右側膝蓋之間的角度。

Once you've computed all the angles needed to identify the pose, you can check to see if there's a match, in which case you've recognized the pose.

下列程式碼片段示範如何使用 X 和 Y 座標來計算兩個主體部分之間的角度。這個分類方法有幾項限制。如果只勾選 X 和 Y,計算的角度會因主體和相機之間的角度而有所不同。使用正面、正向的正面圖片即可獲得最佳結果。此外,您也可以利用 Z 座標來擴充這個演算法,看看哪一種做法是否適合您的用途。

在 Android 上計算地標角度

下列方法會計算三個地標之間的角度。它可確保傳回的角度介於 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 上計算地標角度

下列方法會計算三個地標之間的角度。它可確保傳回的角度介於 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]];