Ориентация 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
См. мой ответ на этот связанный вопрос: Сглаживание данных с датчика
Программный низкочастотный фильтр в основном является модифицированной версией этого. Действительно, в этом ответе я даже предоставил ссылку на другой связанный с этим вопрос: Программное обеспечение фильтра низких частот?