Skip to content

FAQ & troubleshooting

GTSAM 4.2 from pip is built against the NumPy 1.x ABI. With NumPy 2 you get a crash, not an exception:

Terminal window
pip install "numpy<2"

Detector backends are optional extras:

Terminal window
pip install -e ".[apriltag]" # AprilGrid
pip install -e ".[aruco]" # ArUco
Terminal window
pip install rosbags

Pure Python — you do not need ROS installed.

bash scripts/setup_env.sh builds the cam_calib environment and runs the test suite, which separates an install problem from a data problem.

Check tag_family, rows, cols, and tag_spacing against the board you actually printed — rows/cols are inner corners for a chessboard but tag counts for an AprilGrid. Multi-scale detection handles far and near boards; it cannot fix a wrong family.

By design: pooled-pose filter (≥20% of tags across cameras), per-corner ≥5 views, per-camera ≥3 views, minimum 6 tags per frame. If most frames are dropped the recording is too sparse — get closer, or move more slowly.

The sanity check caught a degenerate init. Common on wide fisheye with the wrong model — try equidistant. max_init_frames caps how many frames the OpenCV init uses.

corner_subsample: top_left is already the default (3–5× faster). Also: factor_hz to subsample frames, --parallel on the regression runner, and cache_dir, which makes re-runs skip detection entirely.

Known: double_sphere and eucm stereo fits can slide into an fx–xi / alpha basin on TUM-VI while single-camera fits stay stable. Fit each camera alone, then pin those intrinsics for the joint solve — the recipe is in examples/configs/tumvi_cam_imu/. See Camera model.

Reprojection error is low but the extrinsics look off

Section titled “Reprojection error is low but the extrinsics look off”

A low residual means the solution explains its own observations. A parameter the motion never constrained settles near its initial value without raising the error. Read the motion-excitation grade in the HTML report, and for camera–IMU run camcalib roundtrip.

That is a real finding. Usual causes: wrong camera model, wrong IMU convention, an interpolated accelerometer corrupting td (try gyro_only or estimate_t_ga), or insufficient excitation. See Round-trip validation.

td or the lever arm look wrong on a dynamic rig

Section titled “td or the lever arm look wrong on a dynamic rig”

Check state_hz. It defaults to 30 Hz; below that, reprojection, lever, and td alias on dynamic-motion rigs. Gentle sequences are insensitive to it.

Its Bosch BMI055 publishes a 62.5 Hz accelerometer interpolated to 200 Hz, which injects a gyro-accel timing offset. Either set gyro_only: true on that IMU or turn on solve.estimate_t_ga. See IMU model.

IMU intrinsics come back at their initial values

Section titled “IMU intrinsics come back at their initial values”

They were not observed. Scale and misalignment need excitation on every axis; Tg needs simultaneous rotation and strong linear acceleration. See Degeneracy.

Check the IMU convention first — kalibr, rpng, and calyx produce different matrices for the same device — then sensor numbering, then transform direction. camcalib.io.kalibr reads Kalibr camchains directly, which is the reliable way to compare.

Every translation is off by the same factor

Section titled “Every translation is off by the same factor”

tag_size does not match the printed board. Measure a tag with calipers.

Results differ between sequences of the same rig

Section titled “Results differ between sequences of the same rig”

Those parameters were not constrained by the motion in them. Run a batch sweep — spread across sequences is the most honest accuracy signal available without ground truth.

Open an issue on the repository with your config, the console output, and what the rig is.