IMU Processing with a 1D Kalman Filter
Overview
Convert raw IMU acceleration data into a stable estimate of the car's distance to a wall. You will implement a 1D Kalman filter that fuses a kinematic model with sporadic range measurements.
Prerequisites
- Completed Camera Calibration and Lidar Projection
- IMU publishing
/imu/data_raw(MPU6050 or equivalent) - Range sensor providing wall distance (lidar, ultrasonic, or depth camera)
Learning Objectives
- Students can fuse and filter noisy sensor data to get a robust estimate of the car's state.
- Parse raw IMU acceleration and convert to linear velocity and position
- Model process and measurement noise for a 1D Kalman filter
- Fuse IMU integration with range updates to suppress drift
- Validate the filter against ground truth logs
1. Inspect Raw IMU Data
- Plot a short bag of
/imu/data_rawusingrqt_multiplotor Python:ros2 bag record /imu/data_raw -o imu_wall - Check axes alignment and bias. Expect ~9.81 m/s² on the gravity axis when stationary.
2. Bias Removal and Integration
Create a ROS 2 node imu_integrator.py:
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Imu
from geometry_msgs.msg import PointStamped
class IMUIntegrator(Node):
def __init__(self):
super().__init__('imu_integrator')
self.declare_parameter('acc_bias', 0.05)
self.declare_parameter('dt', 0.01)
self.bias = self.get_parameter('acc_bias').value
self.dt = self.get_parameter('dt').value
self.vel = 0.0
self.pos = 0.0
self.sub = self.create_subscription(Imu, '/imu/data_raw', self.callback, 10)
self.pub = self.create_publisher(PointStamped, '/wall_estimate/raw_integrated', 10)
def callback(self, msg: Imu):
ax = msg.linear_acceleration.x - self.bias
self.vel += ax * self.dt
self.pos += self.vel * self.dt
out = PointStamped()
out.header = msg.header
out.point.x = self.pos
self.pub.publish(out)
rclpy.init()
node = IMUIntegrator()
rclpy.spin(node)
Run the node and monitor drift over 30 seconds. Note bias sensitivity.
3. Design the Kalman Filter
Let the state be [position, velocity]ᵀ. Define:
- Process model xₖ = A xₖ₋₁ + B uₖ + wₖ, where A = [[1, dt], [0, 1]]
- Control input uₖ from acceleration, B = [[0.5 dt²], [dt]]
- Measurement zₖ from wall distance sensor observing position only
- Tune Q (process covariance) to reflect IMU noise; start with diag([1e-4, 1e-3])
- Set R (measurement covariance) using the sensor datasheet variance
4. Implement the Filter
Add to imu_kf_node.py:
import numpy as np
from rclpy.qos import QoSProfile
class IMUKalman(Node):
def __init__(self):
super().__init__('imu_kalman')
dt = self.declare_parameter('dt', 0.01).value
self.A = np.array([[1.0, dt], [0.0, 1.0]])
self.B = np.array([[0.5 * dt ** 2], [dt]])
self.H = np.array([[1.0, 0.0]])
self.Q = np.diag([1e-4, 1e-3])
self.R = np.array([[0.02 ** 2]])
self.x = np.zeros((2, 1))
self.P = np.eye(2) * 0.1
qos = QoSProfile(depth=10)
self.sub_imu = self.create_subscription(Imu, '/imu/data_raw', self.imu_cb, qos)
self.sub_range = self.create_subscription(PointStamped, '/wall_range', self.range_cb, qos)
self.pub = self.create_publisher(PointStamped, '/wall_estimate/kalman', qos)
self.latest_acc = 0.0
def imu_cb(self, msg: Imu):
acc = msg.linear_acceleration.x
self.latest_acc = acc
u = np.array([[acc]])
self.x = self.A @ self.x + self.B @ u
self.P = self.A @ self.P @ self.A.T + self.Q
self.publish_estimate(msg.header)
def range_cb(self, msg: PointStamped):
z = np.array([[msg.point.x]])
S = self.H @ self.P @ self.H.T + self.R
K = self.P @ self.H.T @ np.linalg.inv(S)
y = z - (self.H @ self.x)
self.x = self.x + K @ y
self.P = (np.eye(2) - K @ self.H) @ self.P
def publish_estimate(self, header):
out = PointStamped()
out.header = header
out.point.x = float(self.x[0, 0])
self.pub.publish(out)
Run colcon build, source the workspace, and launch both nodes.
5. Validate
- Collect a rosbag while moving toward and away from a wall.
- Plot
/wall_estimate/raw_integratedvs/wall_estimate/kalman. - Compute RMS error relative to
/wall_rangeusing a notebook orrqt_plot. - Adjust
QandRuntil the filter is responsive but smooth.
6. Troubleshooting
- Sudden spikes usually mean outlier range readings; add a median filter.
- If the estimate lags, decrease
Ror increaseQto trust IMU more. - Bias drift requires recalibration; log static periods and recompute bias.
Wrap-Up
Commit the Kalman filter node, add launch file entries, and document tuning values. These estimates feed into the upcoming obstacle avoidance controller.