Несовместимые значения датчика ориентации на Android для азимута / рыскания и крена

У меня проблемы с получением хороших показаний датчика ориентации. Показания датчиков казались ненадежными, поэтому я проверил свой код с помощью двух бесплатных приложений для тестирования датчиков ( Sensor Tester (Dicotomica) и Sensor Monitoring (Программное обеспечение R)). Я обнаружил, что, хотя мои показания часто совпадали с приложениями для тестирования датчиков, иногда значения азимута / рыскания и крена отличались на 40 градусов, хотя показания основного тона в основном совпадали. Два бесплатных приложения всегда были согласны друг с другом.

Я поместил свой код в крошечную активность Android и получил то же несоответствие. Код выглядит следующим образом:

public class MainActivity extends Activity implements  SensorEventListener {

    private SensorManager mSensorManager;
    private float[] AccelerometerValues;
    private float[] MagneticFieldValues;
    private float[] RotationMatrix;
    private long nextRefreshTime;           // used to ensure dump to LogCat occurs no more than 4 times a second
    private DecimalFormat df;               // used for dumping sensors to LogCat

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSensorManager = (SensorManager)getSystemService(android.content.Context.SENSOR_SERVICE);
        Sensor SensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(this, SensorAccelerometer, SensorManager.SENSOR_DELAY_UI);  
        Sensor SensorMagField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        mSensorManager.registerListener(this, SensorMagField, SensorManager.SENSOR_DELAY_UI);
        AccelerometerValues = new float[3];
        MagneticFieldValues = new float[3];
        RotationMatrix = new float[9];
        nextRefreshTime = 0;
        df = new DecimalFormat("#.00");
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            System.arraycopy(event.values, 0, AccelerometerValues, 0, AccelerometerValues.length);
        else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
                System.arraycopy(event.values, 0, MagneticFieldValues, 0, MagneticFieldValues.length);

        if (AccelerometerValues != null && MagneticFieldValues != null) {
            if(SensorManager.getRotationMatrix(RotationMatrix, null, AccelerometerValues, MagneticFieldValues)) {
                float[] OrientationValues = new float[3];
                SensorManager.getOrientation(RotationMatrix, OrientationValues);

                // chance conventions to match sample apps
                if (OrientationValues[0] < 0) OrientationValues[0] += 2*(float)Math.PI;
                OrientationValues[2] *= -1;

                // dump to logcat 4 times a second
                long currentTimeMillis = System.currentTimeMillis();
                if (currentTimeMillis > nextRefreshTime) {
                    nextRefreshTime = currentTimeMillis+250;
                    Log.i("Sensors",    // arrange output so that numbers line up in columns :-)
                            "(" + AngleToStr(OrientationValues[0]) + "," + AngleToStr(OrientationValues[1]) + "," + AngleToStr(OrientationValues[2])
                            + ") ("+FloatToStr(AccelerometerValues[0]) + "," + FloatToStr(AccelerometerValues[1]) + "," + FloatToStr(AccelerometerValues[2])
                            + ") ("+FloatToStr(MagneticFieldValues[0]) + "," + FloatToStr(MagneticFieldValues[1]) + "," + FloatToStr(MagneticFieldValues[2])+")");
                }               
            }
        }               
    }

    private String AngleToStr(double AngleInRadians) {
        String Str = "   "+Integer.toString((int)Math.toDegrees(AngleInRadians));
        return Str.substring(Str.length() - 3);
    }
    private String FloatToStr(float flt) {
        String Str = "      "+df.format(flt);
        return Str.substring(Str.length() - 6);
    }   

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onAccuracyChanged(Sensor arg0, int arg1) { }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}

Я использую Galaxy Note 2 под управлением Jelly Bean 4.1.1. Может кто-нибудь сказать мне, что я делаю не так?

Обновление 24 марта 2013: дополнительная информация. (1) Я отключил переключение между портретным и альбомным режимами в манифесте, поэтому getWindowManager(). GetDefaultDisplay(). GetRotation() всегда равен нулю. Следовательно, я не думаю, что remapCoordSystem здесь поможет, потому что это для переключения осей, в то время как ошибки, которые я вижу, не большие ошибки, они намного более тонкие. (2) Я проверил чувствительность точности, и несоответствия возникают, когда оба датчика утверждают, что имеют высокую точность.

В качестве примера несоответствий, которые я вижу, когда приведенный выше код дает мне (азимут, высота, крен) = (235,-52,-11), тогда два бесплатных приложения показывают схожие значения. Но когда я вижу (278, -58, -52), приложения показывают (256, -58, -26), такие большие различия как по азимуту, так и по крену, хотя высота звука кажется нормальной.

4 ответа

Решение

Я думаю, что лучший способ определить ваши углы ориентации, когда устройство не плоское, это использовать более подходящую систему угловых координат, чем стандартные углы Эйлера, которые вы получаете от SensorManager.getOrientation(...), Я предлагаю тот, который я опишу здесь, на math.stackexchange.com. Я также поместил некоторый код, который реализует это в ответе здесь. Помимо хорошего определения азимута, он также имеет лучшее определение угла наклона, который эта методология определяет как вращение вне горизонтальной плоскости, независимо от того, по какой оси происходит вращение.

Вы можете получить полную информацию по двум ссылкам, которые я дал в первом абзаце. Тем не менее, в итоге, ваша матрица вращения R от SensorManager.getRotationMatrix(...)

