Skip to main content
Open In Colab The IMU section ended with a TurtleBot whose IMU-only position estimate was meters off after ten seconds. A camera has the opposite profile. It sees fixed points in the world, so its errors do not accumulate, but it delivers a few frames a second, and a single camera cannot tell how far away a point is. Visual-inertial odometry (VIO) runs both: the IMU predicts the motion between frames, and each frame corrects the prediction and the IMU’s biases. Corke treats the two halves separately, inertial navigation in section 3.4 and visual odometry in section 14.8.3 (Corke, 2023). This section joins them in one filter. This section builds a VIO filter for the IMU inside the D435i. That IMU is a Bosch BMI055 fixed to the camera body (librealsense), so the rotation and offset between the IMU and the camera are known from the factory. The filter is an extended Kalman filter like the one in the EKF section, but its state contains a 3D orientation and the two sensor biases. In this section RWBR_{WB}, always written with its frame subscripts, is the rotation from the body frame {B}\{B\} to the world frame {W}\{W\}. RtR_t without subscripts is the measurement noise covariance, as in the rest of the chapter.

The D435i IMU

The constants below are the ones the course simulator gives the D435i’s IMU. The white noise comes from the BMI055 noise densities, 0.014 °/s/√Hz for the gyroscope and 150 µg/√Hz for the accelerometer, converted to one sample at 200 Hz. The real camera streams the gyroscope at 200 Hz and the accelerometer at 63 Hz by default (librealsense); the simulator runs both at 200 Hz, and so does this section. Each bias has two parts. A constant part is drawn when the sensor powers on, with a standard deviation of 5 mrad/s for the gyroscope and 0.05 m/s² for the accelerometer. A second part wanders slowly as a first-order Gauss-Markov process, which decays toward zero with a time constant of a few hundred seconds while noise keeps pushing it. These bias values were chosen to make the drift visible within a minute, not taken from a datasheet.

The scene

The TurtleBot drives the loop of the IMU section, a circle of radius 1.5 m at 0.2 m/s for 60 s, with the camera 20 cm above the floor. The room’s walls are 4 m from the center, and 240 points on them, up to 2.5 m high, form a map that the robot already has. Each camera frame sees some of these points, and the filter knows which map point each pixel belongs to. Building that map and finding those correspondences is the work of SLAM and of the visual SLAM section. This section isolates the fusion of the two sensors. The code below generates the true motion, the true biases and the IMU readings, and then integrates the readings with no correction, as in the IMU section.
The IMU-only estimate is 2.5 m off after 10 s and 116 m off after 60 s. Most of the error comes from the gyroscope bias, whose horizontal components tilt the estimated gravity vector.

The camera measurement

The camera is the D435i’s color camera, with the nominal intrinsics of the real camera from the course’s camera configuration: fx=fy=617f_x = f_y = 617 px and the principal point at (321, 240) in a 640 by 480 image. The camera’s optical frame has zz forward, xx right and yy down, while the robot’s body frame has xx forward, yy left and zz up. The fixed rotation RBCR_{BC} maps one to the other. A map point mm appears at the pixel pB=RWB⊤(m−pWB),(x,y,z)⊤=RBC⊤pB,h(RWB,pWB)=[cx+fxx/zcy+fyy/z].p_B = R_{WB}^\top (m - p_{WB}), \qquad (x, y, z)^\top = R_{BC}^\top p_B, \qquad h(R_{WB}, p_{WB}) = \begin{bmatrix} c_x + f_x x / z \\ c_y + f_y y / z \end{bmatrix}. The camera sees a point if it lies at least 0.3 m in front of it, within 6 m, and inside the image. The filter uses up to 15 visible points per frame, five frames a second, each with one pixel of noise.

An error-state Kalman filter

An EKF linearizes around its estimate and adds a correction vector to it. That works for position, velocity and biases, but a rotation matrix cannot be corrected by adding to it. The error-state filter separates the two jobs. It keeps a nominal estimate (RWB,p,v,bg,ba)(R_{WB}, p, v, b_g, b_a) that it propagates with the full nonlinear model, and a small error vector δx=(δθ,δp,δv,δbg,δba)∈R15\delta x = (\delta\theta, \delta p, \delta v, \delta b_g, \delta b_a) \in \mathbb R^{15} with covariance Σt\Sigma_t. The orientation error δθ\delta\theta is a rotation vector applied on the right, RWBtrue=RWB e[δθ]×R_{WB}^{\text{true}} = R_{WB} \, e^{[\delta\theta]_\times}, so it lives in the body frame where the gyroscope measures.

Prediction

