filters


I've got some real time accelerometer and gyroscope data coming in on my project to recognise hand gestures here. Naturally, I would like to be able to remove jitter and noise from the data as painlessly as possible. So we are into the world of real time digital filtering. Many books have been written on this subject and it is easy to dive 'down the rabbit hole' and lose a lot of your life testing filters. I want something that is 'good enough' quickly. From this stackoverflow answer, I got the idea for a simple implementation of a moving average filter:

x' x'+α(xx'

where α<1


The first term, x', is the new filtered value, calculated by taking the last filtered value and adding α.(last measured value-last filtered value). 
So how to simulate and implement this?  

Simulation

First of all, I will simulate the idea. Filters are implemented by using convolution. The input data is convolved with a filter operator. So I tried out a filter operator:

(1-αα)

Here's a simple script I ran using a Jupyter notebook and Python 3. I lifted the base code from the matplotlib examples page. I generated a sine wave and add some random noise in line 6. The filter function is defined in line 5, I am using alpha = 0.5 for this example. The function np.convolve in line 7 implements the filter. I had to knock off the last element of the filtered and difference data to get them all to plot, as one of the characteristics of a filter is that it will elongate the data set.  Really, you need to 'pad' a data set at each end before applying convolution to remove 'edge effects' of the filter. But we are looking to quickly test and implement a filter here, not get bogged down in the technical minutia of filter design. Rabbit hole. Avoid.

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. ALPHA = 0.5
  4. x = np.linspace(02 * np.pi100)
  5. filter = (1-ALPHA,ALPHA*1)
  6. y = 2 * np.sin(x) + 0.1 * np.random.normal(x)
  7. y_filt = np.convolve(y, filter)
  8. y_diff = y - y_filt[:-1]
  9.  
  10. print(y)
  11. print(y_filt[:-1])
  12. print(y_diff[:-1])
  13.  
  14. fig, (ax0, ax1, ax2) = plt.subplots(nrows=3)
  15.  
  16. ax0.plot(x, y)
  17. ax0.set_title('input')
  18.  
  19. ax1.plot(x, y_filt[:-1])
  20. ax1.set_title('output')
  21.  
  22. ax2.plot(x, y_diff)
  23. ax2.set_title('difference')
  24.  
  25. # Hide the right and top spines
  26. ax1.spines['right'].set_visible(False)
  27. ax1.spines['top'].set_visible(False)
  28. # Only show ticks on the left and bottom spines
  29. ax1.yaxis.set_ticks_position('left')
  30. ax1.xaxis.set_ticks_position('bottom')
  31.  
  32. # Tweak spacing between subplots to prevent labels from overlapping
  33. plt.subplots_adjust(hspace=0.5)
  34.  
  35. plt.show()
For an input, filtered output and difference plot, see below. Note that the difference plot is on a different scaling to the input and filtered output. Looks to have the same amplitude and that some of the random noise has been removed. It is not perfect, but it has helped and was fast and easy to implement. 
simple filter, input, output, difference
Will it be good enough in real life? Time to test.

Implementation

I am using micropython v1.7 on a pyboard v1.0 with an mpu6050 accelerometer/gyroscope for my hardware platform - see the diagram below. So how hard could it be to implement a simple one point filter? Errrr....

pyboard and mpu6050 3-axis accelerometer/gyroscope board

The filter code is straightforwards, see the snippet below. The function filter takes the latest sensor value as new_value and uses the last filtered value as old_value, returning the latest filtered value. I am using ALPHA as 0.5 for this test.
  1.     def filter(self, old_value, new_value):
  2.         ''' simple moving average filter '''
  3.         return (new_value*ALPHA + (1-ALPHA)*old_value)

This function is called from the main sensor scan and process while loop for each of the x,y and z accelerometers, shown in the snippet below.
  1.         while(True):
  2.             if (self.run_flag):
  3.                 if(self.acc_read_flag):
  4.                     self.counter+= 1
  5.                     (delta, x, y, z) = self.read_acc()
  6.                     x_acc = x
  7.                     x = self.filter(old_x, x)
  8.                     y = self.filter(old_y, y)
  9.                     z = self.filter(old_z, z)
  10.                     print(START, self.counter, delta, x_acc, x, x-x_acc, END)
  11.                     old_x, old_y, old_z = x, y, z

Have a look at the plot below. This shows the x-axis from an imu6050 module being sampled through a pyboard v1.0. at 100Hz. I wrote the firmware for this board using micropython v1.7 and the display software using python 3.4 with the pyqtgraph library. The x scale shows samples, the y scale shows acceleration in g.

accelerometer data for one axis of a 3-axis accelerometer on an imu6050

So what can we see? The raw data looks jittery, the filtered data looks smoother and we can see the jitter that has been taken out in the difference plot. To characterise this filter properly I would need to start looking at the frequency spectrum of the raw and filtered data. But this is heading down the rabbit hole again. 
I've quickly implemented a filter that looks to be doing what I want it to - removing noise from data. I can play with the alpha value to change the amount of smoothing. 'The proof is in the eating'. If I can get my gesture recognition system to work with this simple filter implemented, then it is good enough. 
Comments