Convierta los valores del campo magnético X, Y, Z del dispositivo en el marco de referencia global

Cuando utiliza el sensor TYPE_MAGNETOMETER, obtiene los valores X, Y, Z de la intensidad del campo magnético en relación con la orientación del dispositivo. Lo que quiero obtener es convertir estos valores en un marco de referencia global, aclarando: el usuario toma el dispositivo, mide estos valores, que gira el dispositivo en algunos grados alrededor de cualquier eje y obtiene ~ los mismos valores. Por favor, encuentre preguntas similares a continuación: Obtener valores de campo magnético en coordenadas globales ¿Cómo puedo obtener el vector de campo magnético, independientemente de la rotación del dispositivo? En esta solución de muestra de respuesta se describe (es para la aceleración lineal, pero creo que no importa): https://stackoverflow.com/a/11614404/2152255 Lo usé y obtuve 3 valores, X siempre es muy pequeño (no creo que sea correcto), Y y Z están bien, pero aún cambian un poco cuando giro el dispositivo. ¿Cómo podría ajustarse? ¿Y podría resolverse todo? Utilizo un filtro de Kalman simple para aproximar los valores de medición, porque sin él obtengo valores diferentes, incluso si el dispositivo no se mueve / gira en absoluto. Por favor, encuentra mi código a continuación:

import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.opengl.Matrix; import android.os.Bundle; import android.view.View; import android.widget.CheckBox; import android.widget.TextView; import com.test.statistics.filter.kalman.KalmanState; import com.example.R; /** * Activity for gathering magnetic field statistics. */ public class MagneticFieldStatisticsGatheringActivity extends Activity implements SensorEventListener { public static final int KALMAN_STATE_MAX_SIZE = 80; public static final double MEASUREMENT_NOISE = 5; /** Sensor manager. */ private SensorManager mSensorManager; /** Magnetometer spec. */ private TextView vendor; private TextView resolution; private TextView maximumRange; /** Magnetic field coordinates measurements. */ private TextView magneticXTextView; private TextView magneticYTextView; private TextView magneticZTextView; /** Sensors. */ private Sensor mAccelerometer; private Sensor mGeomagnetic; private float[] accelerometerValues; private float[] geomagneticValues; /** Flags. */ private boolean specDefined = false; private boolean kalmanFiletring = false; /** Rates. */ private float nanoTtoGRate = 0.00001f; private final int gToCountRate = 1000000; /** Kalman vars. */ private KalmanState previousKalmanStateX; private KalmanState previousKalmanStateY; private KalmanState previousKalmanStateZ; private int previousKalmanStateCounter = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main2); mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGeomagnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); vendor = (TextView) findViewById(R.id.vendor); resolution = (TextView) findViewById(R.id.resolution); maximumRange = (TextView) findViewById(R.id.maximumRange); magneticXTextView = (TextView) findViewById(R.id.magneticX); magneticYTextView = (TextView) findViewById(R.id.magneticY); magneticZTextView = (TextView) findViewById(R.id.magneticZ); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST); mSensorManager.registerListener(this, mGeomagnetic, SensorManager.SENSOR_DELAY_FASTEST); } /** * Refresh statistics. * * @param view - refresh button view. */ public void onClickRefreshMagneticButton(View view) { resetKalmanFilter(); } /** * Switch Kalman filtering on/off * * @param view - Klaman filetring switcher (checkbox) */ public void onClickKalmanFilteringCheckBox(View view) { CheckBox kalmanFiltering = (CheckBox) view; this.kalmanFiletring = kalmanFiltering.isChecked(); } @Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { return; } synchronized (this) { switch(sensorEvent.sensor.getType()){ case Sensor.TYPE_ACCELEROMETER: accelerometerValues = sensorEvent.values.clone(); break; case Sensor.TYPE_MAGNETIC_FIELD: if (!specDefined) { vendor.setText("Vendor: " + sensorEvent.sensor.getVendor() + " " + sensorEvent.sensor.getName()); float resolutionValue = sensorEvent.sensor.getResolution() * nanoTtoGRate; resolution.setText("Resolution: " + resolutionValue); float maximumRangeValue = sensorEvent.sensor.getMaximumRange() * nanoTtoGRate; maximumRange.setText("Maximum range: " + maximumRangeValue); } geomagneticValues = sensorEvent.values.clone(); break; } if (accelerometerValues != null && geomagneticValues != null) { float[] Rs = new float[16]; float[] I = new float[16]; if (SensorManager.getRotationMatrix(Rs, I, accelerometerValues, geomagneticValues)) { float[] RsInv = new float[16]; Matrix.invertM(RsInv, 0, Rs, 0); float resultVec[] = new float[4]; float[] geomagneticValuesAdjusted = new float[4]; geomagneticValuesAdjusted[0] = geomagneticValues[0]; geomagneticValuesAdjusted[1] = geomagneticValues[1]; geomagneticValuesAdjusted[2] = geomagneticValues[2]; geomagneticValuesAdjusted[3] = 0; Matrix.multiplyMV(resultVec, 0, RsInv, 0, geomagneticValuesAdjusted, 0); for (int i = 0; i < resultVec.length; i++) { resultVec[i] = resultVec[i] * nanoTtoGRate * gToCountRate; } if (kalmanFiletring) { KalmanState currentKalmanStateX = new KalmanState(MEASUREMENT_NOISE, accelerometerValues[0], (double)resultVec[0], previousKalmanStateX); previousKalmanStateX = currentKalmanStateX; KalmanState currentKalmanStateY = new KalmanState(MEASUREMENT_NOISE, accelerometerValues[1], (double)resultVec[1], previousKalmanStateY); previousKalmanStateY = currentKalmanStateY; KalmanState currentKalmanStateZ = new KalmanState(MEASUREMENT_NOISE, accelerometerValues[2], (double)resultVec[2], previousKalmanStateZ); previousKalmanStateZ = currentKalmanStateZ; if (previousKalmanStateCounter == KALMAN_STATE_MAX_SIZE) { magneticXTextView.setText("x: " + previousKalmanStateX.getX_estimate()); magneticYTextView.setText("y: " + previousKalmanStateY.getX_estimate()); magneticZTextView.setText("z: " + previousKalmanStateZ.getX_estimate()); resetKalmanFilter(); } else { previousKalmanStateCounter++; } } else { magneticXTextView.setText("x: " + resultVec[0]); magneticYTextView.setText("y: " + resultVec[1]); magneticZTextView.setText("z: " + resultVec[2]); } } } } } private void resetKalmanFilter() { previousKalmanStateX = null; previousKalmanStateY = null; previousKalmanStateZ = null; previousKalmanStateCounter = 0; } @Override public void onAccuracyChanged(Sensor sensor, int i) { } } 

