자세 분류 옵션

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 알고리즘을 사용하여 타겟 포즈 위치의 확률 임곗값을 모니터링했습니다. 예를 들면 다음과 같습니다.

  • '아래' 포즈 클래스의 확률이 처음으로 지정된 기준점을 초과하면 알고리즘은 '아래' 포즈 클래스가 입력되었다고 표시합니다.
  • 확률이 기준점 아래로 떨어지면 알고리즘에서 '아래' 포즈 클래스가 종료되었다고 표시하고 카운터를 늘립니다.
그림 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는 전사 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]];