Spaces:
Sleeping
Sleeping
import copy | |
import math | |
import time, random | |
import torch | |
import numpy as np | |
import cv2 | |
import smplx | |
import pickle | |
import trimesh | |
import os, glob | |
import argparse | |
from .lib.networks.faceverse_torch import FaceVerseModel | |
def estimate_rigid_transformation(src, tgt): | |
src = src.transpose() | |
tgt = tgt.transpose() | |
mu1, mu2 = src.mean(axis=1, keepdims=True), tgt.mean(axis=1, keepdims=True) | |
X1, X2 = src - mu1, tgt - mu2 | |
K = X1.dot(X2.T) | |
U, s, Vh = np.linalg.svd(K) | |
V = Vh.T | |
Z = np.eye(U.shape[0]) | |
Z[-1, -1] *= np.sign(np.linalg.det(U.dot(V.T))) | |
R = V.dot(Z.dot(U.T)) | |
t = mu2 - R.dot(mu1) | |
orient, _ = cv2.Rodrigues(R) | |
orient = orient.reshape([-1]) | |
t = t.reshape([-1]) | |
return orient, t | |
def load_smpl_beta(body_fitting_result_fpath): | |
if os.path.isdir(body_fitting_result_fpath): | |
body_fitting_result = torch.load( | |
os.path.join(body_fitting_result_fpath, 'checkpoints/latest.pt'), map_location='cpu') | |
betas = body_fitting_result['betas']['weight'] | |
elif body_fitting_result_fpath.endswith('.pt'): | |
body_fitting_result = torch.load(body_fitting_result_fpath, map_location='cpu') | |
betas = body_fitting_result['beta'].reshape(1, -1) | |
elif body_fitting_result_fpath.endswith('.npz'): | |
body_fitting_result = np.load(body_fitting_result_fpath) | |
betas = body_fitting_result['betas'].reshape(1, -1) | |
betas = torch.from_numpy(betas.astype(np.float32)) | |
else: | |
raise ValueError('Unknown body fitting result file format: {}'.format(body_fitting_result_fpath)) | |
return betas | |
def load_face_id_scale_param(face_fitting_result_fpath): | |
if os.path.isfile(face_fitting_result_fpath): | |
face_fitting_result = dict(np.load(face_fitting_result_fpath)) | |
id_tensor = face_fitting_result['id_coeff'].astype(np.float32) | |
scale_tensor = face_fitting_result['scale'].astype(np.float32) | |
id_tensor = torch.from_numpy(id_tensor).reshape(1, -1) | |
scale_tensor = torch.from_numpy(scale_tensor).reshape(1, -1) | |
else: | |
param_paths = sorted(glob.glob(os.path.join(face_fitting_result_fpath, '*', 'params.npz'))) | |
param = np.load(param_paths[0]) | |
id_tensor = torch.from_numpy(param['id_coeff']).reshape(1, -1) | |
scale_tensor = torch.from_numpy(param['scale']).reshape(1, 1) | |
return id_tensor, scale_tensor | |
def calc_smplx2faceverse(body_fitting_result_fpath, face_fitting_result_fpath, output_dir): | |
device = torch.device('cuda') | |
os.makedirs(output_dir, exist_ok=True) | |
betas = load_smpl_beta(body_fitting_result_fpath) | |
id_tensor, scale_tensor = load_face_id_scale_param(face_fitting_result_fpath) | |
smpl = smplx.SMPLX(model_path='./AnimatableGaussians/smpl_files/smplx', gender='neutral', | |
use_pca=True, num_pca_comps=45, flat_hand_mean=True, batch_size=1) | |
flame = smplx.FLAME(model_path='./AnimatableGaussians/smpl_files/FLAME2019', gender='neutral', batch_size=1) | |
pose = np.zeros([63], dtype=np.float32) | |
pose = torch.from_numpy(pose).unsqueeze(0) | |
smpl_out = smpl(body_pose=pose, betas=betas) | |
verts = smpl_out.vertices.detach().cpu().squeeze(0).numpy() | |
flame_out = flame() | |
verts_flame = flame_out.vertices.detach().cpu().squeeze(0).numpy() | |
smplx2flame_data = np.load('./data/smpl_models/smplx_mano_flame_correspondences/SMPL-X__FLAME_vertex_ids.npy') | |
verts_flame_on_smplx = verts[smplx2flame_data] | |
orient, t = estimate_rigid_transformation(verts_flame, verts_flame_on_smplx) | |
R, _ = cv2.Rodrigues(orient) | |
rel_transf = np.eye(4) | |
rel_transf[:3, :3] = R | |
rel_transf[:3, 3] = t.reshape(-1) | |
np.save('%s/flame_to_smplx.npy' % (output_dir), rel_transf.astype(np.float32)) | |
# TODO: DELETE ME | |
with open('./debug/debug_verts_smplx.obj', 'w') as fp: | |
for v in verts: | |
fp.write('v %f %f %f\n' % (v[0], v[1], v[2])) | |
with open('./debug/debug_verts_flame_in_smplx.obj', 'w') as fp: | |
for v in np.matmul(verts_flame, R.transpose()) + t.reshape([1, 3]): | |
fp.write('v %f %f %f\n' % (v[0], v[1], v[2])) | |
# align Faceverse to T-pose FLAME on SMPL-X | |
faceverse_mesh = trimesh.load_mesh('./data/smpl_models/faceverse2flame/faceverse_icp.obj', process=False) | |
verts_faceverse_ref = np.matmul(np.asarray(faceverse_mesh.vertices), R.transpose()) + t.reshape(1, 3) | |
# TODO: DELETE ME | |
with open('./debug/debug_verts_faceverse_ref.obj', 'w') as fp: | |
for v in verts_faceverse_ref: | |
fp.write('v %f %f %f\n' % (v[0], v[1], v[2])) | |
model_dict = np.load('./data/faceverse_models/faceverse_simple_v2.npy', allow_pickle=True).item() | |
faceverse_model = FaceVerseModel(model_dict, batch_size=1) | |
faceverse_model.init_coeff_tensors() | |
coeffs = faceverse_model.get_packed_tensors() | |
fv_out = faceverse_model.forward(coeffs=coeffs) | |
verts_faceverse = fv_out['v'].squeeze(0).detach().cpu().numpy() | |
# calculate the relative transformation between FLAME in canonical pose and its position on SMPL-X | |
orient, t = estimate_rigid_transformation(verts_faceverse, verts_faceverse_ref) | |
orient = torch.from_numpy(orient.astype(np.float32)).unsqueeze(0).to(device) | |
t = torch.from_numpy(t.astype(np.float32)).unsqueeze(0).to(device) | |
# optimize the Faceverse to fit SMPL-X | |
faceverse_model.init_coeff_tensors( | |
rot_coeff=orient, trans_coeff=t, id_coeff=id_tensor.to(device), scale_coeff=scale_tensor.to(device)) | |
nonrigid_optim_params = [ | |
faceverse_model.get_exp_tensor(), faceverse_model.get_rot_tensor(), faceverse_model.get_trans_tensor(), | |
# faceverse_model.get_scale_tensor(), faceverse_model.get_id_tensor() | |
] | |
nonrigid_optimizer = torch.optim.Adam(nonrigid_optim_params, lr=1e-1) | |
verts_faceverse_ref = torch.from_numpy(verts_faceverse_ref.astype(np.float32)).to(device).unsqueeze(0) | |
for iter in range(1000): | |
coeffs = faceverse_model.get_packed_tensors() | |
fv_out = faceverse_model.forward(coeffs=coeffs) | |
verts_pred = fv_out['v'] | |
loss = torch.mean(torch.square(verts_pred - verts_faceverse_ref)) | |
if iter % 10 == 0: | |
print(loss.item()) | |
nonrigid_optimizer.zero_grad() | |
loss.backward() | |
nonrigid_optimizer.step() | |
np.savez('%s/faceverse_param_to_smplx.npz' % (output_dir), { | |
'id': faceverse_model.get_id_tensor().detach().cpu().numpy(), | |
'exp': faceverse_model.get_exp_tensor().detach().cpu().numpy(), | |
'rot': faceverse_model.get_rot_tensor().detach().cpu().numpy(), | |
'transl': faceverse_model.get_trans_tensor().detach().cpu().numpy(), | |
'scale': faceverse_model.get_scale_tensor().detach().cpu().numpy(), | |
}) | |
# calculate SMPLX to faceverse space transformation (without scale) | |
orient = faceverse_model.get_rot_tensor().detach().cpu().numpy() | |
transl = faceverse_model.get_trans_tensor().detach().cpu().numpy() | |
rotmat, _ = cv2.Rodrigues(orient) | |
transform_mat = np.eye(4) | |
transform_mat[:3, :3] = rotmat | |
transform_mat[:3, 3] = transl | |
transform_mat = np.linalg.inv(transform_mat) | |
np.save('%s/smplx_to_faceverse_space.npy' % (output_dir), transform_mat.astype(np.float32)) | |
# calculate SMPLX to faceverse distance | |
dists = [] | |
verts_faceverse_ref = verts_faceverse_ref.detach().cpu().numpy() | |
for v in verts: | |
dist = np.linalg.norm(v.reshape(1, 3) - verts_faceverse_ref, axis=-1) | |
dist = np.min(dist) | |
dists.append(dist) | |
dists = np.asarray(dists) | |
np.save('%s/smplx_verts_to_faceverse_dist.npy' % (output_dir), dists.astype(np.float32)) | |
# sample nodes on facial area | |
dists_ = np.ones_like(dists) | |
smplx_topo_new = np.load('./data/smpl_models/smplx_topo_new.npy') | |
valid_vids = set(smplx_topo_new.reshape([-1]).tolist()) | |
dists_[np.asarray(list(valid_vids))] = dists[np.asarray(list(valid_vids))] | |
vids_on_face = np.where(dists_ < 0.01)[0] | |
verts_on_face = verts[vids_on_face] | |
geod_dist_mat = np.linalg.norm(np.expand_dims(verts_on_face, axis=0) - np.expand_dims(verts_on_face, axis=1), | |
axis=2) | |
nodes = [0] # nose | |
dist_nodes_to_rest_points = geod_dist_mat[nodes[0]] | |
for i in range(1, 256): | |
idx = np.argmax(dist_nodes_to_rest_points) | |
nodes.append(idx) | |
new_dist = geod_dist_mat[idx] | |
update_flag = new_dist < dist_nodes_to_rest_points | |
dist_nodes_to_rest_points[update_flag] = new_dist[update_flag] | |
# with open('./debug/debug_face_nodes.obj', 'w') as fp: | |
# for n in verts_on_face[np.asarray(nodes)]: | |
# fp.write('v %f %f %f\n' % (n[0], n[1], n[2])) | |
vids_on_faces_sampled = vids_on_face[np.asarray(nodes)] | |
vids_on_faces_sampled = np.ascontiguousarray(vids_on_faces_sampled).astype(np.int32) | |
np.save('%s/vids_on_faces_sampled.npy' % (output_dir), vids_on_faces_sampled) | |
# test SMPLX-to-faceverse space transformation (without scale) | |
verts_smpl_in_faceverse = np.matmul(verts, transform_mat[:3, :3].transpose()) + \ | |
transform_mat[:3, 3].reshape(1, 3) | |
with open('./debug/debug_verts_smpl_in_faceverse.obj', 'w') as fp: | |
for v in verts_smpl_in_faceverse: | |
fp.write('v %f %f %f\n' % (v[0], v[1], v[2])) | |
# save personalized, canonical faceverse model | |
faceverse_model.init_coeff_tensors(id_coeff=id_tensor.to(device), scale_coeff=scale_tensor.to(device)) | |
coeffs = faceverse_model.get_packed_tensors() | |
fv_out = faceverse_model.forward(coeffs=coeffs) | |
verts_faceverse = fv_out['v'].squeeze(0).detach().cpu().numpy() | |
with open('./debug/debug_verts_faceverse.obj', 'w') as fp: | |
for v in verts_faceverse: | |
fp.write('v %f %f %f\n' % (v[0], v[1], v[2])) | |
if __name__ == '__main__': | |
# body_fitting_result_dir = 'D:/UpperBodyAvatar/code/smplfitting_multiview_sequence_smplx/results/Shuangqing/zzr_fullbody_20221130_01_2k/whole.pt' | |
# face_fitting_result_dir = 'D:\\UpperBodyAvatar\\data\\Shuangqing\\zzr_face_20221130_01_2k\\faceverse_params' | |
# | |
# output_dir = './data/faceverse/' | |
# result_suffix = 'shuangqing_zzr' | |
# body_fitting_result_dir = 'D:/Product/FullAppRelease/smplfitting_multiview_sequence_smplx/results/body_data_model_20231224/whole.pt' | |
# face_fitting_result_dir = 'D:/Product/data/HuiyingCenter/20231224_model/model_20231224_face_data/faceverse_params' | |
# | |
# output_dir = './data/faceverse/' | |
# result_suffix = 'huiyin_model20231224' | |
body_fitting_result_dir = 'D:/Product/FullAppRelease/smplfitting_multiview_sequence_smplx/results/body_data_male20230530_betterhand/whole.pt' | |
face_fitting_result_dir = 'D:/Product/data/HuiyingCenter/20230531_models/male20230530_face_data/faceverse_params' | |
output_dir = './data/body_face_stitching/huiyin_male20230530' | |
calc_smplx2faceverse(body_fitting_result_dir, face_fitting_result_dir, output_dir) | |