The low pass filter

A moving average, then the exponential filter that does the same job in one line and no memory.

U4.3Noise and filteringUniversity30 min

Do this lesson in the simulator

A moving robot cannot stop and average. It needs an estimate that is always available, updated as each reading arrives, and smoother than the raw stream.

The moving average

Keep the last n readings in a list and average them:

window.append(raw)
if len(window) > N:
    window.pop(0)
estimate = sum(window) / len(window)

It works, it is easy to explain, and it costs n numbers of memory and n additions per tick. Its response to a step is a ramp n steps long, and it takes about n/2 steps for the output to reach the new value. So more smoothing means more delay, in a fixed and obvious way.

The exponential filter

The same job in one line and no memory at all:

filtered = alpha * raw + (1 - alpha) * filtered

Each new reading gets weight alpha; everything before it keeps the rest. The weights decay geometrically into the past, which is why it is called an exponentially weighted moving average, and in signal processing it is a first order low pass filter.

alpha behaviour roughly like a window of
0.5 fast, barely smoothed 3 readings
0.2 a reasonable default 9 readings
0.05 very smooth, very slow 39 readings

The equivalent window is about (2 - alpha) / alpha. Worth knowing so that "alpha = 0.1" turns into a picture rather than a mystery.

from bugbot import *
connect()

ALPHA = 0.25
filtered = distance()
forward(45)
for i in range(60):
    raw = distance()
    filtered = ALPHA * raw + (1 - ALPHA) * filtered
    plot("raw", raw)
    plot("filtered", filtered)
    if filtered < 30:
        stop()
    wait(0.1)
stop()

Run this in the simulator

Two lines on the chart: a jagged one and a smooth one that follows it a little late. Both facts matter, and the second is the subject of U4.5.

It is the same idea as the Kalman filter

Look at the update again, rearranged:

filtered = filtered + alpha * (raw - filtered)

"Take your current belief, and move it towards the new measurement by a fraction of the difference." That is precisely the shape of the Kalman update in U6.3. The whole difference is that alpha there is not a constant you chose: it is computed each tick from how uncertain the belief is compared with the measurement.

Everything you learn about choosing alpha here is a way of feeling what that filter is doing for you automatically.

Starting it

filtered = 0 as a starting value means the first second of output is the filter climbing from zero to reality, which looks exactly like a real signal and is not one. Start it at the first reading instead. Small detail, and it removes a startup transient that is otherwise blamed on the hardware.

Task: a low pass filter

Drive towards the wall with an exponential filter running, plotting raw and filtered as you go, and print the alpha: you chose.

from bugbot import *
connect()

ALPHA = 0.25
filtered = distance()

Challenges

  1. Run the same drive at alpha 0.05, 0.2 and 0.6 and compare the charts.
  2. Implement the moving average as well and plot all three. Which is smoother for the same delay?
  3. Filter imu()[1] instead. What is a sensible alpha for a signal you want to use in a controller?