Spaces:
Running
Running
| 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) | |