Files
hand-motion-pipeline/third_party/FoundationPose/learning/training/predict_pose_refine.py
T
liyang ae28d55f81 Update to 2026-09-17 pipeline snapshot; add weights, L20 assets and recording via Git LFS
Source: RGB-D -> Dyn-HaMR -> L20 retargeting -> FoundationPose -> reference repair -> SPIDER,
documented in docs/PIPELINE_LATEST.md and docs/SETUP_AND_WEIGHTS.md. Adds FoundationPose and
nvdiffrast upstream snapshots, requirements/pipeline_venv.txt and the FoundationPose weight
manifest/downloader.

Assets (Git LFS): weights/ (WiLoR detector, HandFlow denoiser, UniDepth-L), FoundationPose
checkpoints, HaMeR checkpoint, Dyn-HaMR HMP model and BMC constraints, L20 URDF/meshes, the
20260915_171525 D405 recording and the two box CADs. MANO models are not redistributed
(third_party/hamer/_DATA/data/mano/README.txt). Environments, caches and run outputs excluded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:43:37 +08:00

297 lines
14 KiB
Python

# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
import functools
import os,sys,kornia
import time
code_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(f'{code_dir}/../../')
import numpy as np
import torch
from omegaconf import OmegaConf
from learning.models.refine_network import RefineNet
from learning.datasets.h5_dataset import *
from Utils import *
from datareader import *
@torch.inference_mode()
def make_crop_data_batch(render_size, ob_in_cams, mesh, rgb, depth, K, crop_ratio, xyz_map, normal_map=None, mesh_diameter=None, cfg=None, glctx=None, mesh_tensors=None, dataset:PoseRefinePairH5Dataset=None):
logging.info("Welcome make_crop_data_batch")
H,W = depth.shape[:2]
args = []
method = 'box_3d'
tf_to_crops = compute_crop_window_tf_batch(pts=mesh.vertices, H=H, W=W, poses=ob_in_cams, K=K, crop_ratio=crop_ratio, out_size=(render_size[1], render_size[0]), method=method, mesh_diameter=mesh_diameter)
logging.info("make tf_to_crops done")
B = len(ob_in_cams)
poseA = torch.as_tensor(ob_in_cams, dtype=torch.float, device='cuda')
bs = 512
rgb_rs = []
depth_rs = []
normal_rs = []
xyz_map_rs = []
bbox2d_crop = torch.as_tensor(np.array([0, 0, cfg['input_resize'][0]-1, cfg['input_resize'][1]-1]).reshape(2,2), device='cuda', dtype=torch.float)
bbox2d_ori = transform_pts(bbox2d_crop, tf_to_crops.inverse()).reshape(-1,4)
for b in range(0,len(poseA),bs):
extra = {}
rgb_r, depth_r, normal_r = nvdiffrast_render(K=K, H=H, W=W, ob_in_cams=poseA[b:b+bs], context='cuda', get_normal=cfg['use_normal'], glctx=glctx, mesh_tensors=mesh_tensors, output_size=cfg['input_resize'], bbox2d=bbox2d_ori[b:b+bs], use_light=True, extra=extra)
rgb_rs.append(rgb_r)
depth_rs.append(depth_r[...,None])
normal_rs.append(normal_r)
xyz_map_rs.append(extra['xyz_map'])
rgb_rs = torch.cat(rgb_rs, dim=0).permute(0,3,1,2) * 255
depth_rs = torch.cat(depth_rs, dim=0).permute(0,3,1,2) #(B,1,H,W)
xyz_map_rs = torch.cat(xyz_map_rs, dim=0).permute(0,3,1,2) #(B,3,H,W)
Ks = torch.as_tensor(K, device='cuda', dtype=torch.float).reshape(1,3,3)
if cfg['use_normal']:
normal_rs = torch.cat(normal_rs, dim=0).permute(0,3,1,2) #(B,3,H,W)
logging.info("render done")
rgbBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(rgb, dtype=torch.float, device='cuda').permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='bilinear', align_corners=False)
if rgb_rs.shape[-2:]!=cfg['input_resize']:
rgbAs = kornia.geometry.transform.warp_perspective(rgb_rs, tf_to_crops, dsize=render_size, mode='bilinear', align_corners=False)
else:
rgbAs = rgb_rs
if xyz_map_rs.shape[-2:]!=cfg['input_resize']:
xyz_mapAs = kornia.geometry.transform.warp_perspective(xyz_map_rs, tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
else:
xyz_mapAs = xyz_map_rs
xyz_mapBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(xyz_map, device='cuda', dtype=torch.float).permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='nearest', align_corners=False) #(B,3,H,W)
if cfg['use_normal']:
normalAs = kornia.geometry.transform.warp_perspective(normal_rs, tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
normalBs = kornia.geometry.transform.warp_perspective(torch.as_tensor(normal_map, dtype=torch.float, device='cuda').permute(2,0,1)[None].expand(B,-1,-1,-1), tf_to_crops, dsize=render_size, mode='nearest', align_corners=False)
else:
normalAs = None
normalBs = None
logging.info("warp done")
mesh_diameters = torch.ones((len(rgbAs)), dtype=torch.float, device='cuda')*mesh_diameter
pose_data = BatchPoseData(rgbAs=rgbAs, rgbBs=rgbBs, depthAs=None, depthBs=None, normalAs=normalAs, normalBs=normalBs, poseA=poseA, poseB=None, xyz_mapAs=xyz_mapAs, xyz_mapBs=xyz_mapBs, tf_to_crops=tf_to_crops, Ks=Ks, mesh_diameters=mesh_diameters)
pose_data = dataset.transform_batch(batch=pose_data, H_ori=H, W_ori=W, bound=1)
logging.info("pose batch data done")
return pose_data
class PoseRefinePredictor:
def __init__(self,):
logging.info("welcome")
self.amp = True
self.run_name = "2023-10-28-18-33-37"
model_name = 'model_best.pth'
code_dir = os.path.dirname(os.path.realpath(__file__))
ckpt_dir = f'{code_dir}/../../weights/{self.run_name}/{model_name}'
self.cfg = OmegaConf.load(f'{code_dir}/../../weights/{self.run_name}/config.yml')
self.cfg['ckpt_dir'] = ckpt_dir
self.cfg['enable_amp'] = True
########## Defaults, to be backward compatible
if 'use_normal' not in self.cfg:
self.cfg['use_normal'] = False
if 'use_mask' not in self.cfg:
self.cfg['use_mask'] = False
if 'use_BN' not in self.cfg:
self.cfg['use_BN'] = False
if 'c_in' not in self.cfg:
self.cfg['c_in'] = 4
if 'crop_ratio' not in self.cfg or self.cfg['crop_ratio'] is None:
self.cfg['crop_ratio'] = 1.2
if 'n_view' not in self.cfg:
self.cfg['n_view'] = 1
if 'trans_rep' not in self.cfg:
self.cfg['trans_rep'] = 'tracknet'
if 'rot_rep' not in self.cfg:
self.cfg['rot_rep'] = 'axis_angle'
if 'zfar' not in self.cfg:
self.cfg['zfar'] = 3
if 'normalize_xyz' not in self.cfg:
self.cfg['normalize_xyz'] = False
if isinstance(self.cfg['zfar'], str) and 'inf' in self.cfg['zfar'].lower():
self.cfg['zfar'] = np.inf
if 'normal_uint8' not in self.cfg:
self.cfg['normal_uint8'] = False
logging.info(f"self.cfg: \n {OmegaConf.to_yaml(self.cfg)}")
self.dataset = PoseRefinePairH5Dataset(cfg=self.cfg, h5_file='', mode='test')
self.model = RefineNet(cfg=self.cfg, c_in=self.cfg['c_in']).cuda()
logging.info(f"Using pretrained model from {ckpt_dir}")
ckpt = torch.load(ckpt_dir)
if 'model' in ckpt:
ckpt = ckpt['model']
self.model.load_state_dict(ckpt)
self.model.cuda().eval()
logging.info("init done")
self.last_trans_update = None
self.last_rot_update = None
@torch.inference_mode()
def predict(self, rgb, depth, K, ob_in_cams, xyz_map, normal_map=None, get_vis=False, mesh=None, mesh_tensors=None, glctx=None, mesh_diameter=None, iteration=5):
'''
@rgb: np array (H,W,3)
@ob_in_cams: np array (N,4,4)
'''
torch.set_default_tensor_type('torch.cuda.FloatTensor')
logging.info(f'ob_in_cams:{ob_in_cams.shape}')
tf_to_center = np.eye(4)
ob_centered_in_cams = ob_in_cams
mesh_centered = mesh
logging.info(f'self.cfg.use_normal:{self.cfg.use_normal}')
if not self.cfg.use_normal:
normal_map = None
crop_ratio = self.cfg['crop_ratio']
logging.info(f"trans_normalizer:{self.cfg['trans_normalizer']}, rot_normalizer:{self.cfg['rot_normalizer']}")
bs = 1024
B_in_cams = torch.as_tensor(ob_centered_in_cams, device='cuda', dtype=torch.float)
if mesh_tensors is None:
mesh_tensors = make_mesh_tensors(mesh_centered)
rgb_tensor = torch.as_tensor(rgb, device='cuda', dtype=torch.float)
depth_tensor = torch.as_tensor(depth, device='cuda', dtype=torch.float)
xyz_map_tensor = torch.as_tensor(xyz_map, device='cuda', dtype=torch.float)
trans_normalizer = self.cfg['trans_normalizer']
if not isinstance(trans_normalizer, float):
trans_normalizer = torch.as_tensor(list(trans_normalizer), device='cuda', dtype=torch.float).reshape(1,3)
for _ in range(iteration):
logging.info("making cropped data")
pose_data = make_crop_data_batch(self.cfg.input_resize, B_in_cams, mesh_centered, rgb_tensor, depth_tensor, K, crop_ratio=crop_ratio, normal_map=normal_map, xyz_map=xyz_map_tensor, cfg=self.cfg, glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, mesh_diameter=mesh_diameter)
B_in_cams = []
for b in range(0, pose_data.rgbAs.shape[0], bs):
A = torch.cat([pose_data.rgbAs[b:b+bs].cuda(), pose_data.xyz_mapAs[b:b+bs].cuda()], dim=1).float()
B = torch.cat([pose_data.rgbBs[b:b+bs].cuda(), pose_data.xyz_mapBs[b:b+bs].cuda()], dim=1).float()
logging.info("forward start")
with torch.cuda.amp.autocast(enabled=self.amp):
output = self.model(A,B)
for k in output:
output[k] = output[k].float()
logging.info("forward done")
if self.cfg['trans_rep']=='tracknet':
if not self.cfg['normalize_xyz']:
trans_delta = torch.tanh(output["trans"])*trans_normalizer
else:
trans_delta = output["trans"]
elif self.cfg['trans_rep']=='deepim':
def project_and_transform_to_crop(centers):
uvs = (pose_data.Ks[b:b+bs]@centers.reshape(-1,3,1)).reshape(-1,3)
uvs = uvs/uvs[:,2:3]
uvs = (pose_data.tf_to_crops[b:b+bs]@uvs.reshape(-1,3,1)).reshape(-1,3)
return uvs[:,:2]
rot_delta = output["rot"]
z_pred = output['trans'][:,2]*pose_data.poseA[b:b+bs][...,2,3]
uvA_crop = project_and_transform_to_crop(pose_data.poseA[b:b+bs][...,:3,3])
uv_pred_crop = uvA_crop + output['trans'][:,:2]*self.cfg['input_resize'][0]
uv_pred = transform_pts(uv_pred_crop, pose_data.tf_to_crops[b:b+bs].inverse().cuda())
center_pred = torch.cat([uv_pred, torch.ones((len(rot_delta),1), dtype=torch.float, device='cuda')], dim=-1)
center_pred = (pose_data.Ks[b:b+bs].inverse().cuda()@center_pred.reshape(len(rot_delta),3,1)).reshape(len(rot_delta),3) * z_pred.reshape(len(rot_delta),1)
trans_delta = center_pred-pose_data.poseA[b:b+bs][...,:3,3]
else:
trans_delta = output["trans"]
if self.cfg['rot_rep']=='axis_angle':
rot_mat_delta = torch.tanh(output["rot"])*self.cfg['rot_normalizer']
rot_mat_delta = so3_exp_map(rot_mat_delta).permute(0,2,1)
elif self.cfg['rot_rep']=='6d':
rot_mat_delta = rotation_6d_to_matrix(output['rot']).permute(0,2,1)
else:
raise RuntimeError
if self.cfg['normalize_xyz']:
trans_delta *= (mesh_diameter/2)
B_in_cam = egocentric_delta_pose_to_pose(pose_data.poseA[b:b+bs], trans_delta=trans_delta, rot_mat_delta=rot_mat_delta)
B_in_cams.append(B_in_cam)
B_in_cams = torch.cat(B_in_cams, dim=0).reshape(len(ob_in_cams),4,4)
B_in_cams_out = B_in_cams@torch.tensor(tf_to_center[None], device='cuda', dtype=torch.float)
torch.cuda.empty_cache()
self.last_trans_update = trans_delta
self.last_rot_update = rot_mat_delta
if get_vis:
logging.info("get_vis...")
canvas = []
padding = 2
pose_data = make_crop_data_batch(self.cfg.input_resize, torch.as_tensor(ob_centered_in_cams), mesh_centered, rgb, depth, K, crop_ratio=crop_ratio, normal_map=normal_map, xyz_map=xyz_map_tensor, cfg=self.cfg, glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, mesh_diameter=mesh_diameter)
for id in range(0, len(B_in_cams)):
rgbA_vis = (pose_data.rgbAs[id]*255).permute(1,2,0).data.cpu().numpy()
rgbB_vis = (pose_data.rgbBs[id]*255).permute(1,2,0).data.cpu().numpy()
row = [rgbA_vis, rgbB_vis]
H,W = rgbA_vis.shape[:2]
if pose_data.depthAs is not None:
depthA = pose_data.depthAs[id].data.cpu().numpy().reshape(H,W)
depthB = pose_data.depthBs[id].data.cpu().numpy().reshape(H,W)
elif pose_data.xyz_mapAs is not None:
depthA = pose_data.xyz_mapAs[id][2].data.cpu().numpy().reshape(H,W)
depthB = pose_data.xyz_mapBs[id][2].data.cpu().numpy().reshape(H,W)
zmin = min(depthA.min(), depthB.min())
zmax = max(depthA.max(), depthB.max())
depthA_vis = depth_to_vis(depthA, zmin=zmin, zmax=zmax, inverse=False)
depthB_vis = depth_to_vis(depthB, zmin=zmin, zmax=zmax, inverse=False)
row += [depthA_vis, depthB_vis]
if pose_data.normalAs is not None:
pass
row = make_grid_image(row, nrow=len(row), padding=padding, pad_value=255)
row = cv_draw_text(row, text=f'id:{id}', uv_top_left=(10,10), color=(0,255,0), fontScale=0.5)
canvas.append(row)
canvas = make_grid_image(canvas, nrow=1, padding=padding, pad_value=255)
pose_data = make_crop_data_batch(self.cfg.input_resize, B_in_cams, mesh_centered, rgb, depth, K, crop_ratio=crop_ratio, normal_map=normal_map, xyz_map=xyz_map_tensor, cfg=self.cfg, glctx=glctx, mesh_tensors=mesh_tensors, dataset=self.dataset, mesh_diameter=mesh_diameter)
canvas_refined = []
for id in range(0, len(B_in_cams)):
rgbA_vis = (pose_data.rgbAs[id]*255).permute(1,2,0).data.cpu().numpy()
rgbB_vis = (pose_data.rgbBs[id]*255).permute(1,2,0).data.cpu().numpy()
row = [rgbA_vis, rgbB_vis]
H,W = rgbA_vis.shape[:2]
if pose_data.depthAs is not None:
depthA = pose_data.depthAs[id].data.cpu().numpy().reshape(H,W)
depthB = pose_data.depthBs[id].data.cpu().numpy().reshape(H,W)
elif pose_data.xyz_mapAs is not None:
depthA = pose_data.xyz_mapAs[id][2].data.cpu().numpy().reshape(H,W)
depthB = pose_data.xyz_mapBs[id][2].data.cpu().numpy().reshape(H,W)
zmin = min(depthA.min(), depthB.min())
zmax = max(depthA.max(), depthB.max())
depthA_vis = depth_to_vis(depthA, zmin=zmin, zmax=zmax, inverse=False)
depthB_vis = depth_to_vis(depthB, zmin=zmin, zmax=zmax, inverse=False)
row += [depthA_vis, depthB_vis]
row = make_grid_image(row, nrow=len(row), padding=padding, pad_value=255)
canvas_refined.append(row)
canvas_refined = make_grid_image(canvas_refined, nrow=1, padding=padding, pad_value=255)
canvas = make_grid_image([canvas, canvas_refined], nrow=2, padding=padding, pad_value=255)
torch.cuda.empty_cache()
return B_in_cams_out, canvas
return B_in_cams_out, None