30

I have a 3D sensor which measures v(x,y,z) data. I'm only using the x and y data. Smoothing only x and y would be enough.

If I use a log to show the data, it shows me something like this: (time) 0.1 ... (Data log) x = 1.1234566667 (time) 0.2 ... (Data log) x = 1.1245655666 (time) 0.3 ... (data log) x = 1.2344445555

Well the data is more exact actually, but I want to smooth between the 1.1234 value and the 1.2344 value, because for me it's the same, I can use integers to, showing only "x= 1" but I need the decimals too, then, I need to show a sort of "smoothed" value here.

Anyone has any idea? I'm programming in c# but not all the functions are working, so I need to build my own function.

HamZa
  • 14,671
  • 11
  • 54
  • 75
Mworks
  • 301
  • 1
  • 4
  • 3

6 Answers6

70

The simplest is to do a moving average of your data. That is, to keep an array of sensor data readings and average them. Something like this (pseudocode):

  data_X = [0,0,0,0,0];

  function read_X () {
      data_X.delete_first_element();
      data_X.push(get_sensor_data_X());
      return average(data_X);
   }

There is a trade-off when doing this. The larger the array you use, the smoother the result will be but the larger the lag between the result and the actual reading is. For example:

                           /\_/\
                        /\/     \_/\
  Sensor reading:  __/\/            \/\
                                       \/\  _/\___________
                                          \/
                              _
                           __/ \_
                       ___/      \__
  Small array:     ___/             \_/\_       _
                                         \   __/ \________
                                          \_/

                                 ____
                              __/    \__
                           __/           \__
  Large array:     _______/                 \__      __
                                               \_   /  \__
                                                 \_/


(forgive my ASCII-ART but I'm hoping it's good enough for illustration).

If you want fast response but good smoothing anyway then what you'd use is a weighted average of the array. This is basically Digital Signal Processing (with capital DSP) which contrary to its name is more closely related to analog design. Here's a short wikipedia article about it (with good external links which you should read if you want to go down this path): http://en.wikipedia.org/wiki/Digital_filter

Here's some code from SO about a low pass filter which may suit your needs: Low pass filter software?. Notice that in the code in that answer he's using an array of size 4 (or order 4 in signal processing terminology because such filters are called fourth-order filter, it can actually be modeled by a 4th order polynomial equation: ax^4 + bx^3 + cx^2 + dx).

Community
  • 1
  • 1
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Thank you very much!!! This really helped me, I'm implementing the code right now, from now on I'll be helping people just like you, well, if I can of course. Thax!! – Mworks Jan 07 '11 at 00:20
  • 3
    A moving average filter is a special case of the low pass filter that is a pretty crummy filter (in terms of performance). A first order low pass filter is often (usually?) better than moving averages in terms of frequency response and computational load and program complexity. For many applications you can ignore these details, for example a compass display that can respond slowly, a moving average would be great. If you have a game where you want fast response using noisy sensors, the moving average will be a poor solution because of the lag it incurs for a given amount of filtering. – Hucker Oct 05 '11 at 14:55
  • 18
    Great answer made awesome by the ASCII art – Spaceghost Jan 28 '12 at 21:41
  • A different question linked to this one, here is the Java+Android code that I created based on your pseudocode: http://stackoverflow.com/a/24600534/663058 – CenterOrbit Jul 06 '14 at 21:40
26

So I came here looking to solve the same problem (sensor input smoothing in Android) and here's what I came up with:

/*
 * time smoothing constant for low-pass filter
 * 0 ≤ α ≤ 1 ; a smaller value basically means more smoothing
 * See: http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization
 */
static final float ALPHA = 0.2f;

protected float[] accelVals;

public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        accelVals = lowPass( event.values, accelVals );

    // use smoothed accelVals here; see this link for a simple compass example:
    // http://www.codingforandroid.com/2011/01/using-orientation-sensors-simple.html
}

/**
 * @see http://en.wikipedia.org/wiki/Low-pass_filter#Algorithmic_implementation
 * @see http://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter
 */
protected float[] lowPass( float[] input, float[] output ) {
    if ( output == null ) return input;

    for ( int i=0; i<input.length; i++ ) {
        output[i] = output[i] + ALPHA * (input[i] - output[i]);
    }
    return output;
}

Thank you @slebetman for pointing me toward the Wikipedia link, which after a little reading drew me to the algorithm on the wikipedia Low-pass filter article. I won't swear I have the best algorithm (or even right!) but anecdotal evidence seems to indicate it's doing the trick.

felixd
  • 383
  • 2
  • 7
  • 20
thom_nic
  • 7,809
  • 6
  • 42
  • 43
  • 3
    just wanted to say that if you use this code on android, return input.copy() initially instead of the array itself. My sensor writes into the same array, i.e. input and output are the same array and smoothing won't work. – Robert Ende Oct 11 '14 at 22:56
8

