ML Kit Pose Detection API를 사용하면 다양한 신체 부위의 상대적 위치를 확인하여 자세에 대한 의미 있는 해석을 도출할 수 있습니다. 이 페이지에서는 몇 가지 예를 보여줍니다.
k-NN 알고리즘을 사용한 자세 분류 및 반복 계산
자세 감지를 가장 자주 사용하는 방법 중 하나는 피트니스 추적입니다. 특정 피트니스 자세와 반복 횟수를 인식하는 자세 분류기를 빌드하는 것은 개발자에게 어려운 작업입니다.
이 섹션에서는 MediaPipe Colab을 사용하여 맞춤 포즈 분류기를 빌드한 방법을 설명하고 ML Kit 샘플 앱에서 작동하는 분류기를 보여줍니다.
Google Colaboratory에 익숙하지 않은 경우 소개 가이드를 확인하세요.
k-근접 이웃 알고리즘 (k-NN)을 사용하는 이유는 자세를 쉽고 간편하게 시작할 수 있기 때문입니다. 알고리즘은 학습 세트의 가장 가까운 샘플을 기반으로 객체의 클래스를 결정합니다.
인식기를 빌드하고 학습시키려면 다음 단계를 따르세요.
1. 이미지 샘플 수집
다양한 출처에서 타겟 운동의 이미지 샘플을 수집했습니다. 각 운동에 푸시업을 위한 '위' 및 '아래' 위치와 같은 수백 개의 이미지를 선택합니다. 다양한 카메라 각도, 환경 조건, 체형, 다양한 운동을 다루는 샘플을 수집하는 것이 중요합니다.

2. 샘플 이미지에서 자세 감지 실행
이렇게 하면 학습에 사용할 수 있는 일련의 포즈가 생성됩니다. 다음 단계에서 자체 모델을 학습시킬 것이므로 자세 감지 자체에는 관심이 없습니다.
커스텀 포즈 분류를 위해 선택한 k-NN 알고리즘에는 각 샘플에 대한 특성 벡터 표현과 포즈 샘플에 가장 가까운 타겟을 찾기 위해 두 벡터 간의 거리를 계산하는 측정항목이 필요합니다. 즉, 방금 얻은 포즈 랜드마크를 변환해야 합니다.
자세 랜드마크를 특징 벡터로 변환하기 위해, 미리 정의된 자세 관절 목록 간의 쌍별 거리를 사용합니다(예: 손목과 어깨 사이의 거리, 발목과 엉덩이, 왼쪽과 오른쪽 손목). 이미지 크기는 다양할 수 있으므로 랜드마크를 변환하기 전에 자세를 정규화하여 몸통 크기와 세로 몸통 방향을 동일하게 설정했습니다.
3. 모델 학습 및 반복 횟수
MediaPipe Colab을 사용하여 분류 기준의 코드에 액세스하고 모델을 학습했습니다.
반복을 계산하기 위해 다른 Colab 알고리즘을 사용하여 타겟 포즈 위치의 확률 임계값을 모니터링했습니다. 예를 들면 다음과 같습니다.
- '아래' 포즈 클래스가 주어진 임곗값을 처음으로 통과할 때 알고리즘은 '낮음' 포즈 클래스를 입력한 것으로 표시합니다.
- 확률이 임곗값 아래로 떨어지면 알고리즘은 'down' 포즈 클래스가 종료되었음을 표시하고 카운터를 늘립니다.

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 Colab 및 MediaPipe 분류 가이드를 확인하세요.
랜드마크 거리를 계산하여 간단한 동작 인식
두 개 이상의 랜드마크가 서로 가까이 있으면 동작을 인식하는 데 사용할 수 있습니다. 예를 들어 한 쪽 손가락 한 개 이상의 랜드마크가 코의 랜드마크에 가까우면 사용자가 얼굴을 만질 가능성이 가장 높다고 추론할 수 있습니다.

각도 휴리스틱으로 요가 자세 인식
다양한 관절의 각도를 계산하여 요가 자세를 파악할 수 있습니다. 예를 들어 아래 그림 2는 Warrior II 요가 자세를 보여줍니다. 이 포즈를 식별하는 대략적인 각도는 다음과 같이 작성됩니다.

이 자세는 다음과 같은 대략적인 신체 부위 각도의 조합으로 설명할 수 있습니다.
- 양쪽 어깨를 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]];