Lesson 1: Smoothing Input

Table of Contents

  1. Noisy input
  2. Moving window filters
    1. Moving average filter
      1. Arduino implementation
      2. Arduino code
      3. Simple C++ class
    2. Weighted moving average
    3. Exponential moving average
      1. Arduino EWMA implementation
    4. Moving median filter
    5. Why use a median filter?
  3. Other filters
  4. Activity
  5. Arduino filtering libraries
  6. Resources

By now, you may have noticed that your analog input data can be noisy. How can we smooth our input and what factors should we consider?

Video Smoothing an analog input signal using a moving average filter with window size of 10. The raw analog input is shown in blue; the smoothed data is shown in red. Graph made with the built-in Arduino Serial Plotter.

This is a big, complex question. Indeed, there is an entire field called digital signal processing (DSP), which explores techniques to sample, smooth, or otherwise transform signals, like sensor data, into more usable forms. In signal processing, filters refer to algorithmic methods and/or hardware approaches to remove or attenuate unwanted components of a signal.

In this lesson, we will cover a class of digital filters called smoothing algorithms (aka signal filters), why they’re helpful, and potential tradeoffs in their implementation and use.


DIVE DEEPER:

DSP is a vast, complex area but even simple signal processing techniques like those covered in this lesson are helpful. If you’d like to dive deeper into DSP, see our Signal Processing lessons, which introduce quantization and sampling, signal comparisons, and frequency analysis. For a more technical introduction to digital filters, see Chapter 14: Introduction to Digital Filters in Steven W. Smith’s book The Scientist and Engineer’s Guide to Digital Signal Processing. We also recommend Jack Schaelder’s interactive primer on DSP.


Noisy input

When reading sensor data using analog input pins on a microcontroller (e.g., via analogRead on the Arduino), there are many sources of noise, including electromagnetic interference, sensor noise, mechanical noise (for electro-mechanical sensors like potentiometers), stray capacitance, unstable voltage sources, and/or small imperfections in the ADC. Oh my!

Even with a simple potentiometer, we can observe noise on our input pin. In the video below, we are not touching the potentiometer and yet the analog input is oscillating between 142 and 143 (0.694V and 0.699V)—shown as the blue line. You may have experienced this too in your own potentiometer-based projects or in the Arduino potentiometer lesson. In this case, we fixed this “input noise” by smoothing the signal using a moving average filter—shown in red—which we will describe in this lesson.

Video. In this video, we’re graphing the raw analog input (blue line) from a potentiometer along with a “smoothed” version (red line). Although we’re not touching or using the potentiometer, the analog input is oscillating between 142 and 143 (0.694V and 0.699V). We smooth this noise using a moving average filter (window size = 10)—shown in red. Note that, depending on the oscillation pattern, a different window size or smoothing approach may be necessary. Read more about potentiometer noise here. Graph made with the built-in Arduino Serial Plotter.

In addition to smoothing a potentiometer input signal with a digital filter (a software solution), we could, instead, use a hardware solution: add in a small ceramic capacitor (0.1µF or 0.47 µF) from the potentiometer wiper to ground. However, the focus of this lesson is on software solutions (putting the digital in DSP).

Moving window filters

The most common digital filters use a moving window (or buffer) to smooth a signal in realtime. Typically, larger window sizes result in a “smoother” but more distorted signal. Larger windows also incur “lag”—the filtered signal’s responsiveness to changes in the raw signal—and require more storage and computation time.

As each sensor and physical computing project is unique, we encourage you to experiment with different smoothing algorithms and window sizes to achieve your desired behavior.


SPECIAL CASE: DATA LOGGING

Note that if you’re logging data (e.g., to a storage card or the cloud) for research or data experiments, it’s often best to first capture and transmit the raw data so that you can experiment with signal processing approaches post hoc (e.g., offline in Jupyter Notebook). Through offline analysis with these raw logs, you can determine an ideal sampling frequency and filtering approach for your particular hardware components and problem domain. Then, you can implement this approach in your field-deployed system, which can result in reduced computational overhead, bandwidth, power, etc..