Определение матрицы вращения R

где (Ex, Ey, Ez), (Nx, Ny, Nz) и (Gx, Gy, Gz) - векторы, указывающие на восток, север и в направлении силы тяжести. Тогда требуемый угол азимута

Определение азимутального угла фи

Все зависит от ориентации устройства. Если устройство не плоское и имеется некоторое вращение, т.е. не совсем книжное или альбомное, азимут неправильный. Вы должны позвонить remapCoordSystem перед звонком getOrientation в этом случае. Вы также должны хотя бы отфильтровать значения акселерометра.
Для более подробной информации смотрите мой ответ в разделе Преобразование значений магнитного поля X, Y, Z из устройства в глобальную систему отсчета.
А для значения плоскостности см. Как измерить наклон телефона в плоскости XY с помощью акселерометра в Android

Вы должны проверить свое приложение следующим образом:

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

  2. Во время работы приложения поверните устройство на новую позицию, подождите, пока значение не станет стабильным (необязательно, чтобы это значение было одинаковым, но отклонение составляет около 1 или 2 градусов). В той же позиции сравните ваши значения со значениями других приложений. Повторите то же самое, но с другими приложениями, работающими при повороте.
    Думаю, на этом шаге: ваши стабильные значения должны немного отличаться от значений других приложений, но для того, чтобы ваши значения стали стабильными, требуется больше времени. Кроме того, при остановке на новой позиции разница между первыми несколькими значениями и стабильным значением для вашего приложения больше, чем для другого.

  3. Теперь поместите следующий код перед комментарием //dump в logcat и зарегистрируйте его, как вы делаете для ориентации
    float вращение = (float) Math.atan2(RotationMatrix[6], RotationMatrix[7]);
    Поместите устройство в вертикальное положение в коробке, чтобы оно не могло вращаться. Убедитесь, что приведенное выше значение вращения максимально близко к 0 (оно немного подпрыгнет).
    Повторите шаги 1 и 2 с устройством в вертикальном положении.
    Я предполагаю, что ваш результат будет таким же, как мои предположения для шагов 1 и 2 выше.

  4. Поместите устройство в наклонное вертикальное положение в коробке, чтобы оно не могло вращаться дальше. Убедитесь, что приведенное выше значение вращения максимально приближено к 25 или -25 (оно немного подпрыгнет).
    Повторите шаг 1, теперь устройство наклонено вертикально.
    Я предполагаю, что ваш результат будет сильно отличаться от других приложений.
    Вы можете повторить шаг 4 с другим значением вращения.

Если мои догадки верны, вернись, и я дам тебе объяснения. В то же время вы можете прочитать мой ответ в Магнитные поля, матрица вращения и глобальные координаты

Я могу ответить на часть этой загадки. Все будет хорошо, когда устройство лежит на столе, но когда оно в вертикальном положении и угол наклона составляет плюс или минус 90 градусов, вы столкнетесь с блокировкой карданного подвеса. Когда это происходит, азимут и крен не определены. В этой ситуации азимут и крен означают одно и то же, и если вы посмотрите на математику, вы увидите, что либо азумит + крен определен (когда шаг = +90 градусов), либо азимутальный крен определен (когда шаг = -90 градусов), но они оба не определены сами по себе. В приведенном вами примере обратите внимание, что сумма азимут + крен примерно одинакова между вашим приложением и другими приложениями.

Кроме того, вы можете использовать Android Sensor.TYPE_GRAVITY, чтобы вам не нужно было использовать фильтр низких частот для показаний акселерометра. Sensor.TYPE_GRAVITY - это датчик, который рассчитывается от других датчиков, чтобы попытаться устранить эффекты ускорения от датчика акселерометра. Однако, если вы хотите, чтобы ваше приложение работало на старых версиях Android до появления Sensor.TYPE_GRAVITY, вам нужно будет выполнить работу самостоятельно.

@ Сказано для getOrientation(), используйте:

azimuth = atan2((Ey-Nx), (Ex-Ny))

Однако, если вы посмотрите на реализацию Android, это на самом деле

azimuth = atan2(Ey, Ny)

Это также даст вам значения в диапазоне от -180 до 180.

Ниже приведен исходный код, указанный в SensorManager.class

public static float[] getOrientation(float[] R, float values[]) {
    /*
     * 4x4 (length=16) case:
     *   /  R[ 0]   R[ 1]   R[ 2]   0  \
     *   |  R[ 4]   R[ 5]   R[ 6]   0  |
     *   |  R[ 8]   R[ 9]   R[10]   0  |
     *   \      0       0       0   1  /
     *
     * 3x3 (length=9) case:
     *   /  R[ 0]   R[ 1]   R[ 2]  \
     *   |  R[ 3]   R[ 4]   R[ 5]  |
     *   \  R[ 6]   R[ 7]   R[ 8]  /
     *
     */
    if (R.length == 9) {
        values[0] = (float)Math.atan2(R[1], R[4]);
        values[1] = (float)Math.asin(-R[7]);
        values[2] = (float)Math.atan2(-R[6], R[8]);
    } else {
        values[0] = (float)Math.atan2(R[1], R[5]);
        values[1] = (float)Math.asin(-R[9]);
        values[2] = (float)Math.atan2(-R[8], R[10]);
    }
    return values;
}
Другие вопросы по тегам