Ориентация Android Compass на ненадежную (фильтр нижних частот)

Я создаю приложение, в котором мне нужно разместить ImageView в зависимости от ориентации устройства. Я использую значения от датчиков MagneticField и Accelerometer для расчета ориентации устройства с помощью

SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticFieldValues)
SensorManager.getOrientation(rotationMatrix, values);
double degrees = Math.toDegrees(values[0]);

Моя проблема в том, что позиционирование ImageView очень чувствительно к изменениям ориентации. Постоянное сканирование изображения по экрану. (потому что степени меняются)

Я читал, что это может быть связано с тем, что мое устройство близко к вещам, которые могут повлиять на показания магнитного поля. Но это не единственная причина.

Я попытался загрузить некоторые приложения и обнаружил, что 3D-компас "и" Compass" остается очень устойчивым в своих показаниях (при установке фильтра шума вверх), мне бы хотелось, чтобы в моем приложении было такое же поведение.

Я читал, что могу настроить "шум" моих чтений, добавив " фильтр нижних частот", но я не знаю, как это реализовать (из-за отсутствия математики).

Я надеюсь, что кто-то может помочь мне создать более устойчивое чтение на моем устройстве, где небольшое движение к устройству не повлияет на текущую ориентацию. Прямо сейчас я делаю небольшой

if (Math.abs(lastReadingDegrees - newReadingDegrees) > 1) { updatePosition() }

Для фильтрации помех от шума. Но он не очень хорошо работает:)

Ответы

Ответ 1

Хотя я не использовал компас на Android, базовая обработка, показанная ниже (в JavaScript), вероятно, сработает для вас.

Он основан на фильтрах нижних частот на акселерометре, рекомендованных командой Windows Phone с изменениями в соответствии с компасом (циклическое поведение каждые 360).

Я предполагаю, что показания компаса находятся в градусах, поплавок между 0-360, а выход должен быть аналогичным.

Вы хотите выполнить 2 действия в фильтре:

  • Если изменение невелик, чтобы предотвратить появление gitter, постепенно переходите к этому направлению.
  • Если изменение велико, чтобы предотвратить задержку, немедленно перейдите в это направление (и его можно отменить, если вы хотите, чтобы компас двигался только плавно).

Для этого мы будем иметь 2 константы:

  • Плавающий float, который определяет, насколько плавное движение будет (1 не сглаживается, а 0 никогда не обновляется, по умолчанию - 0,5). Мы будем называть его SmoothFactorCompass.
  • Порог, в котором расстояние достаточно велико, чтобы сразу поворачиваться (0 всегда прыгает, 360 никогда не прыгает, по умолчанию 30). Мы будем называть его SmoothThresholdCompass.

У нас есть одна переменная, сохраненная во всех вызовах, float, называемая oldCompass, и это результат алгоритма.

Таким образом, ограничение переменной:

var SmoothFactorCompass = 0.5;
var SmoothThresholdCompass = 30.0;
var oldCompass = 0.0;

и функция получает newCompass и возвращает результат oldCompass.

if (Math.abs(newCompass - oldCompass) < 180) {
    if (Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
        oldCompass = newCompass;
    }
    else {
        oldCompass = oldCompass + SmoothFactorCompass * (newCompass - oldCompass);
    }
}
else {
    if (360.0 - Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
        oldCompass = newCompass;
    }
    else {
        if (oldCompass > newCompass) {
            oldCompass = (oldCompass + SmoothFactorCompass * ((360 + newCompass - oldCompass) % 360) + 360) % 360;
        } 
        else {
            oldCompass = (oldCompass - SmoothFactorCompass * ((360 - newCompass + oldCompass) % 360) + 360) % 360;
        }
    }
}

Я вижу, что проблема была открыта 5 месяцев назад и, вероятно, уже не актуальна, но я уверен, что другие программисты могут найти ее полезной.

Одед Элиада.

Ответ 2

Этот фильтр нижних частот работает для углов в радианах. Используйте функцию добавить для каждого чтения компаса, а затем вызовите средний, чтобы получить среднее значение.

public class AngleLowpassFilter {

    private final int LENGTH = 10;

    private float sumSin, sumCos;

    private ArrayDeque<Float> queue = new ArrayDeque<Float>();

    public void add(float radians){

        sumSin += (float) Math.sin(radians);

        sumCos += (float) Math.cos(radians);

        queue.add(radians);

        if(queue.size() > LENGTH){

            float old = queue.poll();

            sumSin -= Math.sin(old);

            sumCos -= Math.cos(old);
        }
    }

    public float average(){

        int size = queue.size();

        return (float) Math.atan2(sumSin / size, sumCos / size);
    }
}

Используйте Math.toDegrees() или Math.toRadians() для преобразования.

Ответ 3

Имейте в виду, что, например, среднее значение 350 и 10 не равно 180. Мое решение:

int difference = 0;
for(int i= 1;i <numberOfAngles;i++){
    difference += ( (angles[i]- angles[0] + 180 + 360 ) % 360 ) - 180;
}
averageAngle = (360 + angles[0] + ( difference / numberOfAngles ) ) % 360;

Ответ 4

Фильтр низких частот (LPF) блокирует быстро меняющиеся сигналы и
допускает только медленные изменения сигналов. Это означает, что любой маленький внезапные изменения будут проигнорированы.

Стандартный способ реализовать это в программном обеспечении - это взять среднее значение
из последних N выборок и сообщить об этом значении. Начните с N как минимум 3 и
продолжайте увеличивать N, пока не найдете достаточно сглаженного ответа в своем приложении.

Помните, что чем выше вы делаете N, тем медленнее реагируете на систему.

Ответ 5

См. мой ответ на этот связанный вопрос: Сглаживание данных с датчика

Программный низкочастотный фильтр в основном является модифицированной версией этого. Действительно, в этом ответе я даже предоставил ссылку на другой связанный с этим вопрос: Программное обеспечение фильтра низких частот?