Moving average filter

The most common filter in DSP is the moving average filter (or moving mean filter), which slides a window of size \(N\) over a raw signal, computes the average over that window, and uses this average as the smoothed value.

\[MA=\frac{X_{1} + X_{2} + \ldots + X_{N}}{N}\]

This filter is a type of low-pass filter because it smooths out (eliminates) the high frequency oscillations in the signal.

You can control the filter’s performance by tweaking the size of the sliding window. The animation below demonstrates a sliding window of size 3. The blue line corresponds to the raw input signal; the orange line, the smoothed filter output. For illustrative purposes, we only show the sliding window applied to a subset of data.

Video This video illustrates a moving average filter of window size 3 over a subset of data. Animation made in PowerPoint.

Despite its simplicity, a moving average filter often all you will need in your physical computing projects. This, of course, depends on your use context and the underlying sensors + circuit setup. Thus, we encourage you to play around with various smoothing approaches and tuning parameters. If you’re sampling a sensor 20 times per second (~20Hz), then a window size of 10 will capture roughly 500ms of data. So, it may be useful to think in terms of time rather than samples.

Below, we compute three different moving average filter window sizes: 5, 10, and 20 and show the resulting filter output in red, green, and yellow, respectively.

Video This video graphs raw analog input (blue) and filtered output from three different moving average window sizes: 5 (red line), 10 (green), and 20 (yellow). To produce this video, we used this code and the Arduino Serial Plotter. You should try it yourself!

Arduino implementation

The official Arduino signal smoothing tutorial uses a moving average filter. Cleverly, their code uses an optimization (which we borrow below) to avoid iterating over the entire window to compute each new average. Instead, we simply subtract the least recent reading in our sliding window from a running total. The code also uses a circular buffer to eliminate needless memory allocations, which is important on constrained systems like microcontrollers.

// read the sensor value
int sensorVal = analogRead(SENSOR_INPUT_PIN);

// subtract the last reading from our sliding window
_sampleTotal = _sampleTotal - _samples[_curReadIndex];

// add in current reading to our sliding window
_samples[_curReadIndex] = sensorVal;

// add the reading to the total
_sampleTotal = _sampleTotal + _samples[_curReadIndex];

// calculate the average:
_sampleAvg = _sampleTotal / SMOOTHING_WINDOW_SIZE;

// advance to the next position in the array
_curReadIndex = _curReadIndex + 1;

// if we're at the end of the array...
if (_curReadIndex >= SMOOTHING_WINDOW_SIZE) {
  // ...wrap around to the beginning:
  _curReadIndex = 0;
}

Arduino code

You can find our full implementation on GitHub as MovingAverageFilter.ino, which is also displayed below:

Simple C++ class

Signal filtering is a perfect opportunity to create a class to hide complexity, avoid code redundancy, and handle data processing. In our Makeability Lab Arduino Library, we created the MovingAveragefilter.hpp class, which simplifies using a moving average filter. But you could make your own, of course, or use other libraries

Here’s a demonstration of how to use MovingAveragefilter.hpp:

We also use the MovingAveragefilter.hpp class in our demonstration of various sliding window sizes on the moving average output (code here).

Weighted moving average

In the moving average algorithm above, we assign equal weight to all data in our filter window. You could imagine, however, designing an algorithm that assigns higher weights to more recent data (with the theory that recency correlates to relevancy). And, indeed, there are a class of algorithms called weighted moving averages (WMA) that do just this, including linear weighting schemes with weights that drop off linearly in the filter window and exponential weighting schemes where weights drop off exponentially.

Recall that a regular moving average is:

\[MA=\frac{X_{1} + X_{2} + \ldots + X_{N}}{N}\]

Then the weighted version is simply:

\[WMA=\frac{w_{1}X_{1} + w_{2}X_{2} + \ldots + w_{n}X_{n}}{w_{1} + w_{2} + \ldots + w_{n}}\]

If the weight coefficients are precomputed to sum to one (\(\sum_{i=1}^{n} w_{i} = 1\)), then the above equation simply becomes:

