Lecture 6 — State Estimation and Fusion (I)

One estimate from noisy sensors: from the complementary filter to Kalman by hand


Topics: Course feedback · Lab 2 · Complementary filter · Bayes filter · Kalman filter

Halfway Through the Course

The course as one line. Seven lectures so far, with today: The Course and ROS 2, Gazebo and ROS 2, Open-Source Robots, Inside the Drive, Building the Robot, Sensors and Noise, and today, in green, State Estimation. About seven more, hollow, with their topics in rough order: SLAM and localisation, navigation with Nav2, computer vision, path planning, control and drones, autonomy end to end. Under the line: Lab 1, a robot from Gazebo Fuel, and Lab 2, your own robot, as solid bars; under the second half a dashed bar: most likely two more labs, on these topics
  • Lab 1 — a ready-made robot from Gazebo Fuel, driven from ROS 2: commands in, odometry out, its path plotted
  • Lab 2 — your own robot in URDF with a lidar and an IMU: noise from a real datasheet, checked against ground truth
  • Ahead — about 7 more lectures on these topics, in rough order; most likely two more labs on them
KAU · Autonomous Robotic Systems Engineering · Lecture 6

Grading: a Reminder

One bar of 100 points split by weight: Labs 50 in blue, Talk 20 in orange, Exam 20 in purple, Open source 10 in green
  • Labs — the README is what you hand in: how to run it, what you see, your numbers
  • Talk — 15–20 min on a topic of your choice, agreed with me
  • Exam — a 15–20 min interview: your results, your decisions and where they stop working
  • Open source — a repository and its issues, reviewed: where you could contribute
KAU · Autonomous Robotic Systems Engineering · Lecture 6

Talk Ideas: Topics We Only Touched

The six lectures behind us left to right; under each, two orange cards with talk topics. The Course and ROS 2: QoS, what a topic does not promise; Humble to Jazzy to Kilted, what changed. Gazebo and ROS 2: physics engines DART, Bullet, TPE; Gazebo, MuJoCo, Isaac Sim compared. Open-Source Robots: beyond DiffDrive, Ackermann, mecanum, tracks; a real autopilot in Gazebo, PX4, ArduPilot. Inside the Drive: ros2_control, the same drive on real hardware; calibrating odometry, UMBmark. Building the Robot: TF in time, the buffer, extrapolation errors; evo, ATE and RPE without your own script. Sensors and Noise: IMU noise, random walk, Allan variance; syncing sensors, timestamps, message_filters

15–20 min, your topic, agreed with me. What stayed unclear in your notes.txt can be a topic too.

📖 roscon.ros.org · 📖 docs.ros.org/en/rolling/Releases.html

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Open Source: Repositories We Used

The open-source part left to right: a repository from our lectures, its issues looked through, one or several taken apart, where exactly you could contribute — green and graded: your review of the repository and its issues, an overview is fine; then a pull request, grey and dashed: not required
Lecture 🎯 Public repositories we used
The Course and ROS 2 github.com/ros2/common_interfaces · github.com/ROBOTIS-GIT/turtlebot3
Gazebo and ROS 2 github.com/gazebosim/gz-physics · github.com/ros2/rviz
Open-Source Robots github.com/gazebosim/ros_gz · github.com/ros2/teleop_twist_keyboard
Inside the Drive github.com/gazebosim/gz-sim · github.com/clearpathrobotics/clearpath_common
Building the Robot github.com/ros/robot_state_publisher · github.com/MichaelGrupp/evo
Sensors and Noise github.com/SteveMacenski/slam_toolbox · github.com/MOGI-ROS/Week-5-6-Gazebo-sensors
State Estimation github.com/cra-ros-pkg/robot_localization · github.com/CCNYRoboticsLab/imu_tools
KAU · Autonomous Robotic Systems Engineering · Lecture 6

Today: the Complementary Filter, Bayes, Kalman, the EKF

1 · The Complementary FilterTwo poor sensors, one good angle: mostly the gyroscope, a little of the accelerometer
2 · Noise and the AverageA guess and its doubt: averaging shrinks the doubt, but lags when the robot moves
3 · The Bayes Filter: Predict and UpdateHow a scan changes what the robot believes: motion blurs it, a scan sharpens it
4 · The Kalman Filter in 1DThe robot drives to the wall: predict with the wheels, update with the lidar, the best weight at every step
5 · The State and Its CovarianceA robot on the floor: position and heading, and errors that depend on each other
6 · When the Robot Turns: the EKFA straight-line model loses a turning robot; the extended Kalman filter follows the curve
7 · Fusing Wheels and GyroscopeOur robot in ROS 2: the wheels slip in turns, the gyroscope drifts, one filter fuses both
KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Gyroscope Drifts, the Accelerometer Shakes

Run I in Gazebo and RViz: the four-wheel robot circles on level ground, then drives onto the cross slope and stops on it; RViz's IMU plugin draws the accelerometer's vector over the robot model held level, leaning in the turn though the robot is level, and on the slope; dark boxes in the corners give the true roll and the accelerometer's; below, roll over time with a cursor: the truth black, the accelerometer red, the gyroscope blue
  • The gyroscope is smooth, but its small error adds up: it drifts
  • The accelerometer does not drift, but it shakes, and in a turn it shows a tilt that is not there
  • Together: each is good where the other is poor — so we fuse them

Legend
tilt — how far the robot leans: the angle between its up axis and the vertical
roll — the robot's tilt to the side, °
black line — the true roll, from Gazebo
blue line — the roll from the gyroscope
red line — the roll from the accelerometer
grey vertical line — the moment the pictures show
red arrow in RViz — what the accelerometer feels; RViz holds the robot model level
dark boxes — the roll at this moment: the truth in Gazebo, the accelerometer's in RViz

📖 porabook.com/book/

KAU · Autonomous Robotic Systems Engineering · Lecture 6

How Each Sensor Measures the Roll

Two drawings. Top, the gyroscope: the roll over time as a blue staircase of small steps ω Δt that follows the dashed truth up onto the slope and then creeps above it; a brace at the end marks b t, the bias added up. Bottom, the accelerometer: our robot from behind on the cross slope; gravity's push a points straight up and splits along the robot's own axes into a_z and a_y; the angle φ between a and the robot's z; dashed, in a turn, the push toward the centre leans the vector further

Each sensor's own roll — the ingredients of the filter:

Gyroscope — its rate, added up step by step:

