MediaPipe 사용해보기

CNH·2024년 1월 20일

개발

목록 보기
6/17


싸피 2학기 프로젝트로 MediaPipe라는 라이브러리를 사용하게 되었다. MediaPipe는 동작인식 AI모델로, 아래 사진처럼 사진 혹은 동영상에 저렇게 스켈레톤을 그려준다. 아래와 같은 사진은 그렇게 신기하지 않을 것이고, 그만큼 레퍼런스도 많다. 이번 프로젝트는 이 라이브러리를 사용해서 댄스 초보자들이 본인의 춤이 어디가 틀렸는지 쉽게 알아볼 수 있도록 도와주는 프로젝트이다. 아래와 같은 느낌으로? (사진 출처)

따라서 여러 자료를 찾아보던 도중, 여기를 참고하면 대략적인 느낌을 잡을 수 있을 것 같았다. 코드도 유튜브 동영상 설명란에 기재되어 있으니 이 코드를 한 번 따라가 보려고 한다. 우선 아래 코드를 따라 치면 기본 세팅은 끝난다.

# mediapipe설치
!pip install mediapipe opencv-python

# 사전작업
import cv2
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose

# 탐지
cap = cv2.VideoCapture(0)
## Setup mediapipe instance
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        # Recolor image to RGB
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False
      
        # Make detection
        results = pose.process(image)
        
    
        # Recolor back to BGR
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Render detections
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )               
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

그 다음 실제로 각도를 계산해보자. 우선 아래 그림이 MediaPipe에서 제공하는 관절? 점? 정보이다. (33개)

# 2. Determining Joints
cap = cv2.VideoCapture(0)
## Setup mediapipe instance
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        # Recolor image to RGB
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False
      
        # Make detection
        results = pose.process(image)
    
        # Recolor back to BGR
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Extract landmarks
        try:
            landmarks = results.pose_landmarks.landmark
            print(landmarks)
        except:
            pass
        
        
        # Render detections
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )               
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

여기 results.pose_landmarks.landmark가 중요해 보인다. 우선 results, 즉 pose.process(image)는 영상의 분석결과?가 담겨있는 것 같다. try절 안에 landmarks를 출력해보면

[x: 0.08949225
y: 0.62972224
z: -0.79956245
visibility: 0.99791294
, x: 0.13338819
y: 0.5633135
z: -0.7905304
visibility: 0.9949792
, x: 0.15390673
y: 0.56560576
z: -0.79057825
visibility: 0.99570376
, x: 0.17515573
y: 0.56893575
z: -0.79080796
visibility: 0.99480563 ...]

이런 식으로 나온다. []안을 살펴보면 x,y,z,visibility가 하나의 원소가 된 배열임을 알 수 있는데, 이 배열의 크기를 살펴보면 33, 즉 위에서 봤던 갯수이다. 즉 각 점에 대한 정보가 landmarks인거고, 실제로 위의 코드에서는 while문이 돌 때 계속 landmarks를 출력한 것이니 저 []배열이 n개 출력된다. 암튼 이제 이 landmarks 배열 안에 점들을 가지고 계산하면 팔 각도같은 것을 계산할 수 있어보인다.

def calculate_angle(a,b,c):
    a = np.array(a) # First
    b = np.array(b) # Mid
    c = np.array(c) # End
    
    radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
    angle = np.abs(radians*180.0/np.pi)
    
    if angle >180.0:
        angle = 360-angle
        
    return angle 

위와 같이 각도를 계산할 수 있는 함수를 만들 수 있다. 각도를 계산하려면 3개의 점이 필요할 텐데, 각 점이 a, b, c인 듯 하다. 실제 코드와 함께 쓰면

cap = cv2.VideoCapture(0)
## Setup mediapipe instance
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        # Recolor image to RGB
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False
      
        # Make detection
        results = pose.process(image)
    
        # Recolor back to BGR
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Extract landmarks
        try:
            landmarks = results.pose_landmarks.landmark
            
            # Get coordinates
            shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
            elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
            wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
            
            # Calculate angle
            angle = calculate_angle(shoulder, elbow, wrist)
            
            # Visualize angle
            cv2.putText(image, str(angle), 
                           tuple(np.multiply(elbow, [640, 480]).astype(int)), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA
                                )
                       
        except:
            pass
        
        
        # Render detections
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )               
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

아래처럼 각도가 나오는 것이다. 위에서 shoulder, elbow, wrist 지정했던 곳을 내가 원하는 곳으로 바꾸면 원하는 곳의 각도 등을 계산할 수 있을 것으로 예상된다.

profile
끄적끄적....

0개의 댓글