full_gaussian_avatar / render_utils /calc_smplx2faceverse.py
pengc02's picture
upload_part1
7648567
raw
history blame
11.1 kB
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)