본문 바로가기
머신러닝딥러닝/딥러닝

mediapipe pose classification skeleton angle calculator(관절 각도 계산)

by orangecode 2023. 5. 2.
728x90

mediapipe의 포즈추정을 이용한 포즈 분류 model을 제작하는데 사용할

skeleton angle calculator(관절 각도 계산기)를 사용해보려한다.

 

mediapipe에서 사용된 blaze pose를 이용해서 33개의 landmarks 좌표값을 뽑아낼 수 있다.

33개의 관절 좌표 중 관절각도를 계산하려면 3개 관절의 좌표를 이용해서 사잇각을 구해줄 수 있다.

 

해당 사잇각을 pose마다 main이 되는 신체 관절의 사잇각으로 활용하여

pose classification 성능을 높여보려고 한다.

포즈 분류의 기준
1. skeleton landmarks의 좌표 값이 일치할 때 분류
2. 주요 관절의 각도가 기준점을 충족할 때 분류

 

1. pose classification 모델 생성

#classification
# 추출한 skeleton landmarks 좌표 x,y,z 값을 input으로 활용
inputs = tf.keras.layers.Input(shape=(99))
layer = tf.keras.layers.Dense(64, activation='relu')(inputs)
layer = tf.keras.layers.Dense(32, activation='relu')(layer)
output = tf.keras.layers.Dense(4, activation='softmax')(layer)
model = tf.keras.models.Model(inputs, output)
model.compile(loss='categorical_crossentropy', optimizer=tf.keras.optimizers.Adam(), metrics=['accuracy'])
model.summary()

 

일반적인 backbone classification 모델에 skeleton data를 train시켜서 image 혹은 video가 들어올 때

대상 image/video의 사람 skeleton landmarks가 model에 train된 값을 나타낸다면 자세를 분류해줄 수 있다.

 

skeleton landmarks의 일치성으로 포즈를 분류해줄 수 있지만,

포즈 분류를 보다  세밀하게 확정해주기 위해 model에만 분류를 맡기지 않고 

skeleton data의 angle을 계산하여 한번 더 후처리하여 더 컴팩트한 분류를 하기 위함이다.

 

 

2. skeleton angle calculator

 

x, y, z 3개의 landmark 사이의 각도를 계산할 수 있는 함수를 생성한다.

 

첫 번째 landmark는 첫번째 라인 시작점으로 간주하고,

두 번째 landmark는 첫번째 라인 끝점과 두번째 라인 시작점으로 간주하고,

세 번째 landmark는 첫번째 라인의 끝점으로으로 간주된다.

# 앵글 계산 함수
def calculateAngle(landmark1, landmark2, landmark3):

    # Get the required landmarks coordinates.
    x1, y1, _ = landmark1
    x2, y2, _ = landmark2
    x3, y3, _ = landmark3

    # Calculate the angle between the three points
    angle = math.degrees(math.atan2(y3 - y2, x3 - x2) - math.atan2(y1 - y2, x1 - x2))
    
    # Check if the angle is less than zero.
    if angle < 0:

        # Add 360 to the found angle.
        angle += 360
    
    # Return the calculated angle.
    return angle
    
# 함수 실행
# Calculate the angle between the three landmarks.
angle = calculateAngle((558, 326, 0), (642, 333, 0), (718, 321, 0))

# Display the calculated angle.
print(f'The calculated angle is {angle}')

 

POSE 마다 분류를 판단할 메인 각도 정하기
포즈 포즈 예시 메인 각도
Lunge
- 왼다리 사잇각
- 오른다리 사잇각
Pushup
- 왼팔/오른팔 사잇각
Squat
- 왼다리/오른다리 사잇각
Jumping jack

- 왼팔/오른팔 사잇각

 

 

# 분류 함수

