将设备的磁场X,Y,Z值转换为全局参考帧

当您使用TYPE_MAGNETOMETER传感器时,您可以获得与设备方向相关的X,Y,Z磁场强度值。 我想要得到的是将这些值转换为全局参考框架,澄清:用户需要设备,测量这些值,而不是围绕任何轴旋转设备一定的度数,并获得相同的值。 请find以下类似的问题: 在全局坐标系中获取磁场值 如何获得与设备旋转无关的磁场vector? 在这个答案示例解决scheme描述(它是为线性加速,但我认为没关系): https : //stackoverflow.com/a/11614404/2152255我用它,我有3个值,X总是非常小(不要以为这是正确的),Y和Z是可以的,但是当我旋转设备时他们仍然有点变化。 如何调整? 而且能解决所有的问题吗? 我使用简单的卡尔曼滤波器来近似测量值,因为即使设备没有移动/旋转,我也会得到不同的值。 请在下面find我的代码:

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

谢谢大家阅读这篇文章,并提前发表一些关于这个问题的想法。

Solutions Collecting From Web of "将设备的磁场X,Y,Z值转换为全局参考帧"

在我对上面提供的链接上的检查答案的评论中,我提到了我在计算加速度时参考真北方的简单答案

我再次回答这个问题,并加以澄清。 答案是旋转matrix磁场值的乘积。 如果你进一步阅读“X总是很小”是正确的值。

加速度计和磁场传感器分别测量设备的加速度和设备位置处的地球的磁场。 它们是三维空间中的向量,分别称它们为am
如果静止不动并旋转设备,理论上m不会因为周围物体的磁场干扰而改变(实际上,如果由于地球的磁场在短距离内变化很小而移动, m应该变化不大)。 但是,即使在大多数情况下不应该激化,情况也会改变。

现在,三维空间中的vectorv可以由相对于某个基( e_1e_2e_3 )的三元组(v_1,v_2,v_3)表示,即v = v_1e_1 + v_2e_2 + v_3e_3 。 (v_1,v_2,v_3)被称为v相对于基础( e_1e_2e_3 )的坐标

在Android设备中,基础是( xyz ),对于大多数手机来说, x沿着短边并指向右边, y沿着长边朝上, z垂直于屏幕指出。
现在这个基础随着设备位置的变化而变化。 人们可以将这些基数看作是时间( x (t), y (t), z (t))的函数,在math术语中它是一个移动的坐标系。

因此即使m不变,但由传感器返回的事件值也是不同的,因为基础不同(后面我会谈到波动)。 因为event.values是无用的,因为它给了我们坐标,但我们不知道什么是基础,也就是说我们知道的一些基础。

现在的问题是:是否有可能findam相对于固定世界的坐标( w_1w_2w_3 ),其中w_1指向东, w_2指向磁北, w_3指向天空?

答案是肯定的,只要满足两个重要的假设。
通过这两个假设,计算(仅仅几个交叉乘积)基础matrixR从基础( xyz )到基础( w_1w_2w_3 )的变化是简单的,在Android中称为旋转matrix 。 然后,通过将R与关于( xyz )的v的坐标相乘来获得向量v相对于基( w_1w_2w_3 )的坐标。 因此, m相对于世界坐标系的坐标仅仅是由TYPE_MAGNETIC_FIELD传感器返回的旋转matrix和事件值的乘积 ,并且类似于a

在android中, 旋转matrix是通过调用getRotationMatrix(float [] R,float [] I,float [] gravity,float [] geomagnetic)获得的 ,我们通常返回重力参数的返回加速度计值和磁场值地磁。

两个重要的假设是:
1- 重力参数表示位于w_3中的vector,更特别地,它是仅受重力影响的vector的负数。
因此,如果您不加滤波地传递加速度计值,则旋转matrix将稍微偏离。 这就是为什么您需要对加速度计进行过滤,以便filter值几乎只是负向量vector。 由于重力加速度是加速度计vector的主要因素,所以通常低通滤波器就足够了。
2- 地磁参数表示位于由w_2w_3向量跨越的平面中的向量。 那是在北天飞机上。 因此,在( w_1w_2w_3 )的基础上,第一个坐标应该是0.因此,如上所述的“X总是非常小”是正确的值,理想情况下应该是0.现在磁字段值将会波动很多。 这是预料之中的,正如一个普通的指南针针头不会静止,如果你把它放在你的手中,而你的手稍微摇了一下。 此外,您可能会受到周围物体的干扰,在这种情况下,磁场值是不可预测的。 我曾经testing过坐在“石头”桌旁的指南针应用程序,我的指南针closures了90多度,只用一个真正的指南针,发现我的应用程序没有问题,“石头”桌子产生了一个真正的强磁场。
以重力作为主导因素,您可以过滤加速度计的数值,但是没有任何其他的知识,您如何使用fitler磁性值? 你怎么知道是否有周围物体的干扰?

旋转matrix的理解,您可以更多的了解设备空间位置等方面的知识。

按照上面的解释,做到这一点

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