返回汇报

RGB 批次入口:拟合、尺度与渲染

原路径:/SSD2/yangzijing/egoview-v9-foundationhand-s9l10-20260901/v9_benchmark/ego0824_rgb_demo.py
SHA256:bc6b4e4e838b6ff40ab6acbdcbe790c95f334659b2b6d514fd011add415eb8ff

   1 """Isolated, explicitly uncalibrated RGB demo branch for the 08479125 archive."""
   2 import argparse, os, time, json
   3 from pathlib import Path
   4 from dataclasses import replace, asdict
   5 os.environ.setdefault('PYOPENGL_PLATFORM','egl')
   6 import numpy as np
   7 from v9_benchmark.ego0824_p3r2 import OUT, PILOT, read
   8 from v9_benchmark.hf_0907_s9_worker import load_npz as load
   9 from v9_fusion.io import write_json as write, sha256_file as sha
  10 ROOT=Path(__file__).resolve().parents[1]
  11 DEST=Path(os.environ.get('EGO0824_DEMO_ROOT',str(OUT/'rgb_demo')))
  12 
  13 def rows():return read(OUT/'input_inventory.json')['sequences']
  14 def rgb_fit_receipt(report):
  15  report=dict(report)
  16  report['estimated_canonical_K']=report.pop('native_sensor_K',None)
  17  report['measured_sensor_intrinsics_used']=False
  18  report['ground_truth_used']=False
  19  report['wrist_depth_policy']='RGB and metric MANO size prior jointly fitted with assumed canonical K; estimated scale, not calibrated GT'
  20  report['palm_initialization']=dict(report['palm_initialization'],method='S9_palm_SQPNP_seed_with_assumed_canonical_K')
  21  report['receipt_label_correction']='Shared fit engine contains historical calibrated/native labels; this batch supplies explicitly estimated K. Numerical outputs unchanged.'
  22  return report
  23 def link(src,dst):
  24  if not dst.exists():dst.symlink_to(Path(src).resolve(),target_is_directory=Path(src).is_dir())
  25 
  26 def prepare(row):
  27  sid=row['sequence_id'];src=OUT/'native_rgb_analysis/work'/sid;w=DEST/'work'/sid;w.mkdir(parents=True,exist_ok=True)
  28  for name in ['rgb','sam_dense']:link(src/name,w/name)
  29  link(read(src/'raw_s9_receipt.json')['prediction_path'],w/'raw_s9.npz')
  30  p=read(src/'prepared.json');p.update(profile='V9P3R2_RGB_demo',camera_geometry='canonical_not_calibrated',production_geometry_ready=True,ground_truth_used=False,intrinsics_source='historical rgb_raster_half_width assumption',depth_payload=False)
  31  write(w/'prepared.json',p)
  32  z=load(src/'rgb_candidates.npz');m=load(OUT/'native_tracking_ablations_v3'/sid/'repair_candidate.npz');mapping=m['mapping'].copy();tracks=m['tracks'].copy();n=row['frame_count']
  33  audit=OUT/'native_inputs'/sid/'strict_frame_audit.json'
  34  bad=read(audit)['strict_missing_frame_ids'] if audit.exists() else [];mapping[bad]=-1;tracks[bad]=-1
  35  e=load(src/'semantic_observations.npz')
  36  for k in ['valid','detected','detector_observed','v5r_mmpose_valid']:e[k][:]=False
  37  for k in ['confidence','sam_score','v5r_mmpose_keypoint_scores']:e[k][:]=0
  38  for k in ['boxes_xyxy','v5r_mmpose_keypoints_2d']:e[k][:]=np.nan
  39  e['sam_index'][:]=-1;e['observation_track_ids']=tracks
  40  for f,s in np.argwhere(mapping>=0):
  41   i=z['offsets'][f]+mapping[f,s]
  42   for k in ['valid','detected','v5r_mmpose_valid']:e[k][f,s]=True
  43   for target,source in [('boxes_xyxy','box'),('v5r_mmpose_keypoints_2d','keypoints'),('v5r_mmpose_keypoint_scores','scores'),('confidence','yolo_score'),('sam_score','sam_score'),('sam_index','sam_index')]:e[target][f,s]=z[source][i]
  44   e['detector_observed'][f,s]=z['yolo_score'][i]>=.3
  45  # Without measured depth, a SAM mask and fewer than the fitter's four
  46  # reliable joints cannot constrain a partial hand's pose and root depth.
  47  # Retain the observation in the source cache, but do not fabricate 3D.
  48  unsupported=e['valid']&((e['v5r_mmpose_keypoint_scores']>=.35).sum(-1)<4)
  49  for key in ['valid','detected','v5r_mmpose_valid']:e[key][unsupported]=False
  50  e['observation_track_ids'][unsupported]=-1
  51  fragment=np.zeros_like(unsupported)
  52  for s in range(2):
  53   ids=np.flatnonzero(e['valid'][:,s]);runs=np.split(ids,np.flatnonzero((np.diff(ids)!=1)|(e['observation_track_ids'][ids[1:],s]!=e['observation_track_ids'][ids[:-1],s]))+1)
  54   for run in runs:
  55    if not len(run) or len(run)>2:continue
  56    score=e['v5r_mmpose_keypoint_scores'][run,s]
  57    if ((score>=.35).sum(-1)<12).all() and not (score[:,[0,5,9,13,17]]>=.6).any():fragment[run,s]=True
  58  for key in ['valid','detected','v5r_mmpose_valid']:e[key][fragment]=False
  59  e['observation_track_ids'][fragment]=-1
  60  write(w/'rgb_observability.json',dict(policy='minimum 4 reliable same-frame RGB joints; reject isolated 1-2 frame runs with <12 reliable joints and no confident palm joint, lacking depth/pose constraints',insufficient_joints_slots=np.argwhere(unsupported).tolist(),weak_fragment_slots=np.argwhere(fragment).tolist(),rejected_slots=np.argwhere(unsupported|fragment).tolist(),rejected_count=int((unsupported|fragment).sum()),smoothing_used=False,presence_invention=False,scope='isolated uncalibrated RGB batch; P3R2 production defaults unchanged'))
  61  np.savez_compressed(w/'semantic_observations.npz',**e)
  62  k=np.broadcast_to(np.array([[960.,0,960],[0,960,540],[0,0,1]],np.float32),(n,3,3)).copy()
  63  np.savez_compressed(w/'camera_intrinsics.npz',K=k,timestamps_s=row['timestamps_s'],source_pts_s=row['source_pts_s'],source_frame_ids=np.arange(n),source_frame_valid=~np.isin(np.arange(n),bad))
  64  write(w/'input_binding.json',dict(source_sha256=row['source_sha256'],source_archive_sha256=row['source_archive_sha256'],source_video=row['source_video'],raw_s9_sha256=sha(w/'raw_s9.npz'),observations_sha256=sha(w/'semantic_observations.npz'),damaged_frames=bad,tracking_variant='native_tracking_ablations_v3/repair_candidate',tracking_acceptance='isolated RGB assignment regression checked; final 3D visual acceptance pending',camera_geometry='uncalibrated canonical pinhole',K=k[0].tolist(),depth_for_hand_fit=False,depth_trajectory_authorization='User explicit batch-only historical RGB override; no production defaults changed'))
  65  return w
  66 
  67 def fit(row,mano):
  68  from v9_benchmark.p3r_fit_worker import constraints_for,repaired_config
  69  from v9_benchmark.p3r1_anomaly_experiment import hand_arrays
  70  from v9_fusion.s9_sequence_fit import fit_sequence
  71  from v9_fusion.hand_surface import SurfaceConfig
  72  import torch
  73  w=DEST/'work'/row['sequence_id']
  74  if (w/'fit_complete.json').exists():return
  75  w=prepare(row);e=load(w/'semantic_observations.npz');raw=load(w/'raw_s9.npz');k=load(w/'camera_intrinsics.npz')['K'];surface=constraints_for(w,e)
  76  # R2 requires explicit absence of depth, and disables containment on partial
  77  # masks without reliable depth. RGB predicted depth is not a sensor factor.
  78  surface['mask_depth_seed_m']=np.zeros(e['valid'].shape,np.float32)
  79  partial=(e['v5r_mmpose_keypoint_scores']>=.35).sum(-1)<12
  80  surface['containment_weight']=np.where(partial,0,surface['mask_weight']).astype(np.float32)
  81  np.savez_compressed(w/'surface_constraints.npz',**surface)
  82  conf=replace(repaired_config(False,quality_refinement=True),respect_observation_track_boundaries=True,partial_mask_initialization=True,palm_confidence_guard=True)
  83  sc=SurfaceConfig(containment_weight=.8,coverage_weight=.25,optimize_subject_shape=True)
  84  torch.manual_seed(20260914);np.random.seed(20260914);start=time.monotonic()
  85  def progress(r):
  86   if r['step']%120==1:print(row['sequence_id'],r['step'],r['loss'],flush=True)
  87  result,report=fit_sequence(raw,e,k,mano,conf,surface_constraints=surface,surface_config=sc,progress=progress)
  88  np.savez_compressed(w/'fitted.npz',**result);write(w/'fit_engine_receipt.json',report);write(w/'fit.json',rgb_fit_receipt(report))
  89  h=hand_arrays(result,e);np.savez_compressed(w/'final_hand_camera.npz',**h)
  90  write(w/'fit_complete.json',dict(frames=row['frame_count'],valid_slots=h['hand_valid'].sum(0).tolist(),config=asdict(conf),surface_config=asdict(sc),seconds=time.monotonic()-start,model='FoundationHand-S9L10 (1e-6)',hand_geometry_source='S9L10 + P3R2 RGB observation/MANO fit only',hawor_geometry_used=False,code_sha256={str(p.relative_to(ROOT)):sha(p) for p in [Path(__file__),ROOT/'v9_fusion/s9_sequence_fit.py',ROOT/'v9_fusion/s9_pose_initialization.py']}))
  91  print('FIT_COMPLETE',row['sequence_id'],flush=True)
  92 
  93 def build_state(row):
  94  import cv2
  95  from egoview.v5_pipeline.state import NormalizedInput,HandPrediction,CameraTrajectory,SceneExclusionPrediction,SceneDepthPrediction,PredictionState,DA3_SCENE_DEPTH_SCHEMA_VERSION,thaw_json
  96  from egoview.v5_pipeline.fusion import build_da3_scene_map,fuse_v5_hands_to_world
  97  from egoview.v5r_pipeline.runner import V7_SCENE_FUSION_CONFIG
  98  from egoview.v5r_scene_scale import align_da3_scene_scale,_MANO_SCALE_POLICY
  99  from v9_benchmark.hf_0907_contract import mano_topology
 100  sid=row['sequence_id'];w=DEST/'work'/sid;n=row['frame_count'];rec,seg=sid.split('__');cache=ROOT/'artifacts/v9_eval/work'/rec/seg/'v7_prediction_state_cache'
 101  receipt=read(cache/'cache_receipt.json');m=read(cache/'prediction_state_metadata.json');binding=receipt['input_binding']
 102  assert binding['source_video_sha256']==row['source_sha256']==sha(row['source_video'])
 103  assert binding['hand_rgb_transform']=='sbs_left_eye' and binding['frame_count']==n and binding['fps']==row['fps']
 104  # Verify recorded cache bytes, independent of current mutable old pipeline code.
 105  for name,record in receipt['artifacts'].items():assert sha(cache/name)==record['sha256'],name
 106  raw=load(cache/'prediction_state.npz');k=load(w/'camera_intrinsics.npz')['K'];e=load(w/'semantic_observations.npz');h=load(w/'final_hand_camera.npz');bad=read(w/'input_binding.json')['damaged_frames']
 107  assert np.array_equal(raw['normalized/frame_ids'],np.arange(n))
 108  ids=np.arange(n);keys=raw['normalized/frame_keys'];nm=m['normalized']['metadata'];nm.update(source=row['source_video'],source_video_sha256=row['source_sha256'],foundationhand_used=True,ground_truth_used=False)
 109  norm=NormalizedInput(rgb_dir=w/'rgb',rgb_paths=tuple(sorted((w/'rgb').glob('*.png'))),frame_ids=ids,frame_keys=keys,timestamp_ns=np.rint(np.array(row['timestamps_s'])*1e9).astype(np.int64),intrinsics=k[0],image_size_hw=np.array([1080,1920]),fps=30.,metadata=nm)
 110  a=dict(e,frame_keys=keys,takes=np.full(n,sid),intrinsics=k[0],joints_camera_m=h['joints_camera_m'],vertices_camera_m=h['vertices_camera_m'],translation_camera_m=h['joints_camera_m'][:,:,0],root_depth_camera_z_m=h['joints_camera_m'][:,:,0,2],valid=h['hand_valid'],mano_rotation_matrices=h['mano_rotation_matrices'],mano_shape=h['mano_shape'])
 111  from scipy.spatial.transform import Rotation
 112  aa=np.full((n,2,16,3),np.nan,np.float32);hv=h['hand_valid']
 113  if hv.any():aa[hv]=Rotation.from_matrix(h['mano_rotation_matrices'][hv].reshape(-1,3,3)).as_rotvec().reshape(-1,16,3)
 114  a.update(global_orient_camera_aa=aa[:,:,0],hand_pose_aa=aa[:,:,1:],betas=h['mano_shape'],mano_hand_size_m=np.full(hv.shape,np.nan,np.float32),root_depth_delta_ray_m=np.zeros(hv.shape,np.float32),root_depth_translation_delta_camera_m=np.zeros((n,2,3),np.float32),root_depth_correction_accepted=np.zeros(hv.shape,bool),root_depth_extent_source=np.full(hv.shape,'P3R2_RGB_joint_fit'),root_depth_solver_reason=np.full(hv.shape,'S9_MANO_RGB_estimated_Z'))
 115  hand=HandPrediction.from_payload(a,dict(profile='V9P3R2_RGB_demo',provider='FoundationHand-S9L10 (1e-6)',mano_scale_policy=_MANO_SCALE_POLICY,mano_scale_modified=False,metric_depth_target_used=False,ground_truth_used=False,sensor_depth_used=False,camera_geometry='canonical_not_calibrated',scale_uncertainty='learned MANO anthropometric prior and assumed focal length; not calibrated metric GT',hawor_geometry_used=False))
 116  # Remove the old hand-driven scene scale before computing this batch's scale.
 117  oldscale=float(m['scene_depth']['metadata']['scene_scale_alignment']['scale_factor'])
 118  dm=m['scene_depth']['metadata'];rawmeta={key:dm[key] for key in ['provider','runtime','provider_metadata','coordinate_convention','depth_quantity','input_modality','backend_invocations','da3_model_invocations']}
 119  rawmeta.update(schema_version=DA3_SCENE_DEPTH_SCHEMA_VERSION,role='shared_rgb_metric_depth_and_scene_map',shared_for_vipe=True,hand_prediction_consumed=False,hand_root_depth_target_used=False,ground_truth_used=False,sensor_depth_used=False,estimated_not_ground_truth=True,old_scene_scale_removed=oldscale)
 120  depthvalues=raw['scene_depth/depth_camera_z_m']/np.float32(oldscale);validdepth=raw['scene_depth/valid_mask'].copy();validdepth[bad]=False
 121  depth=SceneDepthPrediction(frame_ids=ids,frame_keys=keys,depth_camera_z_m=depthvalues,valid_mask=validdepth,intrinsics=raw['scene_depth/intrinsics'],metadata=rawmeta)
 122  faces,model=mano_topology();scale=align_da3_scene_scale(hand,depth,faces[1]);depth=scale.scene_depth
 123  poses=raw['trajectory/camera_to_world'].copy();poses[:,:3,3]*=scale.scale_factor/oldscale;tv=raw['trajectory/valid'].copy();tv[bad]=False
 124  tm=m['trajectory']['metadata'];tm.update(estimated_not_ground_truth=True,old_scene_scale_removed=oldscale,current_sequence_scale=scale.scale_factor,shared_da3_prediction_digest=depth.digest,scale_retargeting='single sequence multiplicative factor applied to all camera translations and all scene depths; no per-frame rescaling')
 125  trajectory=CameraTrajectory(frame_ids=ids,frame_keys=keys,camera_to_world=poses,valid=tv,confidence=raw['trajectory/confidence'],metadata=tm)
 126  exclusion=SceneExclusionPrediction(frame_ids=ids,frame_keys=keys,excluded_mask=raw['scene_exclusion/excluded_mask'],metadata=m['scene_exclusion']['metadata'])
 127  print('SCENE_REBUILD',sid,'scale',scale.scale_factor,scale.reason,flush=True)
 128  scene=build_da3_scene_map(norm,hand,depth,trajectory,exclusion,config=V7_SCENE_FUSION_CONFIG)
 129  world=fuse_v5_hands_to_world(hand,trajectory)
 130  state=PredictionState(normalized=norm,hand=hand,trajectory=trajectory,scene_exclusion=exclusion,scene_depth=depth,scene_map=scene,world_arrays=world,metadata=dict(schema_version='egoview_v5_prediction_state_v3',v5_prediction_digest=hand.digest,shared_da3_prediction_digest=depth.digest,profile='V9P3R2_RGB_demo',estimated_not_ground_truth=True))
 131  np.savez_compressed(w/'world_reconstruction.npz',**world,camera_to_world=poses,camera_valid=tv,K=k,timestamps_s=row['timestamps_s'],faces=faces,scene_points=scene.points_world_m,scene_colors=scene.colors_rgb,scene_support=scene.frame_support)
 132  write(w/'scene_provenance.json',dict(cache=str(cache),cache_artifacts=receipt['artifacts'],input_binding=binding,current_source_sha256=row['source_sha256'],current_decoded_rgb_sha256=row['native_left_decoded_rgb_sha256'],old_hand_geometry_reused=False,old_scene_map_reused=False,old_scene_scale_removed=oldscale,current_scale=thaw_json(depth.metadata['scene_scale_alignment']),trajectory=tm,depth_runtime=rawmeta,scene=thaw_json(scene.metadata),mano=model,all_sources_estimated=True,not_calibrated_ground_truth=True,points=len(scene.points_world_m)))
 133  return state,faces,read(cache/'v7_run_manifest.json')['outputs']['demo']
 134 
 135 def render(row):
 136  import cv2,subprocess
 137  from egoview.v5_pipeline import render as renderer
 138  from v9_fusion.display import _patched_v7_renderer
 139  from v9_benchmark.hand_overlay import HandOverlay
 140  from v9_fusion.constants import V9_MANO_RIGHT
 141  sid=row['sequence_id'];w=DEST/'work'/sid;final=DEST/'final';final.mkdir(parents=True,exist_ok=True);target=final/(sid+'__V9P3R2_RGB_two_column.mp4')
 142  if (w/'render_complete.json').exists():return
 143  state,faces,old=build_state(row);cv2.setNumThreads(2)
 144  config=renderer.DemoRenderConfig(panel_height=720,fps=30.,max_scene_points=300000,point_size=old['point_size'],mano_faces_path=V9_MANO_RIGHT,trajectory_trail_duration_seconds=2.,**old['display_view'])
 145  oldwriter=renderer._write_video;oldoverlay=renderer._CameraMeshOverlayRenderer.render;oldlines=renderer._line_annotations
 146  visibility=np.asarray(state.hand.arrays['valid'])&state.trajectory.valid[:,None]
 147  # Same historical camera/scene view. Upgrade encoding to avoid intermediate
 148  # MPEG4 loss; use native per-side MANO winding in the camera overlay.
 149  index=[0]
 150  def overlay(obj,im,vertices,colors,intrinsics,*,opacity):
 151   f=index[0];index[0]+=1
 152   return HandOverlay(im.shape[1],im.shape[0],faces,obj.renderer).render(im,vertices,colors,intrinsics,opacity=opacity,sides=np.flatnonzero(visibility[f]))
 153  def writer(path,frames,fps):
 154   enc=None;count=0
 155   try:
 156    for count,frame in enumerate(frames,1):
 157     frame=frame.copy();height,width=frame.shape[:2]
 158     cv2.rectangle(frame,(0,0),(width,31),(25,25,25),-1)
 159     text=f'V9P3R2 | RGB estimated geometry - NOT calibrated GT | {sid} | f{count-1}  {(count-1)/fps:.3f}s'
 160     cv2.putText(frame,text,(10,22),cv2.FONT_HERSHEY_SIMPLEX,.53,(245,245,245),1,cv2.LINE_AA)
 161     if count-1 in read(w/'input_binding.json')['damaged_frames']:cv2.putText(frame,'SOURCE DECODE DAMAGE - reconstruction invalid',(12,height-20),cv2.FONT_HERSHEY_SIMPLEX,.8,(0,180,255),2)
 162     if enc is None:enc=subprocess.Popen(['ffmpeg','-hide_banner','-loglevel','error','-y','-f','rawvideo','-pix_fmt','bgr24','-s',f'{width}x{height}','-r',str(fps),'-i','pipe:0','-an','-c:v','libx264','-preset','fast','-crf','18','-threads','2','-pix_fmt','yuv420p','-movflags','+faststart',str(path)],stdin=subprocess.PIPE)
 163     enc.stdin.write(frame.tobytes())
 164     if count%150==0:print('RENDER',sid,count,flush=True)
 165   finally:
 166    if enc is not None:enc.stdin.close()
 167   assert enc is not None and enc.wait()==0
 168   return count
 169  renderer._write_video=writer
 170  def lines(frame_index,*args,**kwargs):
 171   if not state.trajectory.valid[frame_index]:return np.empty((0,2,3),np.float32),np.empty((0,3),np.uint8),np.empty((0,3),np.float32),np.empty((0,3),np.uint8)
 172   return oldlines(frame_index,*args,**kwargs)
 173  renderer._line_annotations=lines
 174  try:
 175   with _patched_v7_renderer(state,config,None,occlusion_radius_m=.018,occlusion_depth_margin_m=.04,overlay_annotations=True,hand_wireframe_rgb=(28,104,65),enable_hand_alignment=False) as patch:
 176    renderer._CameraMeshOverlayRenderer.render=overlay
 177    report=renderer.render_demo(state,target,config=config,demo_hand_visibility=visibility)
 178    report['display_patch']={key:(value() if callable(value) else value) for key,value in patch.items()}
 179    report['display_patch']['hand_alignment']['reason']='same canonical K and shared P3R2 geometry used by both panels; no display-only hand shift'
 180  finally:renderer._write_video=oldwriter;renderer._CameraMeshOverlayRenderer.render=oldoverlay;renderer._line_annotations=oldlines
 181  report.update(camera_hand_source='FoundationHand-S9L10 P3R2 same-frame fitted MANO',world_hand_source='same camera mesh rigidly transformed through estimated ViPE T_wc',estimated_not_ground_truth=True,display_hand_alignment_applied=False,native_left_right_topology=True)
 182  write(w/'render_complete.json',report);print('RENDER_COMPLETE',sid,flush=True)
 183 
 184 def main():
 185  p=argparse.ArgumentParser();p.add_argument('action',choices=['fit','render']);p.add_argument('--sequence',default=PILOT);a=p.parse_args();selected=rows() if a.sequence=='all' else [r for r in rows() if r['sequence_id'] in a.sequence.split(',')]
 186  if a.action=='fit':
 187   from v9_fusion import s9l10_worker as locked
 188   locked._activate_official_runtime()
 189   import torch,yaml
 190   from foundation_hand.models.mano import ManoCameraLayer
 191   from v9_fusion.s9l10 import S9L10_CONFIG,S9L10_PYTHON
 192   assert Path(__import__('sys').executable).samefile(S9L10_PYTHON)
 193   torch.set_num_threads(3);cfg=yaml.safe_load(S9L10_CONFIG.read_text());mano=ManoCameraLayer(cfg['assets']['mano_root'],fix_left_shapedirs=True).cuda().eval();mano.requires_grad_(False)
 194   for row in selected:fit(row,mano)
 195  else:
 196   for row in selected:render(row)
 197 
 198 if __name__=='__main__':main()