15

我正在创建一个应用程序,我需要根据设备的方向定位 ImageView。我使用来自磁场和加速度传感器的值来计算设备方向

SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticFieldValues)
SensorManager.getOrientation(rotationMatrix, values);
double degrees = Math.toDegrees(values[0]);

我的问题是 ImageView 的定位对方向的变化非常敏感。使图像视图不断在屏幕上跳跃。(因为度数变化)

我读到这可能是因为我的设备靠近会影响磁场读数的东西。但这似乎并不是唯一的原因。

我尝试下载一些应用程序,发现“ 3D 指南针”和“指南针”的读数非常稳定(设置噪声过滤器时),我希望在我的应用程序中具有相同的行为。

我读到我可以通过添加“低通滤波器”来调整读数的“噪音”,但我不知道如何实现这一点(因为我缺乏数学)。

我希望有人可以帮助我在我的设备上创建一个更稳定的读数,设备的一点移动不会影响当前的方向。现在我做一个小

if (Math.abs(lastReadingDegrees - newReadingDegrees) > 1) { updatePosition() }

过滤掉一点噪音。但它不是很好用:)

4

5 回答 5

31

虽然我没有在 Android 上使用过指南针,但下面显示的基本处理(在 JavaScript 中)可能对你有用。

它基于Windows Phone 团队推荐的加速度计上的低通滤波器,并进行了修改以适应指南针(每 360 英寸的循环行为)。

我假设罗盘读数以度为单位,0-360 之间的浮点数,输出应该相似。

您想在过滤器中完成两件事:

  1. 如果变化很小,为了防止 gitter,逐渐转向那个方向。
  2. 如果变化很大,为防止滞后,请立即转向该方向(如果您希望指南针仅以平稳的方式移动,则可以取消它)。

为此,我们将有 2 个常量:

  1. 定义运动平滑程度的缓动浮点数(1 表示不平滑,0 表示从不更新,我的默认值为 0.5)。我们将其称为 SmoothFactorCompass。
  2. 距离大到可以立即转弯的阈值(0是一直跳,360是从不跳,我的默认是30)。我们将其称为 SmoothThresholdCompass。

我们在调用中保存了一个变量,一个名为 oldCompass 的浮点数,它是算法的结果。

所以变量定义为:

var SmoothFactorCompass = 0.5;
var SmoothThresholdCompass = 30.0;
var oldCompass = 0.0;

并且该函数接收 newCompass,并返回 oldCompass 作为结果。

if (Math.abs(newCompass - oldCompass) < 180) {
    if (Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
        oldCompass = newCompass;
    }
    else {
        oldCompass = oldCompass + SmoothFactorCompass * (newCompass - oldCompass);
    }
}
else {
    if (360.0 - Math.abs(newCompass - oldCompass) > SmoothThresholdCompass) {
        oldCompass = newCompass;
    }
    else {
        if (oldCompass > newCompass) {
            oldCompass = (oldCompass + SmoothFactorCompass * ((360 + newCompass - oldCompass) % 360) + 360) % 360;
        } 
        else {
            oldCompass = (oldCompass - SmoothFactorCompass * ((360 - newCompass + oldCompass) % 360) + 360) % 360;
        }
    }
}

我看到这个问题是 5 个月前打开的,可能不再相关,但我相信其他程序员可能会觉得它很有用。

奥德·伊利亚达。

于 2011-06-24T01:51:10.347 回答
19

此低通滤波器适用于弧度角。对每个罗盘读数使用add函数,然后调用average得到平均值。

public class AngleLowpassFilter {

    private final int LENGTH = 10;

    private float sumSin, sumCos;

    private ArrayDeque<Float> queue = new ArrayDeque<Float>();

    public void add(float radians){

        sumSin += (float) Math.sin(radians);

        sumCos += (float) Math.cos(radians);

        queue.add(radians);

        if(queue.size() > LENGTH){

            float old = queue.poll();

            sumSin -= Math.sin(old);

            sumCos -= Math.cos(old);
        }
    }

    public float average(){

        int size = queue.size();

        return (float) Math.atan2(sumSin / size, sumCos / size);
    }
}

使用Math.toDegrees()Math.toRadians()转换。

于 2013-09-20T07:32:14.830 回答
4

请记住,例如 350 和 10 的平均值不是 180。我的解决方案:

int difference = 0;
for(int i= 1;i <numberOfAngles;i++){
    difference += ( (angles[i]- angles[0] + 180 + 360 ) % 360 ) - 180;
}
averageAngle = (360 + angles[0] + ( difference / numberOfAngles ) ) % 360;
于 2013-08-05T14:44:51.823 回答
3

低通滤波器 (LPF) 阻止快速变化
的信号,只允许信号的缓慢变化。这意味着任何小的
突然变化都将被忽略。

在软件中实现这一点的标准方法是取
最后 N 个样本的运行平均值并报告该值。从小到 3 的 N 开始并
不断增加 N,直到您在应用程序中找到足够平滑的响应。

请记住,N 越高,系统响应越慢。

于 2011-03-05T03:58:51.893 回答
2

请参阅我对这个相关问题的回答:Smoothing data from a sensor

软件低通滤波器基本上是它的修改版本。事实上,在那个答案中,我什至提供了另一个相关问题的链接:低通滤波器软件?

于 2011-01-15T13:08:29.303 回答