平滑的GPS数据


145

我正在使用GPS数据,每秒获取值并在地图上显示当前位置。问题在于,有时(特别是在精度较低的情况下)值会变化很大,从而使当前位置在地图上的远点之间“跳转”。

我想知道一种避免这种情况的足够简单的方法。作为第一个想法,我考虑过丢弃精度超过特定阈值的值,但是我猜想还有其他更好的方法。程序执行此操作的通常方式是什么?


在尝试计算相关的(微分)值(例如速度和坡度)时,我感到“ GPS噪声”的不利影响,这对于高采样率跟踪记录尤其不连续(因为时间具有整数[一秒]分辨率)。
heltonbiker 2012年

4
(此外,如果您要浏览主要道路,可以使用“
贴紧

我也正面临着这个问题,以获得最佳准确性。
ViruMax 2014年

Answers:


80

这是一个简单的卡尔曼滤波器,可以用于这种情况。它来自我在Android设备上所做的一些工作。

一般的卡尔曼滤波理论都是关于向量的估计,其估计的准确性由协方差矩阵表示。但是,为了估计Android设备上的位置,一般理论简化为一个非常简单的情况。Android位置提供程序将位置表示为纬度和经度,以及指定为以米为单位的单个数字的精度。这意味着,可以用一个数字来测量卡尔曼滤波器中的精度,而不用协方差矩阵,即使卡尔曼滤波器中的位置可以用两个数字来衡量。同样,纬度,经度和米实际上都是所有不同单位的事实也可以忽略不计,因为如果您将比例因子放入卡尔曼滤波器中,以将它们全部转换为相同单位,

该代码可以进行改进,因为它假定当前位置的最佳估计是最后一个已知位置,并且如果有人在移动,则应该可以使用Android的传感器来产生更好的估计。该代码具有单个自由参数Q,以米/秒表示,描述了在没有任何新位置估计的情况下精度下降的速度。Q参数越高,意味着精度下降得越快。当精度下降的速度比人们预期的快一点时,卡尔曼滤波器通常会更好地工作,因此,对于Android手机,我发现Q = 3米/秒的效果很好,尽管我通常走得慢一些。但是,如果乘坐快车旅行,显然应该使用更大的数量。

public class KalmanLatLong {
    private final float MinAccuracy = 1;

    private float Q_metres_per_second;    
    private long TimeStamp_milliseconds;
    private double lat;
    private double lng;
    private float variance; // P matrix.  Negative means object uninitialised.  NB: units irrelevant, as long as same units used throughout

    public KalmanLatLong(float Q_metres_per_second) { this.Q_metres_per_second = Q_metres_per_second; variance = -1; }

    public long get_TimeStamp() { return TimeStamp_milliseconds; }
    public double get_lat() { return lat; }
    public double get_lng() { return lng; }
    public float get_accuracy() { return (float)Math.sqrt(variance); }

    public void SetState(double lat, double lng, float accuracy, long TimeStamp_milliseconds) {
        this.lat=lat; this.lng=lng; variance = accuracy * accuracy; this.TimeStamp_milliseconds=TimeStamp_milliseconds;
    }

    /// <summary>
    /// Kalman filter processing for lattitude and longitude
    /// </summary>
    /// <param name="lat_measurement_degrees">new measurement of lattidude</param>
    /// <param name="lng_measurement">new measurement of longitude</param>
    /// <param name="accuracy">measurement of 1 standard deviation error in metres</param>
    /// <param name="TimeStamp_milliseconds">time of measurement</param>
    /// <returns>new state</returns>
    public void Process(double lat_measurement, double lng_measurement, float accuracy, long TimeStamp_milliseconds) {
        if (accuracy < MinAccuracy) accuracy = MinAccuracy;
        if (variance < 0) {
            // if variance < 0, object is unitialised, so initialise with current values
            this.TimeStamp_milliseconds = TimeStamp_milliseconds;
            lat=lat_measurement; lng = lng_measurement; variance = accuracy*accuracy; 
        } else {
            // else apply Kalman filter methodology

            long TimeInc_milliseconds = TimeStamp_milliseconds - this.TimeStamp_milliseconds;
            if (TimeInc_milliseconds > 0) {
                // time has moved on, so the uncertainty in the current position increases
                variance += TimeInc_milliseconds * Q_metres_per_second * Q_metres_per_second / 1000;
                this.TimeStamp_milliseconds = TimeStamp_milliseconds;
                // TO DO: USE VELOCITY INFORMATION HERE TO GET A BETTER ESTIMATE OF CURRENT POSITION
            }

            // Kalman gain matrix K = Covarariance * Inverse(Covariance + MeasurementVariance)
            // NB: because K is dimensionless, it doesn't matter that variance has different units to lat and lng
            float K = variance / (variance + accuracy * accuracy);
            // apply K
            lat += K * (lat_measurement - lat);
            lng += K * (lng_measurement - lng);
            // new Covarariance  matrix is (IdentityMatrix - K) * Covarariance 
            variance = (1 - K) * variance;
        }
    }
}

1
方差计算不应该是:方差+ = TimeInc_milliseconds * TimeInc_milliseconds * Q_metres_per_second * Q_metres_per_second / 1000000
Horacio

4
@Horacio,我知道你为什么会这样,但是不!从数学上讲,不确定性是通过维纳过程建模的(请参见en.wikipedia.org/wiki/Wiener_process),而通过维纳过程,方差会随着时间线性增长。该变量Q_metres_per_second对应sigma于该Wikipedia文章“相关过程”部分中的变量。 Q_metres_per_second是标准偏差,以米为单位,因此以米而非米/秒为单位。1秒钟后,它对应于分布的标准偏差。
随机

3
我尝试了这种方法和代码,但最终缩短了总距离。太不精确了。
Andreas Rudolph 2014年

1
@ user2999943是的,使用代码来处理您从onLocationChanged()获得的坐标。
随机

2
@Koray如果您没有准确度信息,则不能使用卡尔曼滤波器。这完全是卡尔曼滤波器尝试执行的操作的基础。
随机

75

您正在寻找的是所谓的卡尔曼滤波器。它通常用于平滑导航数据。它不一定很琐碎,可以进行很多调整,但这是一种非常标准的方法,效果很好。有一个KFilter库是C ++实现的。

我的下一个后退将是最小二乘拟合。卡尔曼滤波器将考虑速度将数据平滑化,而最小二乘拟合法将仅使用位置信息。尽管如此,实现和理解绝对更简单。看起来GNU科学图书馆可能已经实现了。


1
谢谢克里斯。是的,我在进行搜索时了解了有关Kalman的信息,但这肯定超出了我的数学知识。您是否知道任何易于阅读(和理解!)的示例代码,或者更好的是,有一些可用的实现?(C / C ++ / Java)
Al。

1
@Al不幸的是,我对Kalman滤波器的唯一了解是通过工作,所以我有一些非常优雅的代码无法显示给您。
克里斯·阿金

没问题:-)我试着寻找,但是由于某种原因,看来卡尔曼的事情是黑魔法。理论页面很多,但几乎没有代码。。谢谢,将尝试其他方法。
Al。

