ROS 2 Learning Path  ยท  Illustrated Textbook

Chapter 9: Odometry and Sensor Fusion

When a single sensor isn't enough
Prerequisite: Chapters 5 and 8
Running project: ARCHO robot
Tool: robot_localization (EKF)
Reading time: 90-110 minutes
What we'll cover in this chapter 9.1A simple question: how far has ARCHO actually moved? 9.2Wheel Odometry and the Drift problem 9.3IMU: the second sensor that helps 9.4Sensor Fusion with robot_localization 9.5Covariance: the language of uncertainty 9.6Why raw LiDAR doesn't go into the EKF 9.7Inspecting the fusion result 9.8Summary, glossary, and exercises

9.1A simple question: how far has ARCHO actually moved?

In Chapter 8, diff_drive_controller estimated ARCHO's path from the rotation of the two wheel encoders and published it on /odom. That looked great โ€” until one day you notice that after half an hour of driving around the warehouse, the robot's reported position is several meters away from its actual position. This phenomenon is called Drift, and it's exactly the problem this chapter solves.

๐Ÿง  Why a single sensor isn't enough

Wheel Odometry is like counting your steps with your eyes closed โ€” accurate and fast in the short term, but every small wheel slip on the ground turns into accumulated error. An IMU is like your inner-ear sense of balance โ€” it can tell how much you've turned, but not how far you've moved forward. Neither is complete on its own; but when combined, they compensate for each other's weaknesses. This combination is called Sensor Fusion.

9.2Wheel Odometry and the Drift problem

Let's look at exactly what gets published on /odom:

dev@archo:~$ ros2 topic echo /diff_drive_controller/odom --once pose: pose: position: {x: 1.842, y: 0.113, z: 0.0} ... covariance: [0.001, 0, 0, ...] twist: twist: linear: {x: 0.3, y: 0.0, z: 0.0} angular: {z: 0.02}

The Odometry message has three main parts:

PartMeaning
poseThe robot's current estimated position and orientation
twistInstantaneous linear and angular velocity
covarianceThe degree of uncertainty in this estimate (fully explained in the next section)
โš ๏ธ Where Drift comes from

Wheel Odometry assumes the wheels roll on the ground exactly as much as they rotate โ€” no more, no less. But in reality, the warehouse floor may be slippery, the wheels may spin in place a little, or the actual wheel radius may differ slightly from the value written in the previous chapter's controllers.yaml due to wear. Each of these small errors accumulates moment by moment โ€” exactly like rounding error in a long calculation.

9.3IMU: the second sensor that helps

The IMU (whose physical Frame we built in Chapter 4) measures linear acceleration and angular velocity at a high rate. Unlike Wheel Odometry, the IMU makes no assumptions about wheel slip โ€” it senses the body's actual motion directly. But the IMU has its own weakness too: high noise over the long term causes its own position estimate to drift as well, just of a different kind.

SensorStrengthWeakness
Wheel OdometryAccurate in the short term, stable rateWheel slip causes cumulative Drift
IMUSenses the body's actual rotation and acceleration directlyNoisy and prone to Drift over the long term, especially for position

9.4Sensor Fusion with robot_localization

To combine these two sources, we use a standard ROS 2 package called robot_localization, which implements an Extended Kalman Filter (EKF).

