이번에는 YOLO11 Pose를 이용해서 포인트를 받아 pygame을 이용해 애니매이션을 만들어 보았다
2025.01.02 - [CV/프로젝트] - [Pose Estimation] Ultralytics YOLO11 Pose 써보기
[Pose Estimation] Ultralytics YOLO11 Pose 써보기
여러 자세 추정 모델을 경험 해보기 위해 우선 접근성이 쉬웠던 Ultralytics에 YOLO11 Pose모델을 사용해보았다. Ultralytics YOLO11 포즈 추정 튜토리얼 | 실시간 오브젝트 추적 및 사람 포즈 감지 ">
c0mputermaster.tistory.com
이전 포스팅에서 써봤던 YOLO11 Pose에서 각 특징점의 포인트를 받아 txt파일을 넘겨주어 pygame으로 애니매이션을 만들어 볼 것이다.
우선 컴퓨터 캠을 이용해서 동영상을 만드는 코드를 작성하였다.
import cv2
# 웹캠 초기화 (0번 카메라 사용)
cap = cv2.VideoCapture(0)
# 비디오의 프레임 크기 설정
frame_width = int(cap.get(3)) # 프레임 너비
frame_height = int(cap.get(4)) # 프레임 높이
# 비디오 출력 설정 (저장할 파일 이름, 코덱, 프레임 속도, 프레임 크기)
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # MP4V 코덱 사용
out = None # 비디오 출력을 초기화하지 않음
is_recording = False # 촬영 여부를 나타내는 변수
while True:
# 웹캠에서 프레임 읽기
ret, frame = cap.read()
if not ret:
print("프레임을 읽을 수 없습니다.")
break
# 's' 키를 누르면 비디오 녹화 시작
if cv2.waitKey(1) & 0xFF == ord('s') and not is_recording:
is_recording = True
out = cv2.VideoWriter('cam.mp4', fourcc, 20.0, (frame_width, frame_height))
print("녹화 시작!")
# 녹화 중일 때만 프레임을 저장
if is_recording:
out.write(frame)
# 프레임을 화면에 출력
cv2.imshow('Webcam Video', frame)
# 'q' 키를 눌러 종료
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 웹캠과 비디오 파일을 닫기
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
그리고 만든 동영상으로 테스트 할 수 있지만 최종적으로 스포츠 영상을 통해 자세를 분석할 것 이기 때문에 짧은 볼링 동영상을 유튜브에서 가져왔다.
https://youtu.be/_fjjntyXrQA?si=vu4vKfKm7DnhVini
그 다음 예측을 수행하는 코드를 작성하여 출력 동영상과 특징점 정보가 있는 txt을 생성한다.
from ultralytics import YOLO
import cv2
# YOLO 모델을 로드 (여기서는 자세 추정 모델을 로드한다고 가정)
model = YOLO("yolo11m-pose.pt") # pose 추정 모델을 로드
# 비디오 파일 경로 지정
video_path = "cam.mp4"
# 비디오 캡처 객체 생성
cap = cv2.VideoCapture(video_path)
# 비디오의 프레임 크기 및 FPS 정보 가져오기
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
# 동영상 출력 객체 생성 (파일로 저장)
output_video_path = "output_video.mp4"
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # mp4 형식으로 저장
out = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height))
# 키포인트 좌표를 저장할 텍스트 파일 열기
keypoints_file = open("keypoints_detail.txt", "w")
keypoints2_file = open("keypoints.txt", "w")
# 비디오에서 프레임 하나씩 읽어오기
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 프레임을 모델에 넣어 예측 수행
results = model(frame) # 프레임을 모델에 넣어 예측
# 예측된 프레임을 출력
annotated_frame = results[0].plot() # 예측 결과가 그려진 프레임 얻기
# keypoints 추출
keypoints = results[0].keypoints # keypoints를 얻기
# keypoints.xy: (x, y) 좌표, keypoints.conf: 각 keypoint의 confidence
xy = keypoints.xy # 픽셀 좌표
conf = keypoints.conf # 신뢰도
# 각 키포인트의 좌표와 신뢰도를 텍스트 파일에 저장
keypoints_text = "Frame {} Keypoints:\n".format(int(cap.get(cv2.CAP_PROP_POS_FRAMES)))
for i in range(len(xy[0])): # 각 사람마다 반복
x, y = xy[0][i] # (x, y) 좌표
c = conf[0][i] # 각 키포인트의 신뢰도
# 텍스트 파일에 좌표와 신뢰도 저장
keypoints_text += "Keypoint {}: ({:.2f}, {:.2f}), Confidence: {:.2f}\n".format(i, x, y, c)
# 텍스트 파일에 키포인트 좌표 저장
keypoints_file.write(keypoints_text + "\n")
# Keypoints2.txt에 프레임 번호와 키포인트 좌표만 저장
frame_number = int(cap.get(cv2.CAP_PROP_POS_FRAMES)) # 현재 프레임 번호
keypoints2_text = f"{frame_number} " # 프레임 번호로 시작
# 17개의 키포인트 좌표를 한 줄로 저장
for i in range(len(xy[0])):
x, y = xy[0][i] # (x, y) 좌표
keypoints2_text += f"{x:.2f} {y:.2f} " # x, y 좌표를 추가
keypoints2_file.write(keypoints2_text.strip() + "\n") # 한 줄 끝에 공백을 없애고 저장
# 프레임을 동영상 파일로 저장
out.write(annotated_frame)
# 결과 화면에 표시 (선택 사항)
cv2.imshow('Annotated Frame', annotated_frame)
# 'q' 키를 누르면 종료
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 자원 해제
cap.release()
out.release()
cv2.destroyAllWindows()
# 텍스트 파일 닫기
keypoints_file.close()
keypoints2_file.close()


