Компас Android, который может компенсировать отклонение и шаг

Я пытаюсь сделать приложение на своем Android-телефоне (Nexus 4), которое будет использоваться в модельной лодке. Я добавил фильтры нижних частот, чтобы отфильтровать gitter от датчиков.

Однако компас стабилен только тогда, когда телефон плоский на спине. Если я наклоняю его (например, поворачивая страницу booK), то заголовок компаса уходит - до 50 *.

Я пробовал это с Sensor.TYPE_MAGNETIC_FIELD с помощью Sensor.TYPE_GRAVITY и Sensor.TYPE_ACCELEROMETER, и эффект тот же.

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

Я работаю над этой проблемой в течение 3 дней и до сих пор не нашел никакого решения, но когда я использую Compass from Catch, их устойчивость остается стабильной, независимо от того, насколько сильно настроен телефон. Поэтому я знаю, что это возможно.

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

Кто-нибудь может помочь, прежде чем мне придется отказаться от моего проекта.

Спасибо, Адам

Ответы

Ответ 1

По поводу совместного падения я думал об этой проблеме в течение нескольких недель, потому что

  1. Как математик, меня не удовлетворил ни один из ответов, которые я видел в другом месте; и
  2. Мне нужен хороший ответ для приложения, над которым я работаю.
Итак, за последние пару дней я придумал свой собственный способ вычисления азимутальное значение для использования в компасе.

Я поместил эту математику, что я использую здесь, на math.stackexchange.com, и я вставил код, который я использовал ниже. Код вычисляет азимут и шаг из исходных данных TYPE_GRAVITY и TYPE_MAGNETIC_FIELD без каких-либо вызовов API, например. SensorManager.getRotationMatrix(...) или SensorManager.getOrientation(...). Возможно, код может быть улучшен, например. используя фильтр нижних частот, если входы оказываются немного беспорядочными. Обратите внимание, что код записывает точность датчиков с помощью метода onAccuracyChanged(Sensor sensor, int accuracy), поэтому, если азимут кажется неустойчивым, еще одна вещь, чтобы проверить, насколько точны каждый датчик. В любом случае, при всех вычислениях, явно видимых в этом коде, если есть проблемы с неустойчивостью (когда точность датчика разумна), их можно было бы решить, посмотрев на неустойчивости на входах или в векторах направления m_NormGravityVector[], m_NormEastVector[] или m_NormNorthVector[].