flowchart LR A["Wheel Odometry
/diff_drive_controller/odom"] --> C["ekf_filter_node"] B["IMU
/imu/data"] --> C C --> D["/odometry/filtered"] C --> E["Transform: odom โ†’ base_link"] style C fill:#eef0ff,stroke:#3d4bf5,color:#211f1a,font-weight:bold style D fill:#eafaf3,stroke:#0e9e6e,color:#211f1a
# archo_bringup/config/ekf.yaml
ekf_filter_node:
  ros__parameters:
    frequency: 50.0
    sensor_timeout: 0.1
    two_d_mode: true
    publish_tf: true
    map_frame: map
    odom_frame: odom
    base_link_frame: base_link
    world_frame: odom

    odom0: /diff_drive_controller/odom
    odom0_config: [false, false, false,
                   false, false, false,
                   true,  true,  false,
                   false, false, true,
                   false, false, false]

    imu0: /imu/data
    imu0_config: [false, false, false,
                  false, false, true,
                  false, false, false,
                  false, false, true,
                  true,  false, false]
    imu0_remove_gravitational_acceleration: true
๐Ÿ“– How to read these true/false matrices

Each _config field has fifteen true/false values, corresponding in order to X, Y, Z, Roll, Pitch, Yaw, Vx, Vy, Vz, Vroll, Vpitch, Vyaw, Ax, Ay, Az. true means "use this value from this sensor." For odom0, for example, only Vx, Vy, and Vyaw (linear and angular velocity) are enabled โ€” because we trust the wheel speeds, not their absolute position. For imu0, Yaw, Vyaw, and longitudinal acceleration (Ax) are enabled โ€” because the IMU is more accurate at sensing rotation than linear motion.

๐Ÿ”ง Why imu0_remove_gravitational_acceleration matters

The IMU's accelerometer always measures Earth's gravity too, even when the robot is completely stationary. If this component isn't removed, the filter thinks the robot is constantly accelerating โ€” one of the most common causes of "a stationary robot slowly drifting in RViz."

9.5Covariance: the language of uncertainty

Covariance is a number that says how trustworthy a measurement is. A very small number means "I'm nearly certain"; a large number means "treat this measurement with caution." The EKF uses these numbers to decide which sensor to trust more, and at which moment.

๐ŸŒ A concrete example

Suppose ARCHO is moving across a very smooth surface โ€” Wheel Odometry is nearly error-free, so it has low Covariance and the EKF trusts it more. But when it passes over a small bump, wheel slip increases; here the IMU (which doesn't sense this kind of disturbance) is relatively weighted more heavily. All this balancing happens automatically and moment-to-moment, performed by the EKF.

9.6Why raw LiDAR doesn't go into the EKF

A reasonable question: why don't we feed /scan data directly into the EKF too? Because raw LiDAR is a LaserScan โ€” a list of distances in various directions โ€” not a Pose or Odometry. The EKF needs data with motion structure (position, velocity, acceleration). If we want to use LiDAR to correct position, we first have to convert it into a Pose via SLAM or map matching (which we'll see in Chapters 10 and 11); it's that Pose, not the raw Scan, that enters the Fusion.

9.7Inspecting the fusion result

ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_tools view_frames
ros2 topic echo /odometry/filtered
โš ๏ธ Common error: Lookup would require extrapolation

As we saw in Chapter 5, this error usually means the Timestamps aren't synchronized, or use_sim_time isn't set consistently across all the involved Nodes (including ekf_filter_node). In simulation, never forget this setting.

โœ… Chapter 9 Checkpoint
  • The /odometry/filtered topic is published at a stable rate (e.g., 50 Hz).
  • The odom โ†’ base_link Transform is available with no extrapolation error.
  • When ARCHO stops, the filtered position also stays fixed (no drifting from IMU gravity).
  • The EKF output's Covariance is smaller than the Covariance of either sensor alone.

9.8Chapter 9 Summary

ARCHO no longer relies on a single sensor source for position estimation. Wheel Odometry and the IMU, each with their own strengths and weaknesses, are combined by robot_localization to produce a more stable estimate on /odometry/filtered โ€” a foundation that SLAM and Nav2 in later chapters simply cannot work correctly without.

๐ŸŒ Connection to the main project

ARCHO Project now has a complete ekf.yaml, and the odom โ†’ base_link chain, which we only defined in Chapter 5, is now actually populated with fused Wheel Odometry and IMU data.

What the next chapter adds

In Chapter 10, ARCHO enters an unknown environment for the first time with no pre-built map, and uses SLAM Toolbox to simultaneously explore the environment and build a map โ€” precisely where this chapter's accurate position estimate is an absolute prerequisite.

Chapter 9 Glossary

Odometry
Estimation of a robot's position and velocity based on internal motion (wheel rotation or accelerometer), without reference to the external environment.
Drift
Cumulative error in a position estimate that grows larger over time.
Sensor Fusion
Combining multiple sensor sources to achieve a more accurate and stable estimate than any single one alone.
EKF (Extended Kalman Filter)
A statistical algorithm for combining noisy measurements from multiple sensors, weighted according to each one's uncertainty.
Covariance
A numerical measure of a measurement's uncertainty; a smaller number means greater confidence.
robot_localization
The standard ROS 2 package for position Sensor Fusion using an EKF or UKF.

Chapter 9 common errors โ€” recap