Tutorial: spherical visual SLAM¶
Field |
Contract |
|---|---|
Application |
Reconstruct and navigate |
Input |
Ordered ERP or calibrated central-fisheye frames |
Output |
Arbitrary-scale trajectory, keyframes, and map diagnostics |
Strategy |
Hybrid frontend and sphere-native geometry |
API |
|
Stability |
Experimental |
Dependencies |
|
Evidence |
Integration fixtures and explicit tracking states |
How this application is composed¶
SLAM reuses the feature, matched-bearing, relative-pose, triangulation and spherical bundle-adjustment components, then adds temporal state, keyframes, local-map culling, relocalization and loop correction. Use the session wrapper: the composition is shown here for traceability, but manually reproducing its state machine would bypass its failure and provenance contracts.
This tutorial closes the sequence from panorama pixels to an incremental camera trajectory. PanorAi exposes two Experimental central-camera routes: one for already stitched equirectangular panoramas and one for a calibrated equidistant fisheye lens. Both return arbitrary-scale visual trajectories.
See the SLAM theme in the spherical computer-vision guide. In particular, SLAM does not filter an ERP or reconstruct one: the default ERP frontend materializes gnomonic faces for feature extraction, maps detections to unit bearings, and performs tracking and mapping in bearing space.
PanorAi-specific: central-camera ray conversion, panorama-frame tracking, spherical 2D–3D refinement and bundle adjustment, explicit tracking states, relocalization/loop evidence and arbitrary-scale trajectory diagnostics.
1. Incremental tracking from central ERP frames¶
from panorai.slam import SphericalIncrementalSLAM
session = SphericalIncrementalSLAM.from_preset(
"sift-flann",
face_sampler="cube",
face_fov_deg=100.0,
face_shape_hw=(320, 320),
max_features=2048,
)
for frame_id, timestamp_s, panorama_rgb in decoded_erp_frames:
tracking = session.add_frame(
panorama_rgb,
timestamp_s=timestamp_s,
frame_id=frame_id,
)
if tracking.pose is not None:
R_world_to_panorama = tracking.pose.R
center_world = tracking.pose.center
else:
log_tracking_failure(frame_id, tracking.reasons)
result = session.finish()
The frontend follows the Stable feature route from Tutorial: spherical features and matching: ERP → overlapping gnomonic views → OpenCV descriptor extraction → panorama bearings. No projected image is blended back into ERP. Accepted geometric inliers seed the local map; spherical 2D–3D refinement and local bundle adjustment minimize angular tangent residuals.
Never invent a pose for tracking.state == "lost". Inspect correspondence
count, parallax, active local points and reasons on every frame. The final
result can contain a useful partial map while still declaring that the full
trajectory was not completed.
2. Supply precomputed spherical features¶
Use add_features() when another component already owns a
SphericalFeatureSet. This route starts directly from unit bearings and avoids
re-projecting an ERP inside SLAM:
features = feature_pipeline.extract(panorama_rgb, panorama_id=frame_id)
tracking = session.add_features(features, timestamp_s=timestamp_s)
The public feature objects are copied. A reset begins a new session with the same configuration.
The CI-sized lifecycle example is executable:
from panorai.slam import (
SphericalIncrementalSLAM,
SphericalIncrementalSLAMOptions,
)
first_features = feature_pipeline.extract(feature_rgb, panorama_id="frame-000")
slam = SphericalIncrementalSLAM(
feature_pipeline=feature_pipeline,
relative_pose_estimator=pose_estimator,
options=SphericalIncrementalSLAMOptions(
min_frame_features=1,
global_refine_on_finish=False,
),
)
first_tracking = slam.add_features(first_features, timestamp_s=0.0)
assert first_tracking.state == "initializing"
assert first_tracking.pose is not None
partial_trajectory = slam.finish()
assert partial_trajectory.scale == "arbitrary"
assert not partial_trajectory.success # one frame cannot define a trajectory
3. Track one calibrated fisheye lens¶
from panorai.slam import EquidistantFisheyeCamera, SphericalVisualSLAM
camera = EquidistantFisheyeCamera.from_kalibr_yaml(
"kalibr_imucam_chain.yaml",
camera_id="cam0",
)
session = SphericalVisualSLAM(camera)
for frame_id, timestamp_s, image in decoded_frames:
session.add_frame(image, timestamp_s=timestamp_s, frame_id=frame_id)
result = session.finish()
Here PanorAi inverts calibrated equidistant fisheye pixels directly into unit bearings. It does not pretend that two non-coincident fisheye lenses form one central camera. Use a single calibrated lens, or a stitched ERP only when its central-camera approximation is justified.
The fisheye frontend builds a temporal graph as frames arrive. Rotation
averaging, tracks, positioning, triangulation and spherical bundle adjustment
run when finish() freezes the graph.
4. Understand the output boundary¶
Output |
Meaning |
|---|---|
|
world-to-panorama or world-to-camera rotation under the documented frame |
|
camera centre in the arbitrary-scale world frame |
tracking state |
explicit initializing/tracking/keyframe/relocalized/lost status |
map observations |
bearing-linked visual landmarks, never reconstructed ERP pixels |
diagnostics |
accepted/rejected edges, inliers, parallax, reprojection and optimization evidence |
Visual-only translation scale is unobservable. Metric evaluation requires an external scale or similarity alignment. Current limits include rolling shutter, IMU fusion, metric scale, dynamic-scene modelling, exact generalized dual- fisheye geometry and bounded-memory marginalization.
Continue with Run spherical visual SLAM for operational guidance and Spherical visual SLAM for the complete Experimental API and limitations.