| --- |
| license: mit |
| library_name: pytorch |
| tags: |
| - 3d-reconstruction |
| - camera-pose-estimation |
| - pointmap |
| - multi-camera-rig |
| - autonomous-driving |
| - waymo |
| - dust3r |
| datasets: |
| - waymo_open_dataset |
| metrics: |
| - pointmap_err |
| - pose_deg |
| - rig_deg |
| model-index: |
| - name: rig3r-waymo |
| results: |
| - task: |
| type: camera-pose-estimation |
| name: Multi-camera rig pose estimation |
| dataset: |
| type: waymo_open_dataset |
| name: Waymo Open Dataset (mini subset, held-out val split) |
| metrics: |
| - type: pose_deg |
| value: 1.4403 |
| name: Pose rotation error (deg) |
| - type: rig_deg |
| value: 1.4075 |
| name: Rig rotation error (deg) |
| - type: pointmap_err |
| value: 0.2159 |
| name: Pointmap L2 error (scale-normalized) |
| --- |
| |
| # Open-Rig3R — Waymo (epoch 50) |
|
|
| Unofficial open reimplementation of **Rig3R**: a rig-aware transformer that takes multiple |
| camera views and predicts per-view pointmaps plus pose and rig raymaps, so a whole camera |
| rig is reconstructed jointly instead of one camera at a time. |
|
|
| Code: [engichang1467/Open-Rig3R](https://github.com/engichang1467/Open-Rig3R) |
|
|
| This checkpoint is epoch 50 of 50 from a single run on a Waymo mini subset. It is a |
| **research checkpoint from a reimplementation**, not a reproduction of the paper's |
| published numbers — see [Limitations](#limitations) before using it for anything. |
|
|
| ## Results |
|
|
| Held-out Waymo val split, at the end of training: |
|
|
| | Metric | Value | |
| |---|---| |
| | Pointmap L2 error (scale-normalized) | **0.2159** | |
| | Pose rotation error | **1.440°** | |
| | Rig rotation error | **1.408°** | |
| | Pose centre error | 0.0156 | |
| | Rig centre error | 0.0161 | |
|
|
| ### Read the validation curve carefully |
|
|
| `val/total` **rises** from 0.395 (epoch 6) to 0.961 (epoch 50). That is not overfitting, |
| and epoch 6 is not the better checkpoint. |
|
|
| The pointmap term is `C * err - alpha * log(C)`, where `C` is the model's own predicted |
| confidence. Over training `C` climbs from 1.2 to 9.98, saturating against the |
| `conf_max: 10.0` ceiling. The same geometric error therefore costs roughly 10x more at |
| epoch 50 than at epoch 6. Meanwhile the unweighted error stays flat or improves |
| (`pointmap_err` 0.279 → 0.216) and both angular errors fall monotonically |
| (14.10° → 1.44°, 14.16° → 1.41°). |
|
|
| | epoch | val/total | pointmap | **pointmap_err** | conf_mean | pose_deg | rig_deg | |
| |---|---|---|---|---|---|---| |
| | 6 | **0.395** | 0.253 | 0.2239 | 3.26 | 3.01 | 3.02 | |
| | 25 | 0.621 | 0.532 | 0.2187 | 6.86 | 1.63 | 1.59 | |
| | 50 | 0.961 | 0.880 | **0.2159** | 9.98 | **1.440** | **1.408** | |
|
|
| `val/total` is confounded by confidence saturation and should not be used for model |
| selection on this run. Select on `pointmap_err`, `pose_deg`, and `rig_deg` — all of which |
| plateau by roughly epoch 45 and are best at epoch 50. |
|
|
| ## Usage |
|
|
| This is a plain `state_dict`, not a `transformers` `PreTrainedModel` — there is no |
| `from_pretrained`. Load it into the `Rig3R` class from the repo, with the **same |
| architecture arguments the checkpoint was trained with** (the class defaults differ and |
| will not load): |
|
|
| ```python |
| from huggingface_hub import hf_hub_download |
| from safetensors.torch import load_file |
| from models.rig3r import Rig3R |
| |
| model = Rig3R( |
| encoder_ckpt=None, # weights come from the checkpoint below, not from DUSt3R |
| img_size=128, |
| patch_size=16, |
| embed_dim=1024, |
| num_decoder_layers=2, # NOT the class default of 6 |
| num_heads=8, |
| mlp_dim=4096, # NOT the class default of 2048 |
| metadata_dropout=0.5, |
| ) |
| |
| path = hf_hub_download("mca183/rig3r-waymo", "model.safetensors") |
| model.load_state_dict(load_file(path)) |
| model.eval() |
| |
| # images: (B, V, 3, 128, 128) — V = n_frames * num_cameras, e.g. 2 * 5 = 10 for the full Waymo rig |
| # metadata is optional; the decoder is trained with per-field dropout so it runs without it |
| outputs = model(images, metadata=None) |
| ``` |
|
|
| `forward` returns a dict: |
|
|
| | key | shape | notes | |
| |---|---|---| |
| | `pointmap` | `(B, V, H*W, 3)` | dense per-pixel 3D points | |
| | `pointmap_conf` | `(B, V, H*W, 1)` | per-pixel confidence — saturated, see [Limitations](#limitations) | |
| | `pose_raymap` | `(B, V, P, 6)` | centre + unit direction per patch | |
| | `rig_raymap` | `(B, V, P, 6)` | centre + unit direction per patch | |
| | `camera_center_pose` | `(B, V, 3)` | one centre per view | |
| | `camera_center_rig` | `(B, V, 3)` | one centre per view | |
| | `features` | `(B, V, P, C)` | decoder patch features, for downstream heads | |
|
|
| `P = (128 / 16)^2 = 64` patches per view. |
|
|
| ## Architecture |
|
|
| | | | |
| |---|---| |
| | Total parameters | 340.2 M (373 tensors, fp32) | |
| | Encoder | 303.2 M — DUSt3R ViT-L/16, **frozen** during training | |
| | Rig-aware decoder | 37.0 M — 2 pre-norm transformer layers, 8 heads, MLP dim 4096 | |
| | Heads | `pointmap_head`, `pose_raymap_head`, `rig_raymap_head` | |
| | Embedding dim | 1024 | |
| | Input resolution | 128 x 128, patch size 16 | |
|
|
| Encoder initialized from `DUSt3R_ViTLarge_BaseDecoder_512_dpt` and kept frozen, so only |
| the 37.0 M decoder and heads were trained. |
|
|
| ## Training |
|
|
| | | | |
| |---|---| |
| | Dataset | Waymo Open Dataset, mini subset, full 5-camera rig (FRONT, FRONT_LEFT, FRONT_RIGHT, SIDE_LEFT, SIDE_RIGHT) | |
| | Views per sample | 10 (`n_frames: 2` x 5 cameras) | |
| | Epochs | 50 | |
| | Batch size | 8 | |
| | Optimizer | AdamW, lr 1e-4, weight decay 0.01 | |
| | Scheduler | Cosine annealing, `eta_min` 1e-6 | |
| | Precision | bf16 autocast, no grad scaler | |
| | Loss weights | `w_point` 1.0, `w_pose` 1.0, `w_rig` 1.0 | |
| | Confidence regularizer | `alpha` 0.2, `beta` 1.0, `conf_max` 10.0 | |
| | Metadata dropout | 0.5 per field (frame index exempt) | |
| | Seed | 0 | |
| | Hardware | NVIDIA A100 80GB PCIe | |
| | Wall clock | ~3h 20m | |
|
|
| `conf_max: 10.0` is a deliberate deviation from the paper: Eq. 3 leaves `-alpha*log(C)` |
| unbounded below, so the ceiling floors the pointmap term at `-alpha*log(10) = -0.46`. |
| Setting it to `null` restores Eq. 3 exactly. |
|
|
| ## Limitations |
|
|
| - **Reimplementation, not the paper.** Unofficial; numbers here are not comparable to published Rig3R results. |
| - **Mini subset.** Trained on a small Waymo subset, not the full dataset. Generalization is untested. |
| - **128 x 128 input.** Well below the paper's resolution; pointmap detail is correspondingly coarse. |
| - **Shallow decoder.** 2 layers rather than 6, and the encoder is frozen throughout. |
| - **Waymo domain only.** Driving scenes, one rig geometry. No indoor, handheld, or non-automotive evaluation. |
| - **Single run, no held-out test.** Metrics are val-split only, one seed, no ablations. |
| - **Confidence is saturated.** `conf_mean` sits at the `conf_max` ceiling of 10.0, so predicted confidence is not calibrated and should not be read as an uncertainty estimate. |
|
|
| ## Dataset terms |
|
|
| Trained on the Waymo Open Dataset, which carries its own license and terms of use. Using |
| this model does not grant any rights to that data — obtain it from Waymo directly and |
| comply with their terms. |
|
|
| ## License |
|
|
| MIT (see the [source repo](https://github.com/engichang1467/Open-Rig3R)). Copyright 2025 |
| Michael Chang. The DUSt3R encoder initialization and the Waymo Open Dataset carry their |
| own separate licenses. |
|
|
| ## Citation |
|
|
| Rig3R (original paper): |
|
|
| ```bibtex |
| @article{rig3r, |
| title = {Rig3R: Rig-Aware Conditioning for Learned 3D Reconstruction}, |
| year = {2025} |
| } |
| ``` |
|
|
| This reimplementation: |
|
|
| ```bibtex |
| @software{open_rig3r, |
| author = {Chang, Michael}, |
| title = {Open-Rig3R: An open reimplementation of Rig3R}, |
| url = {https://github.com/engichang1467/Open-Rig3R}, |
| year = {2025} |
| } |
| ``` |
|
|