\[WMA=w_{1}X_{1} + w_{2}X_{2} + \ldots + w_{n}X_{n}\]

Below, we’ve included sample weights for both linear and exponential weighting schemes with a sliding window size of 15.

Linear Weights Exponential Weights
Linearly decreasing weights for window size 15 Exponentially decreasing weights for window size 15

Figure. Images from Wikipedia.

Exponential moving average

Interestingly, you can implement the exponential moving average (EMA)—also known as the exponentially weighted moving average (EWMA)—without storing a sliding window buffer! This is a very cool optimization and means that you can achieve smoothing without the memory and computation overhead of many other “moving” filter techniques.

The algorithm is:

\[S_{i} = \begin{cases} X_{1} & \text{if i = 1}\\ \alpha \cdot X_{i} + (1 - \alpha) \cdot S_{i-1} & \text{if i $>$ 1} \end{cases}\]

Where:

  • The coefficient \(\alpha\) represents the degree of weighting decrease between 0 and 1. A higher \(\alpha\) discounts older observations faster.
  • \(X_{i}\) is the value at index i.
  • \(S_{i}\) is the value of the EMA at index i.

The coefficient \(\alpha\) determines the exponential dropoff. A higher \(\alpha\) weights more recent data more. For example, the video below shows EWMA performance with \(\alpha\) equal to 0.5 (red line), 0.1 (green line), and 0.01 (yellow line). Notice how closely the \(\alpha=0.5\) EWMA filter tracks the underlying raw signal whereas the \(\alpha=0.01\) filter is quite distorted and lagged. The \(\alpha=0.1\) filter may still be appropriate, depending on your needs. Again, it’s up to you to experiment!

Video This video shows EWMA performance with \(\alpha\) equal to 0.5 (red line), 0.1 (green line), and 0.01 (yellow line). The code used to produce this video is here. Graph made with the built-in Arduino Serial Plotter.

Arduino EWMA implementation

If you’re not used to reading equations, then perhaps the Arduino code below is more clear. The algorithm is quite straightforward and, again, unlike the traditional moving average algorithm, does not require a window buffer!

const int SENSOR_INPUT_PIN = A0;

float _ewmaAlpha = 0.1;  // the EWMA alpha value (α)
double _ewma = 0;        // the EWMA result (Si), initialized to zero

void setup()
{
  Serial.begin(9600); // for printing values to console
  _ewma = analogRead(SENSOR_INPUT_PIN);  //set EWMA (S1) for index 1
}

void loop()
{
  int sensorVal = analogRead(A0); // returns 0 - 1023 (due to 10 bit ADC)
  
  // Apply the EWMA formula 
  _ewma = (_ewmaAlpha * sensorVal) + (1 - _ewmaAlpha) * _ewma;

  Serial.print(sensorVal);      
  Serial.print(",");  
  Serial.println(_ewma);  
  delay(50); // Reading new values at ~20Hz
}

Code. This code is on GitHub.

Moving median filter

A moving median filter is almost the exact same as a moving average filter but takes the median over the sliding window rather than the average.

Video This video shows a moving median filter with a window size of three. Animation made in PowerPoint.

The challenge, however, is calculating the median efficiently. The median is simply the middle value of a sorted array \(X_s\) or, if the size of the array is odd, then it is the average of the two middle values.

\[Med(X) = \begin{cases} X_{s} \begin{bmatrix} \frac{n}{2}\\ \end{bmatrix} & \text{if n is even}\\ \frac{(X_{s}\begin{bmatrix} \frac{n-1}{2}\\ \end{bmatrix} + \begin{bmatrix} \frac{n+1}{2}\\ \end{bmatrix})}{2} & \text{if n is odd} \end{cases}\]

Note: The array \(X\) must be sorted (indicated by the ‘s’ subscript in \(X_s\)). The brackets in the equation above indicate indices, similar to arrays in programming.

But how can we obtain this “middle” value efficiently? We know that re-sorting an array on each value insert can be computationally expensive. Thus, typically realtime median filters use other data structures, like indexable skiplists, to efficiently keep a sorted data structure on inserts and removals.

