Lab 10: Grid Localization using Bayes Filter

Implementing grid localization in simulation using a Bayes Filter.

Overview

The purpose of this lab is to implement grid localization using a Bayes filter in simulation. Pure odometry is insufficient for localization because wheel slip, IMU drift, and other noise sources cause the estimated pose to diverge from the true pose over time. The Bayes filter addresses this by maintaining a probability distribution over all possible robot poses, using a prediction step driven by odometry and an update step driven by sensor measurements to iteratively correct the belief.

The continuous pose space is discretized into a 12x9x18 grid covering [-1.6764, +1.9812) m in x, [-1.3716, +1.3716) m in y, and [-180, +180) degrees in theta, with each cell measuring 0.3048 m x 0.3048 m x 20 degrees. The most probable cell after each update step is taken as the robot's estimated pose. The default sample trajectory was used without modification.

Implementation

The Bayes filter implementation requires five functions: compute_control, odom_motion_model, prediction_step, sensor_model, and update_step, together implementing the full predict-update cycle.

compute_control

Extracts the rotation1, translation, and rotation2 control tuple from two consecutive odometry poses using math.hypot for translation and math.atan2 for heading, with angles normalized to [-180, 180) via loc.mapper.normalize_angle.

Odometry Control Computations
δrot1 = atan2(yt - yt-1, xt - xt-1) - θt-1
δtrans =((xt - xt-1)2 + (yt - yt-1)2)
δrot2 = θt - θt-1 - δrot1
Wiring Diagram
compute_control function extracting rotation1, translation, and rotation2 from consecutive odometry poses.

odom_motion_model

Computes the transition probability between a hypothetical previous and current pose. It calls compute_control to get the "required" control tuple, then compares each component against the actual odometry control tuple using a Gaussian. The three resulting probabilities (rotation1, translation, and rotation2) are multiplied together. Angles are explicitly normalized before evaluation to prevent wrap-around errors (e.g., incorrectly applying a massive penalty to the physical equivalence of 350° and -10°).

Transition Probability
p(xt | xt-1, ut) = prot1 · ptrans · prot2
Wiring Diagram
odom_motion_model function computing transition probability as a product of three Gaussians.

prediction_step

This step propagates the robot's belief state forward by iterating over all possible prior belief cells and applying the odometry motion model to distribute that probability into current cells. Because iterating over the full 3D grid against itself requires a massive number of loop executions, an aggressive optimization is implemented: any prior cell with a probability below 0.0001 is immediately skipped. For the remaining valid cells, loc.mapper.from_map is used to convert grid indices back into continuous physical poses before evaluating the motion model.

Prior Belief Prediction
Bel̅(xt) =p(xt | xt-1, ut) · Bel(xt-1)
Wiring Diagram
prediction_step function iterating over prior belief cells and propagating belief forward using the odometry motion model.

sensor_model

This function computes the likelihood of the 18 current ToF sensor readings given a specific hypothetical grid cell. Rather than using an explicit loop, it takes advantage of NumPy's optimized C-backend to perform a vectorized Gaussian evaluation. It compares the live sensor readings stored in loc.obs_range_data.flatten() against the precomputed ground truth distances for the given cell passed in as the obs array, using the predefined sensor standard deviation.

Sensor Model Likelihood
p(zt | xt) =i=017 p(zti | xt)
Wiring Diagram
sensor_model function computing per-measurement likelihoods using a vectorized Gaussian over the 18 ToF readings.

update_step

The update step incorporates the sensor data to refine the prior belief generated by the prediction step. For every cell in the grid, it fetches the ground truth sensor views using loc.mapper.get_views. It then calls sensor_model to get an array of 18 independent likelihoods, uses np.prod to combine them into a single probability scalar, and multiplies this by the cell's prior belief loc.bel_bar. Finally, to prevent arithmetic underflow and ensure a valid probability distribution, the entire 3D belief grid is divided by its sum to normalize the values back to 1.0.

Posterior Belief Update
Bel(xt) = η · p(zt | xt) · Bel̅(xt)
Wiring Diagram
update_step function multiplying prior belief by sensor likelihoods and normalizing the full belief grid.

Bayes Filter Results

The video below shows the Bayes filter running in the simulator across the full trajectory for Run 1. The green trajectory represents the ground truth path, the blue trajectory represents the odometry path, and the red trajectory represents the filter's most probable belief state at each timestep. As seen in the video, the belief closely tracks the ground truth throughout the run, demonstrating the filter's ability to correct for odometry drift using sensor updates.

Video showcasing the Bayes Filter.

The tables below show the most probable belief state after the update step at each timestep across three independent runs, compared against ground truth. Position values are in meters and angles are in degrees. While the ground truth state from the simulator accumulates angle without bounds, the belief state and the resulting angular error have been properly normalized to the [-180, 180) range to accurately reflect true heading deviation.

Run 1

