7e4ef6f98b
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
169 lines
5.8 KiB
Python
169 lines
5.8 KiB
Python
import os
|
|
import json
|
|
import functools
|
|
|
|
import numpy as np
|
|
|
|
from body_model import OP_NUM_JOINTS
|
|
from scipy.interpolate import interp1d
|
|
from scipy.spatial.transform import Rotation, Slerp
|
|
|
|
|
|
def read_keypoints(keypoint_fn):
|
|
"""
|
|
Only reads body keypoint data of first person.
|
|
"""
|
|
empty_kps = np.zeros((OP_NUM_JOINTS, 3), dtype=np.float32)
|
|
if not os.path.isfile(keypoint_fn):
|
|
return empty_kps
|
|
|
|
with open(keypoint_fn) as keypoint_file:
|
|
data = json.load(keypoint_file)
|
|
|
|
if len(data["people"]) == 0:
|
|
print("WARNING: Found no keypoints in %s! Returning zeros!" % (keypoint_fn))
|
|
return empty_kps
|
|
|
|
person_data = data["people"][0]
|
|
body_keypoints = np.array(person_data["pose_keypoints_2d"], dtype=np.float32)
|
|
body_keypoints = body_keypoints.reshape([-1, 3])
|
|
return body_keypoints
|
|
|
|
|
|
def load_keypoints_with_interp(kp_paths, interp=True):
|
|
"""
|
|
Load 2D keypoints from paths and interpolate missing frames between tmin and tmax.
|
|
|
|
Args:
|
|
kp_paths: List of paths to keypoint JSON files
|
|
interp: Whether to interpolate missing frames
|
|
|
|
Returns:
|
|
joints2d_data: (T, J, 3) array of keypoints with interpolation
|
|
"""
|
|
# Load all keypoints
|
|
joints2d_data = np.stack([read_keypoints(p) for p in kp_paths], axis=0).astype(np.float32)
|
|
|
|
if not interp:
|
|
return joints2d_data
|
|
|
|
# Find which frames have valid keypoints (file exists and has data)
|
|
vis_mask = np.array([os.path.isfile(p) for p in kp_paths])
|
|
# Also check if keypoints are not all zeros
|
|
has_data = ~np.all(joints2d_data == 0, axis=(1, 2))
|
|
vis_mask = vis_mask & has_data
|
|
vis_idcs = np.where(vis_mask)[0]
|
|
|
|
if len(vis_idcs) == 0 or len(vis_idcs) == 1:
|
|
# No interpolation needed
|
|
return joints2d_data
|
|
|
|
# Interpolate only between tmin and tmax (same as MANO interpolation)
|
|
T, J, _ = joints2d_data.shape
|
|
tmin, tmax = min(vis_idcs), max(vis_idcs) + 1
|
|
times = np.arange(tmin, tmax)
|
|
|
|
# Interpolate each joint's x, y coordinates separately
|
|
for j in range(J):
|
|
# Interpolate x coordinate
|
|
x_interp = interp1d(vis_idcs, joints2d_data[vis_idcs, j, 0],
|
|
kind='linear', bounds_error=False)
|
|
# Interpolate y coordinate
|
|
y_interp = interp1d(vis_idcs, joints2d_data[vis_idcs, j, 1],
|
|
kind='linear', bounds_error=False)
|
|
|
|
# Interpolate for frames between tmin and tmax
|
|
for t in times:
|
|
if t not in vis_idcs:
|
|
# Interpolate
|
|
joints2d_data[t, j, 0] = x_interp(t)
|
|
joints2d_data[t, j, 1] = y_interp(t)
|
|
|
|
# Set confidence as average of neighboring visible frames
|
|
prev_vis = vis_idcs[vis_idcs < t]
|
|
next_vis = vis_idcs[vis_idcs > t]
|
|
if len(prev_vis) > 0 and len(next_vis) > 0:
|
|
conf_prev = joints2d_data[prev_vis[-1], j, 2]
|
|
conf_next = joints2d_data[next_vis[0], j, 2]
|
|
joints2d_data[t, j, 2] = (conf_prev + conf_next) / 2.0 * 0.8 # Slightly lower confidence for interpolated
|
|
elif len(prev_vis) > 0:
|
|
joints2d_data[t, j, 2] = joints2d_data[prev_vis[-1], j, 2] * 0.8
|
|
elif len(next_vis) > 0:
|
|
joints2d_data[t, j, 2] = joints2d_data[next_vis[0], j, 2] * 0.8
|
|
|
|
return joints2d_data
|
|
|
|
|
|
def read_mask_path(path):
|
|
mask_path = None
|
|
if not os.path.isfile(path):
|
|
return mask_path
|
|
|
|
with open(path, "r") as f:
|
|
data = json.load(path)
|
|
|
|
person_data = data["people"][0]
|
|
if "mask_path" in person_data:
|
|
mask_path = person_data["mask_path"]
|
|
|
|
return mask_path
|
|
|
|
|
|
def read_mano_preds(pred_path, tid, num_betas=10):
|
|
"""
|
|
reads the betas, body_pose, global orientation and translation of a mano prediction
|
|
exported from phalp outputs
|
|
returns betas (10,), body_pose (23, 3), global_orientation (3,), translation (3,)
|
|
"""
|
|
pose = np.zeros((15, 3))
|
|
rot = np.zeros(3)
|
|
trans = np.zeros(3)
|
|
betas = np.zeros(num_betas)
|
|
if not os.path.isfile(pred_path):
|
|
return pose, rot, trans, betas, int(tid)
|
|
|
|
with open(pred_path, "r") as f:
|
|
data = json.load(f)
|
|
|
|
pose = np.array(data["body_pose"], dtype=np.float32)
|
|
rot = np.array(data["global_orient"], dtype=np.float32)
|
|
trans = np.array(data["cam_trans"], dtype=np.float32)
|
|
betas = np.array(data["betas"], dtype=np.float32)
|
|
is_right = np.array(data["is_right"], dtype=np.float32)
|
|
|
|
return pose, rot, trans, betas, is_right
|
|
|
|
|
|
def load_mano_preds(pred_paths, tid, interp=True, num_betas=10):
|
|
vis_mask = np.array([os.path.isfile(x) for x in pred_paths])
|
|
vis_idcs = np.where(vis_mask)[0]
|
|
|
|
# load single image mano predictions
|
|
stack_fnc = functools.partial(np.stack, axis=0)
|
|
# (N, 16, 3), (N, 3), (N, 3), (N, 10)
|
|
pose, orient, trans, betas, is_right = map(
|
|
stack_fnc, zip(*[read_mano_preds(p, tid, num_betas=num_betas) for p in pred_paths])
|
|
)
|
|
|
|
assert len(np.where(is_right!=int(tid))[0]) == 0
|
|
if not interp:
|
|
return pose, orient, trans, betas, is_right
|
|
|
|
# interpolate the occluded tracks
|
|
orient_slerp = Slerp(vis_idcs, Rotation.from_rotvec(orient[vis_idcs]))
|
|
trans_interp = interp1d(vis_idcs, trans[vis_idcs], axis=0)
|
|
betas_interp = interp1d(vis_idcs, betas[vis_idcs], axis=0)
|
|
|
|
tmin, tmax = min(vis_idcs), max(vis_idcs) + 1
|
|
times = np.arange(tmin, tmax)
|
|
orient[times] = orient_slerp(times).as_rotvec()
|
|
trans[times] = trans_interp(times)
|
|
betas[times] = betas_interp(times)
|
|
|
|
# interpolate for each joint angle
|
|
for i in range(pose.shape[1]):
|
|
pose_slerp = Slerp(vis_idcs, Rotation.from_rotvec(pose[vis_idcs, i]))
|
|
pose[times, i] = pose_slerp(times).as_rotvec()
|
|
|
|
return pose, orient, trans, betas, is_right
|