Low-Pass Filter: The Basics

Low-Pass Filter: The Basics

Discussing the most common forms of low-pass filters with accelerometer data on Android devices.

 Author: Kaleb Kircher

Version: 3.0

4.8.14.3.0

Refactoring History

v1.0 1.9.13.1.0 Kaleb Kircher Authored
v2.0 7.10.13.2.0 Kaleb Kircher Refactored
v3.0 4.8.14.3.0 Kaleb Kircher Refactored

 Copyright: 2012-2015 Kircher Engineering, LLC

Associated Project Applications:

Explore low-pass accelerometer filters on your Android device with Acceleration Explorer! Acceleration Explorer allows the user to view the outputs from multiple filters (including low-pass filters) in real-time making it easy to compare the performance of different filters.

Acceleration Filter Promotional Graphic

You can download the Acceleration Explorer application for free from the Google Play Store!

Are you a developer? You can fork the Acceleration Explorer source code from GitHub!

 

Low-Pass Filters: 

The Theory:

A low-pass filter is a type of electronics filter that attempts to pass low-frequency signals through the filter unchanged while reducing the amplitude of signals with a frequency above what is known as the cutoff frequency. They are used for all sorts of things including audio, images, signal processing and digital filters.

low-pass filter circuit

The concept of a low-pass filter comes from electrical engineering. It is simply a resistor in series with the load and a capacitor in parallel with the load. The capacitor essentially acts a load spring, absorbing high-frequencies, but blocking low-frequencies and forcing them through the load instead. This is because at low-frequencies the capacitor has time to charge so it is equal to in the input voltage forcing the signal to the output. At high frequencies, the capacitor does not have enough time to charge, allowing the signal to go to ground instead of through the output.

The resistance and the capacitance define the time constant (T = RC) which then determines the cut-off frequency (the frequency at which the filter attenuates the signal).

low-pass-equation

Implementation Concept:

Assuming you had two sine looking waves, one at 2Hz and another at 4Hz, it might look like this.

sine-wave-2hz-4hz

An ideal low-pass filter with a cutoff-frequency of 3Hz would completely eliminate the 4Hz signal, while leaving the 2Hz signal untouched. Since we want a 3Hz cutoff-frequency (anything between 2Hz and 4Hz will do), any combination of RC = .053 would produce the desired effect since 3hz = 1/(2*Pi*0.523). A perfect 3Hz low-pass filter would eliminate the 4Hz signal and leave the 2Hz signal... it might look like this.

sine-wave-2Hz

 However, in reality, nothing is ideal and the low-pass filter would not be able to completely eliminate the 4Hz signal, only attenuate it. We might see that the signal still has some of the offending 4Hz wave even after a low-pass filter is applied.

 sine-wave-2Hz-4Hz-attenuated

This is because, in all cases, at the cutoff frequency, the filter attenuates the input power by half or 3 dB. The order of the filter determines the amount of additional attenuation for frequencies higher than the cutoff frequency.

A first-order filter reduces the signal amplitude by half (power reduces by a factor of 4), or 6 dB, every time the frequency doubles. Second, third, etc... order filters attenuate the signal more steeply.

Digital Low-Pass Filters:

By using discrete-time, we can apply a low-pass filter to digital applications. The discrete time implementation of a RC low-pass filter is just the exponentially-weighted moving average.

discrete-time-low-pass-filter

By definition, the smoothing factor \scriptstyle 0 \;\leq\; \alpha \;\leq\; 1. The expression for \scriptstyle \alpha yields the equivalent time constant \scriptstyle RC in terms of the sampling period \scriptstyle \Delta_T and smoothing factor \scriptstyle \alpha:

 low-pass-filter-rc-alpha

If \scriptstyle \alpha \;=\; 0.5, then the \scriptstyle RC time constant is equal to the sampling period. If \scriptstyle \alpha \;\ll\; 0.5, then \scriptstyle RC is significantly larger than the sampling interval, and \scriptstyle \Delta_T \;\approx\; \alpha RC.

Digital Filter Pseudocode:

In pseudocode, the discrete-time low-pass filter would look like this:

for i from 1 to n

y[i] := y[i-1] + α * (x[i] - y[i-1])

Sampling in Discrete Time:

Digital signals require sampling with discrete time which makes things more interesting. An analog signal from an accelerometer looks something like this, with the true signal buried under some other noisy signals caused by all sorts of things.

sine-wave-noisey

To convert to a digital signal, points on the analog signal are sample at discrete time intervals, say 20Hz, and then are converted into bits. The true signal is hidden somewhere in the digital signal, but for all practical purposes is really gone forever due to noise and sample loss. We can only estimate as to what the true signal really is. If we were to sample the analog sine wave's maximum and minimum amplitudes at 20Hz we would get a digital signal that might look like this.

digital_sine_waves