For each IMU sample, the filter subtracts the estimated biases and integrates as in the IMU section: ω^=ω~−bg,f^=f~−ba,a=RWBf^+g,RWB←RWB e[ω^δt]×.\hat\omega = \tilde\omega - b_g, \quad \hat f = \tilde f - b_a, \quad a = R_{WB} \hat f + g, \quad R_{WB} \leftarrow R_{WB} \, e^{[\hat\omega \delta t]_\times}. The error grows according to the Jacobian FF of this step. Its nonzero blocks say that an orientation error rotates with the body, a gyroscope bias error turns into an orientation error, a velocity error turns into a position error, and an orientation error rotates the measured specific force and so becomes a velocity error: ∂δθ′∂δθ=e−[ω^δt]×,∂δθ′∂δbg=−Iδt,∂δp′∂δv=Iδt,∂δv′∂δθ=−RWB[f^]×δt,∂δv′∂δba=−RWBδt.\frac{\partial \delta\theta'}{\partial \delta\theta} = e^{-[\hat\omega\delta t]_\times}, \quad \frac{\partial \delta\theta'}{\partial \delta b_g} = -I\delta t, \quad \frac{\partial \delta p'}{\partial \delta v} = I\delta t, \quad \frac{\partial \delta v'}{\partial \delta\theta} = -R_{WB}[\hat f]_\times \delta t, \quad \frac{\partial \delta v'}{\partial \delta b_a} = -R_{WB}\delta t . The covariance prediction is Σˉt=FΣt−1F⊤+Qt\bar\Sigma_t = F \Sigma_{t-1} F^\top + Q_t, where QtQ_t holds the white noise of the two sensors and the random walk of the two biases. The filter models each bias as a random walk, while the simulated bias decays slowly toward its power-on value; over one minute the difference is negligible.

Correction

At each camera frame the filter stacks the pixel residuals z−hz - h of all the points it sees. Differentiating hh with RWBe[δθ]×≈RWB(I+[δθ]×)R_{WB} e^{[\delta\theta]_\times} \approx R_{WB}(I + [\delta\theta]_\times) gives the two nonzero blocks of the measurement Jacobian, ∂h∂δθ=JπRBC⊤[pB]×,∂h∂δp=−JπRBC⊤RWB⊤,\frac{\partial h}{\partial \delta\theta} = J_\pi R_{BC}^\top [p_B]_\times, \qquad \frac{\partial h}{\partial \delta p} = -J_\pi R_{BC}^\top R_{WB}^\top , where JπJ_\pi is the 2×32 \times 3 Jacobian of the pinhole projection. The biases and the velocity have no direct effect on a pixel. The filter learns them only through their correlation with the pose in Σˉt\bar\Sigma_t. The update is the usual Kalman update with Rt=σpx2IR_t = \sigma_{\text{px}}^2 I. The estimated error is then folded into the nominal state, the rotation by RWB←RWBe[δθ]×R_{WB} \leftarrow R_{WB} e^{[\delta\theta]_\times}, and reset to zero. The project function returns both the pixel and the Jacobian.

Running VIO

The filter starts from the true pose and velocity, and with both bias estimates at zero, with a covariance equal to the spread of the true biases.
Output from cell 8 Seen from above, the VIO path lies on the true circle, while the IMU-only estimate leaves it within a few seconds. Output from cell 9 The VIO error stays below about 2 cm for the whole minute and ends at 6 mm, against 116 m for the IMU alone. Its dashed 2σ2\sigma line sits just above the error, so the filter’s covariance describes its actual accuracy. Fifteen points are visible in almost every frame, which is far more than the six numbers of a pose need; with fewer points, or a frame where the camera faces a blank wall, the IMU carries the estimate alone until the next useful frame. Output from cell 10 The thick lines are the true biases and the thin ones the estimates. Both converge within about 10 s and then track the slow wander of the true biases. At the end the accelerometer bias is known to 0.0017 m/s², against a true bias of 0.031 m/s², and the gyroscope bias to 0.14 mrad/s, against 3.4 mrad/s. The camera never measures a bias. The filter infers them because a wrong bias makes the predicted pose drift in a particular direction, and the next frame shows that drift.

From this filter to VIO in practice

The filter here had a map. A real VIO system builds its landmarks as it goes, by tracking image features over several frames and triangulating them, which is the front end of the visual SLAM section. Systems such as MSCKF keep a short window of past camera poses in the filter state instead of the landmarks, and ORB-SLAM3 optimizes the IMU and camera terms together over a window of keyframes (Campos2020-xm). In the course simulator the same two inputs exist as ROS topics. The camera’s IMU publishes on /camera/imu with the noise and drifting bias described above, in the frame camera_imu_optical_frame, and the color images arrive on /camera/color/image_raw. The simulated camera is smaller than the real one, 320 by 240 pixels with a 60° horizontal field of view, so its focal length is about 277 px and its principal point (160, 120); a filter run in the maze needs those intrinsics. Key references: (Corke, 2023; Campos2020-xm)

References

  • Corke, P. (2023). Robotics, vision and control: Fundamental algorithms in python.

Found an error on this page? Report an issue.