Well there are many ways to smooth sensor data depends what kind of sensor it is and what analogy will suit. I have used these algorithms in my projects:

  1. High Pass Filter[HPF] and Low Pass Filters[LPF]- as expressed in the selected answer.
  2. Moving Average Algorithm -MAA
  3. Gaely's Algorithmm[Better version for MAA]
  4. Fast Fourier Transform -FFT

Code:

HPF-High Pass Filter

private float[] highPass(float x, float y, float z) {
    float[] filteredValues = new float[3];
    gravity[0] = ALPHA * gravity[0] + (1 – ALPHA) * x;
    gravity[1] = ALPHA * gravity[1] + (1 – ALPHA) * y;
    gravity[2] = ALPHA * gravity[2] + (1 – ALPHA) * z;
    filteredValues[0] = x – gravity[0];
    filteredValues[1] = y – gravity[1];
    filteredValues[2] = z – gravity[2];
    return filteredValues;   
    }

LPF-Low Pass Filter

private float[] lowPass(float x, float y, float z) {
    float[] filteredValues = new float[3];
    filteredValues[0] = x * a + filteredValues[0] * (1.0f – a);
    filteredValues[1] = y * a + filteredValues[1] * (1.0f – a);
    filteredValues[2] = z * a + filteredValues[2] * (1.0f – a);
    return filteredValues;
    }

MAA-Moving Average

     private final int SMOOTH_FACTOR_MAA = 2;//increase for better results   but hits cpu bad

     public ArrayList<Float> processWithMovingAverageGravity(ArrayList<Float> list, ArrayList<Float> gList) {
            int listSize = list.size();//input list
            int iterations = listSize / SMOOTH_FACTOR_MAA;
            if (!AppUtility.isNullOrEmpty(gList)) {
                gList.clear();
            }
            for (int i = 0, node = 0; i < iterations; i++) {
                float num = 0;
                for (int k = node; k < node + SMOOTH_FACTOR_MAA; k++) {
                    num = num + list.get(k);
                }
                node = node + SMOOTH_FACTOR_MAA;
                num = num / SMOOTH_FACTOR_MAA;
                gList.add(num);//out put list
            }
            return gList;
        }
Jayant Arora
  • 1,241
  • 2
  • 15
  • 24
2

Here is an example based on the logic in the MotionEvents section of the Event Handling guide for iOS.

float ALPHA = 0.1;

protected float[] lowPass( float[] input, float[] output ) {
    if ( output == null ) return input;

    for ( int i=0; i<input.length; i++ ) {
        output[i] = (input[i] * ALPHA) + (ouptut[i] * (1.0 - ALPHA));
    }
    return output;
}
rockfakie
  • 2,330
  • 3
  • 17
  • 9
1

There is a small but very important typo in @thom_nic's low-pass filter code and result of this realization differs significantly.

protected float[] lowPass( float[] input, float[] output ) {
    if ( output == null ) return input;

    for ( int i=0; i<input.length; i++ ) {
        output[i] = output[i] + ALPHA * (input[i] - output[i]); // ERROR HERE
    }
    return output;
}

On my data, the result (orange line) of this code would be: bad lowpass implementation

You can find right code here wikipedia Low-pass_filter, and C code would be this:

protected float[] lowPass( float[] input, float[] output ) {
    if ( output == null ) return input;

    output[0] = input[0];
    for (int i=1; i<input.length; i++) {
        output[i] = output[i-1] + ALPHA * (input[i] - output[i-1]);
    }
    return output;
}

Please note the replaced indexes (i -> i-1).

On the same data and with the same ALPHA this code produces this: enter image description here

leo
  • 387
  • 1
  • 6
  • 1
    Good catch, but it's not quite correct. In the case of the wiki formula, `output[i-1]` stands for the previous value from the output array containing a history of measurements, but the `lowPass()` function from the @thom_nic answer applies a filter to all metrics of a single measurement. You can't just use these metrics in the same formula. So @thom_nic's function is correct. – Nikolay Sep 21 '21 at 10:42
  • You're right. It's all about attention – leo May 02 '22 at 23:44
0

Digging up an old question here, but if you're in .NET land, you can use the RX to do this for you.

For example, using RX in conjunction with WebClient.DownloadFileAsync to calculate a "smoothed" download speed:

double interval = 2.0; // 2 seconds
long bytesReceivedSplit = 0;

WebClient wc = new WebClient();
var downloadProgress = Observable.FromEventPattern<
    DownloadProgressChangedEventHandler, DownloadProgressChangedEventArgs>(
    h => wc.DownloadProgressChanged += h,
    h => wc.DownloadProgressChanged -= h)
    .Select(x => x.EventArgs);

downloadProgress.Sample(TimeSpan.FromSeconds(interval)).Subscribe(x =>
    {
        Console.WriteLine((x.BytesReceived - bytesReceivedSplit) / interval);
        bytesReceivedSplit = x.BytesReceived;
    });

Uri source = new Uri("http://someaddress.com/somefile.zip");
wc.DownloadFileAsync(source, @"C:\temp\somefile.zip");

Obviously the longer the interval, the greater the smoothing will be, but also the longer you will have to wait for an initial reading.

CatBusStop
  • 3,347
  • 7
  • 42
  • 54