平滑的 GPS 数据

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

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

123797 次浏览

你要找的是 卡尔曼滤波器。它经常用于 平滑的导航数据。它不一定是微不足道的,您可以进行大量的调优,但它是一种非常标准的方法,并且工作得很好。有一个可用的 KFilter 库,它是一个 C + + 实现。

我的下一个退路是 最小二乘拟合。卡尔曼滤波器将平滑考虑速度的数据,而最小二乘拟合方法将只使用位置信息。尽管如此,它的实现和理解肯定更简单。看起来这个 GNU科学数值库可能有一个 ABc1

一种使用较少数学/理论的方法是一次抽样2、5、7或10个数据点,并确定哪些是异常值。衡量一个异常值比衡量一个 Kalman Filter 异常值更不精确的方法是使用以下的 abc0来计算各点之间的所有配对距离,并剔除与其他点距离最远的那个。通常,将这些值替换为与要替换的外围值最接近的值

比如说

平滑五个样本点 A,B,C,D,E

距离之和

距离之和

距离之和

距离之和

ETOTAL = 距离之和 EA EB EC DE

如果 BTOTAL 是最大的,那么如果 BD = min { AB,BC,BD,BE } ,则用 D 替换点 B

这种平滑确定了异常值,并可以通过使用 BD 的中点代替点 D 来平滑位置线。你的经历可能会有所不同,并且存在更严格的数学解决方案。

至于最小二乘拟合,这里有一些其他的东西可以尝试:

  1. 仅仅因为它是最小二乘拟合并不意味着它必须是线性的。你可以用最小二乘法对数据进行二次曲线拟合,这样就可以符合用户正在加速的场景。(注意,最小二乘拟合的意思是使用坐标作为因变量,时间作为独立变量。)

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

  3. 如果精度较低,您可能想要尝试的另一件事是显示单个点,而不是显示一个圆或指示用户可以根据所报告的精度范围的东西。(这就是 iPhone 内置的谷歌地图(Google Maps)应用程序的功能。)

也可以使用样条函数。输入已有的值,并在已知点之间插入点。将它与最小二乘拟合、移动平均值或卡尔曼滤波器(如其他答案中提到的)联系起来,可以让你计算出“已知”点之间的点数。

能够在你的已知值之间插值给你一个很好的平稳过渡和一个/合理/近似的数据,如果你有一个更高的保真度将会出现。http://en.wikipedia.org/wiki/Spline_interpolation

不同的样条曲线有不同的特点。我见过最常用的是秋岛曲线和三次样条曲线。

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

回到卡尔曼滤波器... 我在这里发现了一个用于 GPS 数据的卡尔曼滤波器的 C 实现: http://github.com/lacker/ikalman我还没有试用过,但似乎很有希望。

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

一般的卡尔曼滤波理论都是关于向量的估计,用协方差矩阵表示估计的精度。然而,对于 Android 设备上的位置估计,一般理论归结为一个非常简单的情况。Android 位置服务提供商给出的位置是一个经纬度,精确度则指定为一个以米为单位的数字。这意味着卡尔曼滤波器的精度可以用一个数字来衡量,而不是协方差矩阵,即使卡尔曼滤波器中的位置是用两个数字来衡量的。事实上,纬度、经度和米实际上都是不同的单位,这一点可以忽略不计,因为如果你把比例因子放入卡尔曼滤波器中,将它们转换成相同的单位,那么当把结果转换回原来的单位时,这些比例因子最终会相互抵消。

该代码可以得到改进,因为它假设对当前位置的最佳估计是已知的最后位置,如果有人正在移动,应该可以使用 Android 的传感器来产生更好的估计。代码有一个单一的自由参数 Q,以米每秒表示,它描述了在没有任何新的位置估计的情况下,精度衰减的速度。较高的 Q 参数意味着精度衰减得更快。卡尔曼滤波器通常工作得更好,当准确度下降的速度比人们可能预期的要快一点,所以我发现,对于走在一个安卓手机上,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;
}
}
}

你不应该从每次的位置变化来计算速度。GPS 可能有不准确的位置,但它有准确的速度(超过5公里/小时)。利用 GPS 定位标记的速度。 此外,你不应该这样做,当然,虽然它工程的大部分时间。

GPS 的位置,作为交付,已经卡尔曼滤波,你可能无法改善,在后处理通常你没有像 GPS 芯片一样的信息。

您可以平滑它,但是这也会引入错误。

只要确保在设备静止时移除位置即可,这样可以移除一些设备/配置不移除的跳跃位置。

我通常用加速度计。在短时间内位置的突然变化意味着高加速度。如果这没有反映在加速度传感器遥感测量中,那么几乎可以肯定是由于用于计算位置的“最佳三颗”卫星的变化(我称之为 GPS 心灵传输)。

当一个资产由于 GPS 心灵传输而处于静止和跳跃状态时,如果逐步计算质心,就能有效地交叉越来越多的壳层,从而提高精度。

要做到这一点,当资产不是在休息,你必须估计其可能的下一个位置和方向的基础上的速度,航向和线性和旋转(如果你有陀螺仪)加速度数据。这或多或少是著名的 K 过滤器所做的。你可以在硬件上得到整个东西约150美元的 AHRS 包含一切,但全球定位系统模块,并与一个插孔连接一个。它具有自己的 CPU 和卡尔曼滤波器,结果稳定,相当不错。惯性制导是高度抗抖动,但随着时间漂移。GPS 易于抖动,但不随时间漂移,它们实际上是为了相互补偿。

这可能有点晚了..。

我为 Android 编写了这个 KalmanLocationManager,它包装了两个最常见的位置提供商,网络和 GPS,卡尔曼过滤数据,并提供更新到 LocationListener(就像两个“真正的”提供商)。

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

实际上,它使用三个卡尔曼滤波器,对每个维度: 纬度,经度和高度。 不管怎样,他们是独立的。

这使得矩阵的计算变得更加简单: 我使用3个不同的2x2矩阵,而不是一个6x6的状态转移矩阵。实际上在代码中,我根本不用矩阵。解决了所有的方程和所有的值都是基元(双)。

源代码正在工作,有一个演示活动。 抱歉有些地方没有 javadoc 我会跟上的。

映射到 CoffeeScript 如果有人感兴趣。 * * 编辑-> 对不起使用主干,但你得到的想法。

稍作修改以接受带属性的信标

经度: item.lng,日期: new 日期(item.effect _ at) ,准确性: item.gps _ error }

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]

我已经将 Java 代码从“随机”转换为“ 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
/// https://stackoverflow.com/questions/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
}
}
}

下面是@Stochusty 的 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)
}