Gracias a todos los que leyeron esta publicación y que publicaron algunas ideas sobre el problema de antemano.

En mi comentario sobre la respuesta marcada en el enlace que proporcionó anteriormente, me referí a mi respuesta simple al calcular la aceleración en referencia al norte verdadero

Permítanme responder aquí de nuevo con más aclaraciones. La respuesta es el producto de la matriz de rotación y los valores del campo magnético . Si lees más sobre la “X siempre es muy pequeña” es el valor correcto.

El acelerómetro y los sensores de campo magnético miden la aceleración del dispositivo y el campo magnético de la tierra en la ubicación del dispositivo, respectivamente. Son vectores en 3 espacios dimensionales, llamémoslos a y m respectivamente.
Si te quedas quieto y giras tu dispositivo, teóricamente m no cambia, suponiendo que no haya interferencia magnética de los objetos circundantes (en realidad m debería cambiar poco, si te mueves porque el campo magnético de la tierra debería cambiar poco en una distancia corta). Pero a cambio sí, a pesar de que no debería ser drástico en la mayoría de las situaciones.

Ahora un vector v en el espacio tridimensional puede representarse con 3 tuplas (v_1, v_2, v_3) con respecto a alguna base ( e_1 , e_2 , e_3 ), es decir, v = v_1 e_1 + v_2 e_2 + v_3 e_3 . (v_1, v_2, v_3) se llaman las coordenadas de v con respecto a la base ( e_1 , e_2 , e_3 ).

En dispositivos Android, la base es ( x , y , z ) donde, para la mayoría de los teléfonos, x está en el lado más corto y apunta hacia la derecha, y está en el lado más largo y apuntando hacia arriba yz es perpendicular a la pantalla y apuntando hacia afuera.
Ahora esta base cambia a medida que cambia la posición del dispositivo. Uno puede pensar estas bases como una función del tiempo ( x (t), y (t), z (t)), en términos matemáticos es un sistema de coordenadas en movimiento.

Por lo tanto, aunque m no cambie, los valores de event.values ​​devueltos por los sensores son diferentes porque la base es diferente (hablaré de la fluctuación más adelante). Como es, los valores del evento son inútiles porque nos da las coordenadas pero no sabemos cuál es la base, es decir, con respecto a alguna base que conocemos.

