Различные значения между датчиками TYPE_ACCELEROMETER/TYPE_MAGNETIC_FIELD и TYPE_ORIENTATION

Есть два способа получить 3 значения вращения (азимут, шаг, рулон).

Один регистрирует прослушиватель типа TYPE_ORIENTATION. Это самый простой способ, и я получаю правильный диапазон значений от каждого вращения, поскольку в документации говорится: азимут: [0, 359] Шаг: [-180, 180] roll: [-90, 90]

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

азимут: [-180, 180]. -180/180 - S, 0 я N, 90 E и -90 Вт.
Шаг: [-90, 90]. 90 - 90, -90 - -90, 0 - 0, но -180/180 (лежащий с экраном вниз) - 0.
roll: [-180, 180].

Я должен получать одинаковые значения, но с десятичными знаками, верно?

У меня есть следующий код:

aValues = new float[3];
mValues = new float[3];

sensorListener = new SensorEventListener (){
    public void onSensorChanged (SensorEvent event){
        switch (event.sensor.getType ()){
            case Sensor.TYPE_ACCELEROMETER:
                aValues = event.values.clone ();
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                mValues = event.values.clone ();
                break;
        }

        float[] R = new float[16];
        float[] orientationValues = new float[3];

        SensorManager.getRotationMatrix (R, null, aValues, mValues);
        SensorManager.getOrientation (R, orientationValues);

        orientationValues[0] = (float)Math.toDegrees (orientationValues[0]);
        orientationValues[1] = (float)Math.toDegrees (orientationValues[1]);
        orientationValues[2] = (float)Math.toDegrees (orientationValues[2]);

        azimuthText.setText ("azimuth: " + orientationValues[0]);
        pitchText.setText ("pitch: " + orientationValues[1]);
        rollText.setText ("roll: " + orientationValues[2]);
    }

    public void onAccuracyChanged (Sensor sensor, int accuracy){}
};

Пожалуйста, помогите. Это очень расстраивает.

Должен ли я обрабатывать эти значения или я делаю что-то неправильно?

Спасибо.

Ответ 1

Я знаю, что я играю в никеры, но я много работал над этим материалом в последнее время, поэтому я подумал, что брошу свои 2 ¢.

Устройство не содержит компасов или инклинометров, поэтому оно не измеряет азимут, шаг или рулон напрямую. (Мы называем эти углы Эйлера, BTW). Вместо этого он использует акселерометры и магнитометры, оба из которых производят 3-пространственные векторы XYZ. Они используются для вычисления значений азимута и т.д.

Векторы находятся в координатном пространстве устройства:

Device coordinates

Мировые координаты Y обращены на север, X - на восток, а Z - вверх:

World coordinates

Таким образом, нейтральная ориентация устройства лежит на спине на столе, а верхняя часть устройства обращена на север.

Акселерометр создает вектор в направлении "UP". Магнитометр создает вектор в "северном" направлении. (Обратите внимание, что в северном полушарии это имеет тенденцию указывать вниз из-за магнитного падения.)

Вектор акселерометра и вектор магнитометра можно комбинировать математически через SensorManager.getRotationMatrix(), который возвращает матрицу 3x3, которая будет отображать векторы в координатах устройства в мировые координаты или наоборот. Для устройства в нейтральном положении эта функция вернет идентификационную матрицу.

Эта матрица не меняется с ориентацией экрана. Это означает, что ваше приложение должно знать ориентацию и соответственно компенсировать.

SensorManager.getOrientation() принимает матрицу преобразования и вычисляет значения азимута, тангажа и рулона. Они берутся относительно устройства в нейтральном положении.

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

Если устройство наклонено под углом 90 ° или около него, то использование углов Эйлера разваливается. Математически это вырожденный случай. В этой области, как устройство должно знать, изменяете ли вы азимут или рулон?

Функция SensorManager.remapCoordinateSystem() может использоваться для управления матрицей преобразования, чтобы компенсировать то, что вы знаете о ориентации устройства. Однако мои эксперименты показали, что это не охватывает все случаи, даже некоторые из обычных. Например, если вы хотите переназначить устройство, удерживаемое в вертикальном положении (например, чтобы сделать снимок), вы хотели бы умножить матрицу преобразования на эту матрицу:

1 0 0
0 0 1
0 1 0

перед вызовом getOrientation(), и это не одно из переназначений ориентации, которое remapCoordinateSystem() поддерживает [кто-то, пожалуйста, поправьте меня, если я что-то пропустил здесь].

ОК, так что все это был длинный способ сказать, что если вы используете ориентацию, либо из датчика TYPE_ORIENTATION, либо из getOrientation(), вы, вероятно, ошибаетесь. Единственный раз, когда вы на самом деле хотите, чтобы углы Эйлера отображали информацию о ориентации в удобной для пользователя форме, комментировать фотографию, управлять дисплеем полетного инструмента или что-то подобное.

Если вы хотите выполнять вычисления, связанные с ориентацией устройства, вам почти наверняка будет лучше использовать матрицу преобразования и работать с векторами XYZ.

Работая консультантом, всякий раз, когда кто-то приходит ко мне с проблемой, связанной с углами Эйлера, я подкрепляюсь и спрашиваю их, что они на самом деле пытаются сделать, а затем найдет способ сделать это с помощью векторов.

Возвращаясь к исходному вопросу, getOrientation() должен вернуть три значения в [-180 180] [-90 90] и [-180 180] (после преобразования из радианов). На практике мы рассматриваем азимут как цифры в [0 360], поэтому вам нужно просто добавить 360 к любым отрицательным числам, которые вы получаете. Ваш код выглядит правильно, как написано. Это помогло бы, если бы я точно знал, какие результаты вы ожидали, и что вы получали вместо этого.

Отредактировано для добавления: Еще несколько мыслей. В современных версиях Android используется нечто, называемое "слияние датчиков", что в основном означает, что все доступные входы - акселерометр, магнитометр, гироскоп - объединены вместе в математическом черном ящике (обычно это фильтр Калмана, но зависит от поставщика). В качестве выходов из этого черного ящика берутся все различные датчики - ускорение, магнитное поле, гироскопы, гравитация, линейное ускорение и ориентация.

По возможности вы должны использовать TYPE_GRAVITY, а не TYPE_ACCELEROMETER, как вход для getRotationMatrix().

Ответ 2

Я мог бы снимать в темноте здесь, но если я правильно понял ваш вопрос, вам интересно, почему вы получаете [-179..179] вместо [0..360]?

Обратите внимание, что -180 совпадает с +180 и тем же, что и 180 + N*360, где N - целое число (целое число).

Другими словами, если вы хотите получить те же номера, что и с датчиком ориентации, вы можете сделать это:

// x = orientationValues[0];
// y = orientationValues[1];
// z = orientationValues[2];
x = (x + 360.0) % 360.0;
y = (y + 360.0) % 360.0;
z = (z + 360.0) % 360.0;

Это даст вам значения в диапазоне [0..360], как вы хотели.

Ответ 3

Вам не хватает одного критического вычисления в ваших расчетах.
remapCoordinateSystem вызывают подтверждение getRotationMatrix.

Добавьте это в свой код, и все будет хорошо.
Вы можете прочитать больше об этом здесь.