Yellow Rabbit

Low-pass Filter in Practice

Low-pass Filter for Android Sensors - Practice

As was promised we move from the static smoothing to dynamic one in order to take into account the frequency variation of receiving data from device sensors on Android.

Dynamic Smoothing Ratio

The raw data is accumulated in rawAccData, processed by the filter - in lpfAccData. The variables count and beginTime are needed to calculate the average sampling period. In this simple program, the sensors are polled constantly so these variables can be initialized at startup. In real programs, you need to consider stopping the poll while in pause state, and so on.


    private final int MAX_TESTS_NUM = 200 * 60; // One minute measurement with a frequency of 200Hz
    private final float[] rawAccData = new float[MAX_TESTS_NUM * 3];
    private int rawAccDataIdx = 0;

    // LPF
    private final float[] lpfAccData = new float[MAX_TESTS_NUM * 3];
    private final float[] lpfPrevData = new float[3];
    private int count = 0;
    private float beginTime = System.nanoTime();
    private float rc = 0.002f;

Somewhere in the event handler from the sensors, let’s write the call to read the data:


    public void onSensorChanged(SensorEvent event) {
        // ...
            readSensorData(event);
        // ...

Read the data and apply the filter:


    private void readSensorData(SensorEvent event) {
        final int type = event.sensor.getType();
        if (type == Sensor.TYPE_ACCELEROMETER) {
            System.arraycopy(event.values, 0, rawAccData, rawAccDataIdx, 3);
            applyLPF();
            rawAccDataIdx += 3;
            if (rawAccDataIdx >= rawAccData.length) {
                stopMeasure();
            }
        }
    }

Actually the filter itself: calculate the average sampling period, determine \(\alpha\) and use the formula:


    private void applyLPF() {
        final float tm = System.nanoTime();
        final float dt = ((tm - beginTime) / 1000000000.0f) / count;

        final float alpha = rc / (rc + dt);

        if (count == 0) {
            lpfPrevData[0] = (1 - alpha) * rawAccData[rawAccDataIdx];
            lpfPrevData[1] = (1 - alpha) * rawAccData[rawAccDataIdx + 1];
            lpfPrevData[2] = (1 - alpha) * rawAccData[rawAccDataIdx + 2];
        } else {
            lpfPrevData[0] = alpha * lpfPrevData[0] + (1 - alpha) * rawAccData[rawAccDataIdx];
            lpfPrevData[1] = alpha * lpfPrevData[1] + (1 - alpha) * rawAccData[rawAccDataIdx + 1];
            lpfPrevData[2] = alpha * lpfPrevData[2] + (1 - alpha) * rawAccData[rawAccDataIdx + 2];
        }
        if (isStarted) {
            lpfAccData[rawAccDataIdx]     = lpfPrevData[0];
            lpfAccData[rawAccDataIdx + 1] = lpfPrevData[1];
            lpfAccData[rawAccDataIdx + 2] = lpfPrevData[2];
        }
        ++count;
    }

Practice

Let’s try to apply this filter to extract linear accelerations from the accelerometer signals by getting rid of the gravitational component.

Fixed phone, \(RC=0.002\):

Raw data, filter and difference graph for rc = 0.002 and fixed phone

Fixed phone at an angle, \(RC=0.002\):

Raw data, filter and difference graph for rc = 0.002 and tilted phone

It seems that we managed to get rid of the gravitational component very effectively. And this is not surprising: when \(RC=0.002\) the cutoff frequency of the filter will be \(f_c=79.577471\)Hz and means that virtually everything will pass through the filter, and we essentially subtract it from the input signal of it.

This is not very suitable for isolating linear accelerations.

Adjust the cutoff frequency

Take \(RC = 0.18 \), then the cutoff frequency will be \(f_c = 0.884194 \) Hz, and this will already filter out a significant amount of high frequencies.

Now move and tilt the phone:

Graph for rc = 0.18 and moving phone

When \(RC=0.288731\) and the cutoff frequency, respectively, \(f_c=0.551222\) Hz:

Graph for rc = 0.288731 and moving phone

Conclusion

Using a low-pass filter to extract the gravitational component in the readings of the Android accelerometer is justified only for short periods of time.