yoyozs11's picture
Update scripts/data_processors/smpl/generate_smpls.py
d5e5fc2 verified
Raw
History Blame Contribute Delete
13.8 kB
# =========================================================================
# 🎯 [路徑防禦補丁] 確保子程序被劫持進 venv 後,依然認得自己的專案根目錄路徑
# =========================================================================
import os
import sys
current_script_dir = os.path.dirname(os.path.abspath(__file__))
# 向上推 4 層,精準定位到 /app/champ-master 根目錄
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
# For Windows, remove PYOPENGL_PLATFORM to enable default rendering backend
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()
# Render the result
batch_size = batch["img"].shape[0]
for n in range(batch_size):
# Add all verts and cams to list
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):
# Detect humans in image
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()
# Run HMR2.0 on all detected humans
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)
# Load ultra-lightweight torchvision detector to completely eliminate ViT-H overhead
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']
# Person class is 1 in COCO
valid_idx = (labels == 1) & (scores > 0.5)
person_boxes = boxes[valid_idx].cpu().numpy()
person_scores = scores[valid_idx].cpu().numpy()
# Protection: Only take the person with the highest confidence score
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)
# This PyRender is only used for visualizing, we use Blender after to render different conditions.
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)
# Overlay image
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 = []
# ---------------------------------------------------------
# Phase 1: Detection (Run Detectron2 on all frames to avoid VRAM thrashing)
# ---------------------------------------------------------
print(f"[{video_path}] Phase 1: Running Person Detection...", flush=True)
# 釋放 HMR2 到 CPU,把 VRAM 全留給 Detectron2
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
# ---------------------------------------------------------
# Phase 2: HMR2 (Run pose estimation on detected boxes)
# ---------------------------------------------------------
print(f"[{video_path}] Phase 2: Running 3D Pose Estimation...", flush=True)
# 釋放 Detectron2 到 CPU,把 VRAM 全留給 HMR2
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)