머신러닝딥러닝/딥러닝

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

orangecode 2023. 5. 2. 02:42
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해주는 함수이다.

반응형