我正在尝试在我的Android手机(Nexus 4)上制作应用程序,该手机将用于模型船。我添加了低通滤波器来滤除传感器的gitter。
然而,当手机背面平放时,指南针才稳定。如果我把它向上倾斜,(例如转动一个booK的页面),那么指南针标题就会消失 - 多达50 *。
我使用Sensor.TYPE_GRAVITY和Sensor.TYPE_ACCELEROMETER对Sensor.TYPE_MAGNETIC_FIELD进行了尝试,效果相同。
我使用了here提到的解决方案以及许多其他地方。我的数学并不好,但这必定是一个常见的问题,我发现没有一个API可以解决这个问题令人沮丧。
我已经在这个问题上工作了3天并且仍然没有找到任何解决方案,但是当我使用Compass from Catch时,无论电话倾向多少,他们都会保持稳定。所以我知道这一定是可能的。
我想要做的就是创建一个指南针,如果手机指向北方,则指南针将向北读取,而当手机移动通过任何其他轴(滚动或俯仰)时,指南针不会跳转。
在我不得不放弃我的项目之前,任何人都可以请求帮助。
谢谢, 亚当
答案 0 :(得分:32)
由于共同发病,我几周来一直在考虑这个问题,因为
我把那些我正在使用的数学here on math.stackexchange.com,并且我粘贴了我在下面使用过的代码。该代码根据原始TYPE_GRAVITY
和TYPE_MAGNETIC_FIELD
传感器数据计算方位角和音高,而不需要任何API调用。 SensorManager.getRotationMatrix(...)
或SensorManager.getOrientation(...)
。代码可能会改进,例如如果输入变得有点不稳定,则使用低通滤波器。请注意,代码通过方法onAccuracyChanged(Sensor sensor, int accuracy)
记录传感器的准确性,因此如果方位角看起来不稳定,另外要检查的是每个传感器的准确度。在任何情况下,如果在此代码中明确可见所有计算,如果存在不稳定性问题(当传感器精度合理时),则可以通过查看输入中的不稳定性或方向向量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, (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);
}
}
答案 1 :(得分:13)
好的,我想我解决了。
我没有使用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);
无论手机的摇摆或音高如何,都会返回稳定的方位角。
如果看一下表{1}下面的here at the Android Motion Sensors,它就说ROTATION传感器非常适合指南针,增强现实等。
当你知道如何变得如此简单......但是,我还没有对它进行测试,以确定是否引入了错误。
答案 2 :(得分:2)
你遇到的问题可能是Gimbal lock。如果你考虑一下,当手机直立,所以音高为正负90度时,方位角和滚动都是一样的。如果你研究数学,你会发现在那种情况下,方位角+滚动或方位角滚动是很好定义的,但它们不是单独定义的。因此,当音高接近正负90度时,读数会变得不稳定。有些人选择重新映射坐标系统,试着绕过这个,参见例如How should I calculate azimuth, pitch, orientation when my Android device isn't flat?,也许这可能适合你。
答案 3 :(得分:1)
看看Mixare,这是一款适用于Android和iphone的开源增强现实工具,它有一些很棒的功能可以补偿手机的方向/位置,以便在屏幕上正确显示内容。
编辑:特别是看一下处理传感器事件的MixView java class。
答案 4 :(得分:1)
这是获得磁航向而不受俯仰或滚动影响的另一种方法。
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;
}
答案 5 :(得分:-2)
我发现在某些智能手机型号上,激活相机可以改变COMPASS数据...... 1/10梯度......(与场景光有关)
黑色场景...... 1/2 ......非常白的场景(10个或更多的毕业生)