|
|
|
|
|
|
|
|
|
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') |
|
|