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.
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;
}
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\):
Fixed phone at an angle, \(RC=0.002\):
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.
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:
When \(RC=0.288731\) and the cutoff frequency, respectively, \(f_c=0.551222\) Hz:
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.