You can see that we have shifted out of the frequency domain and into the time domain as the digital signal is always 20Hz (at least in this example it is 20Hz, it could be any frequency). With digital filtering, we are concerned with the amplitude of the signal over time as opposed to just the frequency of the analog signal. With a digital low-pass filters, we want to let signals that exist over long periods of time to pass through and eliminate signals that exist over short periods of time. We do that by using a weighted moving average to let changes in the signal build up slowly instead of all at once.

Examples of Low-Pass Filters:

The Android Devloper low-pass filter example uses a low-pass filter to isolate the force of gravity.

public class LowPassFilter
{
    private float alpha;
    private float[] acceleration;
     
    public float[] lowPassFilter(float[] input)
    {
        // Update the Android Developer low-pass filter
        // y[i] = y[i] * alpha + (1 - alpha) * x[i]
        this.acceleration[0] = alpha * acceleration[0]
                + (1 - alpha) * input[0];
        this.acceleration[1] = alpha * acceleration[1]
                + (1 - alpha) * input[1];
        this.acceleration[2] = alpha * acceleration[2]
                + (1 - alpha) * input[2];
         
        return acceleration;
    }
}

There are similar algorithms available from Wikipedia

public class LowPassFilter
{
	    private float alpha;
	    private float[] acceleration;
	     
	    public float[] lowPassFilter(float[] input)
	    {
	    	// Update the Wikipedia filter
			// y[i] = y[i] + alpha * (x[i] - y[i])
	    	acceleration[0] = acceleration[0] + alpha
					* (input[0] - acceleration[0]);
	    	acceleration[1] = acceleration[1] + alpha
					* (input[1] - acceleration[1]);
	    	acceleration[2] = acceleration[2] + alpha
					* (input[2] - acceleration[2]);
	         
	        return acceleration;
	    }
}

Discrete Time Low-Pass Filter: 

The two implementations accomplish the same thing, but there are important differences as the time constant and delivery rates that determine the value of alpha are the inverse of each other.

Wikipedia alpha:

α := dt / (RC + dt) where RC = time constant and dt = delivery rate

Android Developer alpha:

α := dt / (RC + dt) where dt = time constant and RC = delivery rate

Filter Coefficient/Alpha: A value 0 < a < 1 that determines how much weight should be applied to the high-pass and sometimes the low-pass portion of the signal.

Time Constant/T: The time constant, T, is the relative duration of the signal that the filter will act on. That means that on a low-pass filter, signals that are much longer than the time constant will pass through unaltered, but signals that are shorter than the time constant with be ignored.

Sample Period/dt: The time delta between updates, on average.

Testing:

Now that we have the basics of our low-pass filters, we can try to implement the filters to perform some testing. For collecting test data, a Droid Razr equipped with an STMicroelectronics accelerometer was used. Data was logged via LogCat. 

Static Alpha Results:

The Wikipedia version of the LPF was used, but the Android developer LPF can be implemented in much the same way, except the corresponding alpha value will be 1 minus the alpha value used in the Wikipedia LPF. The Wikipedia LPF takes the previous value and then adds the weighted difference between the new value and the previous value. When alpha is very small, the filter prefers the long term signal and only allows changes to accumulate very slowly. When alpha is close to 1, the filter prefers the short term signal and changes accumulate very quickly. A smaller value for alpha means more smoothing. 

low-pass-filter-static-wiki

We can see that the smoothing effect does not really occur until alpha is smaller than 0.1. Wikipedia states that a = dt/(T+dt) where T is the time constant of the filter and dt is the sample period (time between updates). If alpha is much less than 0.5, then T is much larger than the sample period (time between updates). If you just plug this code into your sensor listener alpha will be equal to 0.5 because T = dt. If you are just updating the filter whenever you receive an update from the sensor, alpha is just equal to 0.5. 

Dynamic Alpha Results:

Most developers will find that an alpha of 0.5 is probably not a good choice. On most Android devices, the maximum update rate is about 50-200Hz. Let's assume T = dt*((1-alpha)/alpha). With alpha at 0.5, you are left with T = dt. At 100Hz, T = 0.01, anything longer than 0.01 of a second gets passed through the filter. In other words, almost everything gets passed through the filter. Since you are implementing a low-pass filter, presumably to reduce noise (or maybe implement linear acceleration), you probably don't want to pass everything through the filter.

A more realistic time constant might be 0.18 seconds. If we set alpha to 0.1 and assume a sample period of 50Hz, we get a time constant of 0.18. In theory, signals that are much longer than 0.18 of a second are passing through the filter, and everything else is being filtered out. The time constant also gives you an idea of how latent your signal relative to the raw signal because of the filtering. As alpha becomes smaller, the time constant becomes larger and the filter performs more smoothing on the signal. As the signal becomes smoother, it also becomes more latent. There is always a trade-off. 

Another important consideration is that the time constant will change with the output frequency of the acceleration sensor. Different Android devices have different acceleration sensors with different output frequencies. Assuming an alpha of 0.5 and an output frequency of 100Hz, T = 0.01, anything longer than 0.01 seconds will be passed through the filter. The same alpha value of 0.5 on a device with an output frequency of 50Hz will have a T = 0.02, anything longer than 0.02 seconds will be passed through the filter. The low-pass filters will not have the same behavior across all devices. To ensure that T is the same across all devices, alpha would have to be chosen with consideration for the sample period, dt, of the device. We can accomplish this by defining our desired time constant, T, and then calculating the corresponding alpha value dynamically based on the sample period of the device, dt.