Step Ground Truth State Belief State Belief Probability Error
x (m)y (m)theta (deg) x (m)y (m)theta (deg) x (m)y (m)theta (deg)
0 0.282-0.087320.3 0.3050.000-50.0 0.9999999 -0.023-0.08710.3
1 0.508-0.524657.4 0.305-0.610-70.0 1.0 0.2030.0867.4
2 0.508-0.524994.4 0.305-0.610-90.0 1.0 0.2030.0864.4
3 0.539-0.9231354.4 0.610-0.914-90.0 1.0 -0.071-0.0084.4
4 0.799-1.0621799.9 0.914-0.91410.0 1.0 -0.116-0.148-10.1
5 1.583-0.9052208.9 1.524-0.91450.0 1.0 0.0590.010-1.1
6 1.668-0.5212597.5 1.829-0.30590.0 1.0 -0.161-0.216-12.5
7 1.746-0.1692963.2 1.829-0.30590.0 1.0 -0.0830.136-6.8
8 1.7560.3223346.0 1.8290.00090.0 0.9999981 -0.0730.32216.0
9 1.7630.6523745.1 1.8290.914150.0 1.0 -0.066-0.262-4.9
10 1.3530.9384116.4 1.5240.914150.0 1.0 -0.1710.0246.4
11 0.4860.8624573.8 0.6100.914-110.0 1.0 -0.124-0.0533.8
12 0.3050.2374978.9 0.3050.305-70.0 1.0 -0.000-0.0678.9
13 0.052-0.0645270.1 0.000-0.305-130.0 1.0 0.0520.2400.1
14 -0.319-0.2145607.6 -0.305-0.305-150.01.0 -0.0140.091-2.4
15 -0.705-0.2075944.6 -0.610-0.305-170.00.9999999 -0.0960.098-5.4

Run 2

Step Ground Truth State Belief State Belief Probability Error
x (m)y (m)theta (deg) x (m)y (m)theta (deg) x (m)y (m)theta (deg)
0 0.282-0.086320.6 0.3050.000-50.0 0.9999999 -0.023-0.08610.6
1 0.510-0.521657.6 0.305-0.610-70.0 1.0 0.2060.0887.6
2 0.510-0.521994.7 0.305-0.610-90.0 1.0 0.2060.0884.7
3 0.543-0.9201354.7 0.610-0.914-90.0 1.0 -0.066-0.0064.7
4 0.804-1.0581800.2 0.914-0.91410.0 1.0 -0.111-0.144-9.8
5 1.581-0.8982209.2 1.524-0.91450.0 1.0 0.0570.016-0.8
6 1.669-0.5082597.3 1.829-0.30590.0 1.0 -0.160-0.203-12.7
7 1.747-0.1572962.9 1.829-0.30590.0 1.0 -0.0810.148-7.1
8 1.7590.3353345.5 1.8290.00090.0 0.9764887 -0.0690.33515.5
9 1.7690.6593744.7 1.8290.914150.0 1.0 -0.060-0.255-5.3
10 1.3610.9484115.9 1.5240.914150.0 1.0 -0.1630.0345.9
11 0.4630.8844571.7 0.6100.914-110.0 1.0 -0.146-0.0301.7
12 0.2630.2774977.5 0.3050.305-70.0 1.0 -0.042-0.0277.5
13 0.009-0.0245269.9 0.0000.000-130.0 1.0 0.009-0.024-0.1
14 -0.357-0.1665606.9 -0.305-0.305-150.01.0 -0.0520.138-3.1
15 -0.750-0.1555943.9 -0.610-0.305-170.00.9996811 -0.1410.150-6.1

Run 3

Step Ground Truth State Belief State Belief Probability Error
x (m)y (m)theta (deg) x (m)y (m)theta (deg) x (m)y (m)theta (deg)
0 0.282-0.087320.2 0.3050.000-50.0 1.0 -0.023-0.08710.2
1 0.508-0.524657.3 0.305-0.610-70.0 1.0 0.2030.0867.3
2 0.508-0.524994.7 0.305-0.610-90.0 1.0 0.2030.0874.7
3 0.541-0.9221354.8 0.610-0.914-90.0 1.0 -0.068-0.0084.8
4 0.802-1.0601799.8 0.914-0.91410.0 1.0 -0.112-0.146-10.2
5 1.574-0.9072209.1 1.524-0.91450.0 1.0 0.0500.008-0.9
6 1.660-0.5232597.3 1.829-0.30590.0 1.0 -0.168-0.218-12.7
7 1.740-0.1722962.9 1.829-0.30590.0 0.9999999 -0.0890.133-7.1
8 1.7510.3203346.5 1.8290.00090.0 0.9981756 -0.0890.34616.5
9 1.7610.6443745.9 1.8290.914150.0 1.0 -0.086-0.239-4.1
10 1.3530.9334116.0 1.5240.914150.0 1.0 -0.1710.0196.0
11 0.4710.8624572.2 0.6100.914-110.0 1.0 -0.139-0.0522.2
12 0.2720.2434977.3 0.3050.305-70.0 1.0 -0.033-0.0627.3
13 0.011-0.0515268.5 0.000-0.305-130.0 0.9999999 0.0110.253-1.5
14 -0.358-0.1855605.5 -0.305-0.305-150.01.0 -0.0540.119-4.5
15 -0.751-0.1645942.6 -0.610-0.305-170.00.8739048 -0.1420.141-7.4

Overall the filter performs well across all three runs using the default sample trajectory, with x and y position errors consistently staying within one grid cell width (0.3048 m). The angular errors after normalization are small throughout, generally staying within roughly 10-17 degrees, which is less than one grid cell in the theta dimension (20 degrees). Results are consistent across all three runs, with the filter converging to the same solution each time.

The update step is clearly the dominant source of accuracy. While the prediction step alone would produce a diffuse belief spread across multiple cells due to noisy odometry, the 18-reading sensor update provides enough information to sharply localize the robot, converging probabilities near 1.0. The filter struggles slightly before initial convergence, and at timestep 15 in run 3 the probability dips to 0.874, likely due to accumulated odometry drift and a noisier sensor sweep. Despite this, the positional estimate remains within one grid cell of ground truth.

Aidan McNay's website was used to inspire sections of this lab.