2
kalman.sourceforge.net/index.php此处是卡尔曼过滤器的C ++实现。
Rostyslav Druzhchenko 2014年

1
@ChrisArguin不客气。请让我知道结果是否良好。
Rostyslav Druzhchenko 2014年

11

这可能会晚一点...

我写了这个适用于Android的KalmanLocationManager,它包装了两个最常见的位置提供程序,即网络和GPS,对数据进行了kalman过滤,然后将更新提供给LocationListener(如两个“真实”提供程序)。

我主要使用它在读数之间“插值”-例如,每100毫秒接收更新(位置预测)(而不是一秒钟的最大gps速率),这使我的位置动画时具有更好的帧速率。

实际上,它对每个维度使用三个卡尔曼滤波器:纬度,经度和海拔高度。无论如何,它们都是独立的。

这使矩阵数学更容易:我使用3个不同的2x2矩阵,而不是使用一个6x6状态转换矩阵。实际上,在代码中,我根本不使用矩阵。解决所有方程式,所有值均为基元(双精度)。

源代码正在运行,并且有一个演示活动。很抱歉在某些地方缺少javadoc,我会跟上。


1
我尝试使用您的lib代码,但是得到了一些不希望的结果,我不确定是否做错了什么...(下面是图片url,蓝色是过滤位置的路径,橙色是原始位置)app.box。 com / s / w3uvaz007glp2utvgznmh8vlggvaiifk
umesh,2015年

