SATA / src /sata /utils /motion_utils.py
zzysteve
Initial commit
5221c8c
Raw
History Blame Contribute Delete
14.2 kB
from fairmotion.ops import conversions
import numpy as np
from IPython import embed
from fairmotion.core import motion as motion_class
from fairmotion.ops import motion as fm_motion_util
from fairmotion.utils import constants
import copy
from fairmotion.ops.motion import transform_poses
""" ====================== STITCHING METHODS ======================= """
def simple_stitch(cur_pose, next_poses, align_id=0):
next_pose = next_poses[align_id]
nextT = next_pose.get_root_facing_transform_byRoot(use_height=False)
curT = cur_pose.get_root_facing_transform_byRoot(use_height=False)
alignT = curT @ np.linalg.inv(nextT)
fm_motion_util.transform_poses(next_poses, alignT, local=False)
return next_poses
def overlap_blend(from_poses, to_poses, skel_preserve=False):
# CAUTION: assert already aligned
assert len(from_poses) == len(to_poses)
F = len(from_poses)
ratios = [0.5 - 0.5 * np.cos(i / (F - 1) * np.pi) for i in range(F)]
for i in range(F):
to_poses[i] = fm_motion_util.blend(
from_poses[i], to_poses[i], ratios[i], skel_preserve
)
return to_poses, ratios
""" ====================== NORMALIZE/DENORMALIZE MOTION ======================="""
## HERE WE MEAN "NORMALIZE" == make every joint xform from parent joint to be identity matrix
## adjust height according to tpose contacts
def motion_normalize(motion, tpose=None):
if tpose is None:
tpose = motion.poses[0]
recon_motion = motion_class.Motion(skel=copy.deepcopy(motion.skel))
recon_motion.set_fps(motion.fps)
for i, joint in enumerate(recon_motion.skel.joints):
if joint.parent_joint is not None:
joint.xform_from_parent_joint = constants.eye_T()
joint.xform_global = constants.eye_T()
for i, joint in enumerate(recon_motion.skel.joints):
jidx = recon_motion.skel.get_index_joint(joint)
jpos = tpose.get_transform(jidx, local=False)[0:3, 3]
if joint.parent_joint is not None:
pidx = recon_motion.skel.get_index_joint(joint.parent_joint)
ppos = tpose.get_transform(pidx, local=False)[0:3, 3]
else:
ppos = jpos
T_new = constants.eye_T()
T_new[0:3, 3] = jpos - ppos
joint.xform_from_parent_joint = T_new
if joint.parent_joint is not None:
joint.xform_global = np.dot(
joint.parent_joint.xform_global,
joint.xform_from_parent_joint,
)
else:
joint.xform_global = joint.xform_from_parent_joint
for el in joint.extra_links:
elpos = np.dot(
tpose.get_transform(jidx, local=False), joint.extra_links[el]
)[0:3, 3]
joint.extra_links[el] = constants.eye_T()
joint.extra_links[el][0:3, 3] = elpos - jpos
num_frames = len(motion.poses)
T_relative = np.zeros((num_frames, motion.skel.num_joints(), 4, 4))
T_relative[...] = constants.eye_T()
for i in range(num_frames):
recon_motion.add_one_frame(T_relative[i])
motion_local = motion.to_matrix(local=True) # [F, J, 4, 4]
motion_global = motion.to_matrix(local=False)
for j, (joint, recon_joint) in enumerate(
zip(motion.skel.joints, recon_motion.skel.joints)
):
if joint.parent_joint is None:
try:
# broadcast ok : [F, 3, 3] @ [3, 3] = [F, 3, 3]
T_relative[:, j, :3, :3] = motion_local[:, j, :3, :3] @ np.linalg.inv(
tpose.data[j][:3, :3]
)
T_relative[:, j, :3, 3] = motion_local[:, j, :3, 3]
except:
embed()
else:
pidx = motion.skel.get_index_joint(joint.parent_joint)
jidx = motion.skel.get_index_joint(joint)
assert j == jidx
gp = motion_global[:, pidx, :3, :3]
gj = motion_global[:, jidx, :3, :3]
p2j = joint.xform_from_parent_joint[:3, :3]
delta_global = constants.eye_R()
delta_global = gj @ np.linalg.inv(gp @ p2j @ tpose.data[j][:3, :3])
new_gp = recon_motion.get_joint_transforms(joint, local=False)[:, :3, :3]
new_p2j = recon_joint.xform_from_parent_joint[:3, :3]
new_pg_p2j = new_gp @ new_p2j
delta_local = constants.eye_R()
delta_local = np.linalg.inv(new_pg_p2j) @ delta_global @ new_pg_p2j
T_relative[:, j, :3, :3] = delta_local
return recon_motion
# TH)
def copy_normalized_joint(norm_motion, out_motion, joint_name):
# assume that parent joint's name is identical
# and skeleton hierarchy is the same other than missing joints
norm_joint = norm_motion.skel.get_joint(joint_name)
norm_parent_joint = norm_joint.parent_joint
out_parent_joint = out_motion.skel.get_joint(norm_parent_joint.name)
# multiply output parent joint's xform inverse
# and multiply input parent joint's xform inverse
I = constants.eye_T()
xform_bw_skel = (
np.linalg.inv(out_parent_joint.xform_global) @ norm_parent_joint.xform_global
)
orig_xform = norm_joint.xform_from_parent_joint
out_xform = xform_bw_skel @ orig_xform
out_new_joint = motion_class.Joint(
joint_name, xform_from_parent_joint=out_xform, parent_joint=out_parent_joint
)
out_motion.skel.add_joint(out_new_joint, out_parent_joint)
for pose in out_motion.poses:
assert pose.skel is out_motion.skel
# add tpose xform for fingers which should be zero in input motion
data = pose.data
pose.data = np.concatenate(
(pose.data, np.expand_dims(out_xform, axis=0)), axis=0
)
# TH)
def motion_denormalize(norm_motion, tpose_motion, add_missing_joints=False):
# Resize tpose_motion's transform
norm_bvh_joints = list(map(lambda x: x.name, norm_motion.skel.joints))
tpose_bvh_joints = list(map(lambda x: x.name, tpose_motion.skel.joints))
parent_names = set(
map(
lambda x: x.parent_joint.name if x.parent_joint is not None else ":None:",
norm_motion.skel.joints,
)
)
error = False
for joint_name in norm_bvh_joints:
in_joint = norm_motion.skel.get_joint(joint_name)
in_parent_name = (
in_joint.parent_joint.name
if in_joint.parent_joint is not None
else ":None:"
)
if joint_name not in tpose_bvh_joints:
if in_parent_name in tpose_bvh_joints and add_missing_joints:
copy_normalized_joint(norm_motion, tpose_motion, joint_name)
tpose_bvh_joints.append(joint_name)
continue
else:
print("Required joint {} is missing in tpose".format(joint_name))
error = True
t_joint = tpose_motion.skel.get_joint(joint_name)
t_parent_name = (
t_joint.parent_joint.name if t_joint.parent_joint is not None else ":None:"
)
if in_parent_name != t_parent_name:
print(
"{}'s parent joint name is inconsistent: {} and {}".format(
joint_name, in_parent_name, t_parent_name
)
)
error = True
if error:
return "Failed to denormalize motion {}".format(norm_motion.name)
# Normalize zero-pose of denormalized skeleton
zero_pose = motion_class.Pose(tpose_motion.skel)
tpose_motion.add_one_frame(zero_pose.data)
norm_tpose_motion = motion_normalize(tpose_motion)
norm_zero_pose = norm_tpose_motion.poses[-1]
# Denormalize motion with normalized zero position skel pose
in_zero_pose = motion_class.Pose(norm_motion.skel)
for i, joint in enumerate(norm_motion.skel.joints):
if joint.name in tpose_bvh_joints:
data_idx = tpose_motion.skel.get_index_joint(joint.name)
in_zero_pose.data[i] = norm_zero_pose.data[data_idx]
norm_motion_denorm = motion_normalize(norm_motion, tpose=in_zero_pose)
return norm_motion_denorm
def tpose_height_to_skel(motion, tpose, apply_=False):
if apply_:
res_motion = motion
else:
res_motion = copy.deepcopy(motion)
tpose_root = tpose.data[0][:3, 3]
tpose_root[0] = 0
tpose_root[2] = 0
for n_pose in res_motion.poses:
n_pose.data[0, :3, 3] -= tpose_root
root_new_xform_global = res_motion.skel.joints[0].xform_global
root_new_xform_global[:3, 3] += tpose_root
res_motion.skel.joints[0].set_xform_global_recursive(root_new_xform_global)
return res_motion
def motion_normalize_h2s(motion, handle_penetration=True):
tpose = motion.poses[0]
if handle_penetration:
skel = motion.skel
joint_names = [j.name for j in skel.joints]
lt = "LeftToeBase_End" if "LeftToeBase_End" in joint_names else "LeftToe_End"
rt = "RightToeBase_End" if "RightToeBase_End" in joint_names else "RightToe_End"
if not (lt in joint_names) or not (rt in joint_names):
print("handle penetration Err")
embed()
exit()
lty = tpose.get_transform(lt, local=False)[1, 3]
rty = tpose.get_transform(rt, local=False)[1, 3]
if (lty + rty) / 2 < 0:
print("handle penetrating t-pose")
print(motion.name)
# embed()
transform_poses([tpose], conversions.p2T([0, -(lty + rty) / 2.0, 0]))
n_motion = motion_normalize(motion, tpose)
n_tpose = n_motion.poses[0]
n_motion.poses = n_motion.poses[1:]
tpose_height_to_skel(n_motion, n_tpose, apply_=True)
# f_ids = get_foot_indices(n_motion.skel)
# n_motion.contact = get_foot_contact_ratio(n_motion.poses, f_ids)
return n_motion, n_tpose
""" ====================== other ... ======================="""
def get_links(skel):
"""
ex)
[[0, 1, 2, 3, 4, 5],
[0, 6, 7, 8, 9, 10],
[0, 11, 12, 13],
[13, 14, 15, 16],
[13, 17, 18, 19, 20],
[13, 21, 22, 23, 24]]
"""
tree = []
mask = np.array([0] * len(skel.joints))
def dfs_tweak(joint):
ji = skel.get_index_joint(joint)
mask[ji] = 1
if ji != 0 and len(joint.child_joints) > 1:
tree[-1].append(ji)
for child in joint.child_joints:
ci = skel.get_index_joint(child)
if len(joint.child_joints) > 1:
tree.append([])
tree[-1].append(ji)
if not mask[ci]:
dfs_tweak(child)
if len(joint.child_joints) == 0:
tree[-1].append(ji)
dfs_tweak(skel.joints[0])
return tree
def get_edge(skel, bidirection=True):
"""
input: skeletona
- bidiretion
- TRUE: (parent, child) and (child, parent)
- FALSE: (parent, child) only
- include_ee: treat end-effector as joint or not
output: edge [2, E]
"""
edge = []
for joint in skel.joints:
ji = skel.get_index_joint(joint)
for child in joint.child_joints:
ci = skel.get_index_joint(child)
edge.append([ji, ci])
if bidirection:
edge.append([ci, ji])
return np.array(edge).transpose(1, 0)
def skel_interpolate(skel_a, skel_b, ratio):
# assert joint names are the same
joint_names_a = [joint.name for joint in skel_a.joints]
joint_names_b = [joint.name for joint in skel_b.joints]
assert set(joint_names_a) == set(joint_names_b)
skel_new = copy.deepcopy(skel_a)
for joint_name in joint_names_a:
joint_a = skel_a.get_joint(joint_name)
joint_b = skel_b.get_joint(joint_name)
joint_new = skel_new.get_joint(joint_name)
joint_new.xform_from_parent_joint[:3, 3] = (
1 - ratio
) * joint_a.xform_from_parent_joint[
:3, 3
] + ratio * joint_b.xform_from_parent_joint[
:3, 3
]
for joint_new in skel_new.joints:
if joint_new.parent_joint is None:
continue
joint_new.xform_global = np.dot(
joint_new.parent_joint.xform_global,
joint_new.xform_from_parent_joint,
)
return skel_new
from fairmotion.utils import contact_utils
def make_motion(
skel,
qR,
ra_T,
c,
first_frame_zero=False,
contact_cleanup=False,
cid=None,
motion=None,
):
"""
skel motion_class.Skel
qR arr [T, J, 3, 3]
ra_T arr [T, 4, 4] # root height applied here
c arr [T, J, 1]
cid list [int]
"""
frame_num, joint_num = qR.shape[0], qR.shape[1]
poses_T = np.zeros((frame_num, joint_num, 4, 4))
poses_T[...] = constants.eye_T()
poses_T[..., :3, :3] = qR
if first_frame_zero:
ra_T[0] = constants.eye_T()
poses_T[:, 0] = ra_T @ poses_T[:, 0]
poses_T[:, 0, 1, 3] = ra_T[:, 1, 3] - skel.joints[0].xform_from_parent_joint[1, 3]
if not motion:
motion = motion_class.Motion(skel=skel)
for f in range(frame_num):
motion.add_one_frame(poses_T[f])
if contact_cleanup:
assert cid is not None
toe_idx = [int(cid[1]), int(cid[3])]
if contact_cleanup:
# print("toe_idx: ", toe_idx)
contact_utils.init_contact(motion, 0, toe_idx)
contact_utils.motion_foot_cleanup(motion, c[:, toe_idx, 0])
np.set_printoptions(precision=5, suppress=True)
return motion, (toe_idx, c) # c[:, toe_idx, 0])
else:
return motion, (list(range(joint_num)), c)
if __name__ == "__main__":
import os
from .mypath import *
from fairmotion.data import bvh
motion_megan = bvh.load(
os.path.join(DATA_DIR, "characters", "polished_bvh", "megan.bvh")
)
n_motion_megan, n_tpose_megan = motion_normalize_h2s(motion_megan)
skel_megan = n_motion_megan.skel
motion_mousey = bvh.load(
os.path.join(DATA_DIR, "characters", "polished_bvh", "mousey.bvh")
)
n_motion_mousey, n_tpose_mousey = motion_normalize_h2s(motion_mousey)
skel_mousey = n_motion_mousey.skel
new_skel = skel_interpolate(skel_megan, skel_mousey, 0.5)