Sistema de captura de movimiento con MediaPipe y Blender para Ingeniería de Sistemas El código que graba el movimiento
import cv2 import mediapipe as mp import numpy as np import json import time
mp_pose = mp.solutions.pose mp_drawing = mp.solutions.drawing_utils
def normalize(v): norm = np.linalg.norm(v) return v / norm if norm > 1e-6 else np.array([0.0, 0.0,0.0])
def from_to_quaternion(src, dst): src = normalize(src) dst = normalize(dst) dot = np.dot(src, dst) if dot > 0.99999: return [1.0, 0.0, 0.0, 0.0] if dot < -0.99999: # Caso 180° axis = np.cross([1.0, 0.0, 0.0], src) if np.linalg.norm(axis) < 0.01: axis = np.cross([0.0, 1.0, 0.0], src) axis = normalize(axis) return [0.0, axis[0], axis[1], axis[2]] cross = np.cross(src, dst) q = np.array([1.0 + dot, cross[0], cross[1], cross[2]]) q /= np.linalg.norm(q) return [float(q[0]), float(q[1]), float(q[2]), float(q[3])] # w, x, y, z
def mp_to_blender(lm): return np.array([-lm.x, lm.z, lm.y]) # ¡Esta línea es la que normalmente funciona perfecto!
bone_definitions = { "Brazo.L2": (mp_pose.PoseLandmark.LEFT_SHOULDER, mp_pose.PoseLandmark.LEFT_ELBOW), "Antebrazo.L": (mp_pose.PoseLandmark.LEFT_ELBOW, mp_pose.PoseLandmark.LEFT_WRIST), "Femur.L": (mp_pose.PoseLandmark.LEFT_HIP, mp_pose.PoseLandmark.LEFT_KNEE), "Tibia.L": (mp_pose.PoseLandmark.LEFT_KNEE, mp_pose.PoseLandmark.LEFT_ANKLE), "Brazo.R2": (mp_pose.PoseLandmark.RIGHT_SHOULDER, mp_pose.PoseLandmark.RIGHT_ELBOW), "Antebrazo.R": (mp_pose.PoseLandmark.RIGHT_ELBOW, mp_pose.PoseLandmark.RIGHT_WRIST), "Femur.R": (mp_pose.PoseLandmark.RIGHT_HIP, mp_pose.PoseLandmark.RIGHT_KNEE), "Tibia.R": (mp_pose.PoseLandmark.RIGHT_KNEE, mp_pose.PoseLandmark.RIGHT_ANKLE), }
cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
with mp_pose.Pose(model_complexity=1, min_detection_confidence=0.7, min_tracking_confidence=0.7) as pose: rest_vectors = None recording = False frames = []
print("≈ Colócate en T-Pose perfecta y pulsa 't' para capturar referencia")
print("Pulsa 's' para empezar a grabar, 'q' para terminar")
while True:
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1) # espejo
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = pose.process(rgb_frame)
if results.pose_world_landmarks:
mp_drawing.draw_landmarks(frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
if rest_vectors is None:
cv2.putText(frame, "T-POSE + pulsa 't'", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,255,0), 3)
else:
status = "GRABANDO..." if recording else "Pulsa 's' para grabar"
cv2.putText(frame, status, (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,0,255), 3)
cv2.imshow('Captura Mocap - pulsa t → s → q', frame)
key = cv2.waitKey(1)
# Capturar T-Pose
if key == ord('t') and results.pose_world_landmarks:
rest_landmarks = results.pose_world_landmarks.landmark
rest_pos = {i: mp_to_blender(landmark) for i, landmark in enumerate(rest_landmarks)}
rest_vectors = {}
for bone, (p, c) in bone_definitions.items():
p_pos = rest_pos[p.value]
c_pos = rest_pos[c.value]
rest_vectors[bone] = normalize(c_pos - p_pos)
print("¡T-Pose capturada!")
# Iniciar grabación
if key == ord('s') and rest_vectors is not None:
recording = True
frames = []
print("¡Grabando!")
# Grabación en vivo
if recording and results.pose_world_landmarks:
landmarks = results.pose_world_landmarks.landmark
current_pos = {i: mp_to_blender(landmark) for i, landmark in enumerate(landmarks) if landmark.visibility > 0.5}
if len(current_pos) >= 28: # necesitamos al menos los puntos necesarios
frame_dict = {}
all_good = True
for bone, (p, c) in bone_definitions.items():
if p.value not in current_pos or c.value not in current_pos:
all_good = False
break
p_pos = current_pos[p.value]
c_pos = current_pos[c.value]
curr_vec = normalize(c_pos - p_pos)
rest_vec = rest_vectors[bone]
q = from_to_quaternion(rest_vec, curr_vec)
frame_dict[bone] = q
if all_good:
frames.append(frame_dict)
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if frames:
data = {
"fps": 30,
"frames": frames
}
with open("C:/MisAdri/mocap_animation.json", "w") as f: # ¡¡¡CAMBIA ESTA RUTA!!!
json.dump(data, f)
print(f"¡Listo! {len(frames)} frames guardados en mocap_animation.json")
else:
print("No se grabó nada")
El Script del Personaje import bpy import json from mathutils import Quaternion
json_path = "C:/MisAdri/mocap_animation.json" # ¡¡¡CAMBIA ESTA RUTA SI ES NECESARIO!!!
with open(json_path, 'r') as f: data = json.load(f)
fps = data['fps'] frames = data['frames']
armature_name = 'Esqueleto' arm = bpy.data.objects.get(armature_name) if not arm or arm.type != 'ARMATURE': raise ValueError(f"No se encontró la armadura '{armature_name}'. Asegúrate de que exista en la escena.")
scene = bpy.context.scene scene.frame_start = 1 scene.frame_end = len(frames) scene.render.fps = fps scene.render.fps_base = 1.0
for frame_idx, frame_data in enumerate(frames): scene.frame_set(frame_idx + 1) # Frames comienzan en 1
for bone_name, q_list in frame_data.items():
# Si usas mapeo: real_bone_name = bone_mapping.get(bone_name, bone_name)
real_bone_name = bone_name # Sin mapeo por defecto
if real_bone_name in arm.pose.bones:
bone = arm.pose.bones[real_bone_name]
# Los cuaterniones en el JSON están en orden [w, x, y, z]
q = Quaternion((q_list[0], q_list[1], q_list[2], q_list[3]))
# Aplicar la rotación local (asumiendo que es rotación relativa al T-Pose)
bone.rotation_quaternion = q
# Insertar keyframe
bone.keyframe_insert(data_path="rotation_quaternion", frame=frame_idx + 1)
else:
print(f"Advertencia: Hueso '{real_bone_name}' no encontrado en la armadura.")
bpy.context.view_layer.update()
print(f"Animación importada: {len(frames)} frames a {fps} FPS.") print("Para una interpolación suave, ajusta las curvas en el Graph Editor si es necesario (usa Slerp para cuaterniones).")