visloc-rs
Structure from Motion, visual-inertial SLAM, 3D Gaussian Splatting and map-based localization — in pure Rust.

Visual-inertial odometry on LaMAria sequence_1_19: Project Aria glasses, 1.0 km city walk, 15 min, both SLAM cameras + IMU, no loop closure. LaMAria Score (official evaluator, higher is better): visloc-rs 50.0, OpenVINS 49.9 (estimate shipped with the LaMAria demo), Basalt with its upstream configuration 7.7 (run through this repository's parity-verified Basalt port). Trajectories are Sim(3)-aligned to the pseudo ground truth as the evaluator does. Details and the other training sequences.

Objects in metres from one camera and an IMU, offline, on KITTI raw drive 0084. Each car's foot is where the bottom of its box meets the ground: the VIO's metric scale puts the camera a measured height above the ground, and gravity gives down. That gives the height, the distance and, from the foot's path, the speed. Against KITTI's 3D boxes on five drives, car heights come out 11 % off (median, 110 cars) with mono + IMU and 6 % with stereo + IMU; pedestrians 7 cm (stereo + IMU). Speed is the weak part: still cars 0.15 m/s, moving cars 2–3 m/s off. With the IMU off (bottom right), the visual-only VO draws the 219 m drive as 5.7 m, 39× too small. Details. Images: KITTI raw (CC BY-NC-SA 3.0).

Map-based localization: EuRoC MH_02 in a map that the stereo-inertial SLAM saved from MH_01, a different flight. The map fixes correct the VIO into the map frame, so every frame gets a map pose (orange): 3,039 of the 3,040 frames, at 1.9 cm RMSE against ground truth. Green: the keypoints that matched the map, searched around where the corrected VIO predicts each landmark (98% of the images match; 81% when each image is matched alone), and rays to those landmarks. The same runs in real time as ROS 2 nodes. Details and the other EuRoC queries.

5,822 photos of a city block → 5,583 camera poses → 4.7M-gaussian splat → 5.9M-triangle mesh in 2.5 h, one gsplat_photos command, no COLMAP or Python, orbited from high above where no input photo was taken. Details.
In a nutshell
visloc-rs turns camera images (optionally with an IMU) into camera poses, 3D maps and photorealistic scenes, and localizes new images against those maps. Everything is Rust: no C++, OpenCV or CUDA toolchain to build. GPU work runs on any GPU through wgpu. Models are read and written in COLMAP format, so results plug into existing tools.
| What | Headline result (measured, real data) | vs |
|---|---|---|
| Photo SfM (unordered images) | ETH3D Electro 1,200 images: 3.46× faster, 25% lower camera-centre error; 9,996 / 10,008 cameras registered across all ten ETH3D many-view scenes | COLMAP 3.9 CPU |
| GPU SfM (video frames) | EuRoC: faster on 8/8 sequences (1.4–7.5×), more accurate on 4/8, equal on 1; COLMAP breaks on MH_05 (194 cm vs 2.6 cm) | COLMAP 4.1 CUDA |
| Stereo-inertial VI-SLAM (Basalt port + online mapper) | Real time on 11/11 EuRoC sequences on the dev machine (1.06–1.68×, thin margin on the slowest); beats ORB-SLAM3 on 9/11 | ORB-SLAM3, Basalt |
| Egocentric VI-SLAM (Project Aria, both SLAM cameras + IMU) | LaMAria, all 5 tracks: mean Score 64.3, above GroundedSLAM (62.3); ahead on Short, Medium and Long, level on Low light, behind on Moving; causal and real time on 4 CPU cores (training sequences; the leaderboard's are test) | GroundedSLAM, Aria's SLAM |
| Metric objects (mono camera + IMU + a 2D detector) | KITTI raw, against the 3D boxes: car heights 11 % off with mono + IMU (110 cars), 6 % with stereo + IMU; pedestrians 7 cm | KITTI tracklets |
| Photos → 3D Gaussian Splatting + mesh | Faster than brush on 5/5 benchmark scenes, PSNR within 0.05 dB on 4 | brush 0.3 |
| Localization against a prebuilt map | OpenLORIS robot rig: 98.96% of 1,250 held-out frames localized, median 2.9 mm; simulated house: 38 ms / frame | — |
| Stereo / RGB-D VO | KITTI 00 1.23 m and 09 2.07 m (ORB-SLAM2: 1.3 m / 3.2 m); TUM fr1_xyz 1.4 cm | ORB-SLAM2 |
Where it still loses, measured the same way: COLMAP CUDA is more accurate on 3 of the 8 EuRoC sequences and registers more of the blurred frames; ORB-SLAM3 wins 2 of 11 EuRoC sequences (MH_04, MH_05); on LaMAria, Aria's own closed-source SLAM stays well ahead (75.9) and GroundedSLAM leads the Moving-platform track; OpenLORIS 10k rig SfM is not yet at COLMAP's RMSE. Every number above links to a benchmark doc with the commands to reproduce it.
Contents
- Sensor support
- Try it
- Structure from Motion — ETH3D, EuRoC GPU SfM vs COLMAP CUDA
- Photos to splat and mesh
- Visual-inertial SLAM
- Localize against a map
- Use from Python and ROS 2
- More results — KITTI, TUM RGB-D, sequential SfM
- Documentation
Sensor support
What each sensor setup can do today. Benchmarked means a measured real-data result is in the repo. Experimental means the code runs but the docs record open gaps.
| Sensor setup | Tasks | Status | Headline result | Entry point |
|---|---|---|---|---|
| Monocular, unordered photos | SfM, 3DGS, mesh | Benchmarked | ETH3D Electro 1,200: 3.46× faster than COLMAP CPU, 3.50 vs 4.68 cm | unordered_sfm_demo, gsplat_photos |
| Monocular video | SfM | Benchmarked | EuRoC: faster than COLMAP 4.1 CUDA on 8/8 sequences, more accurate on 4/8 | gsplat_euroc |
| Posed photos (a COLMAP sparse model) | Dense MVS | Experimental | ETH3D high-res training, 13 scenes at 3200 px on one L4: F1 83.7% at 2 cm in 74 min (ACMMP 83.42%), 4× faster than ACMMP's code on the same GPU; 81.5% end to end from the photos | visloc-mvs example mvs (notes) |
| Monocular video | VO (DPVO port) | Experimental | MH_01 prefix 0.16 m, about 2× DPVO's published error; CPU only | euroc_dpvo_vo_demo |
| Monocular + IMU | VIO (Basalt port) | Experimental | TUM-VI room1–6: metric scale within 3.4% (median 1.3%), ATE 0.02–0.19 m, at most 0.014 m above stereo + IMU; no EuRoC result yet | basalt_euroc_vio_demo --mono (notes) |
| Monocular + IMU, plus a 2D detector | Object height, distance and speed in metres | Experimental | KITTI raw, 5 drives: car heights 11 % off (median, 110 cars), distances 19 %; moving cars' speeds 2–3 m/s off | objects_metric_offline (notes) |
| Stereo | VO / SLAM | Benchmarked | KITTI seq00 1.23 m and seq09 2.07 m, vs 1.3 m and 3.2 m for ORB-SLAM2 | deep_stereo_slam, online_slam_stereo_vo_kitti_demo |
| Stereo + IMU | VIO + mapping (Basalt port) | Benchmarked | Beats ORB-SLAM3 on 9/11 EuRoC sequences; native-Basalt parity within 0.1%; real time (RTF 1.06–1.68) on all 11/11 EuRoC sequences on the dev machine, thin margin on the slowest | basalt_euroc_online_slam_demo |
| RGB-D (as virtual stereo) | VO | Benchmarked | TUM fr1_xyz 0.014 m, fr1_desk 0.026 m (about 1.3–1.6× ORB-SLAM2 RGB-D) | TUM RGB-D |
| Multi-camera rig | SfM | Experimental | OpenLORIS 10k: 9,998/10,000 registered; RMSE parity with COLMAP still open | generalized_rig_sfm |
| Robot rig cameras vs. a prebuilt map | Relocalization | Benchmarked | OpenLORIS (robot-mounted rig): 98.96% of 1,250 held-out frames localized against a map built from other frames, median 2.9 mm | localize_openloris_map |
| Wheeled robot: stereo + IMU (RealSense T265), wheel odometry | Localization in a saved SLAM map, odometry corrected into its frame | Benchmarked | OpenLORIS-Scene office, other sessions up to 2.8 days later with furniture moved: VIO + map gives 94–99.8% of frames a map pose at 2.8–7.6 cm, and 1.1–2.4 cm with the stereo baseline corrected from the wheel odometry; wheel odometry + map 3.5–5.1 cm with its reference point and level calibrated from the VIO, and the same through the ROS 2 nodes from a rosbag2; across the day/night lighting change only with local contrast equalization (daylight session: 33% of frames at 2.6 cm; dawn session: none) | scripts/openloris_to_euroc.py, basalt_euroc_localize_demo --vio or --odometry (notes) |
| Robot camera sequence vs. a prebuilt map | Sequential map-matching localization | Benchmarked (simulation) | RNE house (drjohnson): 387/400 frames at 5.6 cm ATE, 38 ms per frame (median) with --gpu --motion-model. RNE checker corridor: 203/400 at 1.57 m, limited by repetitive texture |
localize_rne_map_sequence |
| Camera + GNSS prior | Tracking | Example only | Synthetic smoke test; no tight GNSS fusion | track_sequence_with_gnss_prior |
| VO + GNSS positions | Joint pose-graph fusion | Experimental (synthetic) | ~1 km synthetic loop: ATE 5.25 m (VO only) → 0.90 m fused; multipath jumps rejected, 15 s dropout bridged. Loosely coupled (receiver ENU positions; no raw pseudoranges, no IMU) | gnss_vo_fusion_demo (notes) |
LiDAR is not supported. Wheel odometry is supported only as the odometry that map fixes correct into a map's frame (above); it is not fused into the VIO.
Try it
Requires Rust 1.88+ only. Datasets are external inputs and are not bundled.
# Photos -> camera poses, Gaussian splat and mesh (GPU via wgpu)
cargo run --release -p visloc-gsplat-train --features gpu,euroc --example gsplat_photos -- --images /path/to/my_photos --out runs/my-scene
# Stereo-inertial VI-SLAM on a EuRoC sequence
cargo run --release --example basalt_euroc_online_slam_demo --features basalt-lm-workspace-reuse -- --euroc-dir /path/to/MH_01_easy --calibration configs/basalt/variants/official_euroc_ds/euroc_ds_calib.json --config configs/basalt/variants/official_euroc_ds/euroc_config.json --out-dir target/basalt_mh01_online
# Smallest end-to-end check: localize a synthetic query, no data needed
cargo run --example localize_dummy
Full options for each are in the sections below; scripts/check.sh runs the
complete local quality gate (fmt, clippy, test, doc).
Prebuilt binaries: each release
attaches gsplat_photos, visloc_mvs, colmap_incremental_mapper and
visloc_web for Linux x86_64, Windows x86_64 and macOS (Apple silicon), so
the photo pipeline runs without a Rust toolchain.
Structure from Motion
visloc-rs registers 9,996/10,008 cameras (99.88%) across every ETH3D low-resolution many-view scene with no mapper run above 3.32 GiB, reconstructs the 1,200-image Electro set 3.46× faster than COLMAP with 25.2% lower camera-centre RMSE, and beats official COLMAP on the 38-image courtyard control (0.5379 cm vs 1.6166 cm).
| Real scene (ETH3D low-res many-view) | Registered / supplied | Centre RMSE | RMSE / extent | Mapper peak RSS |
|---|---|---|---|---|
| terrains | 660/660 | 0.58 cm | 0.12% | 1.56 GiB |
| delivery area | 948/948 | 9.22 cm | 0.99% | 2.31 GiB |
| forest | 1028/1028 | 1.33 cm | 0.19% | 2.65 GiB |
| playground | 955/960 | 6.12 cm | 2.52% | 2.44 GiB |
| electro | 1200/1200 | 3.50 cm | 0.55% | 1.39 GiB |
| lakeside | 1063/1064 | 0.34 cm | 0.08% | 3.19 GiB |
| sand box | 1112/1112 | 2.35 cm | 0.45% | 3.32 GiB |
| storage room | 795/796 | 0.61 cm | 0.42% | 1.71 GiB |
| storage room 2 | 831/832 | 3.48 cm | 2.57% | 1.00 GiB |
| tunnel | 1404/1408 | 14.92 cm | 1.61% | 3.25 GiB |
tunnel includes one 5.32 m outlier (median 2.47 cm, p95 9.41 cm); playground excludes five hash-audited source outliers. The connected 10k corridor stress run, the 300-image reliability gate, the OpenLORIS comparison, and all reproduction commands are in the SfM benchmark details.
Same-input CPU8 Electro 1,200: visloc-rs 3.46× faster and 25.2% lower camera-centre RMSE than COLMAP. Unordered SfM, sequential SfM vs COLMAP, and EuRoC reconstruction evidence are in the SfM benchmark docs.
Against COLMAP 4.2 and GLOMAP on the same inputs
On the 13 ETH3D DSLR training scenes, the visloc-rs mapper is on par with COLMAP 4.2's incremental mapper and its global mapper (GLOMAP), and maps about 7× faster. All three get the same COLMAP SIFT features and exhaustive matches, the GLOMAP paper's setup, and the scoring follows that paper:
| Mapper (pycolmap 4.2.1, 4 threads) | Mean AUC@1° / 3° / 5° | Registered | Mapper time |
|---|---|---|---|
| visloc-rs | 50.9 / 75.5 / 82.1 | 445 / 454 | 28.4 s |
| COLMAP incremental | 50.4 / 75.2 / 81.7 | 445 / 454 | 200.7 s |
| COLMAP global (GLOMAP) | 51.6 / 75.5 / 81.7 | 454 / 454 | 184.4 s |
On visloc-rs's own matches (GPU SIFT, the default) it leads both at every threshold: 53.1 / 78.9 / 85.7. On the 5,822 SmallCity photos, visloc-rs maps in 21.7 min; COLMAP's global mapper had not finished after 94 min. Per-scene tables, timings and caveats: ETH3D AUC benchmark.
Large connected collections
On the connected OpenLORIS 10k stress set, streamed VLAD + LSH cuts candidate generation from 49:49 to 8:51 (5.63×); RMSE parity with COLMAP is still open (0.3890 m vs 0.3843 m). Full tables and caveats: SfM benchmark details.
GPU SfM vs COLMAP (CUDA) on EuRoC
Same 200 undistorted frames per sequence, same fixed intrinsics, same GPU, run back to back. visloc-rs uses GPU SIFT, batched GPU matching and the COLMAP incremental-mapper port. COLMAP 4.1 uses GPU extraction, GPU sequential matching and its default mapper. ATE is the Sim(3) camera-centre RMSE against ground truth.
| Sequence | Time (visloc-rs / COLMAP) | ATE (visloc-rs / COLMAP) | Registered (visloc-rs / COLMAP) |
|---|---|---|---|
| MH_01_easy | 96 s / 634 s | 0.35 / 0.35 cm | 182 / 200 |
| MH_03_medium | 113 s / 340 s | 1.32 / 2.61 cm | 166 / 200 |
| MH_05_difficult | 102 s / 764 s | 2.58 / 193.66 cm | 194 / 200 |
| V1_01_easy | 104 s / 219 s | 2.40 / 2.75 cm | 199 / 200 |
| V1_02_medium | 55 s / 101 s | 1.76 / 1.83 cm | 198 / 200 |
| V2_01_easy | 81 s / 172 s | 3.30 / 1.00 cm | 199 / 200 |
| V1_03_difficult | 45 s / 93 s | 2.17 / 1.98 cm | 67 / 80 |
| V2_03_difficult | 42 s / 60 s | 3.37 / 2.85 cm | 118 / 180 |
visloc-rs is faster on all 8 sequences (1.4–7.5×). It is more accurate on 4 and equal on MH_01. COLMAP is more accurate on V2_01, V1_03 and V2_03, and it registers more frames, most visibly on the blurred V1_03 and V2_03.
COLMAP's results vary between runs: an earlier run gave V2_01 34.9 cm, and MH_05 failed in both runs. visloc-rs is deterministic.
EuRoC MH_05, same 200 frames, same GPU. COLMAP breaks (ATE 193.7 cm, 764 s), visloc-rs stays at 2.58 cm in 102 s. The visloc-rs poses then train a 3D Gaussian Splatting scene: raw frames to splat in pure Rust + wgpu, no COLMAP.