平滑的GPS数据
我正在使用GPS数据,每秒获取数值并在地图上显示当前位置。 问题是,有时候(特别是当准确率低的时候)这些数值会有很大的差异,使得当前的位置在地图上的远点之间“跳跃”。
我想知道一些简单的方法来避免这种情况。 作为第一个想法,我想到了超出某个阈值的准确性丢弃值,但是我想还有其他更好的方法可以做。 程序执行此操作的常用方法是什么?
你在找什么叫卡尔曼滤波器 。 它经常用于平滑导航数据 。 这不一定是微不足道的,你可以做很多调整,但它是一个非常标准的方法,运作良好。 有一个KFilter库是C ++的实现。
我的下一个回退将是最小二乘拟合 。 卡尔曼滤波器将平滑数据采集速度,而最小二乘拟合方法将使用位置信息。 不过,它的实现和理解确实比较简单。 看起来GNU科学图书馆可能有这个实现。
这是一个简单的卡尔曼滤波器,可以用于这种情况。 它来自我在Android设备上做的一些工作。
通用卡尔曼滤波理论全部是关于向量的估计,其中估计的精度由协方差matrix表示。 但是,为了估算Android设备上的位置,一般理论简化为一个非常简单的情况。 Android位置提供商会将该位置作为经纬度,并将精度指定为以米为单位的单个数字。 这意味着即使卡尔曼滤波器中的位置是由两个数字测量的,代替协方差matrix,卡尔曼滤波器中的精度也可以用单个数来测量。 另外事实上,纬度,经度和米实际上是所有不同的单位可以忽略不计,因为如果你把比例因子放入卡尔曼滤波器将它们全部转换成相同的单位,那么这些比例因子在转换结果时最终取消回到原来的单位。
代码可以改进,因为它假设当前位置的最佳估计是最后一个已知位置,如果有人正在移动,应该可以使用Android的传感器来产生更好的估计。 该代码有一个单一的自由参数Q,以米/秒表示,它描述了在没有任何新的位置估计的情况下准确度衰减的速度。 较高的Q参数意味着准确度衰减得更快。 卡尔曼滤波器的精度一般比预期的更快,所以对于Android手机,我发现Q = 3米/秒工作正常,尽pipe我通常走得慢一些。 但是,如果乘坐快速汽车,显然应该使用更大的数量。
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; } } }
这可能会迟一点
我为Android写了这个KalmanLocationManager ,它包装了两个最常见的位置提供者Network和GPS,kalman-过滤数据,并将更新传递给LocationListener
(就像两个“真正的”提供者)。
我主要使用它来在读数之间进行“插值” – 例如每100毫秒接收一次更新(位置预测)(而不是一秒的最大gps速率),这在给我的位置进行animation时给了我一个更好的帧速率。
实际上,它使用三个卡尔曼滤波器,每个维度:纬度,经度和高度。 无论如何,他们都是独立的。
这使得matrix运算变得更容易:我使用3个不同的2x2matrix,而不是使用一个6×6状态转换matrix。 实际上在代码中,我根本不使用matrix。 解决了所有的方程和所有的值是原始的(双)。
源代码正在工作,并有一个演示活动。 对不起,在一些地方缺乏javadoc,我会赶上。
你不应该从每次的位置变化来计算速度。 GPS可能有不准确的位置,但它有准确的速度(5公里/小时以上)。 所以使用GPS位置标记的速度。 而且,当然你不应该那样做,虽然它在大多数时候都适用。
GPS的位置,已交付,已经卡尔曼过滤,你可能无法改善,后处理通常你有不同的信息,如GPS芯片。
你可以平滑它,但这也会引入错误。
只要确保在设备静止时删除位置,就可以消除跳转位置,某些设备/configuration不会被删除。
一种使用较lessmath/理论的方法是每次采样2,5,7或10个数据点,并确定那些是exception的。 与卡尔曼滤波器相比,不太精确的离群值测量方法是使用以下algorithm获取点之间的所有成对距离,并丢弃距离其他距离最远的点。 通常,这些值将被replace为最接近您正在replace的偏离值的值
例如
平滑五个采样点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}
这种平滑决定了exception值,并且可以通过使用BD的中点而不是D点来平滑位置线。 你的里程可能会有所不同,更严格的math解决scheme存在。
你也可以使用样条。 input您所拥有的值并在已知点之间插入点。 将其与最小二乘拟合,移动平均值或卡尔曼滤波器(如其他答案中所述)相结合,可以计算“已知”点之间的点。
如果你有更高的保真度,能够插入你知道的数值给你一个很好的平滑过渡和一个/合理的/近似的数据将出席。 http://en.wikipedia.org/wiki/Spline_interpolation
不同的样条具有不同的特征。 我见过的最常用的是Akima和Cubic样条曲线。
另一种考虑的algorithm是Ramer-Douglas-Peucker线简化algorithm,它在GPS数据的简化中相当常用。 ( http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm )
回到卡尔曼滤波器…我发现一个卡尔曼滤波器的GPS数据的C实现这里: http : //github.com/lacker/ikalman我还没有尝试过,但它似乎很有前途。
至于最小二乘拟合,这里还有其他一些要做的事情:
-
只是因为它的最小二乘拟合并不意味着它必须是线性的。 您可以用最小二乘法拟合数据的二次曲线,那么这将适合用户正在加速的场景。 (请注意,最小二乘拟合我的意思是使用坐标作为因variables,时间作为自variables。)
-
您也可以尝试根据报告的准确性对数据点进行加权。 当精确度较低时,这些数据点较低。
-
另一件你可能想要尝试的是显示一个点,而不是显示一个点,如果精度低,则显示一个圆圈或者表明用户可以基于报告的精度的范围。 (这是iPhone内置的Google地图应用程序的function。)
我通常使用加速度计。 短时间突然发生的位置变化意味着高加速度。 如果这在加速度计遥测中没有反映出来,那几乎肯定是由于用来计算位置的“最佳三颗”卫星(我称之为GPS传送)的变化。
当资产处于静止状态并由于GPS传送而跳动时,如果逐步计算质心,则可以有效地与更大,更大的壳体相交,从而提高精度。
要做到这一点,当资产不rest时,您必须根据速度,航向和线性和旋转(如果您有陀螺仪)加速度数据估计其可能的下一个位置和方向。 这或多或less是着名的K滤镜。 你可以在硬件上以150美元左右的价格获得整个东西,除了GPS模块,还有一个插孔来连接一个。 它有自己的CPU和卡尔曼滤波板上; 结果稳定而且相当好。 惯性导航具有很强的抗抖动能力,但随着时间而漂移。 GPS很容易产生抖动,但不会随着时间的推移而漂移,实际上它们是相互补偿的。
如果有兴趣的话,映射到CoffeeScript。 **编辑 – >对不起,也使用骨干,但你明白了。
修改略有接受与attribs灯塔
{latitude:item.lat,longitude:item.lng,date:new 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]