ϕgyro=Σ ω Δt\textcolor{#1f77b4}{\phi_{gyro} = \Sigma\,\omega\,\Delta t}

smooth, but it drifts: its bias bb adds up too, b tb\,t after a time tt

Accelerometer — the tilt of gravity's push:

ϕacc=atan2(ay,az)\textcolor{#d62728}{\phi_{acc} = \mathrm{atan2}(a_y, a_z)}

no drift, but it shakes, and in a turn it "tilts" sideways without tilting

Legend
ϕgyro\textcolor{#1f77b4}{\phi_{gyro}}, ϕacc\textcolor{#d62728}{\phi_{acc}} — the roll from the gyroscope and from the accelerometer, rad
ω\omega — the turn rate from the gyroscope, rad/s
Δt\Delta t — one IMU step: 0.01 s at 100 Hz
bb — the gyroscope's bias: the same small error in every reading
tt — the time since the start, s
ay,aza_y, a_z — the accelerometer along the robot's own axes: sideways and up, m/s²
atan2\mathrm{atan2} — the angle of a vector from its two parts, with the right sign
blue steps — the gyroscope's sum; dashed — the truth
red arrows — gravity's push aa and its two parts

▶️ s3.amazonaws.com/scolton-www/docs/filter.pdf

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The gyroscope drifts, the accelerometer shakes. How do we get one good angle from the two?

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Answer: the Complementary Filter — 98 % Gyroscope, 2 % Accelerometer

Step 1 — predict with the gyroscope: ϕ−=ϕ^k−1+ω Δt\textcolor{#1f77b4}{\phi^- = \hat\phi_{k-1} + \omega\,\Delta t}
Step 2 — pull 2 % toward the accelerometer:

ϕ^k=α ϕ−+(1−α) ϕacc,α=0.98\textcolor{#2ca02c}{\hat\phi_k} = \alpha\,\textcolor{#1f77b4}{\phi^-} + (1-\alpha)\,\textcolor{#d62728}{\phi_{acc}}, \qquad \alpha = 0.98

The gyroscope's drift is pulled back a little every step; the accelerometer's shaking barely gets in.

One step of the filter, a schematic not to scale. On the roll axis: the old estimate, a hollow green dot; step 1, a blue arrow — the gyroscope's turn ω Δt — to the prediction φ⁻, a blue dot; far to the right the accelerometer's roll φ_acc, a red dot, and a brace under the axis marks the gap between them; step 2: the new estimate, a green dot, only 2 % of the gap from the prediction

Legend
ϕ−\textcolor{#1f77b4}{\phi^-} — the prediction: the old estimate turned by the gyroscope; the minus — before the measurement corrects it, the usual mark of a prediction
ϕ^k−1\hat\phi_{k-1} — the old estimate; the hat — an estimate, not the truth; kk — the step number
ω Δt\omega\,\Delta t — the gyroscope's turn in one step: rate ω\omega × one IMU step Δt\Delta t, 0.01 s
ϕacc\textcolor{#d62728}{\phi_{acc}} — the accelerometer's roll: the angle of gravity's push from its two parts, atan2(ay,az)\mathrm{atan2}(a_y, a_z)
ay,aza_y, a_z — the accelerometer along the robot's own axes: sideways and up, m/s²
ϕ^k\textcolor{#2ca02c}{\hat\phi_k} — the new estimate
the gap ϕacc−ϕ−\textcolor{#d62728}{\phi_{acc}} - \textcolor{#1f77b4}{\phi^-} — how far the accelerometer is from the prediction
α\alpha — the weight of the prediction, between 0 and 1; 1−α1-\alpha — the accelerometer's

▶️ youtu.be/whSw42XddsU
📖 ahrs.readthedocs.io/en/latest/filters/complementary.html

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Gap in Three Cases: Agree, Drift, Shake

The same formula, written with the gap between the two angles:

ϕ^k=ϕ−+0.02 (ϕacc−ϕ−)\textcolor{#2ca02c}{\hat\phi_k} = \textcolor{#1f77b4}{\phi^-} + 0.02\,(\textcolor{#d62728}{\phi_{acc}} - \textcolor{#1f77b4}{\phi^-})

The robot stands still on the slope; the true roll is 10° (example):

case ϕ−\textcolor{#1f77b4}{\phi^-} ϕacc\textcolor{#d62728}{\phi_{acc}} the gap ϕ^k\textcolor{#2ca02c}{\hat\phi_k}
agree 10° 10° 0 10°
drift 1° 11° 10° −1° 10.98°
shake 5° 10° 15° +5° 10.1°
Three rows on one roll axis, the truth dotted at 10 degrees. Agree: the prediction, the accelerometer and the new estimate all at 10. Drift 1: the prediction at 11, the accelerometer at 10, the new estimate at 10.98, almost on the prediction. Shake 5: the prediction at 10, the accelerometer far off at 15, the new estimate at 10.10
  • Agree — no gap, nothing changes: the weights add up to 1
  • Drift — the gyroscope is 1° off; one step takes 2 % of it away: 0.02°
  • Shake — the accelerometer jumps 5°; only 2 % of it gets in: 0.1°

Legend
blue ring — ϕ−\textcolor{#1f77b4}{\phi^-}, the gyroscope's prediction: the old estimate plus one gyroscope step, not its whole sum
red dot — ϕacc\textcolor{#d62728}{\phi_{acc}}, the accelerometer's roll: the angle of gravity's push from its two parts, atan2(ay,az)\mathrm{atan2}(a_y, a_z)
ay,aza_y, a_z — the accelerometer along the robot's own axes: sideways and up, m/s²
green dot — ϕ^k\textcolor{#2ca02c}{\hat\phi_k}, the new estimate; kk — the step number
dotted line — the truth, 10° in all three cases
the gap ϕacc−ϕ−\textcolor{#d62728}{\phi_{acc}} - \textcolor{#1f77b4}{\phi^-} — how far the accelerometer is from the prediction
0.02 — the accelerometer's share, 1−α1-\alpha, with α=0.98\alpha = 0.98

▶️ s3.amazonaws.com/scolton-www/docs/filter.pdf

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Time Constant: How Long an Old Error Lasts

The roll error of the estimate, Δφ_n = 0.98 to the power n times 1°, against the time n·Δt: 1° at the start, after the gyroscope drifted; after 10 steps, 0.1 s, 0.82°; after 50 steps, 0.5 s = τ, 0.36°, a third; after 100 steps, 1 s, 0.13°

Each step, the filter:

ϕ^k=α ϕ−+(1−α) ϕacc\textcolor{#2ca02c}{\hat\phi_k} = \alpha\,\textcolor{#1f77b4}{\phi^-} + (1-\alpha)\,\textcolor{#d62728}{\phi_{acc}}

An old error after nn steps:

Δϕn=αn Δϕ0\textcolor{#2ca02c}{\Delta\phi_n} = \alpha^n\,\Delta\phi_0

The time constant:

τ≈Δt1−α=0.01 s0.02=0.5 s\tau \approx \frac{\Delta t}{1-\alpha} = \frac{0.01\ \text{s}}{0.02} = 0.5\ \text{s}

How long the filter remembers an old error: after τ\tau a third of it is left.

  • Only the prediction ϕ−\textcolor{#1f77b4}{\phi^-} carries the error, weight α\alpha: 1° → 0.98° → 0.96° … (example)
  • A smaller share of the accelerometer, a longer memory: 1−α1-\alpha = 0.01 gives about 1 s
  • Changes slower than τ\tau come from the accelerometer, faster ones from the gyroscope

Legend
green curve — Δϕn\textcolor{#2ca02c}{\Delta\phi_n} over time; dots — the start and after 10, 50, 100 steps
dotted line — the moment t=τt = \tau
Δϕn\textcolor{#2ca02c}{\Delta\phi_n} — the roll error after nn steps: the estimate minus the true roll, °; Δϕ0\Delta\phi_0 — at the start, 1° here
ϕ^k\textcolor{#2ca02c}{\hat\phi_k} — the new estimate; kk — the step number
ϕ−\textcolor{#1f77b4}{\phi^-} — the gyroscope's prediction
ϕacc\textcolor{#d62728}{\phi_{acc}} — the accelerometer's roll
α\alpha — the prediction's weight, 0.98 here; 1−α1-\alpha — the accelerometer's share
nn — the number of steps
τ\tau — the time constant, s
Δt\Delta t — one IMU step: 0.01 s at 100 Hz

▶️ s3.amazonaws.com/scolton-www/docs/filter.pdf

KAU · Autonomous Robotic Systems Engineering · Lecture 6

A Quaternion: One Rotation in Four Numbers

Our robot seen from behind on the cross slope. Its forward axis x points into the screen, red, and is the axis of the turn: the robot has turned about it by the roll φ, so its own z axis, blue, leans by φ from the dashed vertical and its y axis, green, runs along the slope. Beside it the four numbers of this turn, axis (1, 0, 0), angle φ: x = 1 times sin of half φ, y = 0 times sin of half φ = 0, z = 0 times sin of half φ = 0, w = cos of half φ
  • Any orientation is one turn about one axis, set by the turn itself: the line it leaves in place — x\textcolor{#d62728}{x} for a roll, z\textcolor{#1f77b4}{z} for a yaw
  • A quaternion keeps that axis and angle in four numbers, as in Imu, /odom, tf2_echo:

(x, y, z)=axis⋅sin⁡angle2w=cos⁡angle2(\textcolor{#d62728}{x},\ \textcolor{#2ca02c}{y},\ \textcolor{#1f77b4}{z}) = \text{axis} \cdot \sin\tfrac{\text{angle}}{2} \qquad w = \cos\tfrac{\text{angle}}{2}

One rotation in four numbers, and two turns combine by multiplying.

  • Why not roll, pitch, yaw: order matters, they jump at ±180°, and at a pitch of 90° roll and yaw merge
  • On our slope the robot rolls about x\textcolor{#d62728}{x}, forward: axis (1, 0, 0)(\textcolor{#d62728}{1},\ \textcolor{#2ca02c}{0},\ \textcolor{#1f77b4}{0}), angle ϕ\phi = 9.7°, so y=z=0\textcolor{#2ca02c}{y} = \textcolor{#1f77b4}{z} = 0:
    x\textcolor{#d62728}{x} = sin 4.85° = 0.085, ww = cos 4.85° = 0.996
  • No rotation: (0,0,0,1)(0, 0, 0, 1)

Legend
axis — the line the turn leaves in place, as three numbers along x,y,zx, y, z; for the roll (1,0,0)(1, 0, 0) — xx, forward
angle — how far it turns; here the roll ϕ\phi = 9.7°, from the dashed vertical to the robot's zz
x,y,z,wx, y, z, w — the four numbers of a quaternion, as ROS names them; x,y,zx, y, z go with the axes x,y,zx, y, z
sin⁡\sin, cos⁡\cos of half the angle — a quaternion keeps half of it: 4.85°
red ⊗, green, blue — the robot's axes xx (into the screen), yy, zz, as RViz colours them, and their numbers in the quaternion

📖 docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Quaternion-Fundamentals.html

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Formula ↔ imu_tools Code

ϕ^k=α ϕ−+(1−α) ϕacc\textcolor{#2ca02c}{\hat\phi_k} = \alpha\,\textcolor{#1f77b4}{\phi^-} + (1-\alpha)\,\textcolor{#d62728}{\phi_{acc}}

  • ϕ^k\hat\phi_k — the new estimate, kk — the step · ϕ−\phi^- — the gyroscope's prediction · ϕacc\phi_{acc} — the accelerometer's roll · α\alpha — the prediction's weight
  • getPrediction — step 1, turn by the gyroscope
  • getAccCorrection — how far to the accelerometer's "down"; scaleQuaternion — keeps only the share gain of that turn; gain — 1−α1-\alpha (default 0.01, so α=0.99\alpha = 0.99)
  • quaternionMultiplication — the new estimate: the prediction turned by that share
  • w…, a… — the gyroscope's and the accelerometer's readings, dt — one IMU step Δt\Delta t; q… — the orientation as a quaternion (four numbers of one rotation): the mix is a turn, the same idea in 3D; qI — no turn, (0,0,0,1)(0, 0, 0, 1)
C++ · imu_complementary_filter/src/complementary_filter.cpp — the filter inside imu_tools' complementary_filter_node
// Prediction.
getPrediction(wx, wy, wz, dt, q0_pred, q1_pred, q2_pred, q3_pred);

// Correction (from acc):
// q_ = q_pred * [(1-gain) * qI + gain * dq_acc]
getAccCorrection(ax, ay, az, q0_pred, q1_pred, q2_pred, q3_pred,
                 dq0_acc, dq1_acc, dq2_acc, dq3_acc);
gain = gain_acc_;
scaleQuaternion(gain, dq0_acc, dq1_acc, dq2_acc, dq3_acc);
quaternionMultiplication(q0_pred, q1_pred, q2_pred, q3_pred, dq0_acc,
                         dq1_acc, dq2_acc, dq3_acc, q0_, q1_, q2_, q3_);

Parameters: gain_acc, do_adaptive_gain: false, do_bias_estimation: false, use_mag: false

🎯 github.com/CCNYRoboticsLab/imu_tools

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Picking the Time Constant on Our Robot: Three Runs

The complementary filter node on run I at gain_acc 0.001, 0.01 and 0.1 against the true roll: RMS error 4.80, 0.66 and 1.94 degrees

ϕ^k=α ϕ−+(1−α) ϕaccgain_acc=1−ατ≈Δtgain_acc\textcolor{#2ca02c}{\hat\phi_k} = \alpha\,\textcolor{#1f77b4}{\phi^-} + (1-\alpha)\,\textcolor{#d62728}{\phi_{acc}} \qquad \texttt{gain\_acc} = 1-\alpha \qquad \tau \approx \frac{\Delta t}{\texttt{gain\_acc}}

  • complementary_filter_node on our robot, three runs: gain_acc = 0.001, 0.01, 0.1, so τ\tau ≈ 10 s, 1 s, 0.09 s; gain_acc is ours, set once in the node's parameters
  • τ\tau ≈ 10 s: smooth, but it keeps the drift · τ\tau ≈ 0.09 s: quick, but it believes the false roll in the turn · τ\tau ≈ 1 s, the default: the smallest error

Legend
green line — ϕ^k\textcolor{#2ca02c}{\hat\phi_k}, the node's estimate; kk — the step number
light red line — ϕacc\textcolor{#d62728}{\phi_{acc}}, the accelerometer's roll
black line — the truth
ϕ−\textcolor{#1f77b4}{\phi^-} — the gyroscope's prediction
α\alpha — the prediction's weight
gain_acc — imu_tools' name for the accelerometer's share, 1−α1-\alpha
τ\tau — the time constant, s; Δt\Delta t — one IMU step, 0.01 s
RMS error — the root mean square of the node's roll minus the true roll, over the run: its typical error

🎯 github.com/CCNYRoboticsLab/imu_tools

KAU · Autonomous Robotic Systems Engineering · Lecture 6

imu_tools on Our Robot: Roll Holds, Yaw Drifts

The run of the section's first slide again, Gazebo alone: the four-wheel robot circles on level ground, then drives onto the cross slope and stops on it; a dark box gives the true roll and the node's; below, roll over time with a cursor: the truth dashed, complementary_filter_node at gain_acc 0.01 in green on it, the accelerometer and the gyroscope faint behind
  • /imu/data_raw → complementary_filter_node → /imu/data: the same readings, plus an orientation
  • gain_acc = 0.01, the default: the roll is 0.6° off on average, 2° at worst — in the turn
  • standing on the slope it stays 0.6° off: each step the bias bb adds b Δtb\,\Delta t, the accelerometer takes 1 % back; they balance near b τb\,\tau ≈ 0.6°/s × 1 s
  • yaw still drifts: gravity says nothing about a turn around the vertical; later: the lidar fixes it
  • we picked the weight by trying. Is there a best weight — and what if the robot moves?

Legend
dashed line — the true roll, from Gazebo
green line — the node's roll
faint red — the accelerometer; faint blue — the gyroscope
grey vertical line — the moment Gazebo shows
bb — the gyroscope's bias: the same small error in every reading
Δt\Delta t — one IMU step, 0.01 s
τ\tau — the time constant: about 1 s at gain_acc 0.01
yaw — the turn around the vertical

🎯 github.com/CCNYRoboticsLab/imu_tools

KAU · Autonomous Robotic Systems Engineering · Lecture 6

A Belief Is a Guess and Its Doubt

Left: Gazebo, the robot stands before the wall; the red forward ray with its reading z changes every scan; a black line marks the true distance 2.000 m. Right: the readings pile up in 2 mm bins into a histogram, the latest bright red; the red Gaussian of one scan over it, centred on the mean, with ±σ_z; the mean d̂ printed in green in the corner, the truth dashed

N(d^, σd^2)\mathcal N(\hat d,\ \sigma_{\hat d}^2)

A belief is a best guess d^\hat d and its typical error σd^\sigma_{\hat d}: the Gaussian says how likely each distance is.

The red Gaussian is where one scan lands: after one scan d^=z\hat d = z, σd^=σz\sigma_{\hat d} = \sigma_z.

Legend
N(d^,σd^2)\mathcal N(\hat d, \sigma_{\hat d}^2) — a normal distribution, a Gaussian: centre d^\hat d, variance σd^2\sigma_{\hat d}^2
d^\hat d — the best guess of the distance, m; the hat — an estimate
σd^\sigma_{\hat d} — its typical error: the Gaussian's width; σ2\sigma^2 — a variance
z\textcolor{#d62728}{z} — one scan of the lidar's forward ray, m
σz\textcolor{#d62728}{\sigma_z} — how far one scan scatters: 10.1 mm, measured
red bars — the scans, 2 mm bins, the latest bright; red curve — where one scan lands
green number — d^\hat d, the mean so far; dashed — the truth
probability — the area under the curve; ±σ holds 68 %

📖 github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/03-Gaussians.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Averaging Shrinks the Error

Each of 4 readings is off by +1 or −1 cm, equally likely (example): σz\sigma_z = 1 cm

sum of errors ways error of the average squared
±4 1 + 1 ±1 1
±2 4 + 4 ±0.5 0.25
0 6 0 0

the mean square: 0.25 cm², so σzˉ\sigma_{\bar z} = 0.5 cm

σzˉ=σzn\sigma_{\bar z} = \frac{\sigma_z}{\sqrt n}

How many readings to average: 4 halve the error, 16 quarter it.

The error of the mean of n readings for n = 1, 4 and 16: each Gaussian half as wide as the one before

Legend
σz\sigma_z — the error of one reading (one scan)
nn — the number of readings averaged
zˉ\bar z — their average; a bar means an average
σzˉ\sigma_{\bar z} — the error of their average: the root of the mean square
16 = 2⁴ sign combinations of four readings; 1-4-6-4-1 — how many of them give each sum
red — one reading; green — the average of 4 and of 16: bars — 40,000 tries, curves — the Gaussian of the formula, arrows — ±σ

▶️ youtu.be/7mYDHbrLEQo?t=141

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Recursive Average Keeps No Old Scans

d^n=d^n−1+1n(zn−d^n−1)\textcolor{#2ca02c}{\hat d_n} = \textcolor{#2ca02c}{\hat d_{n-1}} + \frac{1}{n}\left(\textcolor{#d62728}{z_n} - \textcolor{#2ca02c}{\hat d_{n-1}}\right)

The recursive average: the same average, keeping no old scans.

Four scans (example):

nn zn\textcolor{#d62728}{z_n}, m the share 1n\frac{1}{n} d^n\textcolor{#2ca02c}{\hat d_n}, m
1 2.04 1 2.04
2 2.02 ½ 2.03
3 2.03 ⅓ 2.03
4 2.07 ¼ 2.04
Python · pandas — a table library: the mean of all scans so far
d_hat = z.expanding().mean()   # running average
The standing scans, their running average converging to the true 2.000 m and the band plus or minus sigma over root n, against the number of scans

Legend
nn — the number of the scan
zn\textcolor{#d62728}{z_n} — the nn-th scan of the lidar's forward ray, m
d^n\textcolor{#2ca02c}{\hat d_n} — the average of the first nn scans; d^n−1\hat d_{n-1} — before the new one
1n\frac{1}{n} — the share of the new scan
zn−d^n−1z_n - \hat d_{n-1} — how much the new scan disagrees
same as (z1+⋯+zn)/n(z_1 + \dots + z_n)/n: multiply both sides by nn
z — all scans in a pandas column (Series), one per row
expanding() — windows that grow by one scan: z1z_1; z1,z2z_1, z_2; z1,z2,z3z_1, z_2, z_3; …
.mean() — each window's average: d_hat = d^1,d^2,…\hat d_1, \hat d_2, \dots
red dots — the scans; green line — their average; black line — the truth, 2.000 m
light band — ±σ_z/√n: the average's expected error; σ_z — one scan's error, 10.1 mm

📖 pandas.pydata.org/docs/reference/api/pandas.Series.expanding.html

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Then average the last 20 scans while driving?

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Answer: No — a Moving Average Is Late

Left, Gazebo from the side with a dark «Gazebo» box: the robot drives to the wall, the red ray on it; below, a zoomed strip with three lanes — the truth d, the ray z and the mean of the last 20 scans, about 19 cm behind. Right, the whole drive in time — the truth, the ray, the mean of the last 20 — with a grey cursor at the same moment, bold dots on the truth and the mean and the lag beside them

lag=n−12 v Δt=9.5⋅0.2 m/s⋅0.1 s=19.0 cm\text{lag} = \frac{n-1}{2}\, v\, \Delta t = 9.5 \cdot 0.2\ \text{m/s} \cdot 0.1\ \text{s} = 19.0\ \text{cm}

How far the average of the last nn scans stays behind a moving robot; measured: 18.8 cm.

Python · pandas — a table library: the mean of the last 20 scans
d_avg = z.rolling(20).mean()

Legend
lag — how far the average is behind the truth, cm
nn — scans in the window, 20
vv — speed toward the wall, m/s
Δt\Delta t — time between scans, 0.1 s
dd — the true distance to the wall, zz — the lidar's forward ray, m
n−12\frac{n-1}{2} — the window's middle lags this many scans: the mean of 0, 1, …, n−1n-1
z — all scans in a pandas column (Series), one per row
rolling(20) — a window of the last 20 scans that slides by one: z1…z20z_1 \dots z_{20}; z2…z21z_2 \dots z_{21}; …
.mean() — each window's average: d_avg, the orange line
black — the truth · red — the ray · orange — the mean of the last 20 scans
grey vertical line — the moment the scene shows; dots — the truth and the mean at it

📖 pandas.pydata.org/docs/reference/api/pandas.Series.rolling.html

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Bayes' Rule: the Belief After a Scan

Four rows of bars over five cells, 1.0 to 1.4 m from the wall. Blue, before the scan, from the wheels: 1/4, 1/2, 1/4 on 1.0, 1.1, 1.2. Red, the scan z = 1.2 m: 1/4, 1/2, 1/4 on 1.1, 1.2, 1.3. Purple, multiplied: 1/8 on 1.1 and on 1.2, their sum p(z) = 1/4. Green, rescaled, after the scan: 1/2 on 1.1 and 1/2 on 1.2

Our robot before the wall, five cells of 10 cm (example):

  • The robot knows its belief before the scan — the wheel odometry says about 1.1 m; how its lidar errs — by a cell at most; and the scan — the forward ray reads 1.2 m
  • It does not know the true distance dd
  • It computes the belief after the scan, for every cell

p(d∣z)=p(z∣d) p(d)p(z)\textcolor{#2ca02c}{p(d \mid z)} = \frac{\textcolor{#d62728}{p(z \mid d)}\ \textcolor{#1f77b4}{p(d)}}{p(z)}

Bayes' rule: from "how likely this scan is at each distance" to "how likely each distance is, given this scan".

Legend
dd — a cell: a distance to the wall, m
zz — the scan: what the lidar's forward ray reads, m
p(⋅)p(\cdot) — a probability, 0 to 1; the bar ∣\mid reads «given»
p(d)\textcolor{#1f77b4}{p(d)} — the belief before the scan (prior): blue bars
p(z∣d)\textcolor{#d62728}{p(z \mid d)} — the likelihood: how likely the ray reads zz if the wall is at dd: red bars
purple bars — the products, cell by cell
p(z)p(z) — their sum; divided by it, the bars add up to 1
p(d∣z)\textcolor{#2ca02c}{p(d \mid z)} — the belief after the scan (posterior): green bars

▶️ www.3blue1brown.com/lessons/bayes-theorem

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Predict on Our Grid: Move the Belief and Blur It

Our drive between scans 48 and 49, bars over 1 cm cells. 1: green, the belief after the last scan, peaked at 1.23 m. 2: the same bars outlined, moved 1.8 cm toward the wall by the wheels' step. 3: blue, the belief before the next scan, moved and blurred, the moved bars outlined behind it. Below: twenty predicts with no scan, 2 s: the blue belief has moved 40 cm, and its spread has grown three times over the green one it started from

bel‾(d)=∑d′p(d∣u,d′) bel(d′)\textcolor{#1f77b4}{\overline{bel}(d)} = \sum_{d'} p(d \mid u, d')\ \textcolor{#2ca02c}{bel(d')}

Predict: each cell collects what the motion brings into it.

Legend
dd — a cell after the motion: a distance to the wall, m
d′d' — a cell before it; the sum runs over all of them
uu — the motion: the wheels' step v Δtv\,\Delta t, here 1.8 cm
bel(d′)\textcolor{#2ca02c}{bel(d')} — the belief after the last scan: green bars
p(d∣u,d′)p(d \mid u, d') — the chance that the step uu takes the robot from d′d' to dd: the wheels err
bel‾(d)\textcolor{#1f77b4}{\overline{bel}(d)} — the belief before the next scan: blue bars; the bar on top — before the scan
outlined bars — the green bars, moved by uu: each blue bar collects from those around it
the belief — one number per cell; the robot keeps nothing else

▶️ github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/02-Discrete-Bayes.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Update on Our Grid: Multiply by the Scan's Likelihood

Scan 49 of our drive over 1 cm cells, step by step. 1: blue, the belief before the scan, from predict, peaked at 1.21 m. 2: red, the scan's likelihood: a Gaussian around z = 1.204 m as wide as the lidar's error. 3: green, the belief after the scan, with the blue bars outlined behind it: higher at 1.21, lower at 1.22, a little closer to the scan; the truth dashed at 1.208 m

bel(d)=η p(z∣d) bel‾(d)\textcolor{#2ca02c}{bel(d)} = \eta\ \textcolor{#d62728}{p(z \mid d)}\ \textcolor{#1f77b4}{\overline{bel}(d)}

Update: Bayes' rule on every cell — multiply by how well the cell explains the scan, then rescale.

Legend
dd — a cell: a distance to the wall, m
zz — the scan: what the lidar's forward ray reads, m
bel‾(d)\textcolor{#1f77b4}{\overline{bel}(d)} — the belief before the scan, from predict: blue bars
p(z∣d)\textcolor{#d62728}{p(z \mid d)} — the likelihood: how likely the scan zz is if the wall is at dd; a Gaussian around zz, as wide as the lidar's error σz\sigma_z: red curve
bel(d)\textcolor{#2ca02c}{bel(d)} — the belief after the scan: green bars; outlined — the blue bars, to compare
η\eta — 1 / the sum of the products, so the bars add up to 1: the 1/p(z)1/p(z) of Bayes' rule
dashed line — the truth

▶️ github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/02-Discrete-Bayes.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Recipe on a Grid: Predict, Update, Repeat

Gazebo from the side, mirrored so the wall is on the left: the robot drives left to the wall; below it three lanes of bars over 1 cm cells around the truth, the distance growing to the right as on the widget — blue after predict, red the scan's p(z | d), green after update — with the truth as a black line; the first frame is scan 49

predict — move and blur: bel‾(d)=∑d′p(d∣u,d′) bel(d′)\textcolor{#1f77b4}{\overline{bel}(d)} = \sum_{d'} p(d \mid u, d')\,\textcolor{#2ca02c}{bel(d')} · scan — its likelihood p(z∣d)\textcolor{#d62728}{p(z \mid d)} · update — multiply, rescale: bel(d)=η p(z∣d) bel‾(d)\textcolor{#2ca02c}{bel(d)} = \eta\,\textcolor{#d62728}{p(z \mid d)}\,\textcolor{#1f77b4}{\overline{bel}(d)} · repeat

dd, d′d' — a cell now and before the motion · uu — the motion · zz — the scan · η\eta — 1 / the sum · σ\sigma in the rows — a typical error · tt — time from the start

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Formula ↔ filterpy.discrete_bayes

bel‾(d)=∑d′p(d∣u,d′) bel(d′)\textcolor{#1f77b4}{\overline{bel}(d)} = \sum_{d'} p(d \mid u, d')\ \textcolor{#2ca02c}{bel(d')}

  • np.roll / shift — move every cell by the motion uu (offset, in cells)
  • convolve(…, kernel) — the formula's sum: each cell collects its neighbours' belief with the weights kernel = [0.1, 0.8, 0.1] (example) — a blur by the wheels' error
  • mode='wrap' — the cells close into a ring; 'constant' — a wall: our drive; cval — what fills the cells that come in from beyond the wall: 0

bel(d)=η p(z∣d) bel‾(d)\textcolor{#2ca02c}{bel(d)} = \eta\ \textcolor{#d62728}{p(z \mid d)}\ \textcolor{#1f77b4}{\overline{bel}(d)}

  • prior * likelihood — cell by cell
  • normalize — divide by the sum: this is η\eta; np.asarray(…, dtype=float) — the belief as an array of fractions
Python · filterpy/discrete_bayes/discrete_bayes.py — filterpy's grid Bayes filter, library code
def predict(pdf, offset, kernel, mode='wrap', cval=0.):
    if mode == 'wrap':
        return convolve(np.roll(pdf, offset), kernel, mode='wrap')

    return convolve(shift(pdf, offset, cval=cval), kernel,
                    cval=cval, mode='constant')

def update(likelihood, prior):
    posterior = prior * likelihood
    return normalize(posterior)

def normalize(pdf):
    pdf /= sum(np.asarray(pdf, dtype=float))
    return pdf
  • dd, d′d' — a cell now and before the motion; uu — the motion; pdf — the belief, one number per cell
  • bel‾\overline{bel}, belbel — the belief before and after the scan: prior, posterior
  • p(z∣d)p(z \mid d) — the scan's likelihood over the cells: likelihood; η\eta — 1 / sum

🎯 github.com/rlabbe/filterpy
▶️ colab.research.google.com/github/StanfordASL/pora-exercises/blob/main/ch11/discrete_bayes.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Kalman Filter: Predict, Then Update

A loop of three boxes: blue predict — the Gaussian of the old estimate, dashed green, shifts into the prediction, its σ larger; red scan z — a Gaussian with a small σ; green update — combine: the two give one Gaussian between them, its σ smaller than both; and back to predict

predict — the wheel odometry moves the guess; the doubt grows

d−=d^−v Δtσd−2=σd^2+σu2\textcolor{#1f77b4}{d^-} = \hat d - v\,\Delta t \qquad \textcolor{#1f77b4}{\sigma_{d^-}^2} = \sigma_{\hat d}^2 + \sigma_u^2

update — the scan pulls the guess; the doubt shrinks

K=σd−2σd−2+σz2K = \frac{\sigma_{d^-}^2}{\sigma_{d^-}^2 + \sigma_z^2}

d^=d−+K (z−d−)σd^2=(1−K) σd−2\textcolor{#2ca02c}{\hat d} = \textcolor{#1f77b4}{d^-} + K\,(\textcolor{#d62728}{z} - \textcolor{#1f77b4}{d^-}) \qquad \textcolor{#2ca02c}{\sigma_{\hat d}^2} = (1 - K)\,\textcolor{#1f77b4}{\sigma_{d^-}^2}

The grid's recipe for one Gaussian: two numbers per step — the guess and its doubt.

Bayes filter on a grid Kalman filter
the belief one number per cell: 201 numbers one Gaussian: d^\hat d and σd^\sigma_{\hat d}
it needs nothing: any shape, even two peaks a belief that stays one Gaussian

Legend
each Gaussian — where the robot may be: the likeliest distance at its centre, less likely to the sides
d^\hat d — the estimate of the distance to the wall, the green centre; the hat — an estimate; dashed green — the last one
σd^\sigma_{\hat d} — its typical error, the width; σ2\sigma^2 — its variance, the doubt
d−\textcolor{#1f77b4}{d^-}, σd−\sigma_{d^-} — the prediction and its error; the minus — before the scan
v Δtv\,\Delta t — one step by the wheel odometry: speed × time
σu\sigma_u — the error one step adds
z\textcolor{#d62728}{z} — the scan of the lidar's forward ray; σz\sigma_z — its error: the lidar's own, constant
KK — the Kalman gain: the scan's weight, from 0 to 1
z−d−z - d^- — the innovation: how far the scan is from the prediction

▶️ youtu.be/ul3u2yLPwU0

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Kalman Predict: the Doubt Grows

The estimate is off by ±1 cm; one step of the wheels adds ±2 cm (example):

estimate step prediction squared
+1 +2 +3 9
+1 −2 −1 1
−1 +2 +1 1
−1 −2 −3 9

mean square 5 = 1 + 4: the variances add

Normal distributions on the error axis: the estimate with σ 1, the step with σ 2 dashed, the prediction with σ √5, larger than both

d−=d^−v Δtσd−2=σd^2+σu2\textcolor{#1f77b4}{d^-} = \hat d - v\,\Delta t \qquad \textcolor{#1f77b4}{\sigma_{d^-}^2} = \sigma_{\hat d}^2 + \sigma_u^2

The Gaussian moves with the robot, its σ grows: variances add.

Python · filterpy/stats/stats.py — filterpy's helper that adds two Gaussians, library code
def add(mean1, var1, mean2, var2):
    return (mean1+mean2, var1+var2)

Only a measurement shrinks the doubt — the update, next.

Legend
d^\hat d — the estimate of the distance to the wall after the last scan, before the robot moves; σd^\sigma_{\hat d} — its typical error, 1 cm (example)
v Δtv\,\Delta t — the step: how far the wheel odometry says the robot moved between two scans
σu\sigma_u — the error one step adds: the wheels', 2 cm (example); σu2\sigma_u^2 — Q in filterpy and robot_localization
d−\textcolor{#1f77b4}{d^-}, σd−\textcolor{#1f77b4}{\sigma_{d^-}} — the prediction and its error; the minus — before the scan
σ2\sigma^2 — a variance; variances add, σ do not: 1 + 4 = 5, so σd−\sigma_{d^-} = √5, not 1 + 2
mean1, var1 — the estimate: d^\hat d, σd^2\sigma_{\hat d}^2; mean2, var2 — the step: −v Δt-v\,\Delta t, σu2\sigma_u^2
green — the estimate; dashed — the step's error; blue — the prediction

▶️ youtu.be/d_qvLDhkg00

KAU · Autonomous Robotic Systems Engineering · Lecture 6

Wheels and Lidar: Which Weight Is Best?

The robot from the side before a wall at distance d, a dashed copy one step v Δt back, the red forward ray to the wall; above the wall a blue Gaussian d⁻ ±2 cm, a red Gaussian z ±1 cm and a dashed green mix d̂ with a question mark; a dotted line up from the wall marks where it truly is
  • The wheels predict: d−=d^k−1−v Δt\textcolor{#1f77b4}{d^-} = \hat d_{k-1} - v\,\Delta t, off by σd−\sigma_{d^-} = 2 cm (example)
  • The lidar measures with its forward ray, one of 360, straight at the wall: z=d+noise\textcolor{#d62728}{z} = d + \text{noise}, off by σz\sigma_z = 1 cm (example)
  • Mix them with a weight ww for the scan:

d^=(1−w) d−+w z=d−+w (z−d−)\textcolor{#2ca02c}{\hat d} = (1-w)\,\textcolor{#1f77b4}{d^-} + w\,\textcolor{#d62728}{z} = \textcolor{#1f77b4}{d^-} + w\,(\textcolor{#d62728}{z} - \textcolor{#1f77b4}{d^-})

The same weighted mix as in the complementary filter: which ww is best?

complementary filter Kalman filter
the weight fixed, picked by eye: 0.02 computed at every step from the two variances
the doubt not kept kept as σ2\sigma^2: how sure the estimate is

Legend
dd — the true distance to the wall
d−\textcolor{#1f77b4}{d^-} — the prediction: the last estimate moved by the wheel odometry's step; the minus — before the scan
σd−\sigma_{d^-} — the prediction's typical error
d^k−1\hat d_{k-1} — the estimate one step earlier, at step k−1k-1
z\textcolor{#d62728}{z} — the lidar's forward ray; σz\sigma_z — its typical error
d^\textcolor{#2ca02c}{\hat d} — the mix, the fused estimate: the new estimate
v Δtv\,\Delta t — how far the robot moved in one step: speed × time
ww — the scan's weight: any number from 0 to 1. The loop's KK is the best ww — found next
blue, red — where the wall may be, by the wheel odometry and by the lidar: likeliest at the centre; dotted line — where it truly is; dashed green — the mix, not known yet

▶️ github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/04-One-Dimensional-Kalman-Filters.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Right Weight Beats Both Sensors

Each error is + or −, equally likely: lidar ±1 cm, wheels ±2 cm (example). Mix with w=0.8w = 0.8:

lidar wheels mix squared
+1 +2 1.2 1.44
+1 −2 0.4 0.16
−1 +2 −0.4 0.16
−1 −2 −1.2 1.44

mean square 0.8 cm² — less than the lidar's 1 cm²

Sigma of the mix against the scan weight w from 0 to 1: 2 cm with wheels only, 1.12 half and half, 0.89 at w = 0.8, 1 cm with the lidar only; shaded where the mix beats the lidar

Legend
ww — the scan's weight: here 0.8 for the lidar, 0.2 for the wheels
lidar, wheels — the error of each, cm; every sign equally likely
mix — 0.8 × the lidar's error + 0.2 × the wheels' error
mean square — the average of the error²
σd^\sigma_{\hat d} — the mix's typical error: the root of the mean square
lidar alone: σz\sigma_z = 1 cm · wheels alone: σd−\sigma_{d^-} = 2 cm (example)
green curve — σd^\sigma_{\hat d} for every weight; red dashed — the lidar alone; shaded — where the mix beats it

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Error of the Mix Is a Parabola

σd^2(w)=(1−w)2 σd−2+w2 σz2\textcolor{#2ca02c}{\sigma_{\hat d}^2}(w) = (1-w)^2\,\textcolor{#1f77b4}{\sigma_{d^-}^2} + w^2\,\textcolor{#d62728}{\sigma_z^2}

The error of the mix for every weight: the sum of the two parts has a bottom.

σd−2=4\sigma_{d^-}^2 = 4, σz2=1\sigma_z^2 = 1 cm² (example)

The mix's variance against w: the prediction part falls, the lidar part grows, their sum has its bottom at w = 0.8; dots at w = 0, 0.2, 0.5, 0.8 and 1 with their values

Legend
σd^2(w)\textcolor{#2ca02c}{\sigma_{\hat d}^2}(w) — the mix's variance, its doubt: the average of its error² over many scans, cm²
ww — the scan's weight, from 0 to 1
σd−2\textcolor{#1f77b4}{\sigma_{d^-}^2} — the prediction's variance: the average of its error²
σz2\textcolor{#d62728}{\sigma_z^2} — the lidar's variance
(1−w)2σd−2(1-w)^2 \sigma_{d^-}^2 — the prediction's part: its error × (1 − w), so its variance × (1 − w)²: blue dashed
w2σz2w^2 \sigma_z^2 — the lidar's part: its error × ww, its variance × w2w^2: red dashed
green — their sum, σd^2\sigma_{\hat d}^2; dots — a few weights, with their values

▶️ youtu.be/ul3u2yLPwU0

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Bottom of the Parabola Is the Kalman Gain KK

K=σd−2σd−2+σz2K = \frac{\textcolor{#1f77b4}{\sigma_{d^-}^2}}{\textcolor{#1f77b4}{\sigma_{d^-}^2} + \textcolor{#d62728}{\sigma_z^2}}

The best weight: the prediction's share of the total doubt.

Three pairs of noises, cm² (example); the picture is the first:

case σd−2\sigma_{d^-}^2 σz2\sigma_z^2 KK
wheels unsure 4\textcolor{#1f77b4}{4} 1\textcolor{#d62728}{1} 4/(4+1)\textcolor{#1f77b4}{4} / (\textcolor{#1f77b4}{4} + \textcolor{#d62728}{1}) = 0.8
lidar unsure 1\textcolor{#1f77b4}{1} 4\textcolor{#d62728}{4} 1/(1+4)\textcolor{#1f77b4}{1} / (\textcolor{#1f77b4}{1} + \textcolor{#d62728}{4}) = 0.2
equally sure 1\textcolor{#1f77b4}{1} 1\textcolor{#d62728}{1} 1/(1+1)\textcolor{#1f77b4}{1} / (\textcolor{#1f77b4}{1} + \textcolor{#d62728}{1}) = 0.5
Top: the parabola of the mix's variance σ²_d̂(w) with its bottom at w = 0.8. Bottom: on the same w axis a bar of the total doubt, 5: the prediction's variance 4 in blue and the lidar's 1 in red; the bar splits at 0.8, right under the bottom

Legend
green curve — the mix's variance σd^2\sigma_{\hat d}^2 for every weight ww of the scan; green dot — its bottom, the best ww
dashed line — from the bottom down to the bar: the bar splits right under it
bar — the total doubt σd−2+σz2\sigma_{d^-}^2 + \sigma_z^2: blue — the prediction's part, red — the lidar's
KK — the Kalman gain: the best weight of the scan, from 0 to 1
σd−2\textcolor{#1f77b4}{\sigma_{d^-}^2} — the prediction's variance: its typical error, squared
σz2\textcolor{#d62728}{\sigma_z^2} — the lidar's variance
σd^2\sigma_{\hat d}^2 — the mix's variance, its doubt, cm²
σd−=0\sigma_{d^-} = 0 → K=0K = 0: keep the prediction · σz=0\sigma_z = 0 → K=1K = 1: take the scan

▶️ github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/04-One-Dimensional-Kalman-Filters.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

From One Number to Matrices

our drive: one number the matrix form: many numbers
the state dd a column of nn numbers: the pose x,y,θx, y, \theta
its doubt σd−2\textcolor{#1f77b4}{\sigma^2_{d^-}} P−\textcolor{#1f77b4}{P^-}, n×nn \times n: each number's variance on the diagonal; off it, how two errors depend on each other — not diagonal
what the sensor reads z=dz = d, the distance itself z=H⋅z = H\cdotstate: HH, m×nm \times n, picks from the state what the sensor reads
the sensor's noise σz2\textcolor{#d62728}{\sigma^2_z} R\textcolor{#d62728}{R}, m×mm \times m: diagonal when each reading errs on its own
a doubt through a number a σ2a=a2σ2a\,\sigma^2 a = a^2\sigma^2 H P−H⊤H\,\textcolor{#1f77b4}{P^-}H^\top: HH on both sides
divide by SS 1/S1/S multiply by S−1S^{-1}, the inverse matrix
the gain KK one number: the scan's weight n×mn \times m: a reading moves every number whose error depends on it
H picks from the state what the sensor reads. Top, our drive: z = 1 · d = d. Bottom, the pose (x, y, θ) and the IMU's yaw: z = [0 0 1] · (x, y, θ) = θ; H's ones grey, what they pick framed

Legend
zz — the reading: the lidar's distance, the IMU's yaw
HH — the measurement matrix: picks from the state what the sensor reads; nn — state numbers, mm — readings
P−\textcolor{#1f77b4}{P^-}, R\textcolor{#d62728}{R} — the prediction's and the sensor's covariance matrices
aa — a number an error is multiplied by; σ2\sigma^2 — that error's variance
H⊤H^\top — HH turned over; SS — the reading's whole doubt; S−1S^{-1} — the inverse of SS; KK — the Kalman gain
grey — HH's ones; framed — what they pick

▶️ github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/06-Multivariate-Kalman-Filters.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6

What H P−H⊤H\,P^-H^\top Computes

H P⁻ Hᵀ — the doubt of what the sensor should read. Top, our drive: 1 · σ²_d⁻ · 1 = σ²_d⁻; a σ² a = a²σ²: an error times a, its variance times a². Bottom, the pose (x, y, θ) and the IMU's yaw: [0 0 1] · P⁻ · [0 0 1]ᵀ = σ²_θ; the ones pick θ's row and column of the 3 × 3 table P⁻; dashed, cov(y, θ): 2 m forward, θ off by 0.1 rad, y off by 0.2 m
  • H P−H⊤H\,\textcolor{#1f77b4}{P^-}H^\top — the doubt of the reading we expect: the prediction's doubt, carried into what the sensor reads
  • HH twice: the error passes through HH, and a variance is a square — so HH on both sides, as aa in a σ2aa\,\sigma^2 a
  • Who fills P−\textcolor{#1f77b4}{P^-}: the filter — predict ties the errors, update shrinks them; we set only the start and the noises

Legend
HH — picks from the state what the sensor reads; H⊤H^\top — the same ones as a column
P−\textcolor{#1f77b4}{P^-} — the prediction's covariance matrix: variances on the diagonal, blue
cov — a covariance: how two errors depend on each other
σx2\sigma^2_x, σy2\sigma^2_y, σθ2\sigma^2_\theta — the variances of xx, yy and the yaw θ\theta; σd−2\sigma^2_{d^-} — our prediction's
aa — a number an error is multiplied by; σ2\sigma^2 — its variance
framed — what the ones pick; dashed — cov(y,θ)(y, \theta)

▶️ www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/

KAU · Autonomous Robotic Systems Engineering · Lecture 6

The Kalman Gain in Matrices, Piece by Piece

The matrix form, as filterpy writes it: K=P−H⊤(HP−H⊤+R)−1K = \textcolor{#1f77b4}{P^-}H^\top(H\textcolor{#1f77b4}{P^-}H^\top + \textcolor{#d62728}{R})^{-1} — our 1D gain, piece by piece:

piece our drive, 1D matrices what it is
1 σd−2\textcolor{#1f77b4}{\sigma^2_{d^-}} H P−H⊤H\,\textcolor{#1f77b4}{P^-}H^\top the doubt of the reading we expect
2 σd−2+σz2\textcolor{#1f77b4}{\sigma^2_{d^-}} + \textcolor{#d62728}{\sigma^2_z} S=HP−H⊤+RS = H\textcolor{#1f77b4}{P^-}H^\top + \textcolor{#d62728}{R} plus the sensor's noise: variances add
3 σd−2\textcolor{#1f77b4}{\sigma^2_{d^-}} on top P−H⊤\textcolor{#1f77b4}{P^-}H^\top HH once: each state number's error paired with the reading's
4 divide by the sum multiply by S−1S^{-1} the inverse instead of a division
KK σd−2σd−2+σz2\dfrac{\textcolor{#1f77b4}{\sigma^2_{d^-}}}{\textcolor{#1f77b4}{\sigma^2_{d^-}} + \textcolor{#d62728}{\sigma^2_z}} P−H⊤S−1\textcolor{#1f77b4}{P^-}H^\top S^{-1} how far each state number moves
The whole doubt S = 5 (example) as a bar: the prediction's part, 4, in blue, the lidar's, 1, in red; the inverse of S, for one number 1/S, is 0.2; a bracket under the blue part: K = 4 × 0.2 = 0.8, the prediction's share

With one number, every piece is the 1D one: KK is the bottom of the parabola again.

Legend
KK — the Kalman gain; HH — picks what the sensor reads
P−\textcolor{#1f77b4}{P^-}, R\textcolor{#d62728}{R} — the prediction's and the sensor's covariance matrices
SS — the reading's whole doubt
bar — SS (example): blue — the prediction's part, red — the lidar's; under the blue part — KK

Linear algebra
A BA\,B — each row of AA times each column of BB, summed; (m×n)(n×p)=m×p(m \times n)(n \times p) = m \times p
A⊤A^\top — rows become columns
A−1A^{-1} — the inverse: A−1A=IA^{-1}A = I, the identity; for a number, 1/A1/A

▶️ github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/06-Multivariate-Kalman-Filters.ipynb

KAU · Autonomous Robotic Systems Engineering · Lecture 6