Aplicación práctica de MediaPipe Pose en Python | Detección de Posturas
¡Hola, hola, Omesitos! En el anterior post habíamos estado hablando sobre MediaPipe Pose. Gracias a este podemos detectar 33 puntos distribuidos en el cuerpo. En esta ocasión veremos como ponerlo en práctica al detectar la postura de Gyomei Himejima, uno de los pilares de Demond Slayer. Pero más allá de este ejemplo, con lo que quiero que te quedes es con el proceso que llevaremos a cabo al usar distintos landmarks y que lo puedas extrapolar a otros proyectos basados en videojuegos, deportes, entre otros. En los que utilices los puntos que ofrecen los modelos de estimación de postura. ¡Empecemos!
🔧 Instalación de MediaPipe
Para llevar a cabo esta práctica debemos instalar MediaPipe, a través de:
pip install mediapipe
Puedes verificar su instalación y versión con: pip freeze
Nota: A continuación te dejo la programación usada en el video. Si quieres ver el paso a paso explicado con más detalle, ¡no te pierdas el video completo!
Detección de postura de Gyomei Himejima con Pose Landmark Detection de MediaPipe
import cv2
import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import numpy as np
def calculate_distance(p1, p2):
"""Calcula la distancia euclidiana entre dos puntos (x, y)."""
p1 = np.array(p1)
p2 = np.array(p2)
return np.linalg.norm(p1 - p2)
# ----- CONFIGURACIÓN DEL MODELO -----
options = vision.PoseLandmarkerOptions(
base_options=BaseOptions(model_asset_path="pose_landmarker_full.task"),
running_mode=vision.RunningMode.VIDEO)
landmarker = vision.PoseLandmarker.create_from_options(options)
# ----- LECTURA DE IMAGEN -----
image = cv2.imread("gyomei.png", cv2.IMREAD_UNCHANGED)
image = cv2.resize(image, None, fx=0.25, fy=0.25)
i = 0 # Contador para deslizar la imagen de Gyomei
# ----- LECTURA DEL VIDEO -----
cap = cv2.VideoCapture("./Video_01.mp4")
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
fps = cap.get(cv2.CAP_PROP_FPS)
# Colores iniciales
wrist_text_color = (255, 255, 255)
index_text_color = (255, 255, 255)
chest_text_color = (255, 255, 255)
for frame_index in range(int(frame_count)):
ret, frame = cap.read()
if ret == False:
break
output_frame = frame.copy()
h, w, _ = frame.shape
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb)
# Marca temporal del frame (milisegundos)
frame_timestamp_ms = int(1000 * frame_index / fps)
# Detección de pose
pose_landmarker_result = landmarker.detect_for_video(frame_rgb, frame_timestamp_ms)
for lm in pose_landmarker_result.pose_landmarks:
# ----- Landmarks para trabajar la postura Gyomei -----
# Oreja izquierda y derecha
left_ear = int(lm[7].x * w), int(lm[7].y * h)
right_ear = int(lm[8].x * w), int(lm[8].y * h)
# Muñeca izquierda y derecha
left_wrist = int(lm[15].x * w), int(lm[15].y * h)
right_wrist = int(lm[16].x * w), int(lm[16].y * h)
# Base del dedo índice izquierdo y derecho
left_index = int(lm[19].x * w), int(lm[19].y * h)
right_index = int(lm[20].x * w), int(lm[20].y * h)
# Hombro izquierdo y derecho
left_shoulder = int(lm[11].x * w), int(lm[11].y * h)
right_shoulder = int(lm[12].x * w), int(lm[12].y * h)
# Cadera izquierda y derecha
left_hip = int(lm[23].x * w), int(lm[23].y * h)
right_hip = int(lm[24].x * w), int(lm[24].y * h)
# ----- Evaluar contacto de manos -----
dist_wrist = calculate_distance(left_wrist, right_wrist)
dist_ear = calculate_distance(left_ear, right_ear)
dist_index = calculate_distance(left_index, right_index)
wrist_text_color = (253, 81, 139) if dist_wrist < dist_ear else (255, 255, 255)
index_text_color = (253, 81, 139) if dist_index < dist_ear else (255, 255, 255)
# Dibujar líneas entre muñecas e índices
cv2.circle(output_frame, left_wrist, 6, (0, 255, 255), -1)
cv2.circle(output_frame, right_wrist, 6, (128, 255, 0), -1)
cv2.line(output_frame, left_wrist, right_wrist, (253, 81, 139), 2)
cv2.circle(output_frame, left_index, 6, (255, 255, 0), -1)
cv2.circle(output_frame, right_index, 6, (255, 0, 255), -1)
cv2.line(output_frame, left_index, right_index, (253, 81, 139), 2)
# ----- Evaluar muñecas sobre el pecho -----
# Punto medio entre hombro y cadera (definición de región torácica)
mid_left_chest = (
left_shoulder[0] + (left_hip[0] - left_shoulder[0]) // 2,
left_shoulder[1] + (left_hip[1] - left_shoulder[1]) // 2
)
mid_right_chest = (
right_shoulder[0] + (right_hip[0] - right_shoulder[0]) // 2,
right_shoulder[1] + (right_hip[1] - right_shoulder[1]) // 2
)
# Crear polígono del área del pecho
mask = np.zeros_like(frame)
chest_polygon = np.array([[right_shoulder], [left_shoulder], [mid_left_chest], [mid_right_chest]])
cv2.fillPoly(mask, pts=[chest_polygon], color=(253, 81, 139))
#cv2.imshow("mask", mask)
# Verificar si las muñecas están dentro del área del pecho
inside_left = cv2.pointPolygonTest(chest_polygon, left_wrist, False)
inside_right = cv2.pointPolygonTest(chest_polygon, right_wrist, False)
chest_text_color = (253, 81, 139) if inside_left > 0 and inside_right > 0 else (255, 255, 255)
# Superpomer la máscara
output_frame = cv2.addWeighted(output_frame, 1, mask, 0.5, 0)
# ----- Visualización -----
cv2.putText(output_frame, "WRIST", (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, wrist_text_color, 2)
cv2.putText(output_frame, "INDEX", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, index_text_color, 2)
cv2.putText(output_frame, "CHEST AREA", (10, 80),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, chest_text_color, 2)
# Visualizar a Gyomei
if wrist_text_color == (253, 81, 139) and index_text_color == (253, 81, 139) and chest_text_color == (253, 81, 139):
if i >= image.shape[0]:
i = image.shape[0]
else:
i += 3
M = np.float32([[1, 0, w//2], [0, 1, h - i]])
translated = cv2.warpAffine(image, M, (w, h))
#cv2.imshow('translated', translated)
mask_gyomei = cv2.cvtColor(translated[:,:,3], cv2.COLOR_GRAY2BGR)
#cv2.imshow('mask_gyomei', mask_gyomei)
output_frame = cv2.subtract(output_frame, mask_gyomei)
output_frame = cv2.add(output_frame, translated[:,:,:3])
else:
i = 0
cv2.imshow('Video', frame)
cv2.imshow('output_frame', output_frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
Y eso ha sido todo por este post, Omesitos.
Espero que les haya resultado útil y que se animen a probarlo.
📌 No olviden revisar el video si desean ver todo el paso a paso en acción.
¡Nos vemos en el siguiente post!



