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

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

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

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

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

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

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

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

Спасибо адам

6 ответов

Решение

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

  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);
    }
}

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

Вместо использования 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, расположенные непосредственно под таблицей 1, это говорит о том, что датчик ROTATION идеально подходит для компаса, дополненной реальности и т. Д.

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

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

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

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;
    }

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

РЕДАКТИРОВАТЬ: в частности, взгляните на Java-класс MixView, который обрабатывает события датчика.

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

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

Другие вопросы по тегам