您所看到的峰值从平均值(橙色线)开始“增长”,就像网络提供商的更新一样。您可以尝试绘制原始网络更新和gps更新吗?也许没有网络更新会更好,这取决于您要实现的目标。顺便说一句,您从哪里获得这些原始的橙色更新?
villoren

1
橙色点来自gps提供商,蓝色点来自Kalman,我在地图上绘制了日志
umesh,2015年

您能以某种文本格式将数据发送给我吗?每个位置更新都有Location.getProvider()字段集。带有所有Location.toString()的一个文件。
villoren

9

您不应根据每次的位置变化来计算速度。GPS的位置可能不准确,但速度准确(高于5 km / h)。因此,请使用GPS位置标记中的速度。而且,尽管它在大多数情况下都是可行的,但您当然不应这样做。

交付的GPS位置已经经过卡尔曼滤波,您可能无法改善,在后处理中,您通常没有与GPS芯片相同的信息。

您可以使其平滑,但这也会引入错误。

只要确保您在设备静止不动时移开位置,即可移开跳跃位置,并且某些设备/配置不会移开。


5
您能为此提供一些参考吗?
ivyleavedtoadflax

1
这些句子有很多信息和专业经验,您确切希望引用哪个句子?速度:搜索多普勒效应和GPS。内部卡尔曼?这是基本的GPS知识,每一篇描述GPS芯片内部工作原理的论文或书籍。smootig错误:每次平滑都会引入错误。站着不动?试试看。
AlexWien 2015年

2
静止不动的“跳来跳去”并不是唯一的错误来源。也有位置在附近跳跃的信号反射(例如,来自山脉)。我的GPS芯片(例如Garmin Dakota 20,SonyEricsson Neo)尚未过滤掉……而真正的笑话是未与大气压力结合使用时GPS信号的高程值。这些值未过滤,或者我不想看到未过滤的值。
hgoebl

1
@AlexWien GPS可以计算一次点到公差的距离,从而为您提供一个具有一定厚度的球体,以卫星为中心的壳体。您在此shell卷中的某个位置。这三个壳体积的交集为您提供一个位置体积,其质心是您计算出的位置。如果您有一组报告的位置并且知道传感器处于静止状态,则计算质心会有效地与更多的壳体相交,从而提高精度。这样可以减少错误。
Peter Wone

6
“交付的GPS位置已经经过卡尔曼滤波,您可能无法改善”。如果您可以指向证实这一点的来源(例如,对于现代智能手机),那将非常有用。我自己看不到任何证据。即使对设备原始位置进行简单的卡尔曼滤波也强烈表明这是不正确的。原始位置不稳定地跳动,而经过过滤的位置通常最接近真实(已知)位置。
sobri

6

我通常使用加速度计。在短时间内突然改变位置意味着高加速度。如果加速度计遥测中没有反映出这一点,那几乎可以肯定是由于用于计算位置的“最佳三颗”卫星的变化(我将其称为GPS远距传输)。

当资产处于静止状态并由于GPS传送而跳来跳去时,如果逐步计算质心,则实际上是在与越来越多的壳体相交,从而提高了精度。

为此,当资产处于静止状态时,您必须根据速度,航向以及线性和旋转(如果有陀螺仪)加速度数据估算其可能的下一个位置和方向。这或多或少是著名的K滤波器所做的。您可以在AHRS上以约$ 150的价格在硬件中获得整个东西,其中包含GPS模块以外的所有东西,并带有用于连接一个人的插孔。板载自己的CPU和卡尔曼滤波功能。结果稳定并且相当不错。惯性制导具有很高的抗抖动能力,但会随时间漂移。GPS容易抖动,但不会随时间漂移,实际上是使GPS相互补偿。


4

一种使用较少数学/理论的方法是一次采样2、5、7或10个数据点,然后确定那些异常值。与卡尔曼滤波器相比,离群值的准确度较低是使用以下算法来获取点之间的所有成对距离,并抛出距离其他点最远的点。通常,这些值将替换为最接近要替换的外围值的值

例如

在五个采样点A,B,C,D,E进行平滑

ATOTAL =距离的总和AB AC AD AE

BTOTAL =距离总和AB BC BD BE

CTOTAL =距离总和AC BC CD CE

DTOTAL =距离的总和DA DB DC DE