Меня очень интересовала бы любая обратная связь, которая у кого-нибудь есть для меня по этому методу. Я нахожу, что он работает как сон в моем собственном приложении, если устройство плоское, вертикальное или где-то посередине. Однако, как я упоминаю в статье math.stackexchange.com, возникают проблемы, связанные с тем, что устройство приближается к тому, чтобы его перевернули вверх дном. В этой ситуации нужно будет тщательно определить, какое поведение требуется.

    import android.app.Activity;
    import android.hardware.Sensor;
    import android.hardware.SensorEvent;
    import android.hardware.SensorEventListener;
    import android.hardware.SensorManager;
    import android.view.Surface;

    public static class OrientationSensor implements  SensorEventListener {

    public final static int SENSOR_UNAVAILABLE = -1;

    // references to other objects
    SensorManager m_sm;
    SensorEventListener m_parent;   // non-null if this class should call its parent after onSensorChanged(...) and onAccuracyChanged(...) notifications
    Activity m_activity;            // current activity for call to getWindowManager().getDefaultDisplay().getRotation()

    // raw inputs from Android sensors
    float m_Norm_Gravity;           // length of raw gravity vector received in onSensorChanged(...).  NB: should be about 10
    float[] m_NormGravityVector;    // Normalised gravity vector, (i.e. length of this vector is 1), which points straight up into space
    float m_Norm_MagField;          // length of raw magnetic field vector received in onSensorChanged(...). 
    float[] m_NormMagFieldValues;   // Normalised magnetic field vector, (i.e. length of this vector is 1)

    // accuracy specifications. SENSOR_UNAVAILABLE if unknown, otherwise SensorManager.SENSOR_STATUS_UNRELIABLE, SENSOR_STATUS_ACCURACY_LOW, SENSOR_STATUS_ACCURACY_MEDIUM or SENSOR_STATUS_ACCURACY_HIGH
    int m_GravityAccuracy;          // accuracy of gravity sensor
    int m_MagneticFieldAccuracy;    // accuracy of magnetic field sensor

    // values calculated once gravity and magnetic field vectors are available
    float[] m_NormEastVector;       // normalised cross product of raw gravity vector with magnetic field values, points east
    float[] m_NormNorthVector;      // Normalised vector pointing to magnetic north
    boolean m_OrientationOK;        // set true if m_azimuth_radians and m_pitch_radians have successfully been calculated following a call to onSensorChanged(...)
    float m_azimuth_radians;        // angle of the device from magnetic north
    float m_pitch_radians;          // tilt angle of the device from the horizontal.  m_pitch_radians = 0 if the device if flat, m_pitch_radians = Math.PI/2 means the device is upright.
    float m_pitch_axis_radians;     // angle which defines the axis for the rotation m_pitch_radians

    public OrientationSensor(SensorManager sm, SensorEventListener parent) {
        m_sm = sm;
        m_parent = parent;
        m_activity = null;
        m_NormGravityVector = m_NormMagFieldValues = null;
        m_NormEastVector = new float[3];
        m_NormNorthVector = new float[3];
        m_OrientationOK = false;
    }

    public int Register(Activity activity, int sensorSpeed) {
        m_activity = activity;  // current activity required for call to getWindowManager().getDefaultDisplay().getRotation()
        m_NormGravityVector = new float[3];
        m_NormMagFieldValues = new float[3];
        m_OrientationOK = false;
        int count = 0;
        Sensor SensorGravity = m_sm.getDefaultSensor(Sensor.TYPE_GRAVITY);
        if (SensorGravity != null) {
            m_sm.registerListener(this, SensorGravity, sensorSpeed);
            m_GravityAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
            count++;
        } else {
            m_GravityAccuracy = SENSOR_UNAVAILABLE;
        }
        Sensor SensorMagField = m_sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (SensorMagField != null) {
            m_sm.registerListener(this, SensorMagField, sensorSpeed);
            m_MagneticFieldAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;     
            count++;
        } else {
            m_MagneticFieldAccuracy = SENSOR_UNAVAILABLE;
        }
        return count;
    }

    public void Unregister() {
        m_activity = null;
        m_NormGravityVector = m_NormMagFieldValues = null;
        m_OrientationOK = false;
        m_sm.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent evnt) {
        int SensorType = evnt.sensor.getType();
        switch(SensorType) {
            case Sensor.TYPE_GRAVITY:
                if (m_NormGravityVector == null) m_NormGravityVector = new float[3];
                System.arraycopy(evnt.values, 0, m_NormGravityVector, 0, m_NormGravityVector.length);                   
                m_Norm_Gravity = (float)Math.sqrt(m_NormGravityVector[0]*m_NormGravityVector[0] + m_NormGravityVector[1]*m_NormGravityVector[1] + m_NormGravityVector[2]*m_NormGravityVector[2]);
                for(int i=0; i < m_NormGravityVector.length; i++) m_NormGravityVector[i] /= m_Norm_Gravity;
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                if (m_NormMagFieldValues == null) m_NormMagFieldValues = new float[3];
                System.arraycopy(evnt.values, 0, m_NormMagFieldValues, 0, m_NormMagFieldValues.length);
                m_Norm_MagField = (float)Math.sqrt(m_NormMagFieldValues[0]*m_NormMagFieldValues[0] + m_NormMagFieldValues[1]*m_NormMagFieldValues[1] + m_NormMagFieldValues[2]*m_NormMagFieldValues[2]);
                for(int i=0; i < m_NormMagFieldValues.length; i++) m_NormMagFieldValues[i] /= m_Norm_MagField;  
                break;
        }
        if (m_NormGravityVector != null && m_NormMagFieldValues != null) {
            // first calculate the horizontal vector that points due east
            float East_x = m_NormMagFieldValues[1]*m_NormGravityVector[2] - m_NormMagFieldValues[2]*m_NormGravityVector[1];
            float East_y = m_NormMagFieldValues[2]*m_NormGravityVector[0] - m_NormMagFieldValues[0]*m_NormGravityVector[2];
            float East_z = m_NormMagFieldValues[0]*m_NormGravityVector[1] - m_NormMagFieldValues[1]*m_NormGravityVector[0];
            float norm_East = (float)Math.sqrt(East_x * East_x + East_y * East_y + East_z * East_z);
            if (m_Norm_Gravity * m_Norm_MagField * norm_East < 0.1f) {  // Typical values are  > 100.
                m_OrientationOK = false; // device is close to free fall (or in space?), or close to magnetic north pole.
            } else {
                m_NormEastVector[0] = East_x / norm_East; m_NormEastVector[1] = East_y / norm_East; m_NormEastVector[2] = East_z / norm_East;

                // next calculate the horizontal vector that points due north                   
                float M_dot_G = (m_NormGravityVector[0] *m_NormMagFieldValues[0] + m_NormGravityVector[1]*m_NormMagFieldValues[1] + m_NormGravityVector[2]*m_NormMagFieldValues[2]);
                float North_x = m_NormMagFieldValues[0] - m_NormGravityVector[0] * M_dot_G;
                float North_y = m_NormMagFieldValues[1] - m_NormGravityVector[1] * M_dot_G;
                float North_z = m_NormMagFieldValues[2] - m_NormGravityVector[2] * M_dot_G;
                float norm_North = (float)Math.sqrt(North_x * North_x + North_y * North_y + North_z * North_z);
                m_NormNorthVector[0] = North_x / norm_North; m_NormNorthVector[1] = North_y / norm_North; m_NormNorthVector[2] = North_z / norm_North;

                // take account of screen rotation away from its natural rotation
                int rotation = m_activity.getWindowManager().getDefaultDisplay().getRotation();
                float screen_adjustment = 0;
                switch(rotation) {
                    case Surface.ROTATION_0:   screen_adjustment =          0;         break;
                    case Surface.ROTATION_90:  screen_adjustment =   (float)Math.PI/2; break;
                    case Surface.ROTATION_180: screen_adjustment =   (float)Math.PI;   break;
                    case Surface.ROTATION_270: screen_adjustment = 3*(float)Math.PI/2; break;
                }
                // NB: the rotation matrix has now effectively been calculated. It consists of the three vectors m_NormEastVector[], m_NormNorthVector[] and m_NormGravityVector[]

                // calculate all the required angles from the rotation matrix
                // NB: see https://math.stackexchange.com/questions/381649/whats-the-best-3d-angular-co-ordinate-system-for-working-with-smartfone-apps
                float sin = m_NormEastVector[1] -  m_NormNorthVector[0], cos = m_NormEastVector[0] +  m_NormNorthVector[1];
                m_azimuth_radians = (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);
                m_pitch_radians = (float) Math.acos(m_NormGravityVector[2]);
                sin = -m_NormEastVector[1] -  m_NormNorthVector[0]; cos = m_NormEastVector[0] -  m_NormNorthVector[1];
                float aximuth_plus_two_pitch_axis_radians = (float)(sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0);
                m_pitch_axis_radians = (float)(aximuth_plus_two_pitch_axis_radians - m_azimuth_radians) / 2;
                m_azimuth_radians += screen_adjustment;
                m_pitch_axis_radians += screen_adjustment;
                m_OrientationOK = true;                                 
            }
        }
        if (m_parent != null) m_parent.onSensorChanged(evnt);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        int SensorType = sensor.getType();
        switch(SensorType) {
            case Sensor.TYPE_GRAVITY: m_GravityAccuracy = accuracy; break;
            case Sensor.TYPE_MAGNETIC_FIELD: m_MagneticFieldAccuracy = accuracy; break;
        }
        if (m_parent != null) m_parent.onAccuracyChanged(sensor, accuracy);
    }
}

