| |
| |
| |
| import os |
| import sys |
| current_script_dir = os.path.dirname(os.path.abspath(__file__)) |
| |
| master_root = os.path.abspath(os.path.join(current_script_dir, "..", "..", "..", "..")) |
| fourd_root = os.path.join(master_root, "4D-Humans") |
|
|
| if master_root not in sys.path: |
| sys.path.insert(0, master_root) |
| if fourd_root not in sys.path: |
| sys.path.insert(0, fourd_root) |
| |
|
|
| import cv2 |
| from pathlib import Path |
| import os |
| import argparse |
| import torch |
| import numpy as np |
| from tqdm import tqdm |
| import platform |
| import pyrender |
|
|
| from scripts.pretrained_models import ( |
| DETECTRON2_MODEL_PATH, |
| HMR2_DEFAULT_CKPT, |
| ) |
|
|
| if "PYOPENGL_PLATFORM" not in os.environ: |
| os.environ["PYOPENGL_PLATFORM"] = "egl" |
|
|
| from hmr2.models import load_hmr2 |
| from hmr2.utils import recursive_to |
| from hmr2.datasets.vitdet_dataset import ViTDetDataset |
| from hmr2.utils.renderer import Renderer, cam_crop_to_full |
| from .smpl_visualizer import SemanticRenderer |
|
|
| |
| sys_name = platform.system() |
| if sys_name == "Windows": |
| os.environ.pop("PYOPENGL_PLATFORM", None) |
|
|
| LIGHT_BLUE = (0.65098039, 0.74117647, 0.85882353) |
|
|
| def predict_smpl(batch, model, model_cfg, figure_scale=None): |
| all_verts = [] |
| all_cam_t = [] |
| with torch.no_grad(), torch.autocast(device_type="cuda", dtype=torch.float16): |
| out = model(batch) |
|
|
| pred_cam = out["pred_cam"] |
| pred_smpl_parameter = out["pred_smpl_params"] |
| if figure_scale is not None: |
| pred_smpl_parameter['betas'][0][1] = float(figure_scale) |
| smpl_output = model.smpl( |
| **{k: v.float() for k, v in pred_smpl_parameter.items()}, |
| pose2rot=False, |
| ) |
| pred_vertices = smpl_output.vertices |
| out["pred_vertices"] = pred_vertices.reshape( |
| batch["img"].shape[0], -1, 3 |
| ) |
|
|
| box_center = batch["box_center"].float() |
| box_size = batch["box_size"].float() |
| img_size = batch["img_size"].float() |
|
|
| scaled_focal_length = ( |
| model_cfg.EXTRA.FOCAL_LENGTH |
| / model_cfg.MODEL.IMAGE_SIZE |
| * img_size.max() |
| ) |
| pred_cam_t_full = cam_crop_to_full( |
| pred_cam, box_center, box_size, img_size, scaled_focal_length |
| ).detach().cpu().numpy() |
| |
| batch_size = batch["img"].shape[0] |
| for n in range(batch_size): |
| |
| verts = out["pred_vertices"][n].detach().cpu().numpy() |
| cam_t = pred_cam_t_full[n] |
| all_verts.append(verts) |
| all_cam_t.append(cam_t) |
|
|
| misc_args = dict( |
| mesh_base_color=LIGHT_BLUE, |
| scene_bg_color=(1, 1, 1), |
| focal_length=scaled_focal_length, |
| ) |
| |
| smpl_outs = { |
| k: v.detach().cpu().numpy() for k, v in pred_smpl_parameter.items() |
| } |
|
|
| results_dict_for_rendering = { |
| "verts": all_verts, |
| "cam_t": all_cam_t, |
| "render_res": img_size[n].cpu().numpy(), |
| "smpls": smpl_outs, |
| "scaled_focal_length": scaled_focal_length.cpu().numpy(), |
| } |
| return results_dict_for_rendering, misc_args |
|
|
| def load_image(img_cv2, detector): |
| |
| with torch.autocast(device_type="cuda", dtype=torch.float16): |
| det_out = detector(img_cv2) |
| det_instances = det_out["instances"] |
| valid_idx = (det_instances.pred_classes == 0) & (det_instances.scores > 0.5) |
| boxes = det_instances.pred_boxes.tensor[valid_idx].cpu().numpy() |
| |
| dataset = ViTDetDataset(model_cfg, img_cv2, boxes) |
| return torch.utils.data.DataLoader( |
| dataset, batch_size=8, shuffle=False, num_workers=0 |
| ) |
|
|
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser(description="Inference SMPL with 4D-Humans") |
| parser.add_argument("--device", type=int, default=0, help="GPU device ID") |
| parser.add_argument( |
| "--reference_imgs_folder", |
| type=str, |
| default="", |
| help="Folder path to reference imgs", |
| ) |
| parser.add_argument( |
| "--driving_video_path", |
| type=str, |
| default="driving_videos", |
| help="Folder path to driving videos", |
| ) |
| parser.add_argument( |
| "--figure_scale", |
| type=int, |
| default=None, |
| help="Adjust the figure scale to better fit extreme shape", |
| ) |
|
|
| args = parser.parse_args() |
|
|
| reference_img_paths = [] |
| if args.reference_imgs_folder: |
| os.makedirs(args.reference_imgs_folder, exist_ok=True) |
| os.makedirs( |
| os.path.join(args.reference_imgs_folder, "visualized_imgs"), exist_ok=True |
| ) |
| os.makedirs(os.path.join(args.reference_imgs_folder, "mask"), exist_ok=True) |
| os.makedirs( |
| os.path.join(args.reference_imgs_folder, "semantic_map"), exist_ok=True |
| ) |
| os.makedirs(os.path.join(args.reference_imgs_folder, "depth"), exist_ok=True) |
| os.makedirs( |
| os.path.join(args.reference_imgs_folder, "smpl_results"), exist_ok=True |
| ) |
|
|
| reference_img_paths = [ |
| path |
| for path in os.listdir(os.path.join(args.reference_imgs_folder, "images")) |
| ] |
|
|
| driving_videos_paths = [args.driving_video_path] |
|
|
| model, model_cfg = load_hmr2(HMR2_DEFAULT_CKPT) |
|
|
| |
| import torchvision |
| class LightweightDetector: |
| def __init__(self, device): |
| print("Loading Faster R-CNN ResNet-50 detector (High Accuracy & Low VRAM)...") |
| self.model = torchvision.models.detection.fasterrcnn_resnet50_fpn_v2(weights=torchvision.models.detection.FasterRCNN_ResNet50_FPN_V2_Weights.DEFAULT) |
| self.model.to(device) |
| self.model.eval() |
| self.device = device |
| |
| def __call__(self, img_cv2): |
| img_rgb = img_cv2[:, :, ::-1].copy() |
| img_tensor = torch.from_numpy(img_rgb).permute(2, 0, 1).float() / 255.0 |
| img_tensor = img_tensor.unsqueeze(0).to(self.device) |
| |
| with torch.no_grad(), torch.autocast(device_type="cuda", dtype=torch.float16): |
| prediction = self.model(img_tensor)[0] |
| |
| labels = prediction['labels'] |
| scores = prediction['scores'] |
| boxes = prediction['boxes'] |
| |
| |
| valid_idx = (labels == 1) & (scores > 0.5) |
| person_boxes = boxes[valid_idx].cpu().numpy() |
| person_scores = scores[valid_idx].cpu().numpy() |
| |
| |
| if len(person_boxes) > 1: |
| best_idx = np.argmax(person_scores) |
| person_boxes = person_boxes[best_idx:best_idx+1] |
| person_scores = person_scores[best_idx:best_idx+1] |
| |
| class MockInstances: |
| def __init__(self, boxes, scores): |
| class MockBoxes: |
| def __init__(self, b): |
| self.tensor = torch.from_numpy(b) |
| self.pred_boxes = MockBoxes(boxes) |
| self.scores = scores |
| self.pred_classes = np.zeros(len(boxes), dtype=int) |
| |
| return {"instances": MockInstances(person_boxes, person_scores)} |
|
|
| detector = LightweightDetector(args.device) |
|
|
| model = model.to(args.device) |
| detector.model.to(args.device) |
| |
| renderer = SemanticRenderer( |
| model_cfg, |
| faces=model.smpl.faces, |
| lbs=model.smpl.lbs_weights, |
| viewport_size=(720, 720), |
| ) |
| for img_path in tqdm(reference_img_paths, desc="Processing Reference Images:"): |
| img_fn, _ = os.path.splitext(os.path.basename(img_path)) |
| if os.path.exists(os.path.join(args.reference_imgs_folder, "smpl_results", f"{img_fn}.npy")): |
| continue |
|
|
| img_cv2 = cv2.imread( |
| str(os.path.join(args.reference_imgs_folder, "images", img_path)) |
| ) |
|
|
| try: |
| renderer.renderer.delete() |
| except Exception: |
| pass |
|
|
| import torch |
| torch.cuda.empty_cache() |
| print(f"\n[DEBUG] Creating OffscreenRenderer for {img_path} ({img_cv2.shape})", flush=True) |
|
|
| renderer.renderer = pyrender.OffscreenRenderer( |
| viewport_width=img_cv2.shape[:2][::-1][0], |
| viewport_height=img_cv2.shape[:2][::-1][1], |
| point_size=1.0, |
| ) |
| img_fn, _ = os.path.splitext(os.path.basename(img_path)) |
| dataloader = load_image(img_cv2, detector) |
|
|
| for batch in dataloader: |
| batch = recursive_to(batch, args.device) |
| results_dict_for_rendering, misc_args = predict_smpl(batch, model, model_cfg, args.figure_scale) |
| |
| print(f"[DEBUG] Rendering {img_path} with PyRender...", flush=True) |
| rendering_results = renderer.render_all_multiple( |
| results_dict_for_rendering["verts"], |
| cam_t=results_dict_for_rendering["cam_t"], |
| render_res=results_dict_for_rendering["render_res"], **misc_args |
| ) |
| print(f"[DEBUG] Render finished for {img_path}", flush=True) |
| |
| valid_mask = rendering_results["Image"][:, :, -1][:, :, np.newaxis] |
| cam_view = ( |
| valid_mask * rendering_results["Image"][:, :, [2, 1, 0]] |
| + (1 - valid_mask) * img_cv2.astype(np.float32)[:, :, ::-1] / 255 |
| ) |
|
|
| cv2.imwrite( |
| os.path.join(args.reference_imgs_folder, "visualized_imgs", f"{img_fn}.png"), |
| 255 * cam_view[:, :, ::-1]) |
| cv2.imwrite( |
| os.path.join(args.reference_imgs_folder, "mask", f"{img_fn}.png"), |
| 255 * rendering_results.get("Mask")[:, :, 0]) |
| cv2.imwrite( |
| os.path.join(args.reference_imgs_folder, "semantic_map", f"{img_fn}.png"), |
| 255 * rendering_results.get("SemanticMap")) |
| np.save( |
| str(os.path.join(args.reference_imgs_folder, "smpl_results", f"{img_fn}.npy")), |
| results_dict_for_rendering) |
| |
| for video_path in tqdm(driving_videos_paths, desc="Processing Driving Videos:"): |
| os.makedirs(video_path, exist_ok=True) |
| os.makedirs(os.path.join(video_path, "smpl_results"), exist_ok=True) |
|
|
| driving_img_paths = [ |
| path for path in os.listdir(os.path.join(video_path, "images")) |
| ] |
| driving_img_paths.sort(key=lambda x: int(x.split(".")[0])) |
| smpls = [] |
| cams = [] |
| |
| |
| |
| print(f"[{video_path}] Phase 1: Running Person Detection...", flush=True) |
| |
| model = model.cpu() |
| torch.cuda.empty_cache() |
| detector.model = detector.model.to(args.device) |
|
|
| frame_boxes = {} |
| for img_path in tqdm(driving_img_paths, desc="Phase 1 (Detection)"): |
| img_cv2 = cv2.imread(str(os.path.join(video_path, "images", img_path))) |
| |
| with torch.autocast(device_type="cuda", dtype=torch.float16): |
| det_out = detector(img_cv2) |
| |
| det_instances = det_out["instances"] |
| valid_idx = (det_instances.pred_classes == 0) & (det_instances.scores > 0.5) |
| boxes = det_instances.pred_boxes.tensor[valid_idx].cpu().numpy() |
| frame_boxes[img_path] = boxes |
| |
| |
| |
| |
| print(f"[{video_path}] Phase 2: Running 3D Pose Estimation...", flush=True) |
| |
| detector.model = detector.model.cpu() |
| torch.cuda.empty_cache() |
| model = model.to(args.device) |
|
|
| smpls = [] |
| cams = [] |
| for img_path in tqdm(driving_img_paths, desc="Phase 2 (HMR2)"): |
| img_cv2 = cv2.imread(str(os.path.join(video_path, "images", img_path))) |
| img_fn, _ = os.path.splitext(os.path.basename(img_path)) |
| boxes = frame_boxes[img_path] |
| |
| dataset = ViTDetDataset(model_cfg, img_cv2, boxes) |
| dataloader = torch.utils.data.DataLoader(dataset, batch_size=8, shuffle=False, num_workers=0) |
|
|
| for batch in dataloader: |
| batch = recursive_to(batch, args.device) |
| results_dict_for_rendering, misc_args = predict_smpl(batch, model, model_cfg) |
| cams.append(results_dict_for_rendering["cam_t"][0]) |
| smpls.append(results_dict_for_rendering["smpls"]) |
| np.save( |
| str(os.path.join(video_path, "smpl_results", f"{img_fn}.npy")), |
| results_dict_for_rendering) |
| |
| np.savez( |
| str(os.path.join(video_path, "smpl_results", f"smpls_group.npz")), |
| smpl=smpls,camera=cams) |
|
|