EKF SLAM from Scratch

Built an EKF SLAM system in C++ and ROS 2 from scratch, including a custom 2D rigid-body geometry library, a physics-based simulator with Gaussian noise, wheel slip, and collision detection, and a LiDAR landmark detection pipeline using unsupervised clustering and circle regression. Implemented the full Extended Kalman Filter SLAM loop with both known and unknown data association.

ROS 2 C++ EKF SLAM Armadillo TurtleBot 3 LiDAR RViz2

System Architecture

turtlelib

A ROS-independent custom C++ library that serves as the mathematical foundation for the entire system. All geometry, kinematics, and twist math are implemented here, keeping calculations cleanly separated from ROS node mechanics.

  • SE(2) transforms — Rigid body transformations in 2D, including composition, inversion, and adjoint operations. Points and twists can be expressed in any frame.
  • Twist integration — Computes the exact Transform2D corresponding to a body following a constant twist for one time unit, handling both pure rotation and simultaneous translation and rotation.
  • Differential drive kinematics — A DiffDrive class implements forward kinematics (wheel positions → robot configuration) and inverse kinematics (desired body twist → wheel velocities), throwing an exception on impossible-to-follow twists.
  • Unit tested with Catch2 — Every function has at least one test case, including approximate floating-point comparisons and edge cases like pure rotation and simultaneous translation.

Simulator (nusim)

A full-featured 2D physics simulator that acts as a stand-in for the real robot, providing ground truth state while injecting realistic sensor imperfections.

  • Gaussian input noise — Zero-mean noise is added to non-zero commanded wheel velocities, modeling motor imprecision.
  • Wheel slip — Uniform random slip is applied independently to each wheel, causing the reported encoder positions to diverge from the true wheel motion.
  • Collision detection — The robot is represented as a circle. On intersection with a cylindrical obstacle, the robot is pushed to a tangent position while the wheels continue spinning simulating the real behavior of a robot bumping into an obstacle.
  • Simulated LiDAR — Ray-casting against obstacle cylinders and arena walls at 5 Hz, with configurable noise, range, and angular resolution matching the real TurtleBot 3 LiDAR sensor.
  • Fake sensor — Publishes noisy relative (x, y) measurements of landmarks with unique IDs, used for known-data-association testing during SLAM development.
Simulator environment with obstacles (red), walls, and simulated LiDAR scan.

LiDAR Landmark Detection

A three-stage pipeline converts raw 2D LiDAR scan data into detected landmark positions, enabling SLAM to run without pre-placed fiducials or sensor tags.

  • Clustering — Consecutive scan points within a distance threshold are grouped into clusters. The scan wraps around, so clusters spanning the 0°/360° boundary are handled explicitly. Clusters with fewer than 3 points are discarded.
  • Circle regression — The Hyper circle-fitting algorithm fits a circle to each cluster using algebraic least squares with SVD decomposition.
  • Radius filtering — Fitted circles outside the expected obstacle radius range (1–10 cm) are rejected, preventing walls and other large features from being classified as landmarks.
Detected landmarks (yellow) overlaid on the simulated environment.

EKF SLAM

The core SLAM algorithm maintains a joint state estimate of the robot pose and all landmark positions, fusing odometry predictions with landmark observations using an Extended Kalman Filter.

  • Predict step — On each odometry update, the robot's pose estimate is propagated forward using the DiffDrive forward kinematics. The covariance matrix grows, reflecting increased uncertainty between sensor updates.
  • Update step — Each detected landmark measurement triggers a linearized Kalman update. The Jacobian of the measurement model is computed analytically and used to correct both the robot pose and the landmark positions simultaneously.
  • Known data association — Validated first using the simulator's fake_sensor topic, which provides landmark IDs directly. Driving into obstacles to corrupt odometry confirmed the filter's ability to recover correct landmark positions even under large drift.
  • Unknown data association — Extended to real LiDAR data using Mahalanobis distance gating. New observations that fall outside the gate distance for all known landmarks are initialized as new landmarks in the map.
  • ROS REP 105 compliance — Publishes a map → odom transform such that the full map → odom → base_footprint chain reflects the SLAM-corrected robot pose.
End of closed-loop run: ground truth (red), odometry (blue), and SLAM estimate (green).

Results

After a closed-loop circuit with noise enabled and a limited landmark detection radius, the SLAM estimate substantially outperformed raw odometry. Final pose error after one loop:

  • Odometry — x: 0.159 m, y: 0.033 m, θ: –0.338 rad
  • SLAM estimate — x: 0.005 m, y: -0.002 m, θ: 0.111 rad

View Code on GitHub