Ответ 2

Хорошо, думаю, я решил это.

Вместо использования Sensor.TYPE_ACCELEROMETER (или TYPE_GRAVITY) и Sensor.TYPE_MAGNETIC_FIELD, я использовал Sensor.TYPE_ROTATION_VECTOR с:

float[] roationV = new float[16];
SensorManager.getRotationMatrixFromVector(roationV, rotationVector);

float[] orientationValuesV = new float[3];
SensorManager.getOrientation(roationV, orientationValuesV);

Это вернуло стабильный азимут независимо от того, катив или шаг телефона.

Если вы посмотрите здесь, на Android Motion Sensors, как раз под Таблицей 1, он говорит, что датчик ROTATION идеально подходит для компаса, дополненной реальности и т.д.

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

Ответ 3

Проблема у вас есть, вероятно, Gimbal lock. Если вы думаете об этом, когда телефон в вертикальном положении, так что шаг плюс или минус 90 градусов, тогда азимут и рулон - это одно и то же. Если вы посмотрите на математику, вы увидите, что в этой ситуации азимут + рулон или азимут-ролл хорошо определены, но они не определяются индивидуально. Поэтому, когда шаг приближается к плюс или минус 90 градусов, показания становятся неустойчивыми. Некоторые люди предпочитают переназначать систему координат tom и обходить ее, см., Например, Как рассчитать азимут, шаг, ориентацию, когда мое устройство Android не плоское?, возможно, это может сработать для вас.

Ответ 4

Взгляните на Mixare, инструмент для расширенной реальности с открытым исходным кодом для Android и iphone, в нем есть замечательные вещи о компенсации ориентация/положение телефона, чтобы правильно отображать вещи на экране.

EDIT: в частности рассмотрим класс Java MixView, который обрабатывает события датчика.

Ответ 5

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

private final static double PI = Math.PI;
private final static double TWO_PI = PI*2;

 case Sensor.TYPE_ROTATION_VECTOR:
                float[] orientation = new float[3];
                float[] rotationMatrix = new float[9];

                SensorManager.getRotationMatrixFromVector(rotationMatrix, rawValues);
                SensorManager.getOrientation(rotationMatrix, orientation);

                float heading = mod(orientation[0] + TWO_PI,TWO_PI);//important
                //do something with the heading
                break;



private double mod(double a, double b){
        return a % b;
    }

Ответ 6

я обнаружил, что на некоторых моделях смартфонов активация камеры может изменить данные COMPASS... 1/10 grads... (связанные со светом сцены)

черная сцена... 1/2.... очень белая сцена (10 или более градаций)