diff --git a/README.md b/README.md
index b2de1e3..94db1f3 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@
-
+
@@ -56,9 +56,6 @@
-:warning: Don't support headless mode currently, `unset PYOPENGL_PLATFORM` before training. (will fix it later...) +:warning: Don't support headless mode currently, `unset PYOPENGL_PLATFORM` before training. ## Command ```bash @@ -25,12 +31,15 @@ CUDA_VISIBLE_DEVICES=0 python -m apps.train -cfg ./configs/train/icon-filter.yam # ICON w/o filter (name: icon-nofilter) CUDA_VISIBLE_DEVICES=0 python -m apps.train -cfg ./configs/train/icon-nofilter.yaml -# ICON-MVP (name: icon-mvp), mvp = minimal viable product, simple (used features) yet efficient (GPU) +# ICON-MVP (name: icon-mvp), mvp = minimal viable product, simple (used features) yet efficient (GPU usage) # https://en.wikipedia.org/wiki/Minimum_viable_product CUDA_VISIBLE_DEVICES=0 python -m apps.train -cfg ./configs/train/icon-mvp.yaml # PIFu (name: pifu) CUDA_VISIBLE_DEVICES=0 python -m apps.train -cfg ./configs/train/pifu.yaml + +# PaMIR (name: pamir) +CUDA_VISIBLE_DEVICES=0 python -m apps.train -cfg ./configs/train/pamir.yaml ``` ## Tensorboard diff --git a/lib/dataloader_demo.py b/lib/dataloader_demo.py index 68f70bd..d985682 100644 --- a/lib/dataloader_demo.py +++ b/lib/dataloader_demo.py @@ -2,52 +2,57 @@ from lib.common.config import get_cfg_defaults from lib.dataset.PIFuDataset import PIFuDataset -args = get_cfg_defaults() -args.merge_from_file("./configs/train/icon-filter.yaml") +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument('-v', + '--show', + action='store_true', + help='vis sampler 3D') + parser.add_argument('-s', + '--speed', + action='store_true', + help='vis sampler 3D') + parser.add_argument('-l', + '--list', + action='store_true', + help='vis sampler 3D') + parser.add_argument('-c', + '--config', + default='./configs/train/icon-filter.yaml', + help='vis sampler 3D') + args_c = parser.parse_args() -# loading cfg file -parser = argparse.ArgumentParser() -parser.add_argument('-v', - '--show', - action='store_true', - help='vis sampler 3D') -parser.add_argument('-s', - '--speed', - action='store_true', - help='vis sampler 3D') -parser.add_argument('-l', - '--list', - action='store_true', - help='vis sampler 3D') -args_c = parser.parse_args() + args = get_cfg_defaults() + args.merge_from_file(args_c.config) -dataset = PIFuDataset(args, split='train', vis=args_c.show) -print(f"Number of subjects :{len(dataset.subject_list)}") -data_dict = dataset[0] + dataset = PIFuDataset(args, split='train', vis=args_c.show) + print(f"Number of subjects :{len(dataset.subject_list)}") + data_dict = dataset[0] -if args_c.list: - for k in data_dict.keys(): - if not hasattr(data_dict[k], "shape"): - print(f"{k}: {data_dict[k]}") - else: - print(f"{k}: {data_dict[k].shape}") + if args_c.list: + for k in data_dict.keys(): + if not hasattr(data_dict[k], "shape"): + print(f"{k}: {data_dict[k]}") + else: + print(f"{k}: {data_dict[k].shape}") -if args_c.show: - # for item in dataset: - item = dataset[0] - dataset.visualize_sampling3D(item, mode='occ') + if args_c.show: + # for item in dataset: + item = dataset[0] + dataset.visualize_sampling3D(item, mode='occ') -if args_c.speed: - # original: 2 it/s - # smpl online compute: 2 it/s - # normal online compute: 1.5 it/s - from tqdm import tqdm - for item in tqdm(dataset): - # pass - for k in item.keys(): - if 'voxel' in k: - if not hasattr(item[k], "shape"): - print(f"{k}: {item[k]}") - else: - print(f"{k}: {item[k].shape}") - print("--------------------") + if args_c.speed: + # original: 2 it/s + # smpl online compute: 2 it/s + # normal online compute: 1.5 it/s + from tqdm import tqdm + for item in tqdm(dataset): + # pass + for k in item.keys(): + if 'voxel' in k: + if not hasattr(item[k], "shape"): + print(f"{k}: {item[k]}") + else: + print(f"{k}: {item[k].shape}") + print("--------------------") diff --git a/lib/dataset/PIFuDataset.py b/lib/dataset/PIFuDataset.py index d54cc2f..b048401 100644 --- a/lib/dataset/PIFuDataset.py +++ b/lib/dataset/PIFuDataset.py @@ -1,7 +1,9 @@ from lib.renderer.mesh import load_fit_body from lib.dataset.hoppeMesh import HoppeMesh +from lib.dataset.body_model import TetraSMPLModel from lib.common.render import Render from lib.dataset.mesh_util import SMPLX, projection, cal_sdf_batch, get_visibility +from lib.pare.pare.utils.geometry import rotation_matrix_to_angle_axis from termcolor import colored import os.path as osp import numpy as np @@ -10,9 +12,9 @@ import trimesh import torch import vedo -from ipdb import set_trace from kaolin.ops.mesh import check_sign import torchvision.transforms as transforms +from ipdb import set_trace class PIFuDataset(): @@ -81,10 +83,12 @@ def __init__(self, cfg, split='train', vis=False): if dataset in ['thuman2']: mesh_dir = osp.join(dataset_dir, "scans") smplx_dir = osp.join(dataset_dir, "fits") + smpl_dir = osp.join(dataset_dir, "smpl") self.datasets_dict[dataset] = { "subjects": np.loadtxt(osp.join(dataset_dir, "all.txt"), dtype=str), "smplx_dir": smplx_dir, + "smpl_dir": smpl_dir, "mesh_dir": mesh_dir, "scale": self.scales[dataset_id] } @@ -149,7 +153,7 @@ def get_subject_list(self, split): print(colored(f"total: {len(subject_list)}", "yellow")) random.shuffle(subject_list) - # subject_list = ["thuman2/0499"] + # subject_list = ["thuman2/0008"] return subject_list def __len__(self): @@ -178,6 +182,7 @@ def __getitem__(self, index): 'scale': self.datasets_dict[dataset]["scale"], 'mesh_path': osp.join(self.datasets_dict[dataset]["mesh_dir"], f"{subject}/{subject}.obj"), 'smplx_path': osp.join(self.datasets_dict[dataset]["smplx_dir"], f"{subject}/smplx_param.pkl"), + 'smpl_path': osp.join(self.datasets_dict[dataset]["smpl_dir"], f"{subject}.pkl"), 'calib_path': osp.join(self.root, render_folder, 'calib', f'{rotation:03d}.txt'), 'vis_path': osp.join(self.root, render_folder, 'vis', f'{rotation:03d}.pt'), 'image_path': osp.join(self.root, render_folder, 'render', f'{rotation:03d}.png') @@ -205,12 +210,16 @@ def __getitem__(self, index): data_dict, is_valid=self.split == "val", is_sdf=self.use_sdf)) data_dict.update(self.load_smpl(data_dict, self.vis)) - if (not self.vis) and (self.split != 'test'): - + if self.prior_type == 'pamir': + data_dict.update(self.load_smpl_voxel(data_dict)) + + if (self.split != 'test') and (not self.vis): + del data_dict['verts'] del data_dict['faces'] - - del data_dict['mesh'] + + if not self.vis: + del data_dict['mesh'] path_keys = [ key for key in data_dict.keys() if '_path' in key or '_dir' in key @@ -267,23 +276,30 @@ def add_noise(self, smpl_betas, noise_type, noise_scale, + type, hashcode): np.random.seed(hashcode) - if 'beta' in noise_type: - if beta_num != 11: - smpl_betas += (np.random.rand(beta_num) - - 0.5) * 2.0 * noise_scale[noise_type.index("beta")] + if type == 'smplx': + noise_idx = self.noise_smplx_idx + else: + noise_idx = self.noise_smpl_idx + + if 'beta' in noise_type and noise_scale[noise_type.index("beta")] > 0.0: + smpl_betas += (np.random.rand(beta_num) - + 0.5) * 2.0 * noise_scale[noise_type.index("beta")] smpl_betas = smpl_betas.astype(np.float32) - if 'pose' in noise_type: - smpl_pose[self.noise_smplx_idx] += ( - np.random.rand(len(self.noise_smplx_idx)) - + if 'pose' in noise_type and noise_scale[noise_type.index("pose")] > 0.0: + smpl_pose[noise_idx] += ( + np.random.rand(len(noise_idx)) - 0.5) * 2.0 * np.pi * noise_scale[noise_type.index("pose")] smpl_pose = smpl_pose.astype(np.float32) - - return torch.as_tensor(smpl_pose[None, ...]), torch.as_tensor(smpl_betas[None, ...]) + if type == 'smplx': + return torch.as_tensor(smpl_pose[None, ...]), torch.as_tensor(smpl_betas[None, ...]) + else: + return smpl_pose, smpl_betas def compute_smpl_verts(self, data_dict, noise_type=None, noise_scale=None): @@ -293,13 +309,13 @@ def compute_smpl_verts(self, data_dict, noise_type=None, noise_scale=None): smplx_param = np.load(data_dict['smplx_path'], allow_pickle=True) smplx_pose = smplx_param["body_pose"] # [1,63] smplx_betas = smplx_param["betas"] # [1,10] - smplx_pose, smplx_betas = self.add_noise( smplx_betas.shape[1], smplx_pose[0], smplx_betas[0], noise_type, noise_scale, + type='smplx', hashcode=(hash(f"{data_dict['subject']}_{data_dict['rotation']}")) % (10**8)) smplx_out, _ = load_fit_body(fitted_path=data_dict['smplx_path'], @@ -315,6 +331,56 @@ def compute_smpl_verts(self, data_dict, noise_type=None, noise_scale=None): return smplx_out.vertices, smplx_dict + def compute_voxel_verts(self, + data_dict, + noise_type=None, + noise_scale=None): + + smpl_param = np.load(data_dict['smpl_path'], allow_pickle=True) + smplx_param = np.load(data_dict['smplx_path'], allow_pickle=True) + + smpl_pose = rotation_matrix_to_angle_axis( + torch.as_tensor(smpl_param['full_pose'][0])).numpy() + smpl_betas = smpl_param["betas"] + + smpl_path = osp.join(self.smplx.model_dir, "smpl/SMPL_MALE.pkl") + tetra_path = osp.join(self.smplx.tedra_dir, + "tetra_male_adult_smpl.npz") + + smpl_model = TetraSMPLModel(smpl_path, tetra_path, 'adult') + + smpl_pose, smpl_betas = self.add_noise( + smpl_model.beta_shape[0], + smpl_pose.flatten(), + smpl_betas[0], + noise_type, + noise_scale, + type='smpl', + hashcode=(hash(f"{data_dict['subject']}_{data_dict['rotation']}")) % (10**8)) + + smpl_model.set_params(pose=smpl_pose.reshape(-1, 3), + beta=smpl_betas, + trans=smpl_param["transl"]) + + verts = (np.concatenate([smpl_model.verts, smpl_model.verts_added], + axis=0) * smplx_param["scale"] + smplx_param["translation"] + ) * self.datasets_dict[data_dict['dataset']]['scale'] + faces = np.loadtxt(osp.join(self.smplx.tedra_dir, "tetrahedrons_male_adult.txt"), + dtype=np.int32) - 1 + + pad_v_num = int(8000 - verts.shape[0]) + pad_f_num = int(25100 - faces.shape[0]) + + verts = np.pad(verts, ((0, pad_v_num), (0, 0)), + mode='constant', + constant_values=0.0).astype(np.float32) + faces = np.pad(faces, ((0, pad_f_num), (0, 0)), + mode='constant', + constant_values=0.0).astype(np.int32) + + + return verts, faces, pad_v_num, pad_f_num + def load_smpl(self, data_dict, vis=False): smplx_verts, smplx_dict = self.compute_smpl_verts( @@ -380,6 +446,22 @@ def load_smpl(self, data_dict, vis=False): return return_dict + def load_smpl_voxel(self, data_dict): + + smpl_verts, smpl_faces, pad_v_num, pad_f_num = self.compute_voxel_verts( + data_dict, self.noise_type, + self.noise_scale) # compute using smpl model + smpl_verts = projection(smpl_verts, data_dict['calib']) + + smpl_verts *= 0.5 + + return { + 'voxel_verts': smpl_verts, + 'voxel_faces': smpl_faces, + 'pad_v_num': pad_v_num, + 'pad_f_num': pad_f_num + } + def get_sampling_geo(self, data_dict, is_valid=False, is_sdf=False): mesh = data_dict['mesh'] @@ -543,13 +625,24 @@ def visualize_sampling3D(self, data_dict, mode='vis'): mesh.visual.vertex_colors = [128.0, 128.0, 128.0, 255.0] vis_list.append(mesh) + if 'voxel_verts' in data_dict.keys(): + print(colored("voxel verts", "green")) + voxel_verts = data_dict['voxel_verts'] * 2.0 + voxel_faces = data_dict['voxel_faces'] + voxel_verts[:, 1] *= -1 + voxel = trimesh.Trimesh( + voxel_verts, voxel_faces[:, [0, 2, 1]], process=False, maintain_order=True) + voxel.visual.vertex_colors = [0.0, 128.0, 0.0, 255.0] + vis_list.append(voxel) + if 'smpl_verts' in data_dict.keys(): + print(colored("smpl verts", "green")) smplx_verts = data_dict['smpl_verts'] smplx_faces = data_dict['smpl_faces'] smplx_verts[:, 1] *= -1 smplx = trimesh.Trimesh( smplx_verts, smplx_faces[:, [0, 2, 1]], process=False, maintain_order=True) - smplx.visual.vertex_colors = ((smplx.vertex_normals + 1.0) * 0.5) + smplx.visual.vertex_colors = [128.0, 128.0, 0.0, 255.0] vis_list.append(smplx) # create a picure diff --git a/lib/renderer/mesh.py b/lib/renderer/mesh.py index fab089e..437467f 100755 --- a/lib/renderer/mesh.py +++ b/lib/renderer/mesh.py @@ -88,6 +88,34 @@ def load_fit_body(fitted_path, scale, smpl_type='smplx', smpl_gender='neutral', return smpl_mesh, smpl_joints +def load_ori_fit_body(fitted_path, smpl_type='smplx', smpl_gender='neutral'): + + param = np.load(fitted_path, allow_pickle=True) + for key in param.keys(): + param[key] = torch.as_tensor(param[key]) + + smpl_model = get_smpl_model(smpl_type, smpl_gender) + model_forward_params = dict(betas=param['betas'], + global_orient=param['global_orient'], + body_pose=param['body_pose'], + left_hand_pose=param['left_hand_pose'], + right_hand_pose=param['right_hand_pose'], + jaw_pose=param['jaw_pose'], + leye_pose=param['leye_pose'], + reye_pose=param['reye_pose'], + expression=param['expression'], + return_verts=True) + + smpl_out = smpl_model(**model_forward_params) + + smpl_verts = smpl_out.vertices[0].detach() + smpl_mesh = trimesh.Trimesh(smpl_verts, + smpl_model.faces, + process=False, maintain_order=True) + + return smpl_mesh + + def save_obj_mesh(mesh_path, verts, faces): file = open(mesh_path, 'w') for v in verts: diff --git a/scripts/render_single.py b/scripts/render_single.py index aa68679..f451733 100644 --- a/scripts/render_single.py +++ b/scripts/render_single.py @@ -1,5 +1,5 @@ import lib.renderer.opengl_util as opengl_util -from lib.renderer.mesh import load_fit_body, load_scan, compute_tangent +from lib.renderer.mesh import load_fit_body, load_scan, compute_tangent, load_ori_fit_body import lib.renderer.prt_util as prt_util from lib.renderer.gl.init_gl import initialize_GL_context from lib.renderer.gl.prt_render import PRTRender @@ -53,6 +53,7 @@ mesh_file = os.path.join( f'./data/{dataset}/scans/{subject}', f'{subject}.{format}') +smplx_file = f'./data/{dataset}/smplx/{subject}.obj' tex_file = f'./data/{dataset}/scans/{subject}/material0.jpeg' fit_file = f'./data/{dataset}/fits/{subject}/smplx_param.pkl' @@ -81,6 +82,12 @@ smpl_type='smplx', smpl_gender='male') +os.makedirs(os.path.dirname(smplx_file), exist_ok=True) +ori_smplx = load_ori_fit_body(fit_file, + smpl_type='smplx', + smpl_gender='male') +ori_smplx.export(smplx_file) + vertices *= scale vmin = vertices.min(0) vmax = vertices.max(0)