
싸피 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 지정했던 곳을 내가 원하는 곳으로 바꾸면 원하는 곳의 각도 등을 계산할 수 있을 것으로 예상된다.
