Brújula Android que puede compensar la inclinación y la inclinación

Estoy tratando de hacer una aplicación en mi teléfono Android (Nexus 4), que se utilizará en un barco modelo. He agregado filtros de paso bajo para filtrar el gitter de los sensores.

Sin embargo, la brújula solo es estable cuando el teléfono está apoyado sobre su espalda. Si lo inclino hacia arriba, (como al girar una página de un booK), el encabezado de la brújula se irá muy lejos, hasta 50 *.

Lo he intentado con Sensor.TYPE_MAGNETIC_FIELD con Sensor.TYPE_GRAVITY y Sensor.TYPE_ACCELEROMETER y el efecto es el mismo.

He usado la solución mencionada aquí , y muchos otros lugares. Mis cálculos no son geniales, pero este debe ser un problema común y me resulta frustrante que no haya una API para manejarlo.

He estado trabajando en este problema durante 3 días y todavía no he encontrado ninguna solución, pero cuando uso la brújula de Catch , la suya se mantiene estable sin importar cuánto se inclina el teléfono. Entonces sé que debe ser posible.

Todo lo que quiero hacer es crear una brújula que, si el teléfono está apuntando, digamos norte, la brújula leerá al norte y no saltará cuando el teléfono se mueva a través de cualquier otro eje (balanceo o inclinación).

¿Puede alguien ayudarme antes de tener que abandonar mi proyecto?

Gracias, Adam

Por coincidencia he estado pensando en este problema durante varias semanas, porque

  1. Como matemático, no he estado satisfecho con ninguna de las respuestas que he visto en otros lugares; y
  2. Necesito una buena respuesta para una aplicación en la que estoy trabajando.

Así que en los últimos días he encontrado mi propia forma de calcular el valor de azimut para usar en una brújula.

He puesto esas matemáticas que estoy usando aquí en math.stackexchange.com , y he pegado el código que he usado a continuación. El código calcula el azimut y el tono de los datos de sensor raw TYPE_GRAVITY y TYPE_MAGNETIC_FIELD , sin ninguna llamada API a, por ejemplo, SensorManager.getRotationMatrix(...) o SensorManager.getOrientation(...) . El código probablemente podría mejorarse, por ejemplo, usando un filtro de paso bajo si las entradas resultan ser un poco erráticas. Tenga en cuenta que el código registra la precisión de los sensores a través del método de onAccuracyChanged(Sensor sensor, int accuracy) , por lo que si el azimut parece inestable, otra cosa para verificar es la precisión de cada sensor. En cualquier caso, con todos los cálculos explícitamente visibles en este código, si hay problemas de inestabilidad (cuando la precisión del sensor es razonable) entonces podrían abordarse observando las inestabilidades en las entradas o en los vectores de dirección m_NormGravityVector[] , m_NormEastVector[] o m_NormNorthVector[] .

Estaría muy interesado en cualquier comentario que alguien tenga sobre este método. Me parece que funciona como un sueño en mi propia aplicación, siempre que el dispositivo esté plano boca arriba, vertical o en algún punto intermedio. Sin embargo, como menciono en el artículo math.stackexchange.com, hay problemas que surgen cuando el dispositivo se acerca a ser volteado. En esa situación, uno debería definir cuidadosamente qué comportamiento desea.

  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, (ie 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, (ie 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); } } 

OK, creo que lo resolví.

En lugar de utilizar Sensor.TYPE_ACCELEROMETER (o TYPE_GRAVITY) y Sensor.TYPE_MAGNETIC_FIELD, utilicé Sensor.TYPE_ROTATION_VECTOR con:

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

Esto devolvió un azimut estable sin importar el balanceo o el tono del teléfono.

Si miras aquí los sensores de movimiento de Android , justo debajo de la Tabla 1, dice que el sensor de ROTACIÓN es ideal para la brújula, la realidad aumentada, etc.

Tan fácil cuando sabes cómo … Sin embargo, aún no lo he probado a lo largo del tiempo para ver si se introducen los errores.

El problema que tienes es probablemente el locking de Gimbal . Si lo piensas bien, cuando el teléfono está en posición vertical, por lo que el tono es de más o menos 90 grados, entonces el azimut y el balanceo son lo mismo. Si observas las matemáticas, verás que en esa situación, ya sea azimuth + roll o acimut-roll está bien definido, pero no están definidos individualmente. Entonces, cuando el tono se acerca a más o menos 90 grados, las lecturas se vuelven inestables. Algunas personas eligen volver a mapear el sistema de coordenadas para intentarlo, por ejemplo, ¿cómo debo calcular el acimut, el tono, la orientación cuando mi dispositivo Android no es plano? , entonces tal vez eso podría funcionar para ti.

Eche un vistazo a Mixare , una herramienta de realidad aumentada de código abierto para Android y iPhone. Tiene algunas cosas excelentes sobre cómo compensar la posición / orientación de los teléfonos para mostrar las cosas correctamente en la pantalla.

EDITAR: en particular, eche un vistazo a la clase mixView java que maneja los eventos del sensor.

Esta es otra forma de obtener el rumbo magnético sin verse afectado por el cabeceo o el balanceo.

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

encontré que en ciertos modelos de teléfonos inteligentes, la activación de la cámara puede cambiar los datos de COMPASS … 1/10 graduados … (relacionado con la luz de la escena)

escena negra … 1/2 …. escena muy blanca (10 o más graduados)