The Java code might look like this: 

*Note how the sample period is calculated using an averaging method. This is done because the sample period between two consecutive updates (sampleTimeNew-sampleTimeOld) is very inconsistent. The averaging method gives a much more steady estimation of the sample period.

// Time constant in seconds
static final float timeConstant = 0.297f;
private float alpha = 0.0f;
private float timestamp = System.nanoTime();
private float timestampOld = System.nanoTime();
private float output[];

private int count = 0;

public void LowPass(float[] input)
{
timestamp = System.nanoTime();

// Find the sample period (between updates).
// Convert from nanoseconds to seconds
dt = 1 / (count / ((timestamp - timestampOld) / 1000000000.0f));

count++;

alpha = timeConstant / (timeConstant + dt);
		
// Calculate alpha
alpha = dt/(timeConstant + dt);
			
// Update the filter
// y[i] = y[i] + alpha * (x[i] - y[i])
output[0] = output[0] + alpha * (input[0] - output[0]);
output[1] = output[1] + alpha * (input[1] - output[1]);
output[2] = output[2] + alpha * (input[2] - output[2]);
        
}

A Droid Razr that was used for testing has a sample frequency of about 50Hz and I used a time constant of 0.18 (contrived so alpha would be equal to about 0.1). I ran two versions of the filter at the same time using the same input data. The dynamic alpha low-pass filter calculated alpha at each update based off a predetermined time constant and the sample period. The static alpha low-pass filter used a fixed alpha of 0.1. The expected value of alpha from the dynamic filter was 0.1. The dynamic alpha, as you might expect, offers no advantage to the static alpha in terms of performance. However, the dynamic alpha will give consistent performance across many devices where the static alpha will not.

Android Developer Low-Pass Filter:

The Android Developer version of the low-pass filter has the same goal as the Wikipedia versions of the filter, but it is implemented and set up differently.

The sample code from Android Developer looks like this:

*Note how the sample period is calculated using an averaging method. This is done because the sample period between two consecutive updates (sampleTimeNew-sampleTimeOld) is very inconsistent. The averaging method gives a much more steady estimation of the sample period.

	// Time constant in seconds
	static final float timeConstant = 0.297f;
	private float alpha = 0.0f;
	private float timestamp = System.nanoTime();
	private float timestampOld = System.nanoTime();
	private float output[];

        private int count = 0;

	public void lowPass(float[] input)
	{
		timestamp = System.nanoTime();

                // Find the sample period (between updates).
		// Convert from nanoseconds to seconds
		dt = 1 / (count / ((timestamp - timestampOld) / 1000000000.0f));

                count++;

		// Calculate alpha
		alpha = timeConstant / (timeConstant + dt);

		output[0] = alpha * output[0] + (1 - alpha) * input[0];
		output[1] = alpha * output[1] + (1 - alpha) * input[1];
		output[2] = alpha * output[2] + (1 - alpha) * input[2];
	}

 

What is different from the Wikipedia LPF?

Alpha is calculated differently in the Android Developer LPF than in the Wikipedia LPF. In the Android Developer version, alpha is calculated as T/(T+dt) instead of dt/(dt+T) like in the Wikipedia version. That means that T = (alpha*dt)/(1-alpha) and that now an alpha close to 0 means very little smooth and an alpha close to 1 means lots of smoothing. It is the opposite of the Wikipedia filter.

Complementary LPF:

This filter can be thought of as something of a combination of a complementary high-pass filter and a low-pass filter. The first part of the equation, alpha*gravity, acts as a high-pass filter. The second part of the equation (1-alpha)*event.values, acts as a low pass filter. The complementary part of the filter means that both parts of the filter should add to 1, which is where the (1-alpha) comes from. It occurs to me that this is probably not the case unless you normalize the inputs and outputs to 1 with a partition function before applying the filter, but it is still an effective low-pass filter.

Static Alpha Results:

The Android Developer LPF acts very much like the Wikipedia LPF except that as alpha increases the filter removes longer signals. 

 

Very much like the Wikipedia LPF, we can see that the smoothing effect does not really occur until alpha is larger than 0.9 (equivalent Wikipedia alpha of 0.1). We now have T = (alpha*dt)/(1-alpha) where T is the time constant of the filter and dt is the sample period (time between updates). 

Dynamic Alpha Results:

Since we now assume T = (alpha*dt)/(1-alpha), and we want T = 0.18 so we an replicate the conditions for the Wikipedia LPF. With these conditions, the static alpha will be equal to 0.9 and the dynamic alpha will be equal to about 0.9. Once again, as you might expect, the dynamic  alpha offers no advantage over the static alpha. However, as with the Wikipedia version of the LPF, the dynamic alpha will give consistent performance across many devices where the static alpha will not.