본문 바로가기
과정/2차 AI프로젝트(해커톤참가)

MediaPipe 각도를 이용한 자세 유추해보기

by 줘요 2023. 10. 2.

안녕하세요! 이번에는 각도를 설정해서 자세를 유추해 보겠습니다!

 

먼저 필요한 라이브러리를 import 해줍니다.

#먼저 MediaPipe 및 OpenCV를 포함하여 필요한 라이브러리를 가져옵니다.
import cv2
import mediapipe as mp
#이미지를 표시하기 위해 사용
import matplotlib.pyplot as plt
#수학 함수를 사용하기 위한 기본 Python 모듈
import math

 

그리고 필요한 변수들을 설정합니다.

#신체 랜드마크 감지를 위해 MediaPipe 포즈 모델을 초기화합니다.
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()

#mp_drawing 변수를 초기화하고 이를 mp.solutions.raw_utils 클래스와 연결
mp_drawing = mp.solutions.drawing_utils

 

이번엔 랜드마크를 표시해 주는 것을 함수로 만들어줄 거예요!

def detectPose(image, pose, display=False):
    #입력 이미지를 복사하여 output_image 변수에 저장
    output_image = image.copy()
    #입력 이미지를 BGR에서 RGB로 변환하여 imageRGB 변수에 저장합니다. 이것은 이미지의 색상 채널을 변경
    imageRGB = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    #변환된 이미지를 사용하여 포즈 객체 pose의 process 메서드를 호출하여 포즈를 감지하고 결과를 results 변수에 저장
    results = pose.process(imageRGB)
    #입력 이미지의 높이와 너비를 height와 width 변수에 저장
    height, width, _ = image.shape
    #저장할 빈 리스트 landmarks를 생성
    landmarks = []

    #포즈 결과에서 pose_landmarks가 존재하는지 확인
    if results.pose_landmarks:
        #이미지에 포즈 랜드마크를 그립니다.
        mp_drawing.draw_landmarks(image=output_image, landmark_list=results.pose_landmarks, connections=mp_pose.POSE_CONNECTIONS)
        
        #포즈의 각 랜드마크에 대해 반복
        for landmark in results.pose_landmarks.landmark:
            landmarks.append((int(landmark.x * width), int(landmark.y * height), (landmark.z * width)))

    if display:
        plt.figure(figsize=[11, 11])
        #원본 이미지와 포즈 감지 결과를 두 개의 서브플롯에 표시
        plt.subplot(121); plt.imshow(image[:, :, ::-1]); plt.title("Original Image"); plt.axis('off');
        plt.subplot(122); plt.imshow(output_image[:, :, ::-1]); plt.title("Output Image"); plt.axis('off');
        mp_drawing.plot_landmarks(results.pose_world_landmarks, mp_pose.POSE_CONNECTIONS)
    else:
        return output_image, landmarks

 

3개의 랜드마크 점들을 이용해 각도를 반환해 주는 함수도 만들어줍니다.

#세 개의 랜드마크 (landmark1, landmark2, landmark3)를 입력으로 받아서 각도를 계산하는 역할
def calculateAngle(landmark1, landmark2, landmark3):
    x1, y1, _ = landmark1
    x2, y2, _ = landmark2
    x3, y3, _ = landmark3

    #atan2 함수는 주어진 두 점 간의 아크탄젠트 값을 계산, 이를 이용하여 각도 산출
    #math.degrees 함수를 사용하여 각도를 도 단위로 변환
    angle = math.degrees(math.atan2(y3 - y2, x3 - x2) - math.atan2(y1 - y2, x1 - x2))
    
    #계산된 각도가 0 미만일 경우, 360을 더하여 양수로 만들어 줌
    if angle < 0:
        angle += 360
    
    return angle

 

사용할 점들을 설정해 주고 각도를 설정하여 어떤 자세를 보여줄지 함수로 만들어줍니다.

def classifyPose(landmarks, output_image, display=False):
    labels= []
    colors= []

#왼쪽 손목과 오른쪽 손목의 각도를 계산
    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])
    
    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])   

#왼쪽 어깨와 오른쪽 어깨의 각도를 계산
    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])

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

     # 양팔
    if (70 < left_shoulder_angle < 180) and (70 <right_shoulder_angle < 180):
        # 만세
        if (left_elbow_angle > 50) and (right_elbow_angle > 50):
            labels.append('manse pose - all arms')


    for label in labels:
        colors.append((0, 255, 0))

    y = 30
    for label, color in zip(labels, colors):
        #이미지에 라벨 추가(라벨 텍스트, 위치, 폰트, 크기, 색상 등)
        cv2.putText(output_image, label, (10, y), cv2.FONT_HERSHEY_PLAIN, 2, color, 2)
        y += 30
    
    
    if display:
        plt.figure(figsize=[10, 10])
        plt.imshow(output_image[:, :, ::-1]); plt.title("Output Image"); plt.axis('off')
    else:
        return output_image, labels

 

마지막으로 웹캠에 어떻게 보여줄 지 설정합니다.

#모델을 초기화하고 설정하여 비디오 스트림에서 포즈 감지를 수행할 준비
pose_video = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.5, model_complexity=1)
#실시간 비디오를 캡처하려면 웹캠 피드를 엽니다.
cap = cv2.VideoCapture(1)

#비디오 프레임을 지속적으로 처리하고 신체 랜드마크를 오버레이하는 루프를 만들자
while True:
    #웹캠에서 프레임을 읽는다.
    ret, frame = cap.read()
    if not ret:
        break
    #거울모드로 변환
    frame = cv2.flip(frame, 1)
    #프레임의 높이와 넓이를 가져옴
    frame_height, frame_width, _ =  frame.shape
    #프레임의 크기를 조정
    frame = cv2.resize(frame, (int(frame_width * (640 / frame_height)), 640))
    #detectPose 함수를 사용하여 포즈 감지를 수행
    frame, landmarks = detectPose(frame, pose_video, display=False)

    if landmarks:
        frame, _ = classifyPose(landmarks, frame, display=False)

    # 랜드마크가 있는 프레임을 보여준다.
    cv2.imshow('Body Landmarks', frame)

    # q를 누르면 while문 종료됨.
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

#프로그램 작업을 마친 후 할당된 리소르를 남기거나 열어두지 않게 함

#비디오 캡처 리소스를 해제
cap.release()
#모든 OpenCV 표시 창을 닫습니다.
cv2.destroyAllWindows()

 

저는 cap = cv2.VideoCapture(1) 이 부분이 1번이지만 아마 0번으로 하셔야 될 거예요! 

 

이제 실행을 하고 미리 설정해 놓은 

 # 양팔

    if (70 < left_shoulder_angle < 180) and (70 <right_shoulder_angle < 180):

        # 만세

        if (left_elbow_angle > 50) and (right_elbow_angle > 50):

            labels.append('manse pose - all arms')


이 코드를 통해 양팔을 들게 되면 웹캠 왼쪽 상단에 manse pose - all arms 이 문구를 볼 수 있게 됩니다.

 

이런 식으로 각도를 더 상세하게 설정해 주면 여러 자세들도 유추하게 만들 수 있습니다!

댓글