问题 如何用罗盘读数和陀螺仪读数获得手机的方位角?
我希望通过以下方法获取手机的当前方向:
- 首先通过获取初始方向(方位角)
getRotationMatrix()
和 getOrientation()
。
- 随着时间的推移添加陀螺仪读数的集成,以获得当前的方向。
电话介绍:
手机的x-y平面与地平面固定。即,处于“发短信行走”的方向。
“getOrientation()
“回归:
Android API允许我轻松获得方向,即方位角,俯仰,滚动,来自 getOrientation()
。
请 注意 那个方法 总是 返回其范围内的值: [0, -PI]
和 [o, PI]
。
我的问题:
自陀螺仪读数整合以来,表示为 dR
,可能很大,所以当我这样做 CurrentOrientation += dR
, CurrentOrientation
可能会超过 [0, -PI]
和 [o, PI]
范围。
需要进行哪些操作才能使我始终获得当前的方向 [0, -PI]
和 [o, PI]
范围是多少?
我在Python中尝试了以下内容,但我非常怀疑它的正确性。
rotation = scipy.integrate.trapz(gyroSeries, timeSeries) # integration
if (headingDirection - rotation) < -np.pi:
headingDirection += 2 * np.pi
elif (headingDirection - rotation) > np.pi:
headingDirection -= 2 * np.pi
# Complementary Filter
headingDirection = ALPHA * (headingDirection - rotation) + (1 - ALPHA) * np.mean(azimuth[np.array(stepNo.tolist()) == i])
if headingDirection < -np.pi:
headingDirection += 2 * np.pi
elif headingDirection > np.pi:
headingDirection -= 2 * np.pi
备注
这并不是那么简单,因为它涉及以下麻烦制造者:
- 方向传感器读数来自
0
至 -PI
, 接着 直接跳动 至 +PI
并逐渐回归 0
通过 +PI/2
。
- gyrocope阅读的整合也会带来一些麻烦。我应该加
dR
到取向或减去 dR
。
在给出确认的答案之前,请先参考Android Documentations。
估计的答案无济于事。
1929
2017-08-06 07:04
起源
答案:
方向传感器实际上从真实磁力计和加速度计获得其读数。
我想也许这就是混乱的根源。文档中说明了哪些内容?更重要的是,某处的文档是否明确说明陀螺仪读数被忽略了?据我所知,本视频中描述的方法已实施:
Android设备上的传感器融合:运动处理的革命
该方法使用陀螺仪并整合其读数。这几乎使问题的其余部分没有实际意义;尽管如此,我会尽力回答。
方向传感器已经为您整合了陀螺仪读数,这就是你如何获得方向。我不明白你为什么这样做。
您没有正确地整合陀螺仪读数,它比复杂 CurrentOrientation += dR
(这是不正确的)。如果你需要集成陀螺仪读数(我不明白为什么,SensorManager已经为你做了),请阅读 方向余弦矩阵IMU:理论 如何正确地做(公式17)。
不要尝试与欧拉角集成 (又名方位角,俯仰,滚动),没有任何好处会出来。
请在计算中使用四元数或旋转矩阵 而不是欧拉角。如果使用旋转矩阵,可以始终将它们转换为欧拉角,请参阅
由Gregory G. Slabaugh从旋转矩阵计算欧拉角
(对于四元数也是如此。)(在非退化情况下)有两种表示旋转的方法,即 你会得到两个欧拉角。选择您需要的范围内的那个。 (的情况下 万向节锁,有无限多的欧拉角,见上面的PDF)。只是承诺在旋转矩阵到欧拉角度转换后,你不会再在计算中再次使用欧拉角。
目前还不清楚你在使用互补滤波器做什么。 你可以实现一个非常好的传感器融合基于 方向余弦矩阵IMU:理论 手稿,基本上是一个教程。这样做并非易事,但我认为你不会找到比这份手稿更好,更易理解的教程。
基于这个手稿实现传感器融合时,我必须发现自己的一件事就是所谓的 积分饱和 可以发生。我通过限制来照顾它 TotalCorrection
(第27页)。如果你实现这种传感器融合,你会明白我在说什么。
更新: 在这里,我回答您在接受答案后在评论中发布的问题。
我认为指南针通过使用重力和磁场给我当前的方向,对吧?陀螺仪是否用于指南针?
是的,如果手机或多或少静止至少半秒钟,您可以通过仅使用重力和指南针来获得良好的方向估计。这是怎么做的: 谁能告诉我重力传感器是否作为倾斜传感器来提高航向精度?
不,罗盘中没有使用陀螺仪。
能否请您解释为什么我所做的整合是错误的?据我所知,如果我的手机的音高指向上,则欧拉角失败。但是我的整合还有什么问题吗?
有两个不相关的东西:(i)整合应该以不同的方式完成,(ii)由于万向节锁定,欧拉角是麻烦的。我再说一遍,这两者是无关的。
至于集成:这是一个简单的例子,你可以如何实际 看到 你的集成有什么问题。设x和y为房间中水平面的轴。拿到手机。将手机围绕x轴(房间的)旋转45度,然后围绕y轴(房间的)旋转45度。然后,从头开始重复这些步骤,但现在先绕y轴旋转,然后绕x轴旋转。手机最终完全不同。如果按照说明进行集成 CurrentOrientation += dR
你会看到没有区别!如果你想正确地进行整合,请阅读上面链接的方向余弦矩阵IMU:理论手稿。
至于欧拉角:他们搞砸了应用程序的稳定性,这对我来说不足以在3D中进行任意旋转。
我仍然不明白你为什么要自己尝试,为什么你不想使用平台提供的方向估计。机会是,你不能做得更好。
8
2017-08-08 15:22
我认为你应该避免使用折旧的“方向传感器”,并使用传感器融合方法,如getRotationVector,getRotationMatrix,它已经实现了已经使用陀螺仪数据的Invensense专用的融合算法。
如果你想要一个简单的传感器融合算法,称为平衡滤波器
(参考 http://www.filedump.net/dumped/filter1285099462.pdf)可以使用。方法如同
http://postimg.org/image/9cu9dwn8z/
这将陀螺仪集成到角度,然后通过高通滤波器去除结果
漂移,并将其添加到平滑的加速度计和指南针结果。集成的高通滤波数据和加速度计/指南针数据以这两个部分相加的方式添加
为了使输出是一个有意义的单位的准确估计。
对于平衡滤波器,可以调整时间常数以调整响应。时间越短
恒定,响应越好,但允许通过的加速度噪声越大。
为了了解它是如何工作的,想象一下你有最新的陀螺仪数据点(以rad / s为单位)存储在陀螺仪中
加速度计的最新角度测量值存储在angle_acc中,而dt是时间
最后的陀螺仪数据到现在为止。然后你的新角度将使用
angle = b *(angle + gyro * dt)+(1 - b)*(angle_acc);
例如,您可以尝试b = 0.98。您可能还希望使用快速陀螺仪测量时间dt,以便陀螺仪在下一次测量之前不会漂移超过几度。平衡滤波器非常有用且易于实现,但不是理想的传感器融合方法。
Invensense的方法涉及一些聪明的算法,可能还有某种形式的卡尔曼滤波器。
来源:专业Android传感器编程,Adam Stroud。
2
2017-08-08 22:29
如果由于磁干扰导致方位角值不准确,那么就我所知,没有什么可以消除它的。要获得稳定的方位角读数,如果TYPE_GRAVITY不可用,则需要过滤加速度计值。如果TYPE_GRAVITY不可用,那么我很确定该设备没有陀螺仪,因此您可以使用的唯一滤波器是低通滤波器。以下代码是使用TYPE_GRAVITY和TYPE_MAGNETIC_FIELD的稳定罗盘的实现。
public class Compass implements SensorEventListener
{
public static final float TWENTY_FIVE_DEGREE_IN_RADIAN = 0.436332313f;
public static final float ONE_FIFTY_FIVE_DEGREE_IN_RADIAN = 2.7052603f;
private SensorManager mSensorManager;
private float[] mGravity;
private float[] mMagnetic;
// If the device is flat mOrientation[0] = azimuth, mOrientation[1] = pitch
// and mOrientation[2] = roll, otherwise mOrientation[0] is equal to Float.NAN
private float[] mOrientation = new float[3];
private LinkedList<Float> mCompassHist = new LinkedList<Float>();
private float[] mCompassHistSum = new float[]{0.0f, 0.0f};
private int mHistoryMaxLength;
public Compass(Context context)
{
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
// Adjust the history length to fit your need, the faster the sensor rate
// the larger value is needed for stable result.
mHistoryMaxLength = 20;
}
public void registerListener(int sensorRate)
{
Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
if (magneticSensor != null)
{
mSensorManager.registerListener(this, magneticSensor, sensorRate);
}
Sensor gravitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
if (gravitySensor != null)
{
mSensorManager.registerListener(this, gravitySensor, sensorRate);
}
}
public void unregisterListener()
{
mSensorManager.unregisterListener(this);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
@Override
public void onSensorChanged(SensorEvent event)
{
if (event.sensor.getType() == Sensor.TYPE_GRAVITY)
{
mGravity = event.values.clone();
}
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
{
mMagnetic = event.values.clone();
}
if (!(mGravity == null || mMagnetic == null))
{
mOrientation = getOrientation();
}
}
private void getOrientation()
{
float[] rotMatrix = new float[9];
if (SensorManager.getRotationMatrix(rotMatrix, null,
mGravity, mMagnetic))
{
float inclination = (float) Math.acos(rotMatrix[8]);
// device is flat
if (inclination < TWENTY_FIVE_DEGREE_IN_RADIAN
|| inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
{
float[] orientation = sensorManager.getOrientation(rotMatrix, mOrientation);
mCompassHist.add(orientation[0]);
mOrientation[0] = averageAngle();
}
else
{
mOrientation[0] = Float.NAN;
clearCompassHist();
}
}
}
private void clearCompassHist()
{
mCompassHistSum[0] = 0;
mCompassHistSum[1] = 0;
mCompassHist.clear();
}
public float averageAngle()
{
int totalTerms = mCompassHist.size();
if (totalTerms > mHistoryMaxLength)
{
float firstTerm = mCompassHist.removeFirst();
mCompassHistSum[0] -= Math.sin(firstTerm);
mCompassHistSum[1] -= Math.cos(firstTerm);
totalTerms -= 1;
}
float lastTerm = mCompassHist.getLast();
mCompassHistSum[0] += Math.sin(lastTerm);
mCompassHistSum[1] += Math.cos(lastTerm);
float angle = (float) Math.atan2(mCompassHistSum[0] / totalTerms, mCompassHistSum[1] / totalTerms);
return angle;
}
}
在你的活动中实例化一个Compass对象,如onCreate,onLesume中的registerListener和onPause中的unregisterListener
private Compass mCompass;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mCompass = new Compass(this);
}
@Override
protected void onPause()
{
super.onPause();
mCompass.unregisterListener();
}
@Override
protected void onResume()
{
super.onResume();
mCompass.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
}
2
2017-08-12 04:03
最好让android的Orientation检测实现处理它。现在,您得到的值是从-PI到PI,您可以将它们转换为度数(0-360)。一些相关部分:
保存要处理的数据:
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
switch (sensorEvent.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
mAccValues[0] = sensorEvent.values[0];
mAccValues[1] = sensorEvent.values[1];
mAccValues[2] = sensorEvent.values[2];
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mMagValues[0] = sensorEvent.values[0];
mMagValues[1] = sensorEvent.values[1];
mMagValues[2] = sensorEvent.values[2];
break;
}
}
计算横滚,俯仰和偏航(方位角)。mR
和 mI
是arrys来保持旋转和倾斜矩阵, mO
是一个临时数组。阵列 mResults
最后有以度为单位的值:
private void updateData() {
SensorManager.getRotationMatrix(mR, mI, mAccValues, mMagValues);
/**
* arg 2: what world(according to app) axis , device's x axis aligns with
* arg 3: what world(according to app) axis , device's y axis aligns with
* world x = app's x = app's east
* world y = app's y = app's north
* device x = device's left side = device's east
* device y = device's top side = device's north
*/
switch (mDispRotation) {
case Surface.ROTATION_90:
SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, mR2);
break;
case Surface.ROTATION_270:
SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, mR2);
break;
case Surface.ROTATION_180:
SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_MINUS_X, SensorManager.AXIS_MINUS_Y, mR2);
break;
case Surface.ROTATION_0:
default:
mR2 = mR;
}
SensorManager.getOrientation(mR2, mO);
//--upside down when abs roll > 90--
if (Math.abs(mO[2]) > PI_BY_TWO) {
//--fix, azimuth always to true north, even when device upside down, realistic --
mO[0] = -mO[0];
//--fix, roll never upside down, even when device upside down, unrealistic --
//mO[2] = mO[2] > 0 ? PI - mO[2] : - (PI - Math.abs(mO[2]));
//--fix, pitch comes from opposite , when device goes upside down, realistic --
mO[1] = -mO[1];
}
CircleUtils.convertRadToDegrees(mO, mOut);
CircleUtils.normalize(mOut);
//--write--
mResults[0] = mOut[0];
mResults[1] = mOut[1];
mResults[2] = mOut[2];
}
2
2017-08-15 07:41