sensors - Compás de Android que puede compensar la inclinación y la inclinación
shake sensor android (6)
Descubrí que en ciertos modelos de teléfonos inteligentes, la activación de la cámara puede cambiar los datos de COMPASS ... 1/10 grados ... (relacionados con la luz de la escena)
escena negra ... 1/2 .... escena muy blanca (10 o más graduados)
Estoy tratando de hacer una aplicación en mi teléfono Android (Nexus 4), que se utilizará en un modelo de barco. He añadido filtros de paso bajo para filtrar el gitter de los sensores.
Sin embargo, la brújula solo es estable cuando el teléfono está plano sobre su parte posterior. Si lo inclino hacia arriba, (como pasar la página de un libro), el rumbo de la brújula desaparece, tanto como 50 *.
He intentado esto 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 here , y muchos otros lugares. Mis cálculos matemáticos no son excelentes, pero este debe ser un problema común y me parece frustrante que no haya una API para lidiar con ellos.
He estado trabajando en este problema durante 3 días y todavía no he encontrado ninguna solución, pero cuando uso Compass de Catch , la suya permanece estable sin importar cuánto se incline el teléfono. Así que sé que debe ser posible.
Todo lo que quiero hacer es crear una brújula que, si el teléfono apunta hacia el norte, la brújula se lea hacia el norte y no salte cuando el teléfono se mueva a través de cualquier otro eje (balanceo o inclinación).
¿Puede alguien ayudarme antes de que tenga que abandonar mi proyecto?
Gracias adan
Eche un vistazo a Mixare , una herramienta de realidad aumentada de código abierto para android y iphone, que tiene algunas cosas excelentes sobre cómo compensar la orientación / posición de los teléfonos para mostrar las cosas correctamente en la pantalla.
EDITAR: en particular, eche un vistazo a la clase Java de MixView que maneja los eventos del sensor.
El problema que tienes es probablemente el bloqueo Gimbal . Si lo piensas bien, cuando el teléfono está en posición vertical por lo que el tono es 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, azimuth + roll o azimuth-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 optan por volver a asignar el sistema de coordenadas tom y tratar de solucionar esto, ver, por ejemplo, ¿Cómo debo calcular el azimut, el tono, la orientación cuando mi dispositivo Android no está plano? , así que quizás eso podría funcionar para ti.
Esta es otra forma de obtener el rumbo magnético sin verse afectado por el tono 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;
}
OK, creo que lo resolví.
En lugar de usar Sensor.TYPE_ACCELEROMETER (o TYPE_GRAVITY) y Sensor.TYPE_MAGNETIC_FIELD, usé 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 inclinación del teléfono.
Si observa aquí los sensores de movimiento de Android , justo debajo de la Tabla 1, dice que el sensor de ROTACIÓN es ideal para brújula, realidad aumentada, etc.
Tan fácil cuando sabes cómo ... Sin embargo, todavía tengo que probar esto con el tiempo para ver si se introducen errores.
Por coincidencia he estado pensando en este problema durante varias semanas, porque
- Como matemático, no estoy satisfecho con ninguna de las respuestas que he visto sugeridas en otra parte; y
- Necesito una buena respuesta para una aplicación en la que estoy trabajando.
He puesto las 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 acimut y el tono a partir de los datos del sensor sin TYPE_MAGNETIC_FIELD
TYPE_GRAVITY
y TYPE_MAGNETIC_FIELD
, sin ninguna llamada de API a, por ejemplo, SensorManager.getRotationMatrix(...)
o SensorManager.getOrientation(...)
. El código probablemente podría mejorarse, por ejemplo, utilizando 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 onAccuracyChanged(Sensor sensor, int accuracy)
, por lo que si el azimut parece inestable, otra cosa que debe 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), se podrían abordar observando las inestabilidades en las entradas o en los vectores de dirección m_NormGravityVector[]
, m_NormEastVector[]
o m_NormNorthVector[]
.
Me interesaría mucho cualquier comentario que alguien tenga para mí sobre este método. Encuentro que funciona como un sueño en mi propia aplicación, siempre que el dispositivo sea plano, vertical o en algún punto intermedio. Sin embargo, como menciono en el artículo math.stackexchange.com, hay problemas que surgen a medida que el dispositivo se acerca al revés. En esa situación, uno tendría que definir cuidadosamente qué comportamiento quiere.
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);
}
}