File size: 13,975 Bytes
dc09dcc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# Prepare input data for SplatAnything

This guide describes how to prepare images and optional camera priors for SplatAnything. To run a prepared scene, see [SplatAnything Inference](README.md#run-locally).

## Scene layout and image requirements

Prepare one scene per call:

```text
data/my_scene/
β”œβ”€β”€ images/
β”‚   β”œβ”€β”€ 000.jpg
β”‚   β”œβ”€β”€ 001.jpg
β”‚   └── ...
└── prior_camera.json  # required for pose conditioning
```

Point `--input_path` at `images/`. Supported formats: PNG, JPG/JPEG, BMP, TIF/TIFF, and WebP (case insensitive). Images are sorted lexicographically; `--max_images N` takes the first N. Use zero-padded names for numeric ordering. Use unique filename stems and images of a static scene with overlapping views. Input directories are not scanned recursively.

Bundled scenes are available under [examples/huggingface_demo/](examples/huggingface_demo/), grouped into `object`, `indoor`, `street` (outdoor), `aerial`, `rig` (autonomous-driving surround views), and `pano` (panorama). Object, street, aerial, rig, and panorama scenes include `prior_camera.json` for pose conditioning. Indoor scenes contain RGB images only; pose mode requires a matching camera JSON supplied with `--cameras`. The `pano` examples include both the source `pano.jpg` and 24 prepared perspective images under `images/`; reconstruction uses those images and their matching cameras. To prepare cameras for your own images, follow [Prepare your camera inputs](#prepare-your-camera-inputs).

`--resolution W H` is width followed by height; **both must be positive multiples of 14**. Images are resized preserving aspect ratio and center-cropped, with intrinsics updated consistently. For camera priors, preprocessing first crops symmetrically about the supplied principal point. Examples: `518 518`, `1022 756`, `1022 1022`.

## Prepare your camera inputs

Camera input is optional. **Use RGB-only inference if you do not have camera calibration.** Pose-conditioned inference needs both of the following for every input image:

- **Camera pose:** orientation and position in one shared world coordinate frame.
- **Intrinsics K:** focal lengths `fx`, `fy` and principal point `cx`, `cy`, in pixels for that exact input image.

A camera position alone, a camera's EXIF focal length, or a list of poses without intrinsics is insufficient. The conversion tool converts existing calibration; it does not estimate cameras from images. If you need calibration first, use a reconstruction/calibration tool such as [COLMAP](https://colmap.github.io/cli.html), or use `--mode rgb`.

| What you already have | Preparation |
|---|---|
| COLMAP sparse reconstruction | Convert its cameras/images files with `colmap` below |
| Calibrated pose and K matrices from SLAM, calibration, or rendering | Export numeric `.npy` arrays and a filename list; use `arrays` below |
| A JSON already following this repository's schema | Run `check` below, then use it directly |
| Images without poses or intrinsics | Run `inference.py --mode rgb` |

[scripts/convert_cameras.py](scripts/convert_cameras.py) runs on CPU with NumPy and Pillow. It needs no model weights, CUDA, PyTorch, or pycolmap. It checks the output using the same camera validation code as inference.

### A. Convert a COLMAP reconstruction

Use the sparse model and the **matching undistorted pinhole images**:

```bash
python scripts/convert_cameras.py colmap \
  --model data/my_scene/sparse/0 \
  --images data/my_scene/images \
  --output data/my_scene/prior_camera.json
```

Both `cameras.bin` + `images.bin` and `cameras.txt` + `images.txt` are supported. Binary files take precedence if both pairs are present; `--model_format txt` selects text explicitly. `points3D` files are not required. Supported camera models are `PINHOLE` and `SIMPLE_PINHOLE`.

The converter handles COLMAP's **world-to-camera** poses and **WXYZ** quaternion order automatically. It also subtracts `0.5` from the principal point coordinates to convert COLMAP's pixel-center convention to this repository's OpenCV convention. Do not invert these poses or shift the intrinsics yourself before this conversion. See the [COLMAP format](https://colmap.github.io/format.html#images-txt) and [pixel-coordinate definition](https://github.com/colmap/colmap/blob/3.10/src/colmap/sensor/models.h#L72).

If COLMAP reports a radial/distorted/fisheye camera model, first run its [image undistorter](https://colmap.github.io/cli.html#example):

```bash
colmap image_undistorter \
  --image_path data/my_scene/images \
  --input_path data/my_scene/sparse/0 \
  --output_path data/my_scene/undistorted \
  --output_type COLMAP

python scripts/convert_cameras.py colmap \
  --model data/my_scene/undistorted/sparse \
  --images data/my_scene/undistorted/images
```

The second command writes `data/my_scene/undistorted/prior_camera.json` by default. For inference, use **that undistorted image directory** with its matching JSON. Do not pair the new cameras with the original distorted images. Every file in `--images` must have a registered camera; move unregistered images out of that input directory or calibrate them before converting. Source subdirectory prefixes are reduced to basenames, and ambiguous duplicate names are rejected.

### B. Convert your own camera matrices

Export these files from your calibration or rendering code:

| File | Contents |
|---|---|
| `poses.npy` | Numeric `[N, 4, 4]` or `[N, 3, 4]` matrices; all poses share a world frame and consistent translation units |
| `intrinsics.npy` | Numeric `[N, 3, 3]` pixel intrinsics, or one `[3, 3]` K shared by every image |
| `image_names.txt` | One image filename per line, in exactly the same order as the pose/K rows |

For example, if row 0 corresponds to `000.jpg` and row 1 to `001.jpg`, the filename list is:

```text
000.jpg
001.jpg
```

The converter matches names explicitly; it never guesses the pose order from how files happen to be stored on disk. Supply intrinsics for the original-resolution **pinhole/undistorted** images. Convert normalized intrinsics to pixels in your calibration code first. Distortion coefficients and quaternion-only pose arrays are not accepted by this interface.

```bash
python scripts/convert_cameras.py arrays \
  --poses data/my_scene/poses.npy \
  --intrinsics data/my_scene/intrinsics.npy \
  --image_names data/my_scene/image_names.txt \
  --pose_convention c2w \
  --camera_axes opencv \
  --images data/my_scene/images \
  --output data/my_scene/prior_camera.json
```

Choose `--pose_convention w2c` if your matrices map world points into a camera frame. Choose `--camera_axes opengl` if your camera has X right, Y up and Z backward, as in Blender/NeRF; the converter flips camera Y/Z after resolving the pose direction. Neither choice can be inferred reliably from a valid matrix, so check your calibration tool's convention. For K matrices using COLMAP's half-integer pixel centers, add `--intrinsics_pixel_center colmap`; ordinary OpenCV K matrices use the default `opencv` setting.

### C. Check the JSON, then run inference

```bash
python scripts/convert_cameras.py check \
  --cameras data/my_scene/prior_camera.json \
  --images data/my_scene/images

python inference.py --input_path data/my_scene/images --mode pose \
  --cameras data/my_scene/prior_camera.json --output_path outputs
```

The checker verifies image coverage, unique IDs, finite rigid pose matrices, positive focal lengths, zero skew, and principal points inside the input images. It cannot verify calibration accuracy or detect that you declared the wrong c2w/w2c convention. COLMAP conversion additionally checks image dimensions against the model. Existing output JSON files are preserved unless conversion is run with `--overwrite`.

You can inspect a real input at [examples/huggingface_demo/aerial/prior_camera.json](examples/huggingface_demo/aerial/prior_camera.json). A small editable [schema template](examples/prior_camera.template.json) is also provided. Its placeholder poses/K illustrate the layout and must be replaced with your own calibration values.

## JSON schema and coordinate convention

Pose-conditioned inference requires `prior_camera.json` with **both camera poses and intrinsics**. Bundled presets select this file automatically with `--example NAME --mode pose`. For custom data, pass `--mode pose --cameras data/my_scene/prior_camera.json`. You may use another filename by supplying its path explicitly.

Match each `camera_id` to the image **filename stem**, e.g. `"000"` for `000.jpg` or `"frame_0000"` for `frame_0000.png`. Include a unique entry for every selected input image in each list. Entries are matched by ID, so their order in the JSON is not significant. The following example describes two original images of size **1920 Γ— 1080**:

```json
{
  "num_cameras": 2,
  "extrinsics": [
    {"camera_id": "000", "matrix": [[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]]},
    {"camera_id": "001", "matrix": [[1,0,0,1], [0,1,0,0], [0,0,1,0], [0,0,0,1]]}
  ],
  "intrinsics": [
    {"camera_id": "000", "matrix": [[1000,0,960], [0,1000,540], [0,0,1]]},
    {"camera_id": "001", "matrix": [[1000,0,960], [0,1000,540], [0,0,1]]}
  ]
}
```

`num_cameras` is optional; matching uses the entries in `extrinsics` and `intrinsics`.

For the WorldMirror-2.0 conditioning path in this repository, supply these camera poses:

| Property | Required representation |
|---|---|
| Pose matrix | A finite **4 Γ— 4 camera-to-world** transform, `T_c2w` |
| Matrix layout | `[[R_c2w, t_c2w], [0, 0, 0, 1]]`, represented as four JSON rows |
| Transform direction | `X_world = R_c2w @ X_camera + t_c2w` using column vectors |
| Camera axes | **OpenCV**: X right, Y down, Z forward |
| Rotation | An orthonormal 3 Γ— 3 matrix with determinant +1 |
| Translation | The camera center in a shared world frame; use consistent units for all views |
| Intrinsics | `[[fx, 0, cx], [0, fy, cy], [0, 0, 1]]`, in pixels at the **original image resolution** |

Use positive `fx` and `fy` and a principal point `(cx, cy)` inside the original image. Supply pinhole images and corresponding intrinsics; lens distortion coefficients are not consumed by this loader. Do not resize the intrinsics yourself: image cropping and resizing update them together with the input images.

If your calibration contains **OpenCV world-to-camera** matrices, invert them first:

```python
import numpy as np

# X_camera = R_w2c @ X_world + t_w2c, as in a COLMAP extrinsic matrix.
T_w2c = np.eye(4)
T_w2c[:3, :3] = R_w2c
T_w2c[:3, 3] = t_w2c
T_c2w = np.linalg.inv(T_w2c)
# T_c2w[:3, 3] is the camera center: -R_w2c.T @ t_w2c.
```

For an **OpenGL camera-to-world** matrix (X right, Y up, Z backward) in the same world frame, convert the camera axes before writing the JSON:

```python
T_c2w_opencv = T_c2w_opengl @ np.diag([1.0, -1.0, -1.0, 1.0])
```

The loader assembles `views["camera_poses"]` as `[1, S, 4, 4]` camera-to-world matrices and `views["camera_intrs"]` as `[1, S, 3, 3]` pixel intrinsics for the processed images. Internally, the WorldMirror-2.0 encoder normalizes camera translations and converts rotations to XYZW quaternions for pose tokens. Supply the original matrices in JSON; no quaternion vectors or manual pose normalization are needed.

By default cameras are re-anchored to the first input view. `--no_cam_align` keeps the supplied frame orientation and origin. Both modes still apply the checkpoint's fixed spacing normalization, so exported coordinates are **not guaranteed to be metric** or at the original camera translation scale.


## Panorama to 24 perspective views

Use [scripts/convert_panorama.py](scripts/convert_panorama.py) for a **full 360Β° Γ— 180Β° equirectangular** image with a 2:1 aspect ratio. Cubemaps, partial panoramas, and fisheye images must first be converted to this format. The tool runs on CPU with NumPy and Pillow; it requires no PyTorch, CUDA, or model weights.

```bash
python scripts/convert_panorama.py \
  --input data/panorama.jpg --output data/my_pano --size 1022 --fov 60
```

```text
data/my_pano/
β”œβ”€β”€ images/frame_0000.png ... frame_0023.png
β”œβ”€β”€ prior_camera.json          # matching OpenCV c2w poses and pixel intrinsics
└── panorama.json              # source size, FOV, and each view's yaw/pitch
```

| Views | Pitch | Yaw |
|---|---|---|
| `frame_0000`–`frame_0007` | 0Β° | 0Β°, 45Β°, …, 315Β° |
| `frame_0008`–`frame_0015` | βˆ’45Β° (down) | 0Β°, 45Β°, …, 315Β° |
| `frame_0016`–`frame_0023` | +45Β° (up) | 0Β°, 45Β°, …, 315Β° |

The panorama center looks along +Z. Positive yaw turns right; positive pitch looks up. Each square view uses `f = size / (2 tan(FOV/2))` and `cx = cy = (size βˆ’ 1)/2`. Pixel sampling wraps across the panorama seam. All 24 cameras share the same center; their rotations and intrinsics are known from the projection, while scene depth is predicted by the model. A single panorama provides no translational baseline or observed metric depth.

The default 60Β° FOV matches the bundled panorama examples. Its tilted rings leave polar caps beyond about Β±75Β° uncovered. Use `--fov 90` for coverage reaching the poles; this changes the view content and focal length. `--size 2044` matches the resolution of the bundled perspective views; `--size 518` produces smaller inputs. Apply any EXIF orientation to the source image before conversion.

```bash
python scripts/convert_cameras.py check \
  --images data/my_pano/images --cameras data/my_pano/prior_camera.json
python inference.py --input_path data/my_pano/images --mode pose \
  --cameras data/my_pano/prior_camera.json --resolution 518 518 --max_images 24
```

To rerun conversion, add `--overwrite`. Only `images/`, `prior_camera.json`, and `panorama.json` are replaced; the source panorama and other files are preserved. Keep the source outside the generated `images/` folder. Try the bundled scenes directly with `python inference.py --example pano/1 --mode pose` or `pano/2`.