姿勢分類選項

有了 ML Kit Pose Detection API,您可以解讀有意義的解釋。 檢視不同身體部位的相對位置。本頁面將展示幾個範例。

使用 k-近鄰演算法進行姿勢分類和重複次數計數

姿勢偵測最常見的應用之一,就是健身追蹤。打造可辨識特定健身姿勢和次數的姿勢分類器 重複出現對開發人員來說並不是件容易的事。

在本節中,我們將說明如何使用 MediaPipe Colab 建構自訂姿勢分類器,並在 ML Kit 範例應用程式中示範可運作的分類器。

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

為了辨識姿勢,我們使用 k-NN 演算法,因為它簡單易用。演算法會根據訓練集內最接近的樣本,判斷物件的類別。

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

1. 收集圖片樣本

我們從各種來源收集目標運動的圖片樣本,我們為每項運動選擇了數百張圖片,例如伏地挺的「起」和「落」位置。請務必收集涵蓋不同鏡頭的樣本 包括角度、環境條件、身體形狀和運動變化版本。

圖 1. 上、下平板支撐姿勢位置

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

這樣會產生一組姿勢地標,可用於訓練。我們並非 偵測自己的姿勢,因為我們會訓練模型 自己的模型

我們為自訂姿勢分類選擇的 k-NN 演算法需要 每個樣本的特徵向量表示法 求兩個向量之間的距離,找出最接近姿勢樣本的目標。 也就是說,我們必須轉換剛取得的姿勢地標。

如要將姿勢地標轉換為特徵向量,我們會使用預先定義姿勢關節清單之間的兩兩距離,例如手腕和肩膀、腳踝和臀部,以及左手腕和右手腕之間的距離。由於圖片的比例可能不同,因此我們會在轉換地標之前,將姿勢標準化,使其具有相同的軀幹大小和垂直軀幹方向。

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

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

為計算重複次數,我們使用其他 Colab 演算法監控 做出目標排名門檻例如:

  • 當「往下」機率分佈pose 類別通過特定門檻 第一次測量時,演算法會標示「向下」就會輸入 pose 類別
  • 如果機率下降到門檻以下,演算法會標記 「向下」pose 類別退出,並增加計數器。
圖 2.重複計數範例

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

Colab 產生的 CSV 檔案可填入你的所有姿勢 樣本。本節將說明如何整合 CSV 檔案與 使用 ML Kit Android 快速入門導覽課程應用程式,查看即時自訂姿勢分類。

嘗試使用快速入門應用程式內附的範例進行姿勢分類

  • 從 GitHub 取得 ML Kit Android 快速入門應用程式專案,並確認該專案能順利建構及執行。
  • 前往 LivePreviewActivity,然後在「設定」頁面啟用「姿勢偵測」Run classification。您現在應該可以分類伏地挺身和深蹲了。

新增自己的 CSV

  • 將 CSV 檔案新增至應用程式的素材資源資料夾。
  • PoseClassifierProcessor 中,更新 POSE_SAMPLES_FILEPOSE_CLASSES 變數,以便與 CSV 檔案和姿勢樣本相符。
  • 建構並執行應用程式。

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

歡迎前往 MediaPipe Colab 瞭解詳情並自行試用。 和 MediaPipe 分類指南

透過計算地標距離來辨識簡單的手勢

如果兩個以上的地標彼此相近,則可用於辨識手勢。舉例來說,如果手上某一或多根手指的地標與鼻子地標相近,您就可以推斷使用者很可能正在觸摸臉部。

圖 3.解讀姿勢

使用角度經驗法則辨識瑜珈姿勢

您可以透過計算各種關節的角度來辨識瑜伽姿勢。適用對象 例如,下方的圖 2 顯示戰士 II 瑜珈姿勢。近似角度 識別這種姿態的寫法如下:

圖 4. 將姿勢拆分為不同角度

這個姿勢可用下列大約身體部位角度組合來描述:

  • 兩個肩帶的 90 度角
  • 雙肘彎曲 180 度
  • 90 度角,前腿和腰部
  • 膝後頂 180 度角
  • 135 度角腰帶

您可以使用姿勢地標來計算這些角度。例如 右前腿和腰部是指從右側線條之間的角度 從右臀部指向右膝。

計算出所有可用於辨識姿勢的角度後,您就可以檢查是否有相符的角度,如果有,就表示您已辨識出姿勢。

下列程式碼片段示範如何使用 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]];