ETOTAL =距离总和EA EB EC DE

如果BTOTAL最大,则在BD = min {AB,BC,BD,BE}的情况下,将B点替换为D点

此平滑确定异常值,可以通过使用BD的中点而不是D点来进行平滑以增强位置线。您的里程可能会有所不同,并且存在数学上更严格的解决方案。


谢谢,我也会试一试。请注意,我要平滑当前位置,因为它是显示的位置,也是用于检索某些数据的位置。我对过去的观点不感兴趣。我最初的想法是使用加权方式,但是我仍然必须看看最好的方法。
Al。

1
Al,这似乎是加权均值的一种形式。如果要进行任何平滑操作,则需要使用“过去”点,因为系统还需要拥有比当前位置更多的位置,才能知道在哪里进行平滑处理。如果您的GPS每秒获取一次数据点,并且您的用户每五秒钟查看一次屏幕,则您可以使用5个数据点而无需他注意!移动平均线也只会延迟一dp。
卡尔

4

至于最小二乘方拟合,以下是一些其他可以尝试的东西:

  1. 仅仅因为最小二乘方拟合并不意味着它必须是线性的。您可以对数据进行二次平方最小二乘拟合,然后这将适合用户加速的情况。(请注意,通过最小二乘拟合,我的意思是将坐标用作因变量,将时间用作自变量。)

  2. 您也可以尝试根据报告的准确性对数据点进行加权。当精度较低时,这些数据点会降低。

  3. 您可能要尝试的另一件事是,而不是显示单个点,如果准确性较低,则显示一个圆圈或一些内容,以指示用户根据报告的准确性所处的范围。(这是iPhone的内置Google Maps应用程序所做的。)


3

您也可以使用样条曲线。输入您拥有的值并在已知点之间插入点。将其与最小二乘拟合,移动平均或卡尔曼滤波器(如其他答案所述)链接在一起,即可计算出“已知”点之间的点。

能够在您的已知值之间进行插值,可以使您获得一个很好的平滑过渡,并且如果您具有更高的保真度,则可以合理/近似地显示出哪些数据。http://en.wikipedia.org/wiki/Spline_interpolation

不同的样条具有不同的特性。我见过的最常用的是Akima和Cubic样条曲线。

