可以补偿倾斜和倾斜的Android Compass

我正在尝试在我的Android手机(Nexus 4)上制作一个应用程序,该应用程序将在模型船中使用。 我添加了低通滤波器来滤除传感器的干扰。

但是,指南针只有在电话背后平稳时才是稳定的。 如果我把它翻起来,(比如转动一个booK的页面),那么指南针的标题就会消失 – 多达50 *。

我已经用Sensor.TYPE_MAGNETIC_FIELD与Sensor.TYPE_GRAVITY和Sensor.TYPE_ACCELEROMETER一起尝试过,效果是一样的。

我已经使用了这里提到的解决scheme,以及其他许多地方。 我的math不是很好,但这肯定是一个普遍的问题,我觉得没有API来处理它是令人沮丧的。

我一直在解决这个问题已经三天了,现在还没有find任何解决办法,但是当我使用Catch的Compass时 ,无论手机倾斜多less,他们都能保持稳定。 所以我知道这一定是可能的。

我所要做的就是创build一个指南针,如果手机指向北方,指南针将向北读取,而不会在手机通过任何其他轴(滚动或俯仰)移动时跳转。

任何人都可以在我不得不放弃我的项目之前请帮忙。

谢谢,亚当

由于同时发生,我一直在考虑这个问题几个星期,因为

  1. 作为一名math家,我并没有对我在别处看到的任何答案感到满意。 和
  2. 我需要一个很好的答案,我正在工作的应用程序。

所以在过去的几天里,我已经提出了自己的方法来计算在指南针中使用的方位angular值。

我已经在math.stackexchange.com上使用了这个math,并且粘贴了我在下面使用的代码。 该代码根据原始TYPE_GRAVITYTYPE_MAGNETIC_FIELD传感器数据计算方位angular和俯仰TYPE_MAGNETIC_FIELD ,而无需对SensorManager.getRotationMatrix(...)SensorManager.getOrientation(...)进行任何API调用。 如果input变得有点不稳定,代码可能可以改进,例如使用低通滤波器。 请注意,代码通过onAccuracyChanged(Sensor sensor, int accuracy)方法onAccuracyChanged(Sensor sensor, int accuracy) ,所以如果方位angular似乎不稳定onAccuracyChanged(Sensor sensor, int accuracy)检查的另一件事是每个传感器的精度。 在任何情况下,如果存在不稳定性问题(当传感器精度合理时),那么可以通过查看input或方向向量中的不稳定性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, (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); } } 

好吧,我想我解决了它。

我没有使用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); 

无论电话的滚动或俯仰如何,这都返回了稳定的方位angular。

如果你看一下Android运动传感器 ,就在表1的下面,它表示ROTATION传感器是指南针,增强现实等的理想select。

当你知道如何很容易….但是,我还没有testing这个随着时间的推移,看看是否引入错误。

你得到的问题可能是Gimbal锁 。 如果你仔细想想,当手机直立时,音高是正负90度,那么方位angular和横滚angular是一样的。 如果你仔细研究math,你会发现,在这种情况下,方位angular+方位angular或方位angular方位angular是很好定义的,但是它们并没有单独定义。 所以当音高接近正负90度时,读数变得不稳定。 有些人select重新configurationtom坐标系统,例如,当我的Android设备不平坦时,我应该如何计算方位angular,俯仰angular,方位angular? ,所以也许这可能适合你。

看看Android和iPhone的开源增强现实工具Mixare ,它有一些很好的东西在那里补偿手机的方向/位置,以正确地在屏幕上显示的东西。

编辑:特别看看处理传感器事件的MixView Java类 。

这是另一种获得磁性航向而不受俯仰或滚转影响的方式。

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

我发现在某些智能手机型号上,相机的激活可以改变COMPASS数据… 1/10渐变…(与场景的光线有关)

黑色场景… 1/2 ….非常白色的场景(10个或更多的gradle生)