| |
| |
| |
|
|
| import os |
| os.environ["PYOPENGL_PLATFORM"] = "egl" |
| os.environ['EGL_DEVICE_ID'] = '0' |
|
|
| import sys |
| from argparse import ArgumentParser |
| import random |
| import pickle as pkl |
| import numpy as np |
| from PIL import Image, ImageOps |
| import torch |
| from tqdm import tqdm |
| import time |
|
|
| from utils import normalize_rgb, render_meshes, get_focalLength_from_fieldOfView, demo_color as color, print_distance_on_image, render_side_views, create_scene, MEAN_PARAMS, CACHE_DIR_MULTIHMR, SMPLX_DIR |
| from model import Model |
| from pathlib import Path |
| import warnings |
|
|
| torch.cuda.empty_cache() |
|
|
| np.random.seed(seed=0) |
| random.seed(0) |
|
|
| def open_image(img_path, img_size, device=torch.device('cuda')): |
| """ Open image at path, resize and pad """ |
|
|
| |
| img_pil = Image.open(img_path).convert('RGB') |
| img_pil = ImageOps.contain(img_pil, (img_size,img_size)) |
|
|
| |
| img_pil_bis = ImageOps.pad(img_pil.copy(), size=(img_size,img_size), color=(255, 255, 255)) |
| img_pil = ImageOps.pad(img_pil, size=(img_size,img_size)) |
|
|
| |
| resize_img = np.asarray(img_pil) |
|
|
| |
| resize_img = normalize_rgb(resize_img) |
| x = torch.from_numpy(resize_img).unsqueeze(0).to(device) |
| return x, img_pil_bis |
|
|
| def get_camera_parameters(img_size, fov=60, p_x=None, p_y=None, device=torch.device('cuda')): |
| """ Given image size, fov and principal point coordinates, return K the camera parameter matrix""" |
| K = torch.eye(3) |
| |
| focal = get_focalLength_from_fieldOfView(fov=fov, img_size=img_size) |
| K[0,0], K[1,1] = focal, focal |
|
|
| |
| if p_x is not None and p_y is not None: |
| K[0,-1], K[1,-1] = p_x * img_size, p_y * img_size |
| else: |
| K[0,-1], K[1,-1] = img_size//2, img_size//2 |
|
|
| |
| K = K.unsqueeze(0).to(device) |
| return K |
|
|
| def load_model(model_name, device=torch.device('cuda')): |
| """ Open a checkpoint, build Multi-HMR using saved arguments, load the model weigths. """ |
| |
| |
| ckpt_path = os.path.join(CACHE_DIR_MULTIHMR, model_name+ '.pt') |
| if not os.path.isfile(ckpt_path): |
| os.makedirs(CACHE_DIR_MULTIHMR, exist_ok=True) |
| print(f"{ckpt_path} not found...") |
| print("It should be the first time you run the demo code") |
| print("Downloading checkpoint from NAVER LABS Europe website...") |
| |
| try: |
| os.system(f"wget -O {ckpt_path} http://download.europe.naverlabs.com/multihmr/{model_name}.pt") |
| print(f"Ckpt downloaded to {ckpt_path}") |
| except: |
| assert "Please contact fabien.baradel@naverlabs.com or open an issue on the github repo" |
|
|
| |
| print("Loading model") |
| ckpt = torch.load(ckpt_path, map_location=device) |
|
|
| |
| kwargs = {} |
| for k,v in vars(ckpt['args']).items(): |
| kwargs[k] = v |
|
|
| |
| kwargs['type'] = ckpt['args'].train_return_type |
| kwargs['img_size'] = ckpt['args'].img_size[0] |
| model = Model(**kwargs).to(device) |
|
|
| |
| model.load_state_dict(ckpt['model_state_dict'], strict=False) |
| print("Weights have been loaded") |
|
|
| return model |
|
|
| def forward_model(model, input_image, camera_parameters, |
| det_thresh=0.3, |
| nms_kernel_size=1, |
| ): |
| |
| """ Make a forward pass on an input image and camera parameters. """ |
| |
| |
| with torch.no_grad(): |
| with torch.cuda.amp.autocast(enabled=True): |
| humans = model(input_image, |
| is_training=False, |
| nms_kernel_size=int(nms_kernel_size), |
| det_thresh=det_thresh, |
| K=camera_parameters) |
|
|
| return humans |
|
|
| def overlay_human_meshes(humans, K, model, img_pil, unique_color=False): |
|
|
| |
| _color = [color[0] for _ in range(len(humans))] if unique_color else color |
| |
| |
| focal = np.asarray([K[0,0,0].cpu().numpy(),K[0,1,1].cpu().numpy()]) |
| princpt = np.asarray([K[0,0,-1].cpu().numpy(),K[0,1,-1].cpu().numpy()]) |
|
|
| |
| verts_list = [humans[j]['verts_smplx'].cpu().numpy() for j in range(len(humans))] |
| faces_list = [model.smpl_layer['neutral'].bm_x.faces for j in range(len(humans))] |
|
|
| |
| pred_rend_array = render_meshes(np.asarray(img_pil), |
| verts_list, |
| faces_list, |
| {'focal': focal, 'princpt': princpt}, |
| alpha=1.0, |
| color=_color) |
|
|
| return pred_rend_array, _color |
|
|
| if __name__ == "__main__": |
| parser = ArgumentParser() |
| parser.add_argument("--model_name", type=str, default='multiHMR_896_L_synth') |
| parser.add_argument("--img_folder", type=str, default='example_data') |
| parser.add_argument("--out_folder", type=str, default='demo_out') |
| parser.add_argument("--save_mesh", type=int, default=0, choices=[0,1]) |
| parser.add_argument("--extra_views", type=int, default=0, choices=[0,1]) |
| parser.add_argument("--det_thresh", type=float, default=0.3) |
| parser.add_argument("--nms_kernel_size", type=float, default=3) |
| parser.add_argument("--fov", type=float, default=60) |
| parser.add_argument("--distance", type=int, default=0, choices=[0,1], help='add distance on the reprojected mesh') |
| parser.add_argument("--unique_color", type=int, default=0, choices=[0,1], help='only one color for all humans') |
| |
| args = parser.parse_args() |
|
|
| dict_args = vars(args) |
|
|
| assert torch.cuda.is_available() |
|
|
| |
| smplx_fn = os.path.join(SMPLX_DIR, 'smplx', 'SMPLX_NEUTRAL.npz') |
| if not os.path.isfile(smplx_fn): |
| print(f"{smplx_fn} not found, please download SMPLX_NEUTRAL.npz file") |
| print("To do so you need to create an account in https://smpl-x.is.tue.mpg.de") |
| print("Then download 'SMPL-X-v1.1 (NPZ+PKL, 830MB) - Use thsi for SMPL-X Python codebase'") |
| print(f"Extract the zip file and move SMPLX_NEUTRAL.npz to {smplx_fn}") |
| print("Sorry for this incovenience but we do not have license for redustributing SMPLX model") |
| assert NotImplementedError |
| else: |
| print('SMPLX found') |
| |
| |
| if not os.path.isfile(MEAN_PARAMS): |
| print('Start to download the SMPL mean params') |
| os.system(f"wget -O {MEAN_PARAMS} https://openmmlab-share.oss-cn-hangzhou.aliyuncs.com/mmhuman3d/models/smpl_mean_params.npz?versionId=CAEQHhiBgICN6M3V6xciIDU1MzUzNjZjZGNiOTQ3OWJiZTJmNThiZmY4NmMxMTM4") |
| print('SMPL mean params have been succesfully downloaded') |
| else: |
| print('SMPL mean params is already here') |
|
|
| |
| suffixes = ('.jpg', '.jpeg', '.png', '.webp') |
| l_img_path = [file for file in os.listdir(args.img_folder) if file.endswith(suffixes) and file[0] != '.'] |
|
|
| |
| model = load_model(args.model_name) |
|
|
| |
| model_name = os.path.basename(args.model_name) |
|
|
| |
| os.makedirs(args.out_folder, exist_ok=True) |
| l_duration = [] |
| for i, img_path in enumerate(tqdm(l_img_path)): |
| |
| |
| save_fn = os.path.join(args.out_folder, f"{Path(img_path).stem}_{model_name}.png") |
|
|
| |
| img_size = model.img_size |
| x, img_pil_nopad = open_image(os.path.join(args.img_folder, img_path), img_size) |
|
|
| |
| p_x, p_y = None, None |
| K = get_camera_parameters(model.img_size, fov=args.fov, p_x=p_x, p_y=p_y) |
|
|
| |
| start = time.time() |
| humans = forward_model(model, x, K, |
| det_thresh=args.det_thresh, |
| nms_kernel_size=args.nms_kernel_size) |
| duration = time.time() - start |
| l_duration.append(duration) |
| |
| |
| img_array = np.asarray(img_pil_nopad) |
| img_pil_visu= Image.fromarray(img_array) |
| pred_rend_array, _color = overlay_human_meshes(humans, K, model, img_pil_visu, unique_color=args.unique_color) |
|
|
| |
| if args.distance: |
| pred_rend_array = print_distance_on_image(pred_rend_array, humans, _color) |
|
|
| |
| l_img = [img_array, pred_rend_array] |
|
|
| |
| if args.extra_views: |
| |
| pred_rend_array_bis, pred_rend_array_sideview, pred_rend_array_bev = render_side_views(img_array, _color, humans, model, K) |
|
|
| |
| _img1 = np.concatenate([img_array, pred_rend_array],1).astype(np.uint8) |
| _img2 = np.concatenate([pred_rend_array_bis, pred_rend_array_sideview, pred_rend_array_bev],1).astype(np.uint8) |
| _h = int(_img2.shape[0] * (_img1.shape[1]/_img2.shape[1])) |
| _img2 = np.asarray(Image.fromarray(_img2).resize((_img1.shape[1], _h))) |
| _img = np.concatenate([_img1, _img2],0).astype(np.uint8) |
| else: |
| |
| _img = np.concatenate([img_array, pred_rend_array],1).astype(np.uint8) |
|
|
| |
| Image.fromarray(_img).save(save_fn) |
| print(f"Avg Multi-HMR inference time={int(1000*np.median(np.asarray(l_duration[-1:])))}ms on a {torch.cuda.get_device_name()}") |
|
|
| |
| if args.save_mesh: |
| |
| l_mesh = [hum['verts_smplx'].cpu().numpy() for hum in humans] |
| mesh_fn = save_fn+'.npy' |
| np.save(mesh_fn, np.asarray(l_mesh), allow_pickle=True) |
| x = np.load(mesh_fn, allow_pickle=True) |
|
|
| |
| l_mesh = [humans[j]['verts_smplx'].detach().cpu().numpy() for j in range(len(humans))] |
| l_face = [model.smpl_layer['neutral'].bm_x.faces for j in range(len(humans))] |
| scene = create_scene(img_pil_visu, l_mesh, l_face, color=None, metallicFactor=0., roughnessFactor=0.5) |
| scene_fn = save_fn+'.glb' |
| scene.export(scene_fn) |
|
|
| print('end') |
|
|