Similar to the moving average filter, we can tweak the moving median filter’s performance by modifying the filter window size. Below, we show a moving median filter with window sizes 5, 11, and 21. For this video, we used our test code MovingMedianFilterWindowSizeDemo.ino, which relies on Luis Llama’s Arduino Median Filter 2 library based on Phil Ekstrom’s “Better Than Average” article.

Video This video shows moving median filter performance with window sizes 5, 11, and 21. Notice how a median filter tends to flatten “peaks” in the signal, which is unlike the other filters we’ve examined. To make this video, we used this code and the built-in Arduino Serial Plotter.

Why use a median filter?

Median filters are widely used in image processing to remove noise from images (image processing is its own subfield of signal processing focusing on 2D DSP techniques). Unlike mean (or average) filters, median filters remove noise while preserving edges—and edges are often a crucial part of other image processing algorithms like the Canny edge detector.

Figure. Median filtering is widely used in image processing where it is particularly effective at removing “speckle” or “salt-and-pepper” noise while preserving edges. Image from Wikipedia.

In the case of 1-dimensional signal processing, which we’ve focused on in this lesson, median filters have the same advantage: they provide a smoothing technique the preserves edges in our signal. For example, what if our data is from an underlying clock signal that should have sharp rising and falling edges, which we don’t wish to smooth. A moving average filter distorts rise and fall times while the median filter sharpens them.

Moving Average Filter Moving Median Filter
The moving average filter distorts the rising and falling edges of the clock signal The median filter both smooths the signal and crispens the clock transition edges

Figure. Images from MathWorks.

Other filters

In this lesson, we covered only a few basic digital filters but many others exist—some which allow you to actually control which frequencies in your signal to eliminate. For example, high-pass filters can remove specific low-frequency components from your signal while keeping high frequencies, low-pass filters eliminate high-frequency components while keeping low frequencies, bandpass filters let you specify a range (or band) of frequencies to keep, etc.

Other popular filters include the Savitzky-Golay filter, the Butterworth filter, and the Kalman filter. The Savitzky-Golay filter, for example, is like a weighted moving average filter that attempts to fit a polynomial over the sliding window (to better fit the underlying signal)—see this MathWorks article.

In general, it is not helpful or appropriate to perform filtering investigations in real-time on sensor data because it’s difficult to replicate input signals and test and compare filtering algorithms. Yes, it’s fine to experiment with filters+real-time data for gut checking or to get something working quickly but not for more deep signal analysis.

Thus, if you’re interested in this area, we suggest experimenting with the filters in SciPy with Jupyter Notebook. You should log the sensor data to your computer and then analyze it “offline” in a testbed environment, as we do in our Step Tracker and Gesture Recognition assignments. This will allow you to experiment with and compare different algorithm approaches and parameter tuning.

Activity

For your prototyping activity, we would like you to choose a sensor from your hardware kits—it could be a potentiometer, a hall effect sensor, a force-sensitive resistor, a photoresistor, an accelerometer, a DIY sensor, etc.—and two different smoothing algorithms to apply to it.

More specifically, build an appropriate circuit for your sensor, read the input on an analog input pin, process this input with two different smoothing algorithms (either those we covered in this lesson or beyond), graph the three signals (raw signal, smoothing algorithm 1 output, smoothing algorithm 2 output), record a video of your input + the graphs, and report on your observations in the prototyping journal. Include a link to your video (or an embedded animated gif).

Arduino filtering libraries

There are lots of Arduino filtering libraries online and general C++ filtering code that could be adapted to the Arduino context. The usual disclaimers apply: we have not had a chance to evaluate all of these libraries. So, use at your own risk. :)

Resources


This website was developed by Professor Jon E. Froehlich and the Makeability Lab using Just the Docs. If you found the website useful or use it in your teaching, we'd love to hear from you: jonf@cs.uw.edu. This website and all code is open source (website GitHub, Arduino GitHub, p5js GitHub). You can find the MakeabilityLab_Arduino_Library here. Found an error? File a GitHub Issue.