Ahora la pregunta es: ¿es posible encontrar las coordenadas de a y m con respecto a la base mundial fija ( w_1 , w_2 , w_3 ) donde w_1 apunta hacia el este, w_2 apunta hacia el norte magnético y w_3 apunta hacia el cielo?

La respuesta es sí siempre que se cumplan 2 supuestos importantes.
Con estas 2 suposiciones, es simple calcular (solo unos cuantos productos cruzados) el cambio de la matriz de base R desde la base ( x , y , z ) hasta la base ( w_1 , w_2 , w_3 ), que en Android se denomina Rotación matriz . Entonces las coordenadas de un vector v con respecto a la base ( w_1 , w_2 , w_3 ) se obtienen multiplicando R con las coordenadas de v con respecto a ( x , y , z ). Por lo tanto, las coordenadas de m con respecto al sistema de coordenadas del mundo son solo el producto de la matriz de rotación y los valores de evento devueltos por el sensor TYPE_MAGNETIC_FIELD y de manera similar para a .

En android, la matriz de rotación se obtiene llamando a getRotationMatrix (float [] R, float [] I, float [] gravity, float [] geomagnética) y normalmente pasamos los valores del acelerómetro devueltos para el parámetro de gravedad y los valores del campo magnético para el geomagnético

Las 2 suposiciones importantes son:
1- El parámetro de gravedad representa un vector que se encuentra en w_3 , más particularmente es el menos del vector influenciado solo por la gravedad.
Por lo tanto, si pasa los valores del acelerómetro sin filtrar, la matriz de rotación estará un poco apagada. Es por eso que necesita filtrar el acelerómetro para que los valores del filtro sean aproximadamente el menos el vector de gravedad. Dado que la aceleración gravitacional es el factor dominante en el vector acelerómetro, normalmente es suficiente el filtro de paso bajo.
2- El parámetro geomagnético representa un vector que se encuentra en el plano abarcado por los vectores w_2 y w_3 . Eso es, yace en el plano del Cielo Norte. Por lo tanto, en términos de la base ( w_1 , w_2 , w_3 ), la primera coordenada debe ser 0. Por lo tanto, la “X es siempre muy pequeña”, como dijiste antes, es el valor correcto, idealmente debería ser 0. Ahora el los valores de campo fluctuarán bastante. Esto es algo esperado, al igual que una aguja de brújula regular no se detendrá si la mantienes en tu mano y tu mano tiembla un poco. Además, puede recibir interferencias de los objetos que lo rodean y, en este caso, los valores del campo magnético son impredecibles. Una vez probé la aplicación de mi brújula sentada cerca de una mesa de “piedra” y mi brújula estaba desconectada más de 90 grados, solo usando una brújula real descubrí que no había nada de malo en mi aplicación y la mesa de “piedra” produce una campo magnético fuerte real.
Con la gravedad como factor dominante, puede filtrar los valores del acelerómetro, pero sin ningún otro conocimiento, ¿cómo se ajustan los valores magnéticos? ¿Cómo se puede saber si hay o no interferencia de los objetos circundantes?

Puede hacer mucho más como un conocimiento completo de la posición espacial de su dispositivo, etc. con la comprensión de la matriz de rotación .

Según la explicación anterior, haz esto

 private static final int TEST_GRAV = Sensor.TYPE_ACCELEROMETER; private static final int TEST_MAG = Sensor.TYPE_MAGNETIC_FIELD; private final float alpha = (float) 0.8; private float gravity[] = new float[3]; private float magnetic[] = new float[3]; public void onSensorChanged(SensorEvent event) { Sensor sensor = event.sensor; if (sensor.getType() == TEST_GRAV) { // Isolate the force of gravity with the low-pass filter. gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]; gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]; gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]; } else if (sensor.getType() == TEST_MAG) { magnetic[0] = event.values[0]; magnetic[1] = event.values[1]; magnetic[2] = event.values[2]; float[] R = new float[9]; float[] I = new float[9]; SensorManager.getRotationMatrix(R, I, gravity, magnetic); float [] A_D = event.values.clone(); float [] A_W = new float[3]; A_W[0] = R[0] * A_D[0] + R[1] * A_D[1] + R[2] * A_D[2]; A_W[1] = R[3] * A_D[0] + R[4] * A_D[1] + R[5] * A_D[2]; A_W[2] = R[6] * A_D[0] + R[7] * A_D[1] + R[8] * A_D[2]; Log.d("Field","\nX :"+A_W[0]+"\nY :"+A_W[1]+"\nZ :"+A_W[2]); } }