Camera–IMU Calibration with Kalibr
1. Convert the ROS2 bag to ROS1
Kalibr runs on ROS1, so convert the recording first. Exclude topics with custom message types (on Unitree robots, /lowstate); the converter cannot read them.
pip3 install "rosbags>=0.9.12"
rosbags-convert --src <ros2_bag> --dst <name>.bag --exclude-topic <custom_topic>
2. Describe the IMU and the target
imu.yaml. Take the noise values from an Allan variance run on your own IMU; the ones below are only a starting point:
rostopic: /imu
accelerometer_noise_density: 0.01
accelerometer_random_walk: 0.0002
gyroscope_noise_density: 0.005
gyroscope_random_walk: 4.0e-06
update_rate: 500 # your IMU rate; check with `ros2 topic hz`
april_6x6.yaml for a 6×6 AprilGrid:
target_type: 'aprilgrid'
tagCols: 6
tagRows: 6
tagSize: 0.088 # edge to edge, metres
tagSpacing: 0.3 # gap / tagSize
codeOffset: 0
3. Build Kalibr in Docker
git clone https://github.com/ethz-asl/kalibr.git
cd kalibr
docker build -t kalibr_noetic -f Dockerfile_ros1_20_04 . # 5–10 minutes
Put imu.yaml, april_6x6.yaml and the ROS1 bag in one folder and start the container from there:
xhost +local:root
docker run -it --rm \
-e "DISPLAY" -e "QT_X11_NO_MITSHM=1" \
-v "/tmp/.X11-unix:/tmp/.X11-unix:rw" \
-v "$(pwd):/data" \
kalibr_noetic /bin/bash
4. Camera intrinsics
rosrun kalibr kalibr_calibrate_cameras \
--bag /data/<name>.bag \
--target /data/april_6x6.yaml \
--models pinhole-radtan \
--topics /camera/image_raw \
--verbose
This writes <name>-camchain.yaml, <name>-results-cam.txt and <name>-report-cam.pdf.
5. Camera–IMU extrinsics
source /catkin_ws/devel/setup.bash
rosrun kalibr kalibr_calibrate_imu_camera \
--bag /data/<name>.bag \
--cam /data/<name>-camchain.yaml \
--imu /data/imu.yaml \
--target /data/april_6x6.yaml \
--verbose
Excite every axis while recording: tilt, roll, yaw and translate the robot in front of the board, including forward and back. Too little translation is the most common reason the extrinsics come out wrong.
6. Is the result any good?
Five things to check:
Camera–IMU translation
- Where:
-results-imucam.txt, last column ofT_ic - Good: Agrees with a tape-measure estimate to within a few centimetres.
- If not: Far too small or far too large means translation was not observable. Move the robot more along each axis.
Time shift
- Where:
-results-imucam.txt,timeshift cam0 to imu0 - Good: A few milliseconds; USB cameras are typically 5–15 ms.
- If not: Exactly 0 means it was not estimated; above 0.1 s suggests a sync problem or an overloaded CPU during recording.
Reprojection error
- Where:
-report-imucam.pdf, page 1 - Good: Under 1 px is fine, under 0.5 px is very good.
- If not: Above 1.5 px: refocus, shorten exposure to reduce blur, make sure the board is flat.
Accelerometer bias
- Where:
-report-imucam.pdf, bias plots - Good: Flat lines that stay inside the sigma bounds.
- If not: Drifting or jumping bias: increase
accelerometer_random_walk(e.g. 0.0002 → 0.001).
Gravity magnitude
- Where:
-results-imucam.txt,Gravity vector in target coords - Good: ≈ 9.81 m/s².
- If not: Around 9.5 or 10.1 points to accelerometer scale errors; do an IMU intrinsic calibration first.
7. Using the result in OpenVINS
- Take
T_cam_imufrom<name>-camchain-imucam.yaml. Check whether your config expects camera-to-IMU or IMU-to-camera and invert if needed. - OpenVINS defines the time offset as
t_imu − t_cam. Check the sign convention against Kalibr's before copying the value.
The full VIO setup is in Odometry on a Quadruped.