另一个要考虑的算法是Ramer-Douglas-Peucker线简化算法,它在GPS数据简化中非常常用。(http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm



0

如果有人感兴趣,则映射到CoffeeScript。**编辑->对不起,也使用主干,但是您明白了。

稍加修改以接受带有attribs的信标

{纬度:item.lat,经度:item.lng,日期:新的Date(item.effective_at),精度:item.gps_accuracy}

MIN_ACCURACY = 1

# mapped from http://stackoverflow.com/questions/1134579/smooth-gps-data

class v.Map.BeaconFilter

  constructor: ->
    _.extend(this, Backbone.Events)

  process: (decay,beacon) ->

    accuracy     = Math.max beacon.accuracy, MIN_ACCURACY

    unless @variance?
      # if variance nil, inititalise some values
      @variance     = accuracy * accuracy
      @timestamp_ms = beacon.date.getTime();
      @lat          = beacon.latitude
      @lng          = beacon.longitude

    else

      @timestamp_ms = beacon.date.getTime() - @timestamp_ms

      if @timestamp_ms > 0
        # time has moved on, so the uncertainty in the current position increases
        @variance += @timestamp_ms * decay * decay / 1000;
        @timestamp_ms = beacon.date.getTime();

      # Kalman gain matrix K = Covarariance * Inverse(Covariance + MeasurementVariance)
      # NB: because K is dimensionless, it doesn't matter that variance has different units to lat and lng
      _k  = @variance / (@variance + accuracy * accuracy)
      @lat = _k * (beacon.latitude  - @lat)
      @lng = _k * (beacon.longitude - @lng)

      @variance = (1 - _k) * @variance

    [@lat,@lng]

试图进行编辑,但是在@lat@lng设置的最后几行中有一个错字。应该+=而不是=
jdixon04 '19

0

我已经将Java代码从@Stochastically转换为Kotlin

class KalmanLatLong
{
    private val MinAccuracy: Float = 1f

    private var Q_metres_per_second: Float = 0f
    private var TimeStamp_milliseconds: Long = 0
    private var lat: Double = 0.toDouble()
    private var lng: Double = 0.toDouble()
    private var variance: Float =
        0.toFloat() // P matrix.  Negative means object uninitialised.  NB: units irrelevant, as long as same units used throughout

    fun KalmanLatLong(Q_metres_per_second: Float)
    {
        this.Q_metres_per_second = Q_metres_per_second
        variance = -1f
    }

    fun get_TimeStamp(): Long { return TimeStamp_milliseconds }
    fun get_lat(): Double { return lat }
    fun get_lng(): Double { return lng }
    fun get_accuracy(): Float { return Math.sqrt(variance.toDouble()).toFloat() }

    fun SetState(lat: Double, lng: Double, accuracy: Float, TimeStamp_milliseconds: Long)
    {
        this.lat = lat
        this.lng = lng
        variance = accuracy * accuracy
        this.TimeStamp_milliseconds = TimeStamp_milliseconds
    }

    /// <summary>
    /// Kalman filter processing for lattitude and longitude
    /// /programming/1134579/smooth-gps-data/15657798#15657798
    /// </summary>
    /// <param name="lat_measurement_degrees">new measurement of lattidude</param>
    /// <param name="lng_measurement">new measurement of longitude</param>
    /// <param name="accuracy">measurement of 1 standard deviation error in metres</param>
    /// <param name="TimeStamp_milliseconds">time of measurement</param>
    /// <returns>new state</returns>
    fun Process(lat_measurement: Double, lng_measurement: Double, accuracy: Float, TimeStamp_milliseconds: Long)
    {
        var accuracy = accuracy
        if (accuracy < MinAccuracy) accuracy = MinAccuracy

        if (variance < 0)
        {
            // if variance < 0, object is unitialised, so initialise with current values
            this.TimeStamp_milliseconds = TimeStamp_milliseconds
            lat = lat_measurement
            lng = lng_measurement
            variance = accuracy * accuracy
        }
        else
        {
            // else apply Kalman filter methodology

            val TimeInc_milliseconds = TimeStamp_milliseconds - this.TimeStamp_milliseconds

            if (TimeInc_milliseconds > 0)
            {
                // time has moved on, so the uncertainty in the current position increases
                variance += TimeInc_milliseconds.toFloat() * Q_metres_per_second * Q_metres_per_second / 1000
                this.TimeStamp_milliseconds = TimeStamp_milliseconds
                // TO DO: USE VELOCITY INFORMATION HERE TO GET A BETTER ESTIMATE OF CURRENT POSITION
            }

            // Kalman gain matrix K = Covarariance * Inverse(Covariance + MeasurementVariance)
            // NB: because K is dimensionless, it doesn't matter that variance has different units to lat and lng
            val K = variance / (variance + accuracy * accuracy)
            // apply K
            lat += K * (lat_measurement - lat)
            lng += K * (lng_measurement - lng)
            // new Covarariance  matrix is (IdentityMatrix - K) * Covarariance
            variance = (1 - K) * variance
        }
    }
}

0

这是@Stochastically为需要的人提供的Java实现的Javascript实现:

class GPSKalmanFilter {
  constructor (decay = 3) {
    this.decay = decay
    this.variance = -1
    this.minAccuracy = 1
  }

  process (lat, lng, accuracy, timestampInMs) {
    if (accuracy < this.minAccuracy) accuracy = this.minAccuracy

    if (this.variance < 0) {
      this.timestampInMs = timestampInMs
      this.lat = lat
      this.lng = lng
      this.variance = accuracy * accuracy
    } else {
      const timeIncMs = timestampInMs - this.timestampInMs

      if (timeIncMs > 0) {
        this.variance += (timeIncMs * this.decay * this.decay) / 1000
        this.timestampInMs = timestampInMs
      }

      const _k = this.variance / (this.variance + (accuracy * accuracy))
      this.lat += _k * (lat - this.lat)
      this.lng += _k * (lng - this.lng)

      this.variance = (1 - _k) * this.variance
    }

    return [this.lng, this.lat]
  }
}

用法示例:

   const kalmanFilter = new GPSKalmanFilter()
   const updatedCoords = []

    for (let index = 0; index < coords.length; index++) {
      const { lat, lng, accuracy, timestampInMs } = coords[index]
      updatedCoords[index] = kalmanFilter.process(lat, lng, accuracy, timestampInMs)
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.