자세 분류 옵션

ML Kit Pose Detection API를 사용하면 다양한 신체 부위의 상대적 위치를 확인하여 자세에 대한 의미 있는 해석을 도출할 수 있습니다. 이 페이지에서는 몇 가지 예를 보여줍니다.

k-NN 알고리즘을 사용한 자세 분류 및 반복 계산

자세 감지를 가장 자주 사용하는 방법 중 하나는 피트니스 추적입니다. 특정 피트니스 자세와 반복 횟수를 인식하는 자세 분류기를 빌드하는 것은 개발자에게 어려운 작업입니다.

이 섹션에서는 MediaPipe Colab을 사용하여 맞춤 포즈 분류기를 빌드한 방법을 설명하고 ML Kit 샘플 앱에서 작동하는 분류기를 보여줍니다.

Google Colaboratory에 익숙하지 않은 경우 소개 가이드를 확인하세요.

k-근접 이웃 알고리즘 (k-NN)을 사용하는 이유는 자세를 쉽고 간편하게 시작할 수 있기 때문입니다. 알고리즘은 학습 세트의 가장 가까운 샘플을 기반으로 객체의 클래스를 결정합니다.

인식기를 빌드하고 학습시키려면 다음 단계를 따르세요.

1. 이미지 샘플 수집

다양한 출처에서 타겟 운동의 이미지 샘플을 수집했습니다. 각 운동에 푸시업을 위한 '위' 및 '아래' 위치와 같은 수백 개의 이미지를 선택합니다. 다양한 카메라 각도, 환경 조건, 체형, 다양한 운동을 다루는 샘플을 수집하는 것이 중요합니다.

그림 1. 위아래로 푸시업 포즈

2. 샘플 이미지에서 자세 감지 실행

이렇게 하면 학습에 사용할 수 있는 일련의 포즈가 생성됩니다. 다음 단계에서 자체 모델을 학습시킬 것이므로 자세 감지 자체에는 관심이 없습니다.

커스텀 포즈 분류를 위해 선택한 k-NN 알고리즘에는 각 샘플에 대한 특성 벡터 표현과 포즈 샘플에 가장 가까운 타겟을 찾기 위해 두 벡터 간의 거리를 계산하는 측정항목이 필요합니다. 즉, 방금 얻은 포즈 랜드마크를 변환해야 합니다.

자세 랜드마크를 특징 벡터로 변환하기 위해, 미리 정의된 자세 관절 목록 간의 쌍별 거리를 사용합니다(예: 손목과 어깨 사이의 거리, 발목과 엉덩이, 왼쪽과 오른쪽 손목). 이미지 크기는 다양할 수 있으므로 랜드마크를 변환하기 전에 자세를 정규화하여 몸통 크기와 세로 몸통 방향을 동일하게 설정했습니다.

3. 모델 학습 및 반복 횟수

MediaPipe Colab을 사용하여 분류 기준의 코드에 액세스하고 모델을 학습했습니다.

반복을 계산하기 위해 다른 Colab 알고리즘을 사용하여 타겟 포즈 위치의 확률 임계값을 모니터링했습니다. 예를 들면 다음과 같습니다.

  • '아래' 포즈 클래스가 주어진 임곗값을 처음으로 통과할 때 알고리즘은 '낮음' 포즈 클래스를 입력한 것으로 표시합니다.
  • 확률이 임곗값 아래로 떨어지면 알고리즘은 'down' 포즈 클래스가 종료되었음을 표시하고 카운터를 늘립니다.
그림 2. 반복 계산의 예

4. ML Kit 빠른 시작 앱과 통합

위의 Colab에서는 모든 포즈 샘플을 채울 수 있는 CSV 파일을 생성합니다. 이 섹션에서는 CSV 파일을 ML Kit Android 빠른 시작 앱과 통합하여 커스텀 포즈 분류를 실시간으로 확인하는 방법을 알아봅니다.

빠른 시작 앱에 번들로 제공되는 샘플로 포즈 분류를 사용해 보세요

  • GitHub에서 ML Kit Android 빠른 시작 앱 프로젝트를 가져와서 잘 빌드되고 실행되는지 확인합니다.
  • LivePreviewActivity으로 이동하여 설정 페이지에서 Run classification 자세 감지를 사용 설정합니다. 이제 팔굽혀펴기와 스쿼트를 분류할 수 있게 되었습니다.

나만의 CSV 추가

  • 앱의 애셋 폴더에 CSV 파일을 추가합니다.
  • PoseClassifierProcessor에서 CSV 및 포즈 샘플과 일치하도록 POSE_SAMPLES_FILEPOSE_CLASSES 변수를 업데이트합니다.
  • 앱을 빌드하여 실행합니다.

샘플이 충분하지 않으면 분류가 제대로 작동하지 않을 수 있습니다. 일반적으로 포즈 클래스당 약 100개의 샘플이 필요합니다.

자세히 알아보고 직접 사용해 보려면 MediaPipe ColabMediaPipe 분류 가이드를 확인하세요.

랜드마크 거리를 계산하여 간단한 동작 인식

두 개 이상의 랜드마크가 서로 가까이 있으면 동작을 인식하는 데 사용할 수 있습니다. 예를 들어 한 쪽 손가락 한 개 이상의 랜드마크가 코의 랜드마크에 가까우면 사용자가 얼굴을 만질 가능성이 가장 높다고 추론할 수 있습니다.

그림 3. 자세 해석하기

각도 휴리스틱으로 요가 자세 인식

다양한 관절의 각도를 계산하여 요가 자세를 파악할 수 있습니다. 예를 들어 아래 그림 2는 Warrior 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
    }

자바

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))

자바

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