Как было обещано для учёта разброса частоты получения данных с датчиков устройств на Android переходим от статического коэффициента сглаживания к динамическому.
Сырые данные накапливаем в rawAccData
, обработанные фильтром - в lpfAccData
. Переменные count
и beginTime
нужны для вычисления среднего периода дискретизации. В этой простой программе датчики опрашиваются постоянно поэтому эти переменные можно инициализировать при запуске. В реальных программах нужно учитывать останов опроса на время паузы и т.д.
private final int MAX_TESTS_NUM = 200 * 60; // одна минута измерений с частотой 200Гц
private final float[] rawAccData = new float[MAX_TESTS_NUM * 3];
private int rawAccDataIdx = 0;
// ФНЧ
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;
Где-то в обработчике событий от датчиков пропишем вызов чтения данных:
public void onSensorChanged(SensorEvent event) {
// ...
readSensorData(event);
// ...
Считываем данные и применяем фильтр:
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();
}
}
}
Собственно сам фильтр: вычисляем средний период дискретизации, определяем \(\alpha\) и используем формулу:
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;
}
Попробуем применить этот фильтр для выделения линейных ускорений из сигналов акселерометра путем избавления от гравитационной составляющей.
Неподвижный телефон, \(RC=0.002\):
Неподвижный телефон под углом, \(RC=0.002\):
Кажется, что нам удалось очень эффективно избавиться от гравитационной составляющей. И это неудивительно: при \(RC=0.002\) частота среза фильтра будет \(f_c=79.577471\)Гц и значит, что через фильтр пройдёт фактически всё, и мы по сути вычитаем из входного сигнала его же.
Это не очень подходит для выделения линейных ускорений.
Возьмём \(RC=0.18\), тогда частота среза будет \(f_c=0.884194\)Гц, а это уже отфильтрует значительное количество верхних частот.
Теперь двигаем и наклоняем телефон:
При \(RC=0.288731\) и частоте среза соответственно \(f_c=0.551222\) Гц:
Используя фильтр нижних частот для выделения гравитационной составляющей в показаниях акселерометра Android оправдано лишь на коротких промежутках времени.