问题描述
我正在尝试在我的 Android 手机 (Nexus 4) 上制作一个应用程序,该应用程序将用于模型船.我添加了低通滤波器来过滤掉传感器中的 gitter.
I'm trying to make a application on my Android phone (Nexus 4), which will be used in a model boat. I've added low pass filters to filter out the gitter from the sensors.
然而,指南针只有在手机背面平放时才是稳定的.如果我将它向上倾斜(例如翻书页),那么罗盘航向就会偏离 - 多达 50*.
However, the compass is only stable when the phone is flat on its back. If I tilt it up, (such as turning a page of a booK), then the compass heading goes way off - as much as 50*.
我已经用 Sensor.TYPE_MAGNETIC_FIELD 和 Sensor.TYPE_GRAVITY 和 Sensor.TYPE_ACCELEROMETER 尝试了这个,效果是一样的.
I've tried this with Sensor.TYPE_MAGNETIC_FIELD with either Sensor.TYPE_GRAVITY and Sensor.TYPE_ACCELEROMETER and the effect is the same.
我使用了此处和许多其他地方提到的解决方案.我的数学不是很好,但这一定是一个常见问题,我发现没有 API 来处理它令人沮丧.
I've used the solution mentioned here, and many other places. My maths is not great but this must be a common problem and I find it frustrating that there is not an API to deal with it.
我已经解决这个问题 3 天了,但仍然没有找到任何解决方案,但是当我使用 Catch 的指南针,无论手机倾斜多少,它们都能保持稳定.所以我知道这一定是可能的.
I've been working on this problem for 3 days and have still not found any solution, but when I use the Compass from Catch, theirs stays stable no matter how much the phone is inclined. So I know it must be possible.
我想要做的就是创建一个指南针,如果手机指向北方,那么指南针将读取北方,并且当手机通过任何其他轴(滚动或俯仰)时不会跳来跳去.
All I want to do is create a compass that if the phone is pointing say north, then the compass will read north, and not jump around when the phone is moved through any other axis (roll or pitch).
在我不得不放弃我的项目之前,任何人都可以帮忙吗.
Can anyone please help before I have to abandon my project.
谢谢,亚当
推荐答案
巧合这个问题我已经思考了好几个星期了,因为
By co-incidence I've been thinking about this problem for several weeks, because
- 作为一个数学家,我一直不满足通过我在其他地方看到的任何答案;并且
- 我需要为我正在开发的应用程序提供一个好的答案.
所以在过去的几天里,我想出了自己的计算方法用于指南针的方位角值.
So over the last couple of days I've come up with my own way of calculating the azimuth value for use in a compass.
我已经把我使用的数学放在了在 math.stackexchange.com 上,我已经粘贴了我在下面使用的代码.该代码根据原始 TYPE_GRAVITY
和 TYPE_MAGNETIC_FIELD
传感器数据计算方位角和俯仰角,无需任何 API 调用,例如SensorManager.getRotationMatrix(...)
或 SensorManager.getOrientation(...)
.代码可能会得到改进,例如如果输入结果有点不稳定,则使用低通滤波器.请注意,代码通过方法 onAccuracyChanged(Sensor sensor, int accuracy)
记录传感器的精度,因此如果方位角看起来不稳定,另一件要检查的事情是每个传感器的精度.在任何情况下,所有计算都在此代码中明确可见,如果存在不稳定问题(当传感器精度合理时),则可以通过查看输入或方向向量中的不稳定性来解决这些问题 m_NormGravityVector[]
、m_NormEastVector[]
或 m_NormNorthVector[]
.
I've put that maths that I'm using here on math.stackexchange.com, and I've pasted the code I've used below. The code calculates the azimuth and pitch from the raw TYPE_GRAVITY
and TYPE_MAGNETIC_FIELD
sensor data, without any API calls to e.g. SensorManager.getRotationMatrix(...)
or SensorManager.getOrientation(...)
. The code could probably be improved e.g. by using a low pass filter if the inputs turn out to be a bit erratic. Note that the code records the accuracy of the sensors via the method onAccuracyChanged(Sensor sensor, int accuracy)
, so if the azimuth seems unstable another thing to check is how accurate each sensor is. In any case, with all the calculations explicitly visible in this code, if there are instability problems (when the sensor accuracy is reasonable) then they could be tackled by looking at the instabilities in the inputs or in the direction vectors m_NormGravityVector[]
, m_NormEastVector[]
or m_NormNorthVector[]
.
我对任何人就这种方法向我提出的任何反馈都非常感兴趣.我发现它在我自己的应用程序中就像做梦一样,只要设备正面朝上、垂直或介于两者之间.然而,正如我在 math.stackexchange.com 文章中提到的,当设备接近颠倒时会出现问题.在那种情况下,人们需要仔细定义自己想要什么行为.
I'd be very interested in any feedback that anyone has for me on this method. I find that it works like a dream in my own app, as long as the device is flat face up, vertical, or somewhere in between. However, as I mention in the math.stackexchange.com article, there are issues that arise as the device gets close to being turned upside down. In that situation, one would need to define carefully what behaviour one wants.
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);
}
}
这篇关于可以补偿倾斜和俯仰的 Android Compass的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!