1 (프레임 번호) 349.41 214.11 371.40 185.61 318.99 187.32 ... (17개의 키포인트 좌표 x, y )
2 (프레임 번호) 348.69 213.72 371.08 186.90 319.40 187.39 ... (17개의 키포인트 좌표 x, y )
....
이런식으로 저장함

이 좌표를 이용해서 애니매이션을 제작하는 코드 작성
import pygame
import time
import cv2
import math
# Pygame 초기화
pygame.init()
# 동영상 파일 경로
video_file = 'cam.mp4'
# 동영상 읽기
cap = cv2.VideoCapture(video_file)
if not cap.isOpened():
print("Error: Could not open video.")
exit()
# 화면 크기 설정 (동영상의 해상도에 맞추기)
WIDTH = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
HEIGHT = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Keypoint Animation")
# 색상 정의
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0) # 선 색상
# 키포인트 크기 (원 크기)
KEYPOINT_RADIUS = 5
# 데이터 파일 경로
input_file = 'Keypoints.txt'
# 키포인트 이름 (순서대로, 영어로 변경)
KEYPOINT_NAMES = [
"Nose", "Left Eye", "Right Eye", "Left Ear", "Right Ear",
"Left Shoulder", "Right Shoulder", "Left Elbow", "Right Elbow",
"Left Wrist", "Right Wrist", "Left Hip", "Right Hip",
"Left Knee", "Right Knee", "Left Ankle", "Right Ankle"
]
# 데이터를 읽어오는 함수
def read_keypoints(file_path):
keypoints_data = []
with open(file_path, 'r') as f:
for line in f:
data = line.split()
frame_number = int(data[0])
keypoints = list(map(float, data[1:]))
keypoints_data.append((frame_number, keypoints))
return keypoints_data
# Keypoints 데이터를 읽어옵니다.
keypoints_data = read_keypoints(input_file)
# 선분의 중심과 위쪽 방향으로 속이 빈 원을 그리는 함수
def draw_perpendicular_circle(screen, p1, p2, color):
# 두 점 p1, p2의 중심 계산
mid_x = (p1[0] + p2[0]) / 2
mid_y = (p1[1] + p2[1]) / 2
# 선분의 길이 계산
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
length = math.sqrt(dx**2 + dy**2)
# 선분의 길이의 절반을 반지름으로 설정
radius = length / 2
# 위쪽 방향으로 수직 벡터 설정
perp_dx = 0
perp_dy = -1 # 위쪽 방향
# 중심에서 수직 방향으로 이동 (반지름 길이만큼)
circle_center = (mid_x + perp_dx * radius, mid_y + perp_dy * radius)
# 원을 테두리만 그리기 위해 width를 2로 설정
pygame.draw.circle(screen, color, (int(circle_center[0]), int(circle_center[1])), int(radius), width=2)
# 동영상 저장을 위한 설정 (OpenCV 사용)
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # mp4v 코덱 사용
output_video = cv2.VideoWriter('animation.mp4', fourcc, 25, (WIDTH, HEIGHT)) # 초당 25프레임으로 저장
def animate_keypoints():
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 20) # 텍스트를 그리기 위한 폰트 설정
# 애니메이션 루프
running = True
current_frame = 0
while running:
# 이벤트 처리 (창을 닫을 때 종료)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 화면 배경을 흰색으로 지우기
screen.fill(WHITE)
# 현재 프레임에 해당하는 키포인트 좌표 가져오기
if current_frame < len(keypoints_data):
_, keypoints = keypoints_data[current_frame]
keypoint_positions = []
# 그리지 않도록 제외할 키포인트 인덱스를 지정
exclude_keypoints = ["Nose", "Left Eye", "Right Eye", "Left Ear", "Right Ear"]
for i in range(0, len(keypoints), 2):
x = keypoints[i]
y = keypoints[i + 1]
# 키포인트 이름을 확인하여 제외할 리스트에 포함되지 않은 경우만 그리기
if KEYPOINT_NAMES[i // 2] not in exclude_keypoints:
# x, y 값이 0이 아니면 키포인트를 그리기
if x != 0 and y != 0:
pygame.draw.circle(screen, RED, (int(x), int(y)), KEYPOINT_RADIUS)
# 키포인트 이름 텍스트 그리기 (원 오른쪽에 텍스트를 표시)
text = font.render(KEYPOINT_NAMES[i // 2], True, BLUE)
# screen.blit(text, (int(x) + 10, int(y) - 10))
# 키포인트 좌표를 항상 기록 (0이든 아니든)
keypoint_positions.append((x, y))
# 이제 선을 그리기 (사람의 형태 만들기)
# 왼쪽 팔꿈치와 왼쪽 어깨 연결
if len(keypoint_positions) > 7 and len(keypoint_positions) > 5:
if keypoint_positions[7][0] != 0 and keypoint_positions[7][1] != 0 and keypoint_positions[5][0] != 0 and keypoint_positions[5][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[7][0], keypoint_positions[7][1]), (keypoint_positions[5][0], keypoint_positions[5][1]), 2)
# 왼쪽 팔꿈치와 왼쪽 손목 연결
if len(keypoint_positions) > 7 and len(keypoint_positions) > 9:
if keypoint_positions[7][0] != 0 and keypoint_positions[7][1] != 0 and keypoint_positions[9][0] != 0 and keypoint_positions[9][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[7][0], keypoint_positions[7][1]), (keypoint_positions[9][0], keypoint_positions[9][1]), 2)
# 오른쪽 팔꿈치와 오른쪽 어깨 연결
if len(keypoint_positions) > 8 and len(keypoint_positions) > 6:
if keypoint_positions[8][0] != 0 and keypoint_positions[8][1] != 0 and keypoint_positions[6][0] != 0 and keypoint_positions[6][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[8][0], keypoint_positions[8][1]), (keypoint_positions[6][0], keypoint_positions[6][1]), 2)
# 오른쪽 팔꿈치와 오른쪽 손목 연결
if len(keypoint_positions) > 8 and len(keypoint_positions) > 10:
if keypoint_positions[8][0] != 0 and keypoint_positions[8][1] != 0 and keypoint_positions[10][0] != 0 and keypoint_positions[10][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[8][0], keypoint_positions[8][1]), (keypoint_positions[10][0], keypoint_positions[10][1]), 2)
# 왼쪽 무릎과 왼쪽 발목 연결
if len(keypoint_positions) > 13 and len(keypoint_positions) > 15:
if keypoint_positions[13][0] != 0 and keypoint_positions[13][1] != 0 and keypoint_positions[15][0] != 0 and keypoint_positions[15][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[13][0], keypoint_positions[13][1]), (keypoint_positions[15][0], keypoint_positions[15][1]), 2)
# 오른쪽 무릎과 오른쪽 발목 연결
if len(keypoint_positions) > 14 and len(keypoint_positions) > 16:
if keypoint_positions[14][0] != 0 and keypoint_positions[14][1] != 0 and keypoint_positions[16][0] != 0 and keypoint_positions[16][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[14][0], keypoint_positions[14][1]), (keypoint_positions[16][0], keypoint_positions[16][1]), 2)
# 몸통 선 (왼쪽 어깨 - 오른쪽 어깨)
if len(keypoint_positions) > 5 and len(keypoint_positions) > 6:
if keypoint_positions[5][0] != 0 and keypoint_positions[5][1] != 0 and keypoint_positions[6][0] != 0 and keypoint_positions[6][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[5][0], keypoint_positions[5][1]), (keypoint_positions[6][0], keypoint_positions[6][1]), 2)
# 왼쪽 어깨와 오른쪽 어깨를 이용해 수직 원 그리기
if len(keypoint_positions) > 5 and len(keypoint_positions) > 6:
left_shoulder = (keypoint_positions[5][0], keypoint_positions[5][1])
right_shoulder = (keypoint_positions[6][0], keypoint_positions[6][1])
if left_shoulder[0] != 0 and left_shoulder[1] != 0 and right_shoulder[0] != 0 and right_shoulder[1] != 0:
draw_perpendicular_circle(screen, left_shoulder, right_shoulder, GREEN)
# 왼쪽 엉덩이와 오른쪽 엉덩이 연결
if len(keypoint_positions) > 11 and len(keypoint_positions) > 12:
if keypoint_positions[11][0] != 0 and keypoint_positions[11][1] != 0 and keypoint_positions[12][0] != 0 and keypoint_positions[12][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[11][0], keypoint_positions[11][1]), (keypoint_positions[12][0], keypoint_positions[12][1]), 2)
# 왼쪽 어깨 - 왼쪽 엉덩이 연결
if len(keypoint_positions) > 5 and len(keypoint_positions) > 11:
if keypoint_positions[5][0] != 0 and keypoint_positions[5][1] != 0 and keypoint_positions[11][0] != 0 and keypoint_positions[11][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[5][0], keypoint_positions[5][1]), (keypoint_positions[11][0], keypoint_positions[11][1]), 2)
# 오른쪽 어깨 - 오른쪽 엉덩이 연결
if len(keypoint_positions) > 6 and len(keypoint_positions) > 12:
if keypoint_positions[6][0] != 0 and keypoint_positions[6][1] != 0 and keypoint_positions[12][0] != 0 and keypoint_positions[12][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[6][0], keypoint_positions[6][1]), (keypoint_positions[12][0], keypoint_positions[12][1]), 2)
# 다리 연결 (왼쪽 엉덩이 - 왼쪽 무릎 - 왼쪽 발목)
if len(keypoint_positions) > 11 and len(keypoint_positions) > 13 and len(keypoint_positions) > 15:
if keypoint_positions[11][0] != 0 and keypoint_positions[11][1] != 0 and keypoint_positions[13][0] != 0 and keypoint_positions[13][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[11][0], keypoint_positions[11][1]), (keypoint_positions[13][0], keypoint_positions[13][1]), 2)
if keypoint_positions[13][0] != 0 and keypoint_positions[13][1] != 0 and keypoint_positions[15][0] != 0 and keypoint_positions[15][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[13][0], keypoint_positions[13][1]), (keypoint_positions[15][0], keypoint_positions[15][1]), 2)
# 다리 연결 (오른쪽 엉덩이 - 오른쪽 무릎 - 오른쪽 발목)
if len(keypoint_positions) > 12 and len(keypoint_positions) > 14 and len(keypoint_positions) > 16:
if keypoint_positions[12][0] != 0 and keypoint_positions[12][1] != 0 and keypoint_positions[14][0] != 0 and keypoint_positions[14][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[12][0], keypoint_positions[12][1]), (keypoint_positions[14][0], keypoint_positions[14][1]), 2)
if keypoint_positions[14][0] != 0 and keypoint_positions[14][1] != 0 and keypoint_positions[16][0] != 0 and keypoint_positions[16][1] != 0:
pygame.draw.line(screen, GREEN, (keypoint_positions[14][0], keypoint_positions[14][1]), (keypoint_positions[16][0], keypoint_positions[16][1]), 2)
# 화면을 OpenCV로 캡처하여 동영상에 추가
video_frame = pygame.surfarray.array3d(pygame.display.get_surface()) # Pygame 화면을 OpenCV 형식으로 변환
video_frame = video_frame.swapaxes(0, 1) # 차원 변경 (Pygame은 (높이, 너비, 색상) 순서이고 OpenCV는 (너비, 높이, 색상) 순서)
output_video.write(video_frame) # 동영상에 프레임 추가
# 화면 업데이트
pygame.display.flip()
# 한 프레임을 1초간 기다리기
clock.tick(25) # 초당 25 프레임
# 다음 프레임으로 이동
current_frame += 1
if current_frame >= len(keypoints_data):
running = False # 모든 프레임을 다 보여주고 종료
# Pygame 종료
pygame.quit()
output_video.release() # 동영상 저장 종료
# 애니메이션 실행
animate_keypoints()
양 어깨의 포인트를 가지고 백터를 계산하여 머리의 크기와 방향을 만들고 포인트들을 연결하였다


다음번에는 포인트를 가지고 몸 사이의 각도를 계산하여 올바른 자세로 투구를 하는지 분석해보고자 한다.
https://github.com/SeungtaekLim/pose_animator
GitHub - SeungtaekLim/pose_animator
Contribute to SeungtaekLim/pose_animator development by creating an account on GitHub.
github.com
'Computer Vision > Project' 카테고리의 다른 글
| [Project] Spring Boot와 Flask를 이용해 모바일용 서버 구축 (0) | 2025.02.01 |
|---|---|
| [Raspberry Pi] 라즈베리파이에서 YOLO11 Pose 써보기 (7) | 2025.01.22 |
| [Raspberry Pi] 원격 접속 세팅과 실시간 USB CAM 사용 (7) | 2025.01.21 |
| [Pose Estimation] OpenPose 써보기 (4) | 2025.01.03 |
| [Pose Estimation] Ultralytics YOLO11 Pose 써보기 (4) | 2025.01.02 |
