러닝 폼을 교정하고 분석할 때 가장 기본이자 중요한 지표 중 하나가 바로 '무릎 각도'입니다.
이번에는 직접 준비한 달리기 샘플 영상에서 AI로 러너의 관절 랜드마크를 추출하고, 실시간 무릎 굴곡 각도를 계산해 오버레이한 뒤 결과 영상을 파일로 저장하는 파이프라인(PoC)을 직접 구현해 보았습니다.
1. 프로젝트 개요 & 기술 스택
- 프로젝트명: RunSome (러닝 자세 분석 PoC)
- 목표: 달리기 측면 영상 기반 실시간 무릎 각도 연산 및 결과 동영상 자동 렌더링
- 개발 환경: Windows 11, VS Code
- 기술 스택: Python 3.10, OpenCV, MediaPipe (0.10.14), NumPy
2. 프로젝트 디렉터리 구조
유지보수와 향후 기능 확장을 고려해 비즈니스 로직과 실행 코드를 깔끔하게 분리했습니다.
runsome_project/
├── data/
│ ├── sample_run.mp4 # 분석 대상 원본 영상
│ └── output_analyzed.mp4 # 자세 분석 결과 영상
├── src/
│ └── utils.py # 각도 연산 비즈니스 로직
├── main.py # 비디오 입출력 및 메인 파이프라인
└── README.md
3. 핵심 코드 구현
① 관절 사잇각 계산 모듈 ()
세 개의 2D 관절 좌표(골반, 무릎, 발목)를 받아 arctan2 삼각함수를 활용해 사잇각을 구합니다. 각도가 180도를 넘어가면 내각 기준으로 보정하도록 처리했습니다.
import numpy as np
def calculate_angle(a, b, c):
"""세 점 a, b, c 사이의 사잇각 계산 (b가 중심 관절점)"""
a = np.array(a)
b = np.array(b)
c = np.array(c)
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)
# 180도 초과 시 내각 기준 변환
if angle > 180.0:
angle = 360.0 - angle
return int(angle)
② 메인 파이프라인 및 비디오 저장 ()
OpenCV의 VideoCapture로 프레임을 읽어와 MediaPipe에 전달하고, 추출된 랜드마크에 각도 텍스트를 입힌 뒤 cv2.VideoWriter를 통해 새로운 동영상 파일로 저장합니다.
import os
import cv2
import mediapipe as mp
import numpy as np
from src.utils import calculate_angle
# 1. MediaPipe Pose 설정
mp_pose = mp.solutions.pose
mp_drawing = mp.solutions.drawing_utils
pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)
# 2. 비디오 I/O 및 코덱 세팅
video_path = 'data/sample_run.mp4'
cap = cv2.VideoCapture(video_path)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
out = cv2.VideoWriter(
'data/output_analyzed.mp4',
cv2.VideoWriter_fourcc(*'mp4v'),
fps,
(width, height)
)
# 3. 프레임별 분석 및 저장 루프
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = pose.process(image)
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
try:
landmarks = results.pose_landmarks.landmark
# 오른쪽 골반, 무릎, 발목 좌표 추출
hip = [landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value].x,
landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value].y]
knee = [landmarks[mp_pose.PoseLandmark.RIGHT_KNEE.value].x,
landmarks[mp_pose.PoseLandmark.RIGHT_KNEE.value].y]
ankle = [landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value].x,
landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value].y]
knee_angle = calculate_angle(hip, knee, ankle)
# 픽셀 좌표 변환 및 화면 텍스트 렌더링
h, w, _ = image.shape
knee_pixel = tuple(np.multiply(knee, [w, h]).astype(int))
cv2.putText(image, f"Knee: {knee_angle} deg",
(knee_pixel[0] - 60, knee_pixel[1] - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, cv2.LINE_AA)
except:
pass
# 관절 연결선 그리기
if results.pose_landmarks:
mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
# 가공된 프레임 저장 및 화면 출력
out.write(image)
cv2.imshow('RunSome Pose Analysis PoC', image)
if cv2.waitKey(25) & 0xFF == ord('q'):
break
cap.release()
out.release()
cv2.destroyAllWindows()
4. 마주친 문제와 해결 (Troubleshooting)
구현하는 과정에서 버전 및 터미널 환경 관련 이슈들이 발생했지만, 원인을 하나씩 짚어가며 해결했습니다.
① AttributeError: module 'mediapipe' has no attribute 'solutions'
- 문제: 로컬 시스템의 파이썬이 최신 버전(3.14/3.13)이라, 레거시 솔루션 모듈 문법을 지원하지 않는
mediapipe 1.x가 설치되었습니다. - 해결: OpenCV와 MediaPipe 호환성이 가장 검증된 Python 3.10 가상환경(
runsome)을 Anaconda로 생성하고, 안정 버전인mediapipe==0.10.14를 설치해 해결했습니다.
② VS Code PowerShell 터미널의 명령 미인식
- 문제: PowerShell 환경 변수에 Anaconda 경로가 등록되어 있지 않아
conda activate명령이 동작하지 않았습니다. - 해결: 시스템 환경 변수를 복잡하게 손대는 대신, 생성한 가상환경의 파이썬 실행기 경로(
runsome\python.exe)를 직접 지정해 실행하거나 VS Code의Select Interpreter에 등록해 우회 실행했습니다.
5. 결과물 & 회고

영상 속 러너의 관절 키포인트가 깔끔하게 피팅되었고, 스윙 레그(공중에 뜬 다리)의 무릎 굴곡 각도(92도)가 튀는 현상 없이 안정적으로 연산되는 것을 확인했습니다.
단순히 튜토리얼 코드를 복붙하는 데 그치지 않고, 파이썬 버전 의존성 문제와 터미널 환경 이슈를 직접 가상환경 격리로 풀어내면서 파이프라인 전체를 완성할 수 있었던 뿌듯한 과정이었습니다.
다음에는 착지 순간(Foot Strike)을 자동으로 감지하는 로직과 상체 기울기 각도를 추가해 볼 계획입니다.
'Projects > 런썸(AI Runner Pose)' 카테고리의 다른 글
| [RunSome 개발일지 #02] 러닝 영상 한 편으로 무릎 각도와 착지 순간(Foot Strike) 잡아내기 (MediaPipe & OpenCV) (0) | 2026.10.09 |
|---|---|
| [RunSome 개발일지 #01] 골반 통증과 도전경력증명서, 그리고 러닝 AI를 시작한 이유 (0) | 2026.10.03 |