
영상 속 사람의 자세를 자동으로 분석해주는 인공지능 프로젝트를 해봤어요!
MediaPipe + OpenCV를 활용해서 좋은 자세와 나쁜 자세를 실시간으로 분류하고
시간 추적 + 경고 메시지 출력까지 구현했어요!
| 도구/라이브러리 | 설명 |
|---|---|
| Google Colab | 클라우드 기반 실습 환경 |
| OpenCV | 컴퓨터 비전 라이브러리 (영상 처리용) |
| MediaPipe | Google의 포즈/손/얼굴 추적 AI 라이브러리 |
| MoviePy | Colab에서 영상 재생하기 위한 유틸 |
| math | 수학 계산 함수 (거리, 각도 등) |
귀-어깨, 골반-어깨를 각각 연결해 각도를 구해요.good_time = (1/fps) * good_frame
bad_time = (1/fps) * bad_frame
bad_time이 3초를 넘으면 중앙에 "Warning!!!!"을 크게 출력!코드는 언제나 각 코드가 무슨 기능을 하는지 주석도 최대한 풍부하게 달아두는 걸 추천해요 :)
import cv2
import mediapipe as mp
import math
from moviepy.editor import VideoFileClip
# 프레임 카운터 초기화
good_frame = 0
bad_frame = 0
# 폰트 설정
font = cv2.FONT_HERSHEY_SIMPLEX
# 색상 설정
blue = (255,127,0)
red = (50,50,255)
green = (127,255,0)
dark_blue = (127,20,0)
light_green = (127,233,100)
yellow = (0,255,255)
pink = (255,0,255)
# Mediapipe Pose 초기화
mp_pose = mp.solutions.pose
pose = mp_pose.Pose()
# 거리 계산 함수
def findDistance(x1, y1, x2, y2):
# 두 점 사이의 거리 계산
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
# 각도 계산 함수
def findAngle(x1, y1, x2, y2):
# 두 벡터 사이 각도를 라디안으로 구한 뒤 도로 변환
cos_value = ((x2-x1)*(-x1) + (y2-y1)*(-y1)) / (
math.sqrt((x2-x1)**2 + (y2-y1)**2) * math.sqrt(x1**2 + y1**2)
)
return math.degrees(math.acos(cos_value))
# 1차 결과: 어깨 거리 + 기울기 표시
cap = cv2.VideoCapture('./data/101_pose.mp4') # 비디오 읽어오기
# 영상 크기
w = int(cap.get(3))
h = int(cap.get(4))
fps = int(cap.get(5))
# 코덱 설정
codec = cv2.VideoWriter_fourcc(*"MP4V")
# 녹화파일 설정
out = cv2.VideoWriter('./p001_result04.mp4', codec, fps, (w, h))
# 시작 표시 남겨주기
print('시작 q(≧▽≦q)')
while cap.isOpened():
# (1) 프레임 이미지 읽어오기
ret, frame = cap.read()
if not ret:
print('종료 ヾ(≧▽≦*)o')
break
fps = cap.get(cv2.CAP_PROP_FPS) # 초당 프레임 수 가져오기
h, w, _ = frame.shape # 높이, 너비
# (2) RGB 변환
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# (3) 포즈 검출
keypoint = pose.process(frame)
# 다시 BGR로
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
# (4) 포즈 랜드마크 좌표 저장
lm = keypoint.pose_landmarks
lmPose = mp_pose.PoseLandmark
l_shldr_x = int(lm.landmark[lmPose.LEFT_SHOULDER].x * w)
l_shldr_y = int(lm.landmark[lmPose.LEFT_SHOULDER].y * h)
r_shldr_x = int(lm.landmark[lmPose.RIGHT_SHOULDER].x * w)
r_shldr_y = int(lm.landmark[lmPose.RIGHT_SHOULDER].y * h)
l_ear_x = int(lm.landmark[lmPose.LEFT_EAR].x * w)
l_ear_y = int(lm.landmark[lmPose.LEFT_EAR].y * h)
l_hip_x = int(lm.landmark[lmPose.LEFT_HIP].x * w)
l_hip_y = int(lm.landmark[lmPose.LEFT_HIP].y * h)
# (5) 어깨 거리 계산
offset = findDistance(l_shldr_x, l_shldr_y, r_shldr_x, r_shldr_y)
# (6) 거리 출력
if offset < 100:
cv2.putText(frame, f"{int(offset)} Aligned", (w-150,30), font, 0.9, green, 2)
else:
cv2.putText(frame, f"{int(offset)} Not Aligned", (w-150,30), font, 0.9, red, 2)
# (7) 랜드마크 점 찍기
cv2.circle(frame, (l_shldr_x, l_shldr_y), 7, yellow, -1)
cv2.circle(frame, (r_shldr_x, r_shldr_y), 7, pink, -1)
cv2.circle(frame, (l_ear_x, l_ear_y), 7, yellow, -1)
cv2.circle(frame, (l_hip_x, l_hip_y), 7, yellow, -1)
# (8) 목/몸통 기울기 계산
neck_inclination = findAngle(l_shldr_x, l_shldr_y, l_ear_x, l_ear_y)
torso_inclination = findAngle(l_hip_x, l_hip_y, l_shldr_x, l_shldr_y)
# (9) 좋은/나쁜 자세 판단
angle_text = f"Neck : {int(neck_inclination)} Torso : {int(torso_inclination)}"
if neck_inclination < 40 and torso_inclination < 10:
bad_frame = 0
good_frame += 1
cv2.putText(frame, angle_text, (10,30), font, 0.9, green, 2)
cv2.putText(frame, str(int(neck_inclination)), (l_shldr_x+10, l_shldr_y), font, 0.9, green, 2)
cv2.putText(frame, str(int(torso_inclination)), (l_hip_x+10, l_hip_y), font, 0.9, green, 2)
cv2.line(frame, (l_shldr_x, l_shldr_y), (l_ear_x, l_ear_y), green, 4)
cv2.line(frame, (l_hip_x, l_hip_y), (l_shldr_x, l_shldr_y), green, 4)
else:
bad_frame += 1
good_frame = 0
cv2.putText(frame, angle_text, (10,30), font, 0.9, red, 2)
cv2.putText(frame, str(int(neck_inclination)), (l_shldr_x+10, l_shldr_y), font, 0.9, red, 2)
cv2.putText(frame, str(int(torso_inclination)), (l_hip_x+10, l_hip_y), font, 0.9, red, 2)
cv2.line(frame, (l_shldr_x, l_shldr_y), (l_ear_x, l_ear_y), red, 4)
cv2.line(frame, (l_hip_x, l_hip_y), (l_shldr_x, l_shldr_y), red, 4)
out.write(frame)
out.release()
cap.release()
# 결과 미리보기
VideoFileClip('./p001_result04.mp4').ipython_display(width=600)
# 2차 결과:자세 유지 시간 + 경고 표시
cap = cv2.VideoCapture('./data/101_pose.mp4')
w = int(cap.get(3))
h = int(cap.get(4))
fps = int(cap.get(5))
codec = cv2.VideoWriter_fourcc(*"MP4V")
out = cv2.VideoWriter('./p001_result05.mp4', codec, fps, (w,h))
print('시작 q(≧▽≦q)')
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print('종료 ヾ(≧▽≦*)o')
break
fps = cap.get(cv2.CAP_PROP_FPS)
h, w, _ = frame.shape
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
keypoint = pose.process(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
lm = keypoint.pose_landmarks
lmPose = mp_pose.PoseLandmark
l_shldr_x = int(lm.landmark[lmPose.LEFT_SHOULDER].x * w)
l_shldr_y = int(lm.landmark[lmPose.LEFT_SHOULDER].y * h)
r_shldr_x = int(lm.landmark[lmPose.RIGHT_SHOULDER].x * w)
r_shldr_y = int(lm.landmark[lmPose.RIGHT_SHOULDER].y * h)
l_ear_x = int(lm.landmark[lmPose.LEFT_EAR].x * w)
l_ear_y = int(lm.landmark[lmPose.LEFT_EAR].y * h)
l_hip_x = int(lm.landmark[lmPose.LEFT_HIP].x * w)
l_hip_y = int(lm.landmark[lmPose.LEFT_HIP].y * h)
offset = findDistance(l_shldr_x, l_shldr_y, r_shldr_x, r_shldr_y)
if offset < 100:
cv2.putText(frame, f"{int(offset)} Aligned", (w-150,30), font, 0.9, green, 2)
else:
cv2.putText(frame, f"{int(offset)} Not Aligned", (w-150,30), font, 0.9, red, 2)
cv2.circle(frame, (l_shldr_x, l_shldr_y), 7, yellow, -1)
cv2.circle(frame, (r_shldr_x, r_shldr_y), 7, pink, -1)
cv2.circle(frame, (l_ear_x, l_ear_y), 7, yellow, -1)
cv2.circle(frame, (l_hip_x, l_hip_y), 7, yellow, -1)
neck_inclination = findAngle(l_shldr_x, l_shldr_y, l_ear_x, l_ear_y)
torso_inclination = findAngle(l_hip_x, l_hip_y, l_shldr_x, l_shldr_y)
angle_text = f"Neck : {int(neck_inclination)} Torso : {int(torso_inclination)}"
if neck_inclination < 40 and torso_inclination < 10:
bad_frame = 0
good_frame += 1
else:
bad_frame += 1
good_frame = 0
# (10) 좋은/나쁜 자세 시간 계산
good_time = (1/fps) * good_frame
bad_time = (1/fps) * bad_frame
# (11) 유지 시간 표시
if good_time > 0:
cv2.putText(frame, f"Good Pose Time : {round(good_time,1)}s", (10,h-20), font, 0.9, green, 2)
else:
cv2.putText(frame, f"Bad Pose Time : {round(bad_time,1)}s", (10,h-20), font, 0.9, red, 2)
# (12) 나쁜 자세 3초 이상 경고
if bad_time > 3:
cv2.putText(frame, 'Warning!!!!', (int(w/2-300), int(h/2)), font, 3, red, 10)
out.write(frame)
out.release()
cap.release()
# 최종 결과 미리보기
VideoFileClip('./p001_result05.mp4').ipython_display(width=600)
Aligned + Good Pose Time Not Aligned + Bad Pose Time Warning!!!! 메시지 출력됨MediaPipe와 OpenCV를 활용해서
영상 속 사람의 자세를 분석하고 실시간 피드백을 주는 코드를 짜봤어요.
처음엔 어렵게 느껴졌지만, 하나하나 구현해보니 논리 흐름이 꽤 직관적이더라고요!
자세 교정, 영상 분석, AI 피드백 시스템 같은 주제에 관심 있다면
꼭 한 번 실습해보길 추천할게요! (❁´◡`❁)
작성일 : 2025.06.23
작성자 : 발라