""" original from https://github.com/vchoutas/smplx modified by Vassilis and Yao """ import pickle import numpy as np import torch import torch.nn as nn from .lbs import ( JointsFromVerticesSelector, Struct, find_dynamic_lmk_idx_and_bcoords, lbs, to_np, to_tensor, vertices2landmarks, ) # SMPLX J14_NAMES = [ "right_ankle", "right_knee", "right_hip", "left_hip", "left_knee", "left_ankle", "right_wrist", "right_elbow", "right_shoulder", "left_shoulder", "left_elbow", "left_wrist", "neck", "head", ] SMPLX_names = [ "pelvis", "left_hip", "right_hip", "spine1", "left_knee", "right_knee", "spine2", "left_ankle", "right_ankle", "spine3", "left_foot", "right_foot", "neck", "left_collar", "right_collar", "head", "left_shoulder", "right_shoulder", "left_elbow", "right_elbow", "left_wrist", "right_wrist", "jaw", "left_eye_smplx", "right_eye_smplx", "left_index1", "left_index2", "left_index3", "left_middle1", "left_middle2", "left_middle3", "left_pinky1", "left_pinky2", "left_pinky3", "left_ring1", "left_ring2", "left_ring3", "left_thumb1", "left_thumb2", "left_thumb3", "right_index1", "right_index2", "right_index3", "right_middle1", "right_middle2", "right_middle3", "right_pinky1", "right_pinky2", "right_pinky3", "right_ring1", "right_ring2", "right_ring3", "right_thumb1", "right_thumb2", "right_thumb3", "right_eye_brow1", "right_eye_brow2", "right_eye_brow3", "right_eye_brow4", "right_eye_brow5", "left_eye_brow5", "left_eye_brow4", "left_eye_brow3", "left_eye_brow2", "left_eye_brow1", "nose1", "nose2", "nose3", "nose4", "right_nose_2", "right_nose_1", "nose_middle", "left_nose_1", "left_nose_2", "right_eye1", "right_eye2", "right_eye3", "right_eye4", "right_eye5", "right_eye6", "left_eye4", "left_eye3", "left_eye2", "left_eye1", "left_eye6", "left_eye5", "right_mouth_1", "right_mouth_2", "right_mouth_3", "mouth_top", "left_mouth_3", "left_mouth_2", "left_mouth_1", "left_mouth_5", "left_mouth_4", "mouth_bottom", "right_mouth_4", "right_mouth_5", "right_lip_1", "right_lip_2", "lip_top", "left_lip_2", "left_lip_1", "left_lip_3", "lip_bottom", "right_lip_3", "right_contour_1", "right_contour_2", "right_contour_3", "right_contour_4", "right_contour_5", "right_contour_6", "right_contour_7", "right_contour_8", "contour_middle", "left_contour_8", "left_contour_7", "left_contour_6", "left_contour_5", "left_contour_4", "left_contour_3", "left_contour_2", "left_contour_1", "head_top", "left_big_toe", "left_ear", "left_eye", "left_heel", "left_index", "left_middle", "left_pinky", "left_ring", "left_small_toe", "left_thumb", "nose", "right_big_toe", "right_ear", "right_eye", "right_heel", "right_index", "right_middle", "right_pinky", "right_ring", "right_small_toe", "right_thumb", ] extra_names = [ "head_top", "left_big_toe", "left_ear", "left_eye", "left_heel", "left_index", "left_middle", "left_pinky", "left_ring", "left_small_toe", "left_thumb", "nose", "right_big_toe", "right_ear", "right_eye", "right_heel", "right_index", "right_middle", "right_pinky", "right_ring", "right_small_toe", "right_thumb", ] SMPLX_names += extra_names part_indices = {} part_indices["body"] = np.array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 123, 124, 125, 126, 127, 132, 134, 135, 136, 137, 138, 143, ]) part_indices["torso"] = np.array([ 0, 1, 2, 3, 6, 9, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 55, 56, 57, 58, 59, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, ]) part_indices["head"] = np.array([ 12, 15, 22, 23, 24, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 125, 126, 134, 136, 137, ]) part_indices["face"] = np.array([ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, ]) part_indices["upper"] = np.array([ 12, 13, 14, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, ]) part_indices["hand"] = np.array([ 20, 21, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 128, 129, 130, 131, 133, 139, 140, 141, 142, 144, ]) part_indices["left_hand"] = np.array([ 20, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 128, 129, 130, 131, 133, ]) part_indices["right_hand"] = np.array([ 21, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 139, 140, 141, 142, 144, ]) # kinematic tree head_kin_chain = [15, 12, 9, 6, 3, 0] # --smplx joints # 00 - Global # 01 - L_Thigh # 02 - R_Thigh # 03 - Spine # 04 - L_Calf # 05 - R_Calf # 06 - Spine1 # 07 - L_Foot # 08 - R_Foot # 09 - Spine2 # 10 - L_Toes # 11 - R_Toes # 12 - Neck # 13 - L_Shoulder # 14 - R_Shoulder # 15 - Head # 16 - L_UpperArm # 17 - R_UpperArm # 18 - L_ForeArm # 19 - R_ForeArm # 20 - L_Hand # 21 - R_Hand # 22 - Jaw # 23 - L_Eye # 24 - R_Eye class SMPLX(nn.Module): """ Given smplx parameters, this class generates a differentiable SMPLX function which outputs a mesh and 3D joints """ def __init__(self, config): super(SMPLX, self).__init__() # print("creating the SMPLX Decoder") ss = np.load(config.smplx_model_path, allow_pickle=True) smplx_model = Struct(**ss) self.dtype = torch.float32 self.register_buffer( "faces_tensor", to_tensor(to_np(smplx_model.f, dtype=np.int64), dtype=torch.long), ) # The vertices of the template model self.register_buffer( "v_template", to_tensor(to_np(smplx_model.v_template), dtype=self.dtype) ) # The shape components and expression # expression space is the same as FLAME shapedirs = to_tensor(to_np(smplx_model.shapedirs), dtype=self.dtype) shapedirs = torch.cat( [ shapedirs[:, :, :config.n_shape], shapedirs[:, :, 300:300 + config.n_exp], ], 2, ) self.register_buffer("shapedirs", shapedirs) # The pose components num_pose_basis = smplx_model.posedirs.shape[-1] posedirs = np.reshape(smplx_model.posedirs, [-1, num_pose_basis]).T self.register_buffer("posedirs", to_tensor(to_np(posedirs), dtype=self.dtype)) self.register_buffer( "J_regressor", to_tensor(to_np(smplx_model.J_regressor), dtype=self.dtype) ) parents = to_tensor(to_np(smplx_model.kintree_table[0])).long() parents[0] = -1 self.register_buffer("parents", parents) self.register_buffer("lbs_weights", to_tensor(to_np(smplx_model.weights), dtype=self.dtype)) # for face keypoints self.register_buffer( "lmk_faces_idx", torch.tensor(smplx_model.lmk_faces_idx, dtype=torch.long) ) self.register_buffer( "lmk_bary_coords", torch.tensor(smplx_model.lmk_bary_coords, dtype=self.dtype), ) self.register_buffer( "dynamic_lmk_faces_idx", torch.tensor(smplx_model.dynamic_lmk_faces_idx, dtype=torch.long), ) self.register_buffer( "dynamic_lmk_bary_coords", torch.tensor(smplx_model.dynamic_lmk_bary_coords, dtype=self.dtype), ) # pelvis to head, to calculate head yaw angle, then find the dynamic landmarks self.register_buffer("head_kin_chain", torch.tensor(head_kin_chain, dtype=torch.long)) # -- initialize parameters # shape and expression self.register_buffer( "shape_params", nn.Parameter(torch.zeros([1, config.n_shape], dtype=self.dtype), requires_grad=False), ) self.register_buffer( "expression_params", nn.Parameter(torch.zeros([1, config.n_exp], dtype=self.dtype), requires_grad=False), ) # pose: represented as rotation matrx [number of joints, 3, 3] self.register_buffer( "global_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(1, 1, 1), requires_grad=False, ), ) self.register_buffer( "head_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(1, 1, 1), requires_grad=False, ), ) self.register_buffer( "neck_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(1, 1, 1), requires_grad=False, ), ) self.register_buffer( "jaw_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(1, 1, 1), requires_grad=False, ), ) self.register_buffer( "eye_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(2, 1, 1), requires_grad=False, ), ) self.register_buffer( "body_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(21, 1, 1), requires_grad=False, ), ) self.register_buffer( "left_hand_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(15, 1, 1), requires_grad=False, ), ) self.register_buffer( "right_hand_pose", nn.Parameter( torch.eye(3, dtype=self.dtype).unsqueeze(0).repeat(15, 1, 1), requires_grad=False, ), ) if config.extra_joint_path: self.extra_joint_selector = JointsFromVerticesSelector(fname=config.extra_joint_path) self.use_joint_regressor = True self.keypoint_names = SMPLX_names if self.use_joint_regressor: with open(config.j14_regressor_path, "rb") as f: j14_regressor = pickle.load(f, encoding="latin1") source = [] target = [] for idx, name in enumerate(self.keypoint_names): if name in J14_NAMES: source.append(idx) target.append(J14_NAMES.index(name)) source = np.asarray(source) target = np.asarray(target) self.register_buffer("source_idxs", torch.from_numpy(source)) self.register_buffer("target_idxs", torch.from_numpy(target)) self.register_buffer( "extra_joint_regressor", torch.from_numpy(j14_regressor).to(torch.float32) ) self.part_indices = part_indices def forward( self, shape_params=None, expression_params=None, global_pose=None, body_pose=None, jaw_pose=None, eye_pose=None, left_hand_pose=None, right_hand_pose=None, ): """ Args: shape_params: [N, number of shape parameters] expression_params: [N, number of expression parameters] global_pose: pelvis pose, [N, 1, 3, 3] body_pose: [N, 21, 3, 3] jaw_pose: [N, 1, 3, 3] eye_pose: [N, 2, 3, 3] left_hand_pose: [N, 15, 3, 3] right_hand_pose: [N, 15, 3, 3] Returns: vertices: [N, number of vertices, 3] landmarks: [N, number of landmarks (68 face keypoints), 3] joints: [N, number of smplx joints (145), 3] """ if shape_params is None: batch_size = global_pose.shape[0] shape_params = self.shape_params.expand(batch_size, -1) else: batch_size = shape_params.shape[0] if expression_params is None: expression_params = self.expression_params.expand(batch_size, -1) if global_pose is None: global_pose = self.global_pose.unsqueeze(0).expand(batch_size, -1, -1, -1) if body_pose is None: body_pose = self.body_pose.unsqueeze(0).expand(batch_size, -1, -1, -1) if jaw_pose is None: jaw_pose = self.jaw_pose.unsqueeze(0).expand(batch_size, -1, -1, -1) if eye_pose is None: eye_pose = self.eye_pose.unsqueeze(0).expand(batch_size, -1, -1, -1) if left_hand_pose is None: left_hand_pose = self.left_hand_pose.unsqueeze(0).expand(batch_size, -1, -1, -1) if right_hand_pose is None: right_hand_pose = self.right_hand_pose.unsqueeze(0).expand(batch_size, -1, -1, -1) shape_components = torch.cat([shape_params, expression_params], dim=1) full_pose = torch.cat( [ global_pose, body_pose, jaw_pose, eye_pose, left_hand_pose, right_hand_pose, ], dim=1, ) template_vertices = self.v_template.unsqueeze(0).expand(batch_size, -1, -1) # smplx vertices, joints = lbs( shape_components, full_pose, template_vertices, self.shapedirs, self.posedirs, self.J_regressor, self.parents, self.lbs_weights, dtype=self.dtype, pose2rot=False, ) # face dynamic landmarks lmk_faces_idx = self.lmk_faces_idx.unsqueeze(dim=0).expand(batch_size, -1) lmk_bary_coords = self.lmk_bary_coords.unsqueeze(dim=0).expand(batch_size, -1, -1) dyn_lmk_faces_idx, dyn_lmk_bary_coords = find_dynamic_lmk_idx_and_bcoords( vertices, full_pose, self.dynamic_lmk_faces_idx, self.dynamic_lmk_bary_coords, self.head_kin_chain, ) lmk_faces_idx = torch.cat([lmk_faces_idx, dyn_lmk_faces_idx], 1) lmk_bary_coords = torch.cat([lmk_bary_coords, dyn_lmk_bary_coords], 1) landmarks = vertices2landmarks(vertices, self.faces_tensor, lmk_faces_idx, lmk_bary_coords) final_joint_set = [joints, landmarks] if hasattr(self, "extra_joint_selector"): # Add any extra joints that might be needed extra_joints = self.extra_joint_selector(vertices, self.faces_tensor) final_joint_set.append(extra_joints) # Create the final joint set joints = torch.cat(final_joint_set, dim=1) # if self.use_joint_regressor: # reg_joints = torch.einsum("ji,bik->bjk", # self.extra_joint_regressor, vertices) # joints[:, self.source_idxs] = reg_joints[:, self.target_idxs] return vertices, landmarks, joints def pose_abs2rel(self, global_pose, body_pose, abs_joint="head"): """change absolute pose to relative pose Basic knowledge for SMPLX kinematic tree: absolute pose = parent pose * relative pose Here, pose must be represented as rotation matrix (batch_sizexnx3x3) """ if abs_joint == "head": # Pelvis -> Spine 1, 2, 3 -> Neck -> Head kin_chain = [15, 12, 9, 6, 3, 0] elif abs_joint == "neck": # Pelvis -> Spine 1, 2, 3 -> Neck -> Head kin_chain = [12, 9, 6, 3, 0] elif abs_joint == "right_wrist": # Pelvis -> Spine 1, 2, 3 -> right Collar -> right shoulder # -> right elbow -> right wrist kin_chain = [21, 19, 17, 14, 9, 6, 3, 0] elif abs_joint == "left_wrist": # Pelvis -> Spine 1, 2, 3 -> Left Collar -> Left shoulder # -> Left elbow -> Left wrist kin_chain = [20, 18, 16, 13, 9, 6, 3, 0] else: raise NotImplementedError(f"pose_abs2rel does not support: {abs_joint}") batch_size = global_pose.shape[0] dtype = global_pose.dtype device = global_pose.device full_pose = torch.cat([global_pose, body_pose], dim=1) rel_rot_mat = ( torch.eye(3, device=device, dtype=dtype).unsqueeze_(dim=0).repeat(batch_size, 1, 1) ) for idx in kin_chain[1:]: rel_rot_mat = torch.bmm(full_pose[:, idx], rel_rot_mat) # This contains the absolute pose of the parent abs_parent_pose = rel_rot_mat.detach() # Let's assume that in the input this specific joint is predicted as an absolute value abs_joint_pose = body_pose[:, kin_chain[0] - 1] # abs_head = parents(abs_neck) * rel_head ==> rel_head = abs_neck.T * abs_head rel_joint_pose = torch.matmul( abs_parent_pose.reshape(-1, 3, 3).transpose(1, 2), abs_joint_pose.reshape(-1, 3, 3), ) # Replace the new relative pose body_pose[:, kin_chain[0] - 1, :, :] = rel_joint_pose return body_pose def pose_rel2abs(self, global_pose, body_pose, abs_joint="head"): """change relative pose to absolute pose Basic knowledge for SMPLX kinematic tree: absolute pose = parent pose * relative pose Here, pose must be represented as rotation matrix (batch_sizexnx3x3) """ full_pose = torch.cat([global_pose, body_pose], dim=1) if abs_joint == "head": # Pelvis -> Spine 1, 2, 3 -> Neck -> Head kin_chain = [15, 12, 9, 6, 3, 0] elif abs_joint == "neck": # Pelvis -> Spine 1, 2, 3 -> Neck -> Head kin_chain = [12, 9, 6, 3, 0] elif abs_joint == "right_wrist": # Pelvis -> Spine 1, 2, 3 -> right Collar -> right shoulder # -> right elbow -> right wrist kin_chain = [21, 19, 17, 14, 9, 6, 3, 0] elif abs_joint == "left_wrist": # Pelvis -> Spine 1, 2, 3 -> Left Collar -> Left shoulder # -> Left elbow -> Left wrist kin_chain = [20, 18, 16, 13, 9, 6, 3, 0] else: raise NotImplementedError(f"pose_rel2abs does not support: {abs_joint}") rel_rot_mat = torch.eye(3, device=full_pose.device, dtype=full_pose.dtype).unsqueeze_(dim=0) for idx in kin_chain: rel_rot_mat = torch.matmul(full_pose[:, idx], rel_rot_mat) abs_pose = rel_rot_mat[:, None, :, :] return abs_pose