Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- .gitattributes +8 -0
- README.md +37 -5
- app.py +1023 -0
- examples/004_sugar_box.glb +3 -0
- examples/006_mustard_bottle.glb +3 -0
- examples/019_pitcher_base.glb +3 -0
- examples/035_power_drill.glb +3 -0
- examples/_meta.json +186 -0
- examples/mustard_bottle_depth.png +0 -0
- examples/mustard_bottle_rgb.png +3 -0
- examples/pitcher_base_depth.png +0 -0
- examples/pitcher_base_rgb.png +3 -0
- examples/power_drill_depth.png +0 -0
- examples/power_drill_rgb.png +3 -0
- examples/sugar_box_depth.png +0 -0
- examples/sugar_box_rgb.png +3 -0
- requirements.txt +13 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/004_sugar_box.glb filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/006_mustard_bottle.glb filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/019_pitcher_base.glb filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/035_power_drill.glb filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/mustard_bottle_rgb.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/pitcher_base_rgb.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
examples/power_drill_rgb.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
examples/sugar_box_rgb.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,13 +1,45 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: green
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.27.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
|
|
|
|
|
|
|
|
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: FoundationPose 6-DoF
|
| 3 |
+
emoji: 📦
|
| 4 |
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.27.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: 6-DoF pose of novel objects from RGB-D plus a CAD model
|
| 10 |
+
python_version: "3.12"
|
| 11 |
+
startup_duration_timeout: 45m
|
| 12 |
pinned: false
|
| 13 |
+
license: other
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# FoundationPose — 6-DoF pose estimation of novel objects
|
| 17 |
+
|
| 18 |
+
[FoundationPose](https://arxiv.org/abs/2312.08344) (NVIDIA, CVPR 2024) estimates the
|
| 19 |
+
full 6-DoF pose of an object it has never seen during training, given
|
| 20 |
+
|
| 21 |
+
* an **RGB image**,
|
| 22 |
+
* the **aligned depth map**,
|
| 23 |
+
* the object's **CAD model**,
|
| 24 |
+
* the **camera intrinsics** (`fx, fy, cx, cy`), and
|
| 25 |
+
* a **2D bounding box** around the object.
|
| 26 |
+
|
| 27 |
+
The demo reproduces the model-based *registration* path of the reference
|
| 28 |
+
implementation: 252 pose hypotheses are sampled on an icosphere (42 viewpoints ×
|
| 29 |
+
6 in-plane rotations), the translation is initialised from the box centre and the
|
| 30 |
+
median depth inside the box, every hypothesis is refined for 5 passes by the
|
| 31 |
+
refiner network, and the scorer network ranks them.
|
| 32 |
+
|
| 33 |
+
The published checkpoints ([`nvidia/foundationpose`](https://huggingface.co/nvidia/foundationpose))
|
| 34 |
+
are ONNX; they are converted to PyTorch with
|
| 35 |
+
[onnx2torch](https://github.com/ENOT-AutoDL/onnx2torch) at startup, so the whole
|
| 36 |
+
pipeline runs natively on ZeroGPU. Rendering uses
|
| 37 |
+
[nvdiffrast](https://github.com/NVlabs/nvdiffrast).
|
| 38 |
+
|
| 39 |
+
## Credits and licences
|
| 40 |
+
|
| 41 |
+
* Weights: NVIDIA Open Model License — <https://huggingface.co/nvidia/foundationpose>
|
| 42 |
+
* Reference code: [NVlabs/FoundationPose](https://github.com/NVlabs/FoundationPose)
|
| 43 |
+
* Example scenes and CAD models: [YCB-Video / BOP](https://huggingface.co/datasets/bop-benchmark/ycbv),
|
| 44 |
+
MIT licence, © 2017 University of Washington Robotics and State Estimation Lab —
|
| 45 |
+
one of FoundationPose's own evaluation datasets.
|
app.py
ADDED
|
@@ -0,0 +1,1023 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FoundationPose — 6-DoF pose estimation of novel objects (NVIDIA, CVPR 2024).
|
| 2 |
+
|
| 3 |
+
Model-based registration path: given an RGB-D frame, the object's CAD model,
|
| 4 |
+
camera intrinsics and a 2D box, sample 252 pose hypotheses on an icosphere,
|
| 5 |
+
refine each with the refiner network, then rank them with the scorer network.
|
| 6 |
+
|
| 7 |
+
Weights: https://huggingface.co/nvidia/foundationpose (ONNX, converted to
|
| 8 |
+
PyTorch at startup with onnx2torch).
|
| 9 |
+
Reference implementation: https://github.com/NVlabs/FoundationPose
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import spaces # noqa: F401 (must be imported before torch / any CUDA touch)
|
| 13 |
+
|
| 14 |
+
import json
|
| 15 |
+
import math
|
| 16 |
+
import os
|
| 17 |
+
import time
|
| 18 |
+
import traceback
|
| 19 |
+
|
| 20 |
+
import cv2
|
| 21 |
+
import gradio as gr
|
| 22 |
+
import numpy as np
|
| 23 |
+
import torch
|
| 24 |
+
import torch.nn.functional as F
|
| 25 |
+
import trimesh
|
| 26 |
+
from huggingface_hub import hf_hub_download
|
| 27 |
+
from PIL import Image
|
| 28 |
+
|
| 29 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 30 |
+
|
| 31 |
+
import kornia # noqa: E402
|
| 32 |
+
import nvdiffrast.torch as dr # noqa: E402
|
| 33 |
+
from onnx2torch import convert # noqa: E402
|
| 34 |
+
|
| 35 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 36 |
+
EX_DIR = os.path.join(HERE, "examples")
|
| 37 |
+
REPO_ID = "nvidia/foundationpose"
|
| 38 |
+
|
| 39 |
+
# ---------------------------------------------------------------------------
|
| 40 |
+
# Constants taken verbatim from the two official FoundationPose config.yml files
|
| 41 |
+
# (refiner: 2023-10-28-18-33-37, scorer: 2024-01-11-20-02-45).
|
| 42 |
+
# ---------------------------------------------------------------------------
|
| 43 |
+
INPUT_RESIZE = (160, 160)
|
| 44 |
+
REFINE_CROP_RATIO = 1.2
|
| 45 |
+
SCORE_CROP_RATIO = 1.1
|
| 46 |
+
ROT_NORMALIZER = 0.3490658503988659 # 20 degrees, in radians
|
| 47 |
+
GLCAM_IN_CVCAM = np.array(
|
| 48 |
+
[[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], dtype=np.float64
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
MAX_SIDE = 1280 # inputs larger than this get downscaled (K is rescaled too)
|
| 52 |
+
MAX_TEX_SIZE = 2000
|
| 53 |
+
|
| 54 |
+
# ---------------------------------------------------------------------------
|
| 55 |
+
# Models — converted from ONNX to PyTorch and moved to CUDA at module scope so
|
| 56 |
+
# that ZeroGPU can pack the weights and stream them in on the first request.
|
| 57 |
+
# ---------------------------------------------------------------------------
|
| 58 |
+
print("Downloading FoundationPose ONNX weights ...", flush=True)
|
| 59 |
+
_refiner_path = hf_hub_download(REPO_ID, "refiner_net.onnx")
|
| 60 |
+
_scorer_path = hf_hub_download(REPO_ID, "score_net.onnx")
|
| 61 |
+
|
| 62 |
+
print("Converting ONNX -> PyTorch ...", flush=True)
|
| 63 |
+
refine_net = convert(_refiner_path).eval().to("cuda")
|
| 64 |
+
score_net = convert(_scorer_path).eval().to("cuda")
|
| 65 |
+
for _p in list(refine_net.parameters()) + list(score_net.parameters()):
|
| 66 |
+
_p.requires_grad_(False)
|
| 67 |
+
print(
|
| 68 |
+
f"refine_net params: {sum(p.numel() for p in refine_net.parameters()):,} | "
|
| 69 |
+
f"score_net params: {sum(p.numel() for p in score_net.parameters()):,}",
|
| 70 |
+
flush=True,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
_GLCTX = None
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def get_glctx():
|
| 77 |
+
"""nvdiffrast CUDA raster context — created lazily inside the GPU worker."""
|
| 78 |
+
global _GLCTX
|
| 79 |
+
if _GLCTX is None:
|
| 80 |
+
_GLCTX = dr.RasterizeCudaContext()
|
| 81 |
+
return _GLCTX
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# ---------------------------------------------------------------------------
|
| 85 |
+
# Geometry / rendering helpers (ports of FoundationPose/Utils.py)
|
| 86 |
+
# ---------------------------------------------------------------------------
|
| 87 |
+
def to_homo_torch(pts):
|
| 88 |
+
ones = torch.ones((*pts.shape[:-1], 1), dtype=pts.dtype, device=pts.device)
|
| 89 |
+
return torch.cat((pts, ones), dim=-1)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def transform_pts(pts, tf):
|
| 93 |
+
if len(tf.shape) >= 3 and tf.shape[-3] != pts.shape[-2]:
|
| 94 |
+
tf = tf[..., None, :, :]
|
| 95 |
+
return (tf[..., :-1, :-1] @ pts[..., None] + tf[..., :-1, -1:])[..., 0]
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def transform_dirs(dirs, tf):
|
| 99 |
+
if len(tf.shape) >= 3 and tf.shape[-3] != dirs.shape[-2]:
|
| 100 |
+
tf = tf[..., None, :, :]
|
| 101 |
+
return (tf[..., :3, :3] @ dirs[..., None])[..., 0]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def so3_exp_map(log_rot, eps=1e-4):
|
| 105 |
+
"""Rodrigues' formula — matches pytorch3d.transforms.so3_exp_map."""
|
| 106 |
+
nrms = (log_rot * log_rot).sum(1)
|
| 107 |
+
rot_angles = torch.clamp(nrms, eps).sqrt()
|
| 108 |
+
rot_angles_inv = 1.0 / rot_angles
|
| 109 |
+
fac1 = rot_angles_inv * rot_angles.sin()
|
| 110 |
+
fac2 = rot_angles_inv * rot_angles_inv * (1.0 - rot_angles.cos())
|
| 111 |
+
skews = torch.zeros(
|
| 112 |
+
(log_rot.shape[0], 3, 3), dtype=log_rot.dtype, device=log_rot.device
|
| 113 |
+
)
|
| 114 |
+
skews[:, 0, 1] = -log_rot[:, 2]
|
| 115 |
+
skews[:, 0, 2] = log_rot[:, 1]
|
| 116 |
+
skews[:, 1, 0] = log_rot[:, 2]
|
| 117 |
+
skews[:, 1, 2] = -log_rot[:, 0]
|
| 118 |
+
skews[:, 2, 0] = -log_rot[:, 1]
|
| 119 |
+
skews[:, 2, 1] = log_rot[:, 0]
|
| 120 |
+
skews_sq = torch.bmm(skews, skews)
|
| 121 |
+
eye = torch.eye(3, dtype=log_rot.dtype, device=log_rot.device)[None]
|
| 122 |
+
return fac1[:, None, None] * skews + fac2[:, None, None] * skews_sq + eye
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def egocentric_delta_pose_to_pose(a_in_cam, trans_delta, rot_mat_delta):
|
| 126 |
+
b = torch.eye(4, dtype=torch.float, device=a_in_cam.device)[None].repeat(
|
| 127 |
+
len(a_in_cam), 1, 1
|
| 128 |
+
)
|
| 129 |
+
b[:, :3, 3] = a_in_cam[:, :3, 3] + trans_delta
|
| 130 |
+
b[:, :3, :3] = rot_mat_delta @ a_in_cam[:, :3, :3]
|
| 131 |
+
return b
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def projection_matrix_from_intrinsics(K, height, width, znear, zfar):
|
| 135 |
+
"""Hartley-Zisserman K -> OpenGL projection matrix ('y_down' convention)."""
|
| 136 |
+
w, h, nc, fc = width, height, znear, zfar
|
| 137 |
+
depth = float(fc - nc)
|
| 138 |
+
q = -(fc + nc) / depth
|
| 139 |
+
qn = -2 * (fc * nc) / depth
|
| 140 |
+
return np.array(
|
| 141 |
+
[
|
| 142 |
+
[2 * K[0, 0] / w, -2 * K[0, 1] / w, (-2 * K[0, 2] + w) / w, 0],
|
| 143 |
+
[0, 2 * K[1, 1] / h, (2 * K[1, 2] - h) / h, 0],
|
| 144 |
+
[0, 0, q, qn],
|
| 145 |
+
[0, 0, -1, 0],
|
| 146 |
+
]
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def depth2xyzmap_t(depth, K):
|
| 151 |
+
"""depth (H,W) torch -> xyz map (H,W,3) torch, in camera frame."""
|
| 152 |
+
H, W = depth.shape[-2:]
|
| 153 |
+
vs, us = torch.meshgrid(
|
| 154 |
+
torch.arange(H, device=depth.device, dtype=torch.float),
|
| 155 |
+
torch.arange(W, device=depth.device, dtype=torch.float),
|
| 156 |
+
indexing="ij",
|
| 157 |
+
)
|
| 158 |
+
xs = (us - K[0, 2]) * depth / K[0, 0]
|
| 159 |
+
ys = (vs - K[1, 2]) * depth / K[1, 1]
|
| 160 |
+
xyz = torch.stack([xs, ys, depth], dim=-1)
|
| 161 |
+
xyz[depth < 0.001] = 0
|
| 162 |
+
return xyz
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def depth2xyzmap_batch_t(depths, K):
|
| 166 |
+
"""depths (B,H,W) -> (B,H,W,3)."""
|
| 167 |
+
B, H, W = depths.shape
|
| 168 |
+
vs, us = torch.meshgrid(
|
| 169 |
+
torch.arange(H, device=depths.device, dtype=torch.float),
|
| 170 |
+
torch.arange(W, device=depths.device, dtype=torch.float),
|
| 171 |
+
indexing="ij",
|
| 172 |
+
)
|
| 173 |
+
xs = (us[None] - K[0, 2]) * depths / K[0, 0]
|
| 174 |
+
ys = (vs[None] - K[1, 2]) * depths / K[1, 1]
|
| 175 |
+
xyz = torch.stack([xs, ys, depths], dim=-1)
|
| 176 |
+
xyz[depths < 0.001] = 0
|
| 177 |
+
return xyz
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _unfold(depth, radius):
|
| 181 |
+
"""(H,W) -> patches (K,H,W) and an in-bounds mask (K,H,W)."""
|
| 182 |
+
k = 2 * radius + 1
|
| 183 |
+
d = depth[None, None]
|
| 184 |
+
patches = F.unfold(d, kernel_size=k, padding=radius).reshape(k * k, *depth.shape)
|
| 185 |
+
inb = F.unfold(
|
| 186 |
+
torch.ones_like(d), kernel_size=k, padding=radius
|
| 187 |
+
).reshape(k * k, *depth.shape) > 0.5
|
| 188 |
+
return patches, inb
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def erode_depth(depth, radius=2, depth_diff_thres=0.001, ratio_thres=0.8, zfar=100.0):
|
| 192 |
+
"""Pure-torch port of FoundationPose's warp erode_depth kernel."""
|
| 193 |
+
patches, inb = _unfold(depth, radius)
|
| 194 |
+
bad = (patches < 0.001) | (patches >= zfar) | ((patches - depth[None]).abs() > depth_diff_thres)
|
| 195 |
+
bad_cnt = (bad & inb).sum(0).float()
|
| 196 |
+
total = inb.sum(0).float().clamp(min=1)
|
| 197 |
+
out = torch.where(bad_cnt / total > ratio_thres, torch.zeros_like(depth), depth)
|
| 198 |
+
out = torch.where((depth < 0.001) | (depth >= zfar), torch.zeros_like(depth), out)
|
| 199 |
+
return out
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def bilateral_filter_depth(depth, radius=2, zfar=100.0, sigmaD=2.0, sigmaR=100000.0):
|
| 203 |
+
"""Pure-torch port of FoundationPose's warp bilateral_filter_depth kernel."""
|
| 204 |
+
k = 2 * radius + 1
|
| 205 |
+
patches, inb = _unfold(depth, radius)
|
| 206 |
+
valid = (patches >= 0.001) & (patches < zfar) & inb
|
| 207 |
+
num_valid = valid.sum(0).float()
|
| 208 |
+
mean_depth = (patches * valid).sum(0) / num_valid.clamp(min=1)
|
| 209 |
+
|
| 210 |
+
dv, du = torch.meshgrid(
|
| 211 |
+
torch.arange(-radius, radius + 1, device=depth.device, dtype=torch.float),
|
| 212 |
+
torch.arange(-radius, radius + 1, device=depth.device, dtype=torch.float),
|
| 213 |
+
indexing="ij",
|
| 214 |
+
)
|
| 215 |
+
# kernel loop order is u (cols) outer, v (rows) inner -> index = du*k + dv
|
| 216 |
+
# F.unfold orders the k*k channels row-major: index = (dv+r)*k + (du+r)
|
| 217 |
+
spatial = torch.exp(-(du * du + dv * dv) / (2.0 * sigmaD * sigmaD)).reshape(k * k, 1, 1)
|
| 218 |
+
|
| 219 |
+
sel = valid & ((patches - mean_depth[None]).abs() < 0.01)
|
| 220 |
+
rng = torch.exp(-((depth[None] - patches) ** 2) / (2.0 * sigmaR * sigmaR))
|
| 221 |
+
w = spatial * rng * sel
|
| 222 |
+
sum_w = w.sum(0)
|
| 223 |
+
out = torch.where(
|
| 224 |
+
(num_valid > 0) & (sum_w > 0), (w * patches).sum(0) / sum_w.clamp(min=1e-12), torch.zeros_like(depth)
|
| 225 |
+
)
|
| 226 |
+
return out
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def sample_views_icosphere(n_views=40):
|
| 230 |
+
sub = 1
|
| 231 |
+
while True:
|
| 232 |
+
m = trimesh.creation.icosphere(subdivisions=sub, radius=1)
|
| 233 |
+
if m.vertices.shape[0] >= n_views:
|
| 234 |
+
break
|
| 235 |
+
sub += 1
|
| 236 |
+
cam_in_obs = np.tile(np.eye(4)[None], (len(m.vertices), 1, 1))
|
| 237 |
+
cam_in_obs[:, :3, 3] = m.vertices
|
| 238 |
+
up = np.array([0, 0, 1.0])
|
| 239 |
+
z = -cam_in_obs[:, :3, 3].copy()
|
| 240 |
+
z /= np.linalg.norm(z, axis=-1, keepdims=True)
|
| 241 |
+
x = np.cross(up.reshape(1, 3), z)
|
| 242 |
+
x[(x == 0).all(axis=-1)] = [1, 0, 0]
|
| 243 |
+
x /= np.linalg.norm(x, axis=-1, keepdims=True)
|
| 244 |
+
y = np.cross(z, x)
|
| 245 |
+
y /= np.linalg.norm(y, axis=-1, keepdims=True)
|
| 246 |
+
cam_in_obs[:, :3, 0] = x
|
| 247 |
+
cam_in_obs[:, :3, 1] = y
|
| 248 |
+
cam_in_obs[:, :3, 2] = z
|
| 249 |
+
return cam_in_obs
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def make_rotation_grid(min_n_views=40, inplane_step=60):
|
| 253 |
+
"""252 pose hypotheses: 42 icosphere viewpoints x 6 in-plane rotations.
|
| 254 |
+
|
| 255 |
+
The reference additionally calls mycpp.cluster_poses(30deg, ...); the minimum
|
| 256 |
+
pairwise geodesic distance in this grid is 31.7deg so that call is a no-op,
|
| 257 |
+
which matches the 252-row output documented on the model card.
|
| 258 |
+
"""
|
| 259 |
+
cam_in_obs = sample_views_icosphere(min_n_views)
|
| 260 |
+
grid = []
|
| 261 |
+
for i in range(len(cam_in_obs)):
|
| 262 |
+
for ang in np.deg2rad(np.arange(0, 360, inplane_step)):
|
| 263 |
+
rz = np.eye(4)
|
| 264 |
+
c, s = np.cos(ang), np.sin(ang)
|
| 265 |
+
rz[0, 0], rz[0, 1], rz[1, 0], rz[1, 1] = c, -s, s, c
|
| 266 |
+
grid.append(np.linalg.inv(cam_in_obs[i] @ rz))
|
| 267 |
+
return np.asarray(grid)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
ROT_GRID = make_rotation_grid()
|
| 271 |
+
print(f"rotation grid: {ROT_GRID.shape}", flush=True)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def compute_crop_window_tf_batch(poses, K, crop_ratio, out_size, mesh_diameter):
|
| 275 |
+
"""box_3d crop: a square window around the projected object centre."""
|
| 276 |
+
B = len(poses)
|
| 277 |
+
r = mesh_diameter * crop_ratio / 2
|
| 278 |
+
offsets = torch.tensor(
|
| 279 |
+
[[0, 0, 0], [r, 0, 0], [-r, 0, 0], [0, r, 0], [0, -r, 0]],
|
| 280 |
+
device=poses.device,
|
| 281 |
+
dtype=torch.float,
|
| 282 |
+
)
|
| 283 |
+
pts = poses[:, :3, 3].reshape(-1, 1, 3) + offsets.reshape(1, -1, 3)
|
| 284 |
+
Kt = torch.as_tensor(K, device=poses.device, dtype=torch.float)
|
| 285 |
+
projected = (Kt @ pts.reshape(-1, 3).T).T
|
| 286 |
+
uvs = (projected[:, :2] / projected[:, 2:3]).reshape(B, -1, 2)
|
| 287 |
+
center = uvs[:, 0]
|
| 288 |
+
rad = torch.abs(uvs - center.reshape(-1, 1, 2)).reshape(B, -1).max(dim=-1)[0]
|
| 289 |
+
left = (center[:, 0] - rad).round()
|
| 290 |
+
right = (center[:, 0] + rad).round()
|
| 291 |
+
top = (center[:, 1] - rad).round()
|
| 292 |
+
bottom = (center[:, 1] + rad).round()
|
| 293 |
+
tf = torch.eye(3, device=poses.device, dtype=torch.float)[None].repeat(B, 1, 1)
|
| 294 |
+
tf[:, 0, 2] = -left
|
| 295 |
+
tf[:, 1, 2] = -top
|
| 296 |
+
new_tf = torch.eye(3, device=poses.device, dtype=torch.float)[None].repeat(B, 1, 1)
|
| 297 |
+
new_tf[:, 0, 0] = out_size[0] / (right - left).clamp(min=1)
|
| 298 |
+
new_tf[:, 1, 1] = out_size[1] / (bottom - top).clamp(min=1)
|
| 299 |
+
return new_tf @ tf
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def _material_image(material):
|
| 303 |
+
for attr in ("baseColorTexture", "image", "emissiveTexture"):
|
| 304 |
+
img = getattr(material, attr, None)
|
| 305 |
+
if img is not None:
|
| 306 |
+
return img
|
| 307 |
+
return None
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def make_mesh_tensors(mesh, max_tex_size=MAX_TEX_SIZE):
|
| 311 |
+
t = {}
|
| 312 |
+
img = None
|
| 313 |
+
if isinstance(mesh.visual, trimesh.visual.texture.TextureVisuals):
|
| 314 |
+
img = _material_image(mesh.visual.material)
|
| 315 |
+
if img is not None and getattr(mesh.visual, "uv", None) is not None:
|
| 316 |
+
arr = np.array(img.convert("RGB"))[..., :3]
|
| 317 |
+
big = max(arr.shape[0], arr.shape[1])
|
| 318 |
+
if big > max_tex_size:
|
| 319 |
+
s = max_tex_size / big
|
| 320 |
+
arr = cv2.resize(arr, fx=s, fy=s, dsize=None)
|
| 321 |
+
t["tex"] = torch.as_tensor(
|
| 322 |
+
np.ascontiguousarray(arr), device="cuda", dtype=torch.float
|
| 323 |
+
)[None] / 255.0
|
| 324 |
+
t["uv_idx"] = torch.as_tensor(mesh.faces, device="cuda", dtype=torch.int)
|
| 325 |
+
uv = torch.as_tensor(np.asarray(mesh.visual.uv), device="cuda", dtype=torch.float).clone()
|
| 326 |
+
uv[:, 1] = 1 - uv[:, 1]
|
| 327 |
+
t["uv"] = uv
|
| 328 |
+
else:
|
| 329 |
+
vc = None
|
| 330 |
+
try:
|
| 331 |
+
vc = np.asarray(mesh.visual.to_color().vertex_colors)
|
| 332 |
+
except Exception:
|
| 333 |
+
pass
|
| 334 |
+
if vc is None or len(vc) != len(mesh.vertices):
|
| 335 |
+
vc = np.tile(np.array([[160, 160, 160, 255]]), (len(mesh.vertices), 1))
|
| 336 |
+
t["vertex_color"] = torch.as_tensor(
|
| 337 |
+
vc[..., :3], device="cuda", dtype=torch.float
|
| 338 |
+
) / 255.0
|
| 339 |
+
t["pos"] = torch.tensor(np.asarray(mesh.vertices), device="cuda", dtype=torch.float)
|
| 340 |
+
t["faces"] = torch.tensor(np.asarray(mesh.faces), device="cuda", dtype=torch.int)
|
| 341 |
+
t["vnormals"] = torch.tensor(
|
| 342 |
+
np.asarray(mesh.vertex_normals), device="cuda", dtype=torch.float
|
| 343 |
+
)
|
| 344 |
+
return t
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def nvdiffrast_render(
|
| 348 |
+
K, H, W, ob_in_cams, mesh_tensors, output_size=None, bbox2d=None, use_light=True, extra=None
|
| 349 |
+
):
|
| 350 |
+
glctx = get_glctx()
|
| 351 |
+
pos = mesh_tensors["pos"]
|
| 352 |
+
pos_idx = mesh_tensors["faces"]
|
| 353 |
+
has_tex = "tex" in mesh_tensors
|
| 354 |
+
|
| 355 |
+
glcam = torch.tensor(GLCAM_IN_CVCAM, device="cuda", dtype=torch.float)[None]
|
| 356 |
+
ob_in_glcams = glcam @ ob_in_cams
|
| 357 |
+
proj = projection_matrix_from_intrinsics(K, height=H, width=W, znear=0.001, zfar=100)
|
| 358 |
+
proj = torch.as_tensor(proj.reshape(-1, 4, 4), device="cuda", dtype=torch.float)
|
| 359 |
+
mtx = proj @ ob_in_glcams
|
| 360 |
+
|
| 361 |
+
if output_size is None:
|
| 362 |
+
output_size = np.asarray([H, W])
|
| 363 |
+
|
| 364 |
+
pts_cam = transform_pts(pos, ob_in_cams)
|
| 365 |
+
pos_homo = to_homo_torch(pos)
|
| 366 |
+
pos_clip = (mtx[:, None] @ pos_homo[None, ..., None])[..., 0]
|
| 367 |
+
if bbox2d is not None:
|
| 368 |
+
l = bbox2d[:, 0]
|
| 369 |
+
t_ = H - bbox2d[:, 1]
|
| 370 |
+
r = bbox2d[:, 2]
|
| 371 |
+
b = H - bbox2d[:, 3]
|
| 372 |
+
tf = torch.eye(4, dtype=torch.float, device="cuda")[None].repeat(len(ob_in_cams), 1, 1)
|
| 373 |
+
tf[:, 0, 0] = W / (r - l)
|
| 374 |
+
tf[:, 1, 1] = H / (t_ - b)
|
| 375 |
+
tf[:, 3, 0] = (W - r - l) / (r - l)
|
| 376 |
+
tf[:, 3, 1] = (H - t_ - b) / (t_ - b)
|
| 377 |
+
pos_clip = pos_clip @ tf
|
| 378 |
+
|
| 379 |
+
rast_out, _ = dr.rasterize(
|
| 380 |
+
glctx, pos_clip, pos_idx, resolution=np.asarray(output_size, dtype=np.int64)
|
| 381 |
+
)
|
| 382 |
+
xyz_map, _ = dr.interpolate(pts_cam, rast_out, pos_idx)
|
| 383 |
+
depth = xyz_map[..., 2]
|
| 384 |
+
if has_tex:
|
| 385 |
+
texc, _ = dr.interpolate(mesh_tensors["uv"], rast_out, mesh_tensors["uv_idx"])
|
| 386 |
+
color = dr.texture(mesh_tensors["tex"], texc, filter_mode="linear")
|
| 387 |
+
else:
|
| 388 |
+
color, _ = dr.interpolate(mesh_tensors["vertex_color"], rast_out, pos_idx)
|
| 389 |
+
|
| 390 |
+
if use_light:
|
| 391 |
+
vnormals_cam = transform_dirs(mesh_tensors["vnormals"], ob_in_cams)
|
| 392 |
+
light_dir_neg = -torch.as_tensor(
|
| 393 |
+
np.array([0, 0, 1.0]), dtype=torch.float, device="cuda"
|
| 394 |
+
)
|
| 395 |
+
diffuse = (
|
| 396 |
+
(F.normalize(vnormals_cam, dim=-1) * F.normalize(light_dir_neg, dim=-1))
|
| 397 |
+
.sum(dim=-1)
|
| 398 |
+
.clip(0, 1)[..., None]
|
| 399 |
+
)
|
| 400 |
+
diffuse_map, _ = dr.interpolate(diffuse, rast_out, pos_idx)
|
| 401 |
+
color = color * 0.8 + diffuse_map * color * 0.5
|
| 402 |
+
|
| 403 |
+
color = color.clip(0, 1)
|
| 404 |
+
color = color * torch.clamp(rast_out[..., -1:], 0, 1)
|
| 405 |
+
color = torch.flip(color, dims=[1])
|
| 406 |
+
depth = torch.flip(depth, dims=[1])
|
| 407 |
+
if extra is not None:
|
| 408 |
+
extra["xyz_map"] = torch.flip(xyz_map, dims=[1])
|
| 409 |
+
return color, depth
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
# ---------------------------------------------------------------------------
|
| 413 |
+
# Refiner / scorer
|
| 414 |
+
# ---------------------------------------------------------------------------
|
| 415 |
+
def _warp_chunked(src, tfs, dsize, mode):
|
| 416 |
+
"""Warp one (C,H,W) source through B different homographies, in chunks.
|
| 417 |
+
|
| 418 |
+
Expanding the source to (B,C,H,W) up front costs ~1 GB at B=252 for a VGA
|
| 419 |
+
frame; chunking keeps the transient under ~256 MB.
|
| 420 |
+
"""
|
| 421 |
+
C, H, W = src.shape
|
| 422 |
+
chunk = max(1, int(2.5e8 / max(C * H * W * 4, 1)))
|
| 423 |
+
outs = []
|
| 424 |
+
for b in range(0, len(tfs), chunk):
|
| 425 |
+
n = len(tfs[b : b + chunk])
|
| 426 |
+
outs.append(
|
| 427 |
+
kornia.geometry.transform.warp_perspective(
|
| 428 |
+
src[None].expand(n, -1, -1, -1).contiguous(),
|
| 429 |
+
tfs[b : b + chunk],
|
| 430 |
+
dsize=dsize,
|
| 431 |
+
mode=mode,
|
| 432 |
+
align_corners=False,
|
| 433 |
+
)
|
| 434 |
+
)
|
| 435 |
+
return torch.cat(outs, dim=0)
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
def _render_hypotheses(poses, mesh_tensors, K, H, W, tf_to_crops, chunk=128):
|
| 439 |
+
"""Render each hypothesis directly into its own 160x160 crop window."""
|
| 440 |
+
bbox2d_crop = torch.as_tensor(
|
| 441 |
+
np.array([0, 0, INPUT_RESIZE[0] - 1, INPUT_RESIZE[1] - 1]).reshape(2, 2),
|
| 442 |
+
device="cuda",
|
| 443 |
+
dtype=torch.float,
|
| 444 |
+
)
|
| 445 |
+
bbox2d_ori = transform_pts(bbox2d_crop, tf_to_crops.inverse()).reshape(-1, 4)
|
| 446 |
+
rgb_rs, xyz_rs = [], []
|
| 447 |
+
for b in range(0, len(poses), chunk):
|
| 448 |
+
extra = {}
|
| 449 |
+
rgb_r, _ = nvdiffrast_render(
|
| 450 |
+
K=K,
|
| 451 |
+
H=H,
|
| 452 |
+
W=W,
|
| 453 |
+
ob_in_cams=poses[b : b + chunk],
|
| 454 |
+
mesh_tensors=mesh_tensors,
|
| 455 |
+
output_size=INPUT_RESIZE,
|
| 456 |
+
bbox2d=bbox2d_ori[b : b + chunk],
|
| 457 |
+
use_light=True,
|
| 458 |
+
extra=extra,
|
| 459 |
+
)
|
| 460 |
+
rgb_rs.append(rgb_r)
|
| 461 |
+
xyz_rs.append(extra["xyz_map"])
|
| 462 |
+
rgb_rs = torch.cat(rgb_rs, dim=0).permute(0, 3, 1, 2) * 255
|
| 463 |
+
xyz_rs = torch.cat(xyz_rs, dim=0).permute(0, 3, 1, 2)
|
| 464 |
+
return rgb_rs, xyz_rs
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def _normalize_xyz(xyz, pose_t, mesh_radius, z_thres):
|
| 468 |
+
"""FoundationPose transform_depth_to_xyzmap (normalize_xyz=True branch)."""
|
| 469 |
+
bs = xyz.shape[0]
|
| 470 |
+
invalid = xyz[:, 2:3] < z_thres
|
| 471 |
+
xyz = xyz - pose_t.reshape(bs, 3, 1, 1)
|
| 472 |
+
xyz = xyz * (1.0 / mesh_radius)
|
| 473 |
+
invalid = invalid.expand(bs, 3, -1, -1) | (torch.abs(xyz) >= 2)
|
| 474 |
+
xyz = xyz.masked_fill(invalid, 0)
|
| 475 |
+
return xyz
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
@torch.inference_mode()
|
| 479 |
+
def refine_poses(poses, mesh_tensors, rgb_t, xyz_map_t, K, mesh_diameter, iterations, net_bs=512):
|
| 480 |
+
H, W = rgb_t.shape[:2]
|
| 481 |
+
B_in_cams = poses
|
| 482 |
+
for _ in range(iterations):
|
| 483 |
+
tf_to_crops = compute_crop_window_tf_batch(
|
| 484 |
+
B_in_cams, K, REFINE_CROP_RATIO, INPUT_RESIZE, mesh_diameter
|
| 485 |
+
)
|
| 486 |
+
rgb_as, xyz_as = _render_hypotheses(B_in_cams, mesh_tensors, K, H, W, tf_to_crops)
|
| 487 |
+
B = len(B_in_cams)
|
| 488 |
+
rgb_bs = _warp_chunked(
|
| 489 |
+
rgb_t.permute(2, 0, 1).contiguous(), tf_to_crops, INPUT_RESIZE, "bilinear"
|
| 490 |
+
)
|
| 491 |
+
xyz_bs = _warp_chunked(
|
| 492 |
+
xyz_map_t.permute(2, 0, 1).contiguous(), tf_to_crops, INPUT_RESIZE, "nearest"
|
| 493 |
+
)
|
| 494 |
+
rgb_as = rgb_as / 255.0
|
| 495 |
+
rgb_bs = rgb_bs / 255.0
|
| 496 |
+
pose_t = B_in_cams[:, :3, 3]
|
| 497 |
+
radius = mesh_diameter / 2
|
| 498 |
+
xyz_as = _normalize_xyz(xyz_as, pose_t, radius, 0.001)
|
| 499 |
+
xyz_bs = _normalize_xyz(xyz_bs, pose_t, radius, 0.001)
|
| 500 |
+
|
| 501 |
+
out_poses = []
|
| 502 |
+
for b in range(0, B, net_bs):
|
| 503 |
+
A = torch.cat([rgb_as[b : b + net_bs], xyz_as[b : b + net_bs]], dim=1).float()
|
| 504 |
+
Bt = torch.cat([rgb_bs[b : b + net_bs], xyz_bs[b : b + net_bs]], dim=1).float()
|
| 505 |
+
trans, rot = refine_net(A, Bt)
|
| 506 |
+
trans = trans.float()
|
| 507 |
+
rot = rot.float()
|
| 508 |
+
trans_delta = trans * (mesh_diameter / 2)
|
| 509 |
+
rot_mat_delta = torch.tanh(rot) * ROT_NORMALIZER
|
| 510 |
+
rot_mat_delta = so3_exp_map(rot_mat_delta).permute(0, 2, 1)
|
| 511 |
+
out_poses.append(
|
| 512 |
+
egocentric_delta_pose_to_pose(
|
| 513 |
+
B_in_cams[b : b + net_bs], trans_delta, rot_mat_delta
|
| 514 |
+
)
|
| 515 |
+
)
|
| 516 |
+
B_in_cams = torch.cat(out_poses, dim=0).reshape(-1, 4, 4)
|
| 517 |
+
return B_in_cams
|
| 518 |
+
|
| 519 |
+
|
| 520 |
+
@torch.inference_mode()
|
| 521 |
+
def score_poses(poses, mesh_tensors, rgb_t, depth_t, K, mesh_diameter, net_bs=512):
|
| 522 |
+
H, W = rgb_t.shape[:2]
|
| 523 |
+
B = len(poses)
|
| 524 |
+
tf_to_crops = compute_crop_window_tf_batch(
|
| 525 |
+
poses, K, SCORE_CROP_RATIO, INPUT_RESIZE, mesh_diameter
|
| 526 |
+
)
|
| 527 |
+
rgb_as, xyz_as = _render_hypotheses(poses, mesh_tensors, K, H, W, tf_to_crops)
|
| 528 |
+
rgb_bs = _warp_chunked(
|
| 529 |
+
rgb_t.permute(2, 0, 1).contiguous(), tf_to_crops, INPUT_RESIZE, "bilinear"
|
| 530 |
+
)
|
| 531 |
+
# The scorer reconstructs xyzB by warping the cropped depth back to full
|
| 532 |
+
# resolution, back-projecting there, then warping into the crop again.
|
| 533 |
+
crop_to_oris = tf_to_crops.inverse()
|
| 534 |
+
depth_bs = _warp_chunked(
|
| 535 |
+
depth_t[None].contiguous(), tf_to_crops, INPUT_RESIZE, "nearest"
|
| 536 |
+
)
|
| 537 |
+
chunk = max(1, int(1.2e8 / max(H * W, 1)))
|
| 538 |
+
xyz_bs = []
|
| 539 |
+
for b in range(0, B, chunk):
|
| 540 |
+
d_ori = kornia.geometry.transform.warp_perspective(
|
| 541 |
+
depth_bs[b : b + chunk],
|
| 542 |
+
crop_to_oris[b : b + chunk],
|
| 543 |
+
dsize=(H, W),
|
| 544 |
+
mode="nearest",
|
| 545 |
+
align_corners=False,
|
| 546 |
+
)
|
| 547 |
+
xyz_ori = depth2xyzmap_batch_t(d_ori[:, 0], K).permute(0, 3, 1, 2)
|
| 548 |
+
xyz_bs.append(
|
| 549 |
+
kornia.geometry.transform.warp_perspective(
|
| 550 |
+
xyz_ori,
|
| 551 |
+
tf_to_crops[b : b + chunk],
|
| 552 |
+
dsize=INPUT_RESIZE,
|
| 553 |
+
mode="nearest",
|
| 554 |
+
align_corners=False,
|
| 555 |
+
)
|
| 556 |
+
)
|
| 557 |
+
del d_ori, xyz_ori
|
| 558 |
+
xyz_bs = torch.cat(xyz_bs, dim=0)
|
| 559 |
+
|
| 560 |
+
rgb_as = rgb_as / 255.0
|
| 561 |
+
rgb_bs = rgb_bs / 255.0
|
| 562 |
+
pose_t = poses[:, :3, 3]
|
| 563 |
+
radius = mesh_diameter / 2
|
| 564 |
+
xyz_as = _normalize_xyz(xyz_as, pose_t, radius, 0.1)
|
| 565 |
+
xyz_bs = _normalize_xyz(xyz_bs, pose_t, radius, 0.1)
|
| 566 |
+
|
| 567 |
+
scores = []
|
| 568 |
+
for b in range(0, B, net_bs):
|
| 569 |
+
A = torch.cat([rgb_as[b : b + net_bs], xyz_as[b : b + net_bs]], dim=1).float()
|
| 570 |
+
Bt = torch.cat([rgb_bs[b : b + net_bs], xyz_bs[b : b + net_bs]], dim=1).float()
|
| 571 |
+
scores.append(score_net(A, Bt).float().reshape(-1))
|
| 572 |
+
return torch.cat(scores, dim=0)
|
| 573 |
+
|
| 574 |
+
|
| 575 |
+
# ---------------------------------------------------------------------------
|
| 576 |
+
# Drawing
|
| 577 |
+
# ---------------------------------------------------------------------------
|
| 578 |
+
def _project(pt, K, ob_in_cam):
|
| 579 |
+
p = K @ ((ob_in_cam @ pt.reshape(4, 1))[:3, :])
|
| 580 |
+
p = p.reshape(-1) / p.reshape(-1)[2]
|
| 581 |
+
return tuple(np.round(p[:2]).astype(int).tolist())
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
def draw_xyz_axis(img_rgb, ob_in_cam, K, scale=0.1, thickness=3):
|
| 585 |
+
img = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
|
| 586 |
+
o = _project(np.array([0, 0, 0, 1.0]), K, ob_in_cam)
|
| 587 |
+
for vec, col in (
|
| 588 |
+
(np.array([scale, 0, 0, 1.0]), (0, 0, 255)),
|
| 589 |
+
(np.array([0, scale, 0, 1.0]), (0, 255, 0)),
|
| 590 |
+
(np.array([0, 0, scale, 1.0]), (255, 0, 0)),
|
| 591 |
+
):
|
| 592 |
+
img = cv2.arrowedLine(
|
| 593 |
+
img, o, _project(vec, K, ob_in_cam), color=col, thickness=thickness,
|
| 594 |
+
line_type=cv2.LINE_AA, tipLength=0.15,
|
| 595 |
+
)
|
| 596 |
+
return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
def draw_posed_3d_box(K, img, ob_in_cam, bbox, line_color=(0, 255, 0), linewidth=2):
|
| 600 |
+
xmin, ymin, zmin = bbox.min(axis=0)
|
| 601 |
+
xmax, ymax, zmax = bbox.max(axis=0)
|
| 602 |
+
|
| 603 |
+
def line3d(start, end, im):
|
| 604 |
+
pts = np.stack((start, end), axis=0).reshape(-1, 3)
|
| 605 |
+
pts = (ob_in_cam @ np.concatenate([pts, np.ones((2, 1))], axis=-1).T).T[:, :3]
|
| 606 |
+
pr = (K @ pts.T).T
|
| 607 |
+
uv = np.round(pr[:, :2] / pr[:, 2].reshape(-1, 1)).astype(int)
|
| 608 |
+
return cv2.line(
|
| 609 |
+
im, uv[0].tolist(), uv[1].tolist(), color=line_color,
|
| 610 |
+
thickness=linewidth, lineType=cv2.LINE_AA,
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
for y in [ymin, ymax]:
|
| 614 |
+
for z in [zmin, zmax]:
|
| 615 |
+
img = line3d(np.array([xmin, y, z]), np.array([xmax, y, z]), img)
|
| 616 |
+
for x in [xmin, xmax]:
|
| 617 |
+
for z in [zmin, zmax]:
|
| 618 |
+
img = line3d(np.array([x, ymin, z]), np.array([x, ymax, z]), img)
|
| 619 |
+
for x in [xmin, xmax]:
|
| 620 |
+
for y in [ymin, ymax]:
|
| 621 |
+
img = line3d(np.array([x, y, zmin]), np.array([x, y, zmax]), img)
|
| 622 |
+
return img
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
# ---------------------------------------------------------------------------
|
| 626 |
+
# Input parsing
|
| 627 |
+
# ---------------------------------------------------------------------------
|
| 628 |
+
def load_depth(path, unit):
|
| 629 |
+
ext = os.path.splitext(path)[1].lower()
|
| 630 |
+
if ext == ".npy":
|
| 631 |
+
d = np.load(path).astype(np.float32)
|
| 632 |
+
else:
|
| 633 |
+
d = np.array(Image.open(path)).astype(np.float32)
|
| 634 |
+
if d.ndim == 3:
|
| 635 |
+
d = d[..., 0]
|
| 636 |
+
scale = {"millimetres (uint16 PNG)": 1e-3, "metres (float)": 1.0, "0.1 mm": 1e-4}[unit]
|
| 637 |
+
return d * scale
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
def load_mesh(path, unit):
|
| 641 |
+
obj = trimesh.load(path, process=False, force="mesh")
|
| 642 |
+
if isinstance(obj, trimesh.Scene):
|
| 643 |
+
obj = obj.to_geometry()
|
| 644 |
+
if not isinstance(obj, trimesh.Trimesh):
|
| 645 |
+
raise gr.Error("Could not read a triangle mesh from the uploaded CAD model.")
|
| 646 |
+
ext = float(np.linalg.norm(obj.extents))
|
| 647 |
+
if unit == "auto":
|
| 648 |
+
if ext > 10.0:
|
| 649 |
+
obj.vertices = np.asarray(obj.vertices) * 1e-3
|
| 650 |
+
note = "auto-detected millimetres"
|
| 651 |
+
else:
|
| 652 |
+
note = "auto-detected metres"
|
| 653 |
+
elif unit == "millimetres":
|
| 654 |
+
obj.vertices = np.asarray(obj.vertices) * 1e-3
|
| 655 |
+
note = "millimetres"
|
| 656 |
+
else:
|
| 657 |
+
note = "metres"
|
| 658 |
+
return obj, note
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
def parse_floats(text, n, name):
|
| 662 |
+
try:
|
| 663 |
+
vals = [float(x) for x in str(text).replace(";", ",").replace(" ", ",").split(",") if x != ""]
|
| 664 |
+
except ValueError:
|
| 665 |
+
raise gr.Error(f"Could not parse {name}: {text!r}")
|
| 666 |
+
if len(vals) != n:
|
| 667 |
+
raise gr.Error(f"{name} needs {n} numbers, got {len(vals)}: {text!r}")
|
| 668 |
+
return vals
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
def mesh_diameter(mesh):
|
| 672 |
+
"""Exact diameter via the convex hull (deterministic form of the reference's
|
| 673 |
+
random-sample pairwise max distance)."""
|
| 674 |
+
try:
|
| 675 |
+
pts = np.asarray(mesh.convex_hull.vertices)
|
| 676 |
+
except Exception:
|
| 677 |
+
pts = np.asarray(mesh.vertices)
|
| 678 |
+
if len(pts) > 4000:
|
| 679 |
+
idx = np.linspace(0, len(pts) - 1, 4000).astype(int)
|
| 680 |
+
pts = pts[idx]
|
| 681 |
+
d = np.linalg.norm(pts[None] - pts[:, None], axis=-1)
|
| 682 |
+
return float(d.max())
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
# ---------------------------------------------------------------------------
|
| 686 |
+
# Inference
|
| 687 |
+
# ---------------------------------------------------------------------------
|
| 688 |
+
def _run_pose(
|
| 689 |
+
rgb_image,
|
| 690 |
+
depth_file,
|
| 691 |
+
mesh_file,
|
| 692 |
+
bbox,
|
| 693 |
+
intrinsics,
|
| 694 |
+
depth_unit,
|
| 695 |
+
mesh_unit,
|
| 696 |
+
refine_iterations,
|
| 697 |
+
progress,
|
| 698 |
+
):
|
| 699 |
+
t_start = time.time()
|
| 700 |
+
if rgb_image is None:
|
| 701 |
+
raise gr.Error("Please provide an RGB image.")
|
| 702 |
+
if depth_file is None:
|
| 703 |
+
raise gr.Error("Please provide a depth map (16-bit PNG or .npy).")
|
| 704 |
+
if mesh_file is None:
|
| 705 |
+
raise gr.Error("Please provide a CAD model of the object.")
|
| 706 |
+
|
| 707 |
+
progress(0.05, desc="Reading inputs")
|
| 708 |
+
rgb = np.array(Image.open(rgb_image).convert("RGB"))
|
| 709 |
+
depth = load_depth(depth_file, depth_unit)
|
| 710 |
+
if depth.shape[:2] != rgb.shape[:2]:
|
| 711 |
+
depth = cv2.resize(depth, (rgb.shape[1], rgb.shape[0]), interpolation=cv2.INTER_NEAREST)
|
| 712 |
+
|
| 713 |
+
fx, fy, cx, cy = parse_floats(intrinsics, 4, "intrinsics (fx,fy,cx,cy)")
|
| 714 |
+
x1, y1, x2, y2 = parse_floats(bbox, 4, "bounding box (x1,y1,x2,y2)")
|
| 715 |
+
K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float64)
|
| 716 |
+
|
| 717 |
+
H, W = rgb.shape[:2]
|
| 718 |
+
if max(H, W) > MAX_SIDE:
|
| 719 |
+
s = MAX_SIDE / max(H, W)
|
| 720 |
+
rgb = cv2.resize(rgb, None, fx=s, fy=s, interpolation=cv2.INTER_AREA)
|
| 721 |
+
depth = cv2.resize(depth, (rgb.shape[1], rgb.shape[0]), interpolation=cv2.INTER_NEAREST)
|
| 722 |
+
K[:2] *= s
|
| 723 |
+
x1, y1, x2, y2 = [v * s for v in (x1, y1, x2, y2)]
|
| 724 |
+
H, W = rgb.shape[:2]
|
| 725 |
+
|
| 726 |
+
x1, x2 = sorted([int(round(x1)), int(round(x2))])
|
| 727 |
+
y1, y2 = sorted([int(round(y1)), int(round(y2))])
|
| 728 |
+
x1 = max(0, min(W - 2, x1)); x2 = max(x1 + 1, min(W - 1, x2))
|
| 729 |
+
y1 = max(0, min(H - 2, y1)); y2 = max(y1 + 1, min(H - 1, y2))
|
| 730 |
+
|
| 731 |
+
# nvdiffrast's CUDA rasteriser needs both dimensions divisible by 8. Pad on
|
| 732 |
+
# the right/bottom so pixel coordinates (and therefore K) are unchanged.
|
| 733 |
+
H0, W0 = H, W
|
| 734 |
+
Hp, Wp = (H + 7) // 8 * 8, (W + 7) // 8 * 8
|
| 735 |
+
if (Hp, Wp) != (H, W):
|
| 736 |
+
rgb = np.pad(rgb, ((0, Hp - H), (0, Wp - W), (0, 0)))
|
| 737 |
+
depth = np.pad(depth, ((0, Hp - H), (0, Wp - W)))
|
| 738 |
+
H, W = Hp, Wp
|
| 739 |
+
|
| 740 |
+
mesh, unit_note = load_mesh(mesh_file, mesh_unit)
|
| 741 |
+
progress(0.15, desc="Preparing mesh")
|
| 742 |
+
|
| 743 |
+
# Centre the mesh on its AABB centre (reference: FoundationPose.reset_object).
|
| 744 |
+
model_center = (np.asarray(mesh.vertices).min(axis=0) + np.asarray(mesh.vertices).max(axis=0)) / 2
|
| 745 |
+
mesh_c = mesh.copy()
|
| 746 |
+
mesh_c.vertices = np.asarray(mesh_c.vertices) - model_center.reshape(1, 3)
|
| 747 |
+
diameter = mesh_diameter(mesh_c)
|
| 748 |
+
if not np.isfinite(diameter) or diameter <= 0:
|
| 749 |
+
raise gr.Error("Degenerate CAD model (zero diameter).")
|
| 750 |
+
|
| 751 |
+
mesh_tensors = make_mesh_tensors(mesh_c)
|
| 752 |
+
|
| 753 |
+
rgb_t = torch.as_tensor(rgb.astype(np.float32), device="cuda", dtype=torch.float)
|
| 754 |
+
depth_t = torch.as_tensor(depth.astype(np.float32), device="cuda", dtype=torch.float)
|
| 755 |
+
|
| 756 |
+
progress(0.25, desc="Filtering depth")
|
| 757 |
+
depth_t = erode_depth(depth_t, radius=2)
|
| 758 |
+
depth_t = bilateral_filter_depth(depth_t, radius=2)
|
| 759 |
+
|
| 760 |
+
# Translation guess from the box centre + median depth inside the box.
|
| 761 |
+
mask = torch.zeros_like(depth_t, dtype=torch.bool)
|
| 762 |
+
mask[y1 : y2 + 1, x1 : x2 + 1] = True
|
| 763 |
+
valid = mask & (depth_t >= 0.001)
|
| 764 |
+
if int(valid.sum()) < 4:
|
| 765 |
+
raise gr.Error(
|
| 766 |
+
"No valid depth inside the box. Check the depth unit and the box coordinates."
|
| 767 |
+
)
|
| 768 |
+
zc = torch.median(depth_t[valid]).item()
|
| 769 |
+
uc = (x1 + x2) / 2.0
|
| 770 |
+
vc = (y1 + y2) / 2.0
|
| 771 |
+
center = (np.linalg.inv(K) @ np.array([uc, vc, 1.0]).reshape(3, 1)).reshape(3) * zc
|
| 772 |
+
|
| 773 |
+
poses = torch.as_tensor(ROT_GRID, device="cuda", dtype=torch.float).clone()
|
| 774 |
+
poses[:, :3, 3] = torch.as_tensor(center.reshape(1, 3), device="cuda", dtype=torch.float)
|
| 775 |
+
|
| 776 |
+
xyz_map_t = depth2xyzmap_t(depth_t, K)
|
| 777 |
+
|
| 778 |
+
progress(0.35, desc=f"Refining {len(poses)} hypotheses x {refine_iterations}")
|
| 779 |
+
t0 = time.time()
|
| 780 |
+
poses = refine_poses(
|
| 781 |
+
poses, mesh_tensors, rgb_t, xyz_map_t, K, diameter, int(refine_iterations)
|
| 782 |
+
)
|
| 783 |
+
t_refine = time.time() - t0
|
| 784 |
+
|
| 785 |
+
progress(0.8, desc="Scoring hypotheses")
|
| 786 |
+
t0 = time.time()
|
| 787 |
+
scores = score_poses(poses, mesh_tensors, rgb_t, depth_t, K, diameter)
|
| 788 |
+
t_score = time.time() - t0
|
| 789 |
+
|
| 790 |
+
order = scores.argsort(descending=True)
|
| 791 |
+
best = poses[order[0]]
|
| 792 |
+
best_score = float(scores[order[0]])
|
| 793 |
+
|
| 794 |
+
tf_to_centered = np.eye(4)
|
| 795 |
+
tf_to_centered[:3, 3] = -model_center
|
| 796 |
+
pose = best.detach().cpu().numpy().astype(np.float64) @ tf_to_centered
|
| 797 |
+
|
| 798 |
+
progress(0.9, desc="Rendering")
|
| 799 |
+
# Overlay: render the mesh at the winning pose over the input image.
|
| 800 |
+
with torch.inference_mode():
|
| 801 |
+
color, _ = nvdiffrast_render(
|
| 802 |
+
K=K,
|
| 803 |
+
H=H,
|
| 804 |
+
W=W,
|
| 805 |
+
ob_in_cams=best[None],
|
| 806 |
+
mesh_tensors=mesh_tensors,
|
| 807 |
+
output_size=(H, W),
|
| 808 |
+
use_light=True,
|
| 809 |
+
)
|
| 810 |
+
render = (color[0].clamp(0, 1).cpu().numpy() * 255).astype(np.uint8)
|
| 811 |
+
alpha = (render.sum(axis=-1) > 0).astype(np.float32)[..., None]
|
| 812 |
+
overlay = (rgb.astype(np.float32) * (1 - 0.65 * alpha) + render.astype(np.float32) * 0.65 * alpha)
|
| 813 |
+
overlay = overlay.clip(0, 255).astype(np.uint8)
|
| 814 |
+
|
| 815 |
+
# Annotated: oriented 3D bounding box + object axes.
|
| 816 |
+
to_origin, extents = trimesh.bounds.oriented_bounds(mesh)
|
| 817 |
+
box = np.stack([-extents / 2, extents / 2], axis=0).reshape(2, 3)
|
| 818 |
+
center_pose = pose @ np.linalg.inv(to_origin)
|
| 819 |
+
annotated = rgb.copy()
|
| 820 |
+
annotated = draw_posed_3d_box(K, annotated, center_pose, box)
|
| 821 |
+
axis_scale = float(np.clip(0.6 * float(extents.max()), 0.03, 0.15))
|
| 822 |
+
annotated = draw_xyz_axis(annotated, center_pose, K, scale=axis_scale, thickness=3)
|
| 823 |
+
annotated = cv2.rectangle(
|
| 824 |
+
annotated, (x1, y1), (x2, y2), color=(255, 180, 0), thickness=1, lineType=cv2.LINE_AA
|
| 825 |
+
)
|
| 826 |
+
|
| 827 |
+
# Undo the multiple-of-8 padding applied for the rasteriser.
|
| 828 |
+
annotated = annotated[:H0, :W0]
|
| 829 |
+
overlay = overlay[:H0, :W0]
|
| 830 |
+
|
| 831 |
+
R = pose[:3, :3]
|
| 832 |
+
t = pose[:3, 3]
|
| 833 |
+
ang = math.degrees(math.acos(float(np.clip((np.trace(R) - 1) / 2, -1, 1))))
|
| 834 |
+
pose_txt = "\n".join(
|
| 835 |
+
" ".join(f"{v: .6f}" for v in row) for row in pose
|
| 836 |
+
)
|
| 837 |
+
total = time.time() - t_start
|
| 838 |
+
report = (
|
| 839 |
+
f"**Object → camera translation** x={t[0]*100:.1f} cm, y={t[1]*100:.1f} cm, z={t[2]*100:.1f} cm\n\n"
|
| 840 |
+
f"**Rotation angle** {ang:.1f}° · **Score logit** {best_score:.3f}\n\n"
|
| 841 |
+
f"CAD model: {len(mesh.vertices):,} vertices, diameter {diameter*100:.1f} cm ({unit_note})\n\n"
|
| 842 |
+
f"252 hypotheses · {int(refine_iterations)} refine passes · "
|
| 843 |
+
f"refine {t_refine:.2f}s · score {t_score:.2f}s · total {total:.2f}s"
|
| 844 |
+
)
|
| 845 |
+
|
| 846 |
+
del mesh_tensors, rgb_t, depth_t, xyz_map_t, poses, scores
|
| 847 |
+
torch.cuda.empty_cache()
|
| 848 |
+
return annotated, overlay, pose_txt, report
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
@spaces.GPU(duration=90)
|
| 852 |
+
def estimate_pose(
|
| 853 |
+
rgb_image: str,
|
| 854 |
+
depth_file: str,
|
| 855 |
+
mesh_file: str,
|
| 856 |
+
bbox: str,
|
| 857 |
+
intrinsics: str,
|
| 858 |
+
depth_unit: str = "millimetres (uint16 PNG)",
|
| 859 |
+
mesh_unit: str = "auto",
|
| 860 |
+
refine_iterations: int = 5,
|
| 861 |
+
progress=gr.Progress(track_tqdm=False),
|
| 862 |
+
):
|
| 863 |
+
"""Estimate the 6-DoF pose of a CAD model in an RGB-D frame with FoundationPose.
|
| 864 |
+
|
| 865 |
+
Args:
|
| 866 |
+
rgb_image: path to the RGB image of the scene.
|
| 867 |
+
depth_file: path to the aligned depth map (16-bit PNG or .npy).
|
| 868 |
+
mesh_file: path to the object's CAD model (.glb/.obj/.ply/.stl).
|
| 869 |
+
bbox: 2D box around the object, "x1,y1,x2,y2" in pixels.
|
| 870 |
+
intrinsics: pinhole camera intrinsics, "fx,fy,cx,cy" in pixels.
|
| 871 |
+
depth_unit: unit of the stored depth values.
|
| 872 |
+
mesh_unit: unit of the CAD model vertices.
|
| 873 |
+
refine_iterations: number of pose-refinement passes (reference uses 5).
|
| 874 |
+
|
| 875 |
+
Returns:
|
| 876 |
+
Annotated image, rendered overlay, 4x4 object-to-camera pose, and a report.
|
| 877 |
+
"""
|
| 878 |
+
try:
|
| 879 |
+
return _run_pose(
|
| 880 |
+
rgb_image,
|
| 881 |
+
depth_file,
|
| 882 |
+
mesh_file,
|
| 883 |
+
bbox,
|
| 884 |
+
intrinsics,
|
| 885 |
+
depth_unit,
|
| 886 |
+
mesh_unit,
|
| 887 |
+
refine_iterations,
|
| 888 |
+
progress,
|
| 889 |
+
)
|
| 890 |
+
except gr.Error:
|
| 891 |
+
raise
|
| 892 |
+
except Exception as e:
|
| 893 |
+
traceback.print_exc()
|
| 894 |
+
raise gr.Error(f"{type(e).__name__}: {e}")
|
| 895 |
+
|
| 896 |
+
|
| 897 |
+
# ---------------------------------------------------------------------------
|
| 898 |
+
# UI
|
| 899 |
+
# ---------------------------------------------------------------------------
|
| 900 |
+
_meta_path = os.path.join(EX_DIR, "_meta.json")
|
| 901 |
+
EXAMPLES = []
|
| 902 |
+
if os.path.exists(_meta_path):
|
| 903 |
+
_meta = json.load(open(_meta_path))
|
| 904 |
+
for _k in ["mustard_bottle", "power_drill", "pitcher_base", "sugar_box"]:
|
| 905 |
+
if _k not in _meta:
|
| 906 |
+
continue
|
| 907 |
+
m = _meta[_k]
|
| 908 |
+
EXAMPLES.append(
|
| 909 |
+
[
|
| 910 |
+
os.path.join(EX_DIR, m["rgb"]),
|
| 911 |
+
os.path.join(EX_DIR, m["depth"]),
|
| 912 |
+
os.path.join(EX_DIR, m["mesh"]),
|
| 913 |
+
",".join(str(int(v)) for v in m["bbox"]),
|
| 914 |
+
",".join(f"{v:.4f}" for v in m["K"]),
|
| 915 |
+
]
|
| 916 |
+
)
|
| 917 |
+
|
| 918 |
+
CSS = """
|
| 919 |
+
.dark .gradio-container { --body-background-fill: #06080d; }
|
| 920 |
+
"""
|
| 921 |
+
|
| 922 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="FoundationPose 6-DoF") as demo:
|
| 923 |
+
gr.Markdown(
|
| 924 |
+
"""
|
| 925 |
+
# FoundationPose — 6-DoF pose of novel objects
|
| 926 |
+
|
| 927 |
+
Give it an **RGB-D frame**, the object's **CAD model**, the **camera intrinsics**
|
| 928 |
+
and a **2D box**, and [NVIDIA FoundationPose](https://huggingface.co/nvidia/foundationpose)
|
| 929 |
+
returns the object's full 6-DoF pose — zero-shot, no training on the object.
|
| 930 |
+
|
| 931 |
+
252 pose hypotheses are sampled on an icosphere, refined by the refiner network, then ranked by the scorer network.
|
| 932 |
+
"""
|
| 933 |
+
)
|
| 934 |
+
|
| 935 |
+
with gr.Row():
|
| 936 |
+
with gr.Column(scale=1):
|
| 937 |
+
rgb_in = gr.Image(type="filepath", label="RGB image", height=280)
|
| 938 |
+
depth_in = gr.File(
|
| 939 |
+
label="Depth map — 16-bit PNG or .npy, aligned to the RGB image",
|
| 940 |
+
file_types=[".png", ".npy", ".tif", ".tiff"],
|
| 941 |
+
)
|
| 942 |
+
bbox_in = gr.Textbox(
|
| 943 |
+
label="2D bounding box — x1,y1,x2,y2 (pixels)",
|
| 944 |
+
placeholder="409,47,542,324",
|
| 945 |
+
info="Click twice on the RGB image to set two opposite corners.",
|
| 946 |
+
)
|
| 947 |
+
k_in = gr.Textbox(
|
| 948 |
+
label="Camera intrinsics — fx,fy,cx,cy (pixels)",
|
| 949 |
+
value="1066.7780,1067.4870,312.9869,241.3109",
|
| 950 |
+
)
|
| 951 |
+
with gr.Column(scale=1):
|
| 952 |
+
mesh_in = gr.Model3D(label="CAD model of the object", height=340)
|
| 953 |
+
with gr.Accordion("Advanced options", open=False):
|
| 954 |
+
depth_unit_in = gr.Radio(
|
| 955 |
+
["millimetres (uint16 PNG)", "metres (float)", "0.1 mm"],
|
| 956 |
+
value="millimetres (uint16 PNG)",
|
| 957 |
+
label="Depth unit",
|
| 958 |
+
)
|
| 959 |
+
mesh_unit_in = gr.Radio(
|
| 960 |
+
["auto", "metres", "millimetres"],
|
| 961 |
+
value="auto",
|
| 962 |
+
label="CAD model unit",
|
| 963 |
+
)
|
| 964 |
+
iters_in = gr.Slider(
|
| 965 |
+
1, 10, value=5, step=1,
|
| 966 |
+
label="Refinement iterations",
|
| 967 |
+
info="The reference implementation uses 5.",
|
| 968 |
+
)
|
| 969 |
+
run_btn = gr.Button("Estimate pose", variant="primary", size="lg")
|
| 970 |
+
|
| 971 |
+
with gr.Row():
|
| 972 |
+
annotated_out = gr.Image(label="3D box + object axes", height=380)
|
| 973 |
+
overlay_out = gr.Image(label="CAD model rendered at the estimated pose", height=380)
|
| 974 |
+
report_out = gr.Markdown()
|
| 975 |
+
pose_out = gr.Code(label="Object → camera pose (4×4, metres)", language=None)
|
| 976 |
+
|
| 977 |
+
corner_state = gr.State(None)
|
| 978 |
+
|
| 979 |
+
def on_click(corner, evt: gr.SelectData):
|
| 980 |
+
x, y = int(evt.index[0]), int(evt.index[1])
|
| 981 |
+
if corner is None:
|
| 982 |
+
return (x, y), f"{x},{y},{x},{y}"
|
| 983 |
+
x0, y0 = corner
|
| 984 |
+
return None, f"{min(x0,x)},{min(y0,y)},{max(x0,x)},{max(y0,y)}"
|
| 985 |
+
|
| 986 |
+
rgb_in.select(on_click, inputs=[corner_state], outputs=[corner_state, bbox_in])
|
| 987 |
+
|
| 988 |
+
inputs = [rgb_in, depth_in, mesh_in, bbox_in, k_in, depth_unit_in, mesh_unit_in, iters_in]
|
| 989 |
+
outputs = [annotated_out, overlay_out, pose_out, report_out]
|
| 990 |
+
run_btn.click(estimate_pose, inputs=inputs, outputs=outputs)
|
| 991 |
+
|
| 992 |
+
if EXAMPLES:
|
| 993 |
+
gr.Examples(
|
| 994 |
+
examples=EXAMPLES,
|
| 995 |
+
inputs=[rgb_in, depth_in, mesh_in, bbox_in, k_in],
|
| 996 |
+
outputs=outputs,
|
| 997 |
+
fn=estimate_pose,
|
| 998 |
+
cache_examples=True,
|
| 999 |
+
cache_mode="lazy",
|
| 1000 |
+
label="Examples — YCB-Video (BOP), one of FoundationPose's own evaluation datasets",
|
| 1001 |
+
)
|
| 1002 |
+
|
| 1003 |
+
gr.Markdown(
|
| 1004 |
+
"""
|
| 1005 |
+
---
|
| 1006 |
+
**Model** [nvidia/foundationpose](https://huggingface.co/nvidia/foundationpose) ·
|
| 1007 |
+
**Paper** [FoundationPose: Unified 6D Pose Estimation and Tracking of Novel Objects](https://arxiv.org/abs/2312.08344) (CVPR 2024, Best Paper Nominee) ·
|
| 1008 |
+
**Code** [NVlabs/FoundationPose](https://github.com/NVlabs/FoundationPose)
|
| 1009 |
+
|
| 1010 |
+
The published checkpoints are ONNX; they are converted to PyTorch with
|
| 1011 |
+
[onnx2torch](https://github.com/ENOT-AutoDL/onnx2torch) at startup. The exported
|
| 1012 |
+
scorer rates each hypothesis independently (`score_logit` per pose), which is the
|
| 1013 |
+
shape NVIDIA ships.
|
| 1014 |
+
|
| 1015 |
+
Example scenes and CAD models come from the
|
| 1016 |
+
[YCB-Video / BOP](https://huggingface.co/datasets/bop-benchmark/ycbv) dataset (MIT licence,
|
| 1017 |
+
© 2017 UW Robotics and State Estimation Lab). Model weights are covered by the
|
| 1018 |
+
NVIDIA Open Model License.
|
| 1019 |
+
"""
|
| 1020 |
+
)
|
| 1021 |
+
|
| 1022 |
+
if __name__ == "__main__":
|
| 1023 |
+
demo.queue(max_size=12).launch(mcp_server=True, show_error=True)
|
examples/004_sugar_box.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d8b3830dd3067ecee4abf16da3624a78353a472b06bc9f2bbaa8f00c500ff65d
|
| 3 |
+
size 1571864
|
examples/006_mustard_bottle.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d9dc011cddcb899fdc8713b4904ca1eb60bda0d0f8e280e726c92de4a247c7a2
|
| 3 |
+
size 1289596
|
examples/019_pitcher_base.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bda6f4993f9c30be5f052c51ca7671a32d5e96e3baf53359f660bbad44f1f497
|
| 3 |
+
size 1212516
|
examples/035_power_drill.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:777e3f8921db35fbc210c936cc3321b05200c1b318783f804daa414c06347767
|
| 3 |
+
size 1089540
|
examples/_meta.json
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mustard_bottle": {
|
| 3 |
+
"name": "006_mustard_bottle",
|
| 4 |
+
"mesh": "006_mustard_bottle.glb",
|
| 5 |
+
"rgb": "mustard_bottle_rgb.png",
|
| 6 |
+
"depth": "mustard_bottle_depth.png",
|
| 7 |
+
"bbox": [
|
| 8 |
+
409,
|
| 9 |
+
47,
|
| 10 |
+
542,
|
| 11 |
+
324
|
| 12 |
+
],
|
| 13 |
+
"K": [
|
| 14 |
+
1066.778,
|
| 15 |
+
1067.487,
|
| 16 |
+
312.9869,
|
| 17 |
+
241.3109
|
| 18 |
+
],
|
| 19 |
+
"gt_pose": [
|
| 20 |
+
[
|
| 21 |
+
-0.9586628303517895,
|
| 22 |
+
-0.2835287329246605,
|
| 23 |
+
-0.024014888195919924,
|
| 24 |
+
0.11510576923433374
|
| 25 |
+
],
|
| 26 |
+
[
|
| 27 |
+
-0.08379337156245308,
|
| 28 |
+
0.3619577494350433,
|
| 29 |
+
-0.9284211691506922,
|
| 30 |
+
-0.041077239594684054
|
| 31 |
+
],
|
| 32 |
+
[
|
| 33 |
+
0.27192578395746053,
|
| 34 |
+
-0.8880300419285506,
|
| 35 |
+
-0.37075363826953395,
|
| 36 |
+
0.7556857413095329
|
| 37 |
+
],
|
| 38 |
+
[
|
| 39 |
+
0.0,
|
| 40 |
+
0.0,
|
| 41 |
+
0.0,
|
| 42 |
+
1.0
|
| 43 |
+
]
|
| 44 |
+
],
|
| 45 |
+
"scene": "000050",
|
| 46 |
+
"im": "1874"
|
| 47 |
+
},
|
| 48 |
+
"power_drill": {
|
| 49 |
+
"name": "035_power_drill",
|
| 50 |
+
"mesh": "035_power_drill.glb",
|
| 51 |
+
"rgb": "power_drill_rgb.png",
|
| 52 |
+
"depth": "power_drill_depth.png",
|
| 53 |
+
"bbox": [
|
| 54 |
+
58,
|
| 55 |
+
246,
|
| 56 |
+
371,
|
| 57 |
+
456
|
| 58 |
+
],
|
| 59 |
+
"K": [
|
| 60 |
+
1066.778,
|
| 61 |
+
1067.487,
|
| 62 |
+
312.9869,
|
| 63 |
+
241.3109
|
| 64 |
+
],
|
| 65 |
+
"gt_pose": [
|
| 66 |
+
[
|
| 67 |
+
-0.9265112328620186,
|
| 68 |
+
0.3554015793763549,
|
| 69 |
+
0.12355990855995728,
|
| 70 |
+
-0.07007990833197376
|
| 71 |
+
],
|
| 72 |
+
[
|
| 73 |
+
-0.053990458994419316,
|
| 74 |
+
-0.4505591780531961,
|
| 75 |
+
0.8911131272403047,
|
| 76 |
+
0.06763430848607617
|
| 77 |
+
],
|
| 78 |
+
[
|
| 79 |
+
0.3723725271017997,
|
| 80 |
+
0.8189545363840561,
|
| 81 |
+
0.4366367182365112,
|
| 82 |
+
0.7316101236716381
|
| 83 |
+
],
|
| 84 |
+
[
|
| 85 |
+
0.0,
|
| 86 |
+
0.0,
|
| 87 |
+
0.0,
|
| 88 |
+
1.0
|
| 89 |
+
]
|
| 90 |
+
],
|
| 91 |
+
"scene": "000050",
|
| 92 |
+
"im": "1324"
|
| 93 |
+
},
|
| 94 |
+
"pitcher_base": {
|
| 95 |
+
"name": "019_pitcher_base",
|
| 96 |
+
"mesh": "019_pitcher_base.glb",
|
| 97 |
+
"rgb": "pitcher_base_rgb.png",
|
| 98 |
+
"depth": "pitcher_base_depth.png",
|
| 99 |
+
"bbox": [
|
| 100 |
+
226,
|
| 101 |
+
69,
|
| 102 |
+
513,
|
| 103 |
+
439
|
| 104 |
+
],
|
| 105 |
+
"K": [
|
| 106 |
+
1066.778,
|
| 107 |
+
1067.487,
|
| 108 |
+
312.9869,
|
| 109 |
+
241.3109
|
| 110 |
+
],
|
| 111 |
+
"gt_pose": [
|
| 112 |
+
[
|
| 113 |
+
-0.7630850378965286,
|
| 114 |
+
-0.6459310027194527,
|
| 115 |
+
0.021784917094012195,
|
| 116 |
+
0.03607193041593472
|
| 117 |
+
],
|
| 118 |
+
[
|
| 119 |
+
-0.23791608153732055,
|
| 120 |
+
0.24940775817574404,
|
| 121 |
+
-0.9387177934933404,
|
| 122 |
+
0.00816719940180273
|
| 123 |
+
],
|
| 124 |
+
[
|
| 125 |
+
0.6009142146924277,
|
| 126 |
+
-0.7215054182161463,
|
| 127 |
+
-0.3439961790363487,
|
| 128 |
+
0.7327526085460678
|
| 129 |
+
],
|
| 130 |
+
[
|
| 131 |
+
0.0,
|
| 132 |
+
0.0,
|
| 133 |
+
0.0,
|
| 134 |
+
1.0
|
| 135 |
+
]
|
| 136 |
+
],
|
| 137 |
+
"scene": "000056",
|
| 138 |
+
"im": "1"
|
| 139 |
+
},
|
| 140 |
+
"sugar_box": {
|
| 141 |
+
"name": "004_sugar_box",
|
| 142 |
+
"mesh": "004_sugar_box.glb",
|
| 143 |
+
"rgb": "sugar_box_rgb.png",
|
| 144 |
+
"depth": "sugar_box_depth.png",
|
| 145 |
+
"bbox": [
|
| 146 |
+
151,
|
| 147 |
+
255,
|
| 148 |
+
413,
|
| 149 |
+
452
|
| 150 |
+
],
|
| 151 |
+
"K": [
|
| 152 |
+
1066.778,
|
| 153 |
+
1067.487,
|
| 154 |
+
312.9869,
|
| 155 |
+
241.3109
|
| 156 |
+
],
|
| 157 |
+
"gt_pose": [
|
| 158 |
+
[
|
| 159 |
+
0.04261657197093155,
|
| 160 |
+
0.8018176555886548,
|
| 161 |
+
0.5960480236123241,
|
| 162 |
+
-0.015860798256254367
|
| 163 |
+
],
|
| 164 |
+
[
|
| 165 |
+
-0.85005714750022,
|
| 166 |
+
0.3425727880987984,
|
| 167 |
+
-0.40005896186039525,
|
| 168 |
+
0.0695873326444807
|
| 169 |
+
],
|
| 170 |
+
[
|
| 171 |
+
-0.5249637632720354,
|
| 172 |
+
-0.48962547962401337,
|
| 173 |
+
0.6961900608197962,
|
| 174 |
+
0.7274594452418055
|
| 175 |
+
],
|
| 176 |
+
[
|
| 177 |
+
0.0,
|
| 178 |
+
0.0,
|
| 179 |
+
0.0,
|
| 180 |
+
1.0
|
| 181 |
+
]
|
| 182 |
+
],
|
| 183 |
+
"scene": "000051",
|
| 184 |
+
"im": "1729"
|
| 185 |
+
}
|
| 186 |
+
}
|
examples/mustard_bottle_depth.png
ADDED
|
examples/mustard_bottle_rgb.png
ADDED
|
Git LFS Details
|
examples/pitcher_base_depth.png
ADDED
|
examples/pitcher_base_rgb.png
ADDED
|
Git LFS Details
|
examples/power_drill_depth.png
ADDED
|
examples/power_drill_rgb.png
ADDED
|
Git LFS Details
|
examples/sugar_box_depth.png
ADDED
|
examples/sugar_box_rgb.png
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# torch pinned to match the prebuilt sm_120 nvdiffrast wheel cell (pt211-cu130-cp312)
|
| 2 |
+
torch==2.11.0
|
| 3 |
+
torchvision==0.26.0
|
| 4 |
+
numpy
|
| 5 |
+
scipy
|
| 6 |
+
networkx
|
| 7 |
+
pillow
|
| 8 |
+
opencv-python-headless
|
| 9 |
+
trimesh
|
| 10 |
+
kornia
|
| 11 |
+
onnx
|
| 12 |
+
onnx2torch
|
| 13 |
+
https://huggingface.co/datasets/multimodalart/zerogpu-blackwell-wheels/resolve/main/wheels/pt211-cu130-cp312/nvdiffrast-0.4.0-cp312-cp312-linux_x86_64.whl
|