def classifyPose(landmarks, output_image, display=False):
    
    # Initialize the label of the pose. It is not known at this stage.
    label = 'Unknown Pose'

    # Specify the color (Red) with which the label will be written on the image.
    color = (0, 0, 255)
    
    # Calculate the required angles.
    #----------------------------------------------------------------------------------------------------------------
    
    # Get the angle between the left shoulder, elbow and wrist points. 
    # 11번, 13번, 15번 landmark 
    # 왼쪽 어깨, 왼쪽 팔꿈치, 왼쪽 손목 landmark angle 값 계산 
    left_elbow_angle = calculateAngle(landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value],
                                      landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value],
                                      landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value])
    
    # 12번, 14번, 16번 landmark 
    # 오른쪽 어깨, 오른쪽 팔꿈치, 오른쪽 손목 landmark angle 값 계산 
    right_elbow_angle = calculateAngle(landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value],
                                       landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value],
                                       landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value])   
    
    # 13번, 15번, 23번 landmark 
    # 왼쪽 어깨, 왼쪽 팔꿈치, 왼쪽 엉덩이, landmark angle 값 계산 
    left_shoulder_angle = calculateAngle(landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value],
                                         landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value],
                                         landmarks[mp_pose.PoseLandmark.LEFT_HIP.value])

    # 12번, 14번, 24번 landmark 
    # 오른쪽 어깨, 오른쪽 팔꿈치, 오른쪽 엉덩이 landmark angle 값 계산  
    right_shoulder_angle = calculateAngle(landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value],
                                          landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value],
                                          landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value])

    # 23번, 25번, 27번 landmark 
    # 왼쪽 엉덩이, 왼쪽 무릎, 왼쪽 발목 landmark angle 값 계산 
    left_knee_angle = calculateAngle(landmarks[mp_pose.PoseLandmark.LEFT_HIP.value],
                                     landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value],
                                     landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value])

    # 24번, 26번, 28번 landmark 
    # 오른쪽 엉덩이, 오른쪽 무릎, 오른쪽 발목  landmark angle 값 계산 
    right_knee_angle = calculateAngle(landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value],
                                      landmarks[mp_pose.PoseLandmark.RIGHT_KNEE.value],
                                      landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value])
    #----------------------------------------------------------------------------------------------------------------
    # 전사포즈인지 확인
    #----------------------------------------------------------------------------------------------------------------

            # 한쪽다리를 곧게 뻗고 있는지 확인한다.
            if left_knee_angle > 165 and left_knee_angle < 195 or right_knee_angle > 165 and right_knee_angle < 195:

                # 한쪽다리를 곧게 뻗고 있다면
                # 다른쪽 다리를 90도 ~ 120 사이로 굽히고 있는지 확인한다.
                if left_knee_angle > 90 and left_knee_angle < 120 or right_knee_angle > 90 and right_knee_angle < 120:

                    # 양팔을 일자로 펼치고, 한쪽 다리는 곧게, 다른쪽 다리는 굽히고 있다면 
                    # 전사자세로 분류한다.
                    label = 'Warrior II Pose' 
                        
    #----------------------------------------------------------------------------------------------------------------
    
    # T포즈인지 확인
    #----------------------------------------------------------------------------------------------------------------
    
            # 양 다리 모두 곧게 뻗고 있는지 확인한다.
            if left_knee_angle > 160 and left_knee_angle < 195 and right_knee_angle > 160 and right_knee_angle < 195:

                # 양팔을 일자로 펼치고, 양쪽다리 모두 일자로 펴고 있다면
                # T포즈로 분류한다
                label = 'T Pose'

    #----------------------------------------------------------------------------------------------------------------
    
    	return output_image, label

 

 

3D XYZ 좌표 3개의점 사잇각 구하기

 

def calculate_3d(point1, point2, point3):
    # Calculate the vectors between the left hand, elbow, and shoulder landmarks
    point_1_2 = [(point2[i] - point1[i]) for i in range(3)]
    point_2_3 = [(point2[i] - point3[i]) for i in range(3)]
    
    # Calculate the dot product and the magnitudes of the vectors
    dot_product = sum([point_1_2[i] * point_2_3[i] for i in range(3)])
    point_1_2_mag = math.sqrt(sum([coord**2 for coord in point_1_2]))
    point_2_3_mag = math.sqrt(sum([coord**2 for coord in point_2_3]))

    # Calculate the angle between the left hand, elbow, and shoulder landmarks in degrees
    angle = math.degrees(math.acos(dot_product / (point_1_2_mag * point_2_3_mag)))
    
    return angle

첫 번째 단계는 입력 포인트를 사용하여 두 벡터를 계산하는 것이다.

벡터를 계산하는데 각 포인트 1, 2, 3의 좌표를 이용하여 계산하게 되는데, POINT2가 점을 이어주는 중간 지점이기때문에 point1과 point2 사이의 벡터와 point2와 point3 사이의 벡터를 계산한다.

 


두번째는 계산된 두 벡터의 내적을 계산하는 것이다. 

내적은 벡터의 좌표 곱의 합을 나타내는 것으로 두 벡터의 x 좌표를 곱하고 더한 다음 두 벡터의 y 좌표를 곱하고 더하고 마지막으로 두 벡터의 z 좌표를 곱하여 계산한다.

 

세 번째는 두 벡터의 크기를 계산하여 각도 계산식에 활용하는 단계이다.

point1과 point2 사이의 벡터 크기와 point2와 point3 사이의 벡터 크기를 계산하고, 내적값에 2 벡터 크기를 곱한 값을 나눈 뒤 역코사인 함수를 사용하면 3D 좌표계 3점 사이의 angle 각도를 구할 수 있다. 

- 벡터 크기 : √(x^2 + y^2 + z^2)

- 각도 = acos(dot_product / (크기1 * 크기2))




 

def calculate_angle(pose_results):

    # 오른팔 정의값
    rhand_index = mp.solutions.pose.PoseLandmark.RIGHT_WRIST.value
    relbow_index = mp.solutions.pose.PoseLandmark.RIGHT_ELBOW.value
    rshoulder_index = mp.solutions.pose.PoseLandmark.RIGHT_SHOULDER.value

    # 왼팔 정의값
    lhand_index = mp.solutions.pose.PoseLandmark.LEFT_WRIST.value
    lelbow_index = mp.solutions.pose.PoseLandmark.LEFT_ELBOW.value
    lshoulder_index = mp.solutions.pose.PoseLandmark.LEFT_SHOULDER.value
      

    # Get the world landmarks
    landmarks = pose_results.pose_world_landmarks
    
    # Get the 3D coordinates of left
    lhand_coords = landmarks.landmark[lhand_index].x, landmarks.landmark[lhand_index].y, landmarks.landmark[lhand_index].z
    lelbow_coords = landmarks.landmark[lelbow_index].x, landmarks.landmark[lelbow_index].y, landmarks.landmark[lelbow_index].z
    lshoulder_coords = landmarks.landmark[lshoulder_index].x, landmarks.landmark[lshoulder_index].y, landmarks.landmark[lshoulder_index].z
 
    # Get the 3D coordinates of right
    rhand_coords = landmarks.landmark[rhand_index].x, landmarks.landmark[lhand_index].y, landmarks.landmark[lhand_index].z
    relbow_coords = landmarks.landmark[relbow_index].x, landmarks.landmark[lelbow_index].y, landmarks.landmark[lelbow_index].z
    rshoulder_coords = landmarks.landmark[rshoulder_index].x, landmarks.landmark[lshoulder_index].y, landmarks.landmark[lshoulder_index].z


    
    # 왼쪽팔 / 오른쪽팔
    left_arm_angle = calculate_3d(lhand_coords, lelbow_coords, lshoulder_coords)
    print('left_arm_Angle: {:.2f} degrees'.format(left_arm_angle))
    right_arm_angle = calculate_3d(rhand_coords, relbow_coords, rshoulder_coords)
    print('right_arm_Angle: {:.2f} degrees'.format(right_arm_angle))

    # 왼쪽 어깨 / 오른쪽 어깨
    left_shoulder_angle = calculate_3d(lelbow_coords, lshoulder_coords, lhip_coords)
    print('left_shoulder_angle: {:.2f} degrees'.format(left_shoulder_angle))
    right_shoulder_angle = calculate_3d(relbow_coords, rshoulder_coords, rhip_coords)
    print('right_shoulder_angle: {:.2f} degrees'.format(right_shoulder_angle))



    return left_arm_angle, right_arm_angle, left_shoulder_angle, right_shoulder_angle

위 코드는 pose 마다 메인이되는 신체 관절 부위의 각도를 구하고 return해주는 함수이다.

반응형

댓글