Files
hand-motion-pipeline/scripts/render_red_depth_comparison.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

40 lines
3.3 KiB
Python

"""Camera overlay and fixed oblique 3D comparison; identical view for both conditions."""
import sys,json,subprocess
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT/'third_party/FoundationPose'))
import cv2,numpy as np,torch,trimesh,nvdiffrast.torch as dr
from Utils import nvdiffrast_render,make_mesh_tensors
from scipy.spatial.transform import Rotation
out=ROOT/'output/depth_ablation_red_20260916';a=np.load(out/'foreground_left_depth_comparison.npz');N=len(a['verts_before']);K=a['K'];P=a['object_pose'];mesh=trimesh.load(out/'red_box_closed.ply');ctx=dr.RasterizeCudaContext();eye=torch.eye(4,device='cuda')[None]
center=np.median(np.einsum('tij,j->ti',P[:,:3,:3],mesh.bounds.mean(0))+P[:,:3,3],axis=0);R=Rotation.from_euler('y',55,degrees=True).as_matrix();virtualK=np.array([[700.,0,424],[0,700,240],[0,0,1.]])
def render(v,f,color,k):
m=trimesh.Trimesh(vertices=v,faces=f,process=False);m.visual.vertex_colors=np.tile([*color,255],(len(v),1));mt=make_mesh_tensors(m)
with torch.inference_mode():c,d,_=nvdiffrast_render(K=k,H=480,W=848,ob_in_cams=eye,glctx=ctx,mesh_tensors=mt,use_light=True)
return (c[0].cpu().numpy()[:,:,::-1]*255).astype('uint8'),d[0].cpu().numpy()
def scene(b,hand,box,k):
colors=[];depth=[]
for v,f,c in [(box,mesh.faces,[60,185,95]),(hand,a['faces'],[80,145,235])]:
cc,d=render(v,f,c,k);colors.append(cc);depth.append(d)
masks=[d>0 for d in depth];front=masks[1]&((~masks[0])|(depth[1]<depth[0]));img=b.copy()
for mask,col in [(masks[0]&~front,colors[0]),(front,colors[1])]:img[mask]=(img[mask]*.25+col[mask]*.75).astype('uint8')
return img
cap=cv2.VideoCapture(str(ROOT/'docs/20260916_104026/color.mp4'));video=out/'depth_before_after.mp4';w=subprocess.Popen(['ffmpeg','-y','-v','error','-f','rawvideo','-pix_fmt','bgr24','-s','1696x960','-r','30','-i','-','-an','-c:v','libx264','-preset','fast','-crf','20','-pix_fmt','yuv420p','-movflags','+faststart',str(video)],stdin=subprocess.PIPE)
for t in range(N):
ok,b=cap.read();assert ok;box=mesh.vertices@P[t,:3,:3].T+P[t,:3,3];panels=[]
for view in ['camera','oblique']:
for variant in ['before','after']:
hand=a['verts_'+variant][t];vv=box
if view=='oblique':hand=(hand-center)@R.T+[0,0,.65];vv=(box-center)@R.T+[0,0,.65]
bg=b if view=='camera' else np.full_like(b,35);pic=scene(bg,hand,vv,K if view=='camera' else virtualK)
name='RGB only' if variant=='before' else 'RGB + depth translation'
cv2.rectangle(pic,(0,0),(848,52),(0,0,0),-1);cv2.putText(pic,f'{name} | {view} | {t/30:.2f}s',(12,23),0,.62,(240,240,240),1)
status='paired evaluation' if a['paired'][t] else 'not in paired metrics'
cv2.putText(pic,status,(12,44),0,.5,(0,220,255),1);panels.append(pic)
panel=np.vstack([np.hstack(panels[:2]),np.hstack(panels[2:])]);w.stdin.write(panel.tobytes())
if t in [30,90,150,210,270]:cv2.imwrite(str(out/f'comparison_{t:04d}.jpg'),panel)
if t%60==0:print('render',t,flush=True)
w.stdin.close();assert w.wait()==0;cap.release();v=cv2.VideoCapture(str(video));n=0
while v.read()[0]:n+=1
assert n==N
(out/'video_validation.json').write_text(json.dumps({'decoded_frames':n,'fps':30,'width':1696,'height':960,'fixed_oblique_yaw_deg':55,'scope':'foreground left hand; depth-only translation, no pose/shape/smoothing changes'},indent=2));print('Video passed',n,flush=True)