Ensemble Kalman filter集合卡尔曼滤波

article/2025/10/5 20:19:03

在气象预测领域,很多时候,模型具有 O ( 10 e 8 ) O(10e8) O(10e8)以上的量级,如果使用传统的卡尔曼滤波,协方差矩阵的更新将是一个~ 10 e 22 10e22 10e22量级的计算操作,因此传统的卡尔曼滤波并不适用。集合卡尔曼滤波(Ensemble Kalman filter)用sample covariance模拟原本的协方差矩阵,用一个小的多的量级的矩阵运算代替原本的协方差矩阵更新。集合卡尔曼滤波与粒子滤波有相通之处,集合卡尔曼滤波假设用来做模拟的ensembles符合高斯分布。

本文将通过一个应用EnKF进行定位的python实例来初探集合卡尔曼滤波的概念。

  • 参考:
    【1】Ensemble_Kalman_filtering P. L. Houtekamer Herschel L. Mitchell
    【2】ensemble_kalman_filter.py

根据参考【1】,集合卡尔曼滤波的公式为:

equations
用通俗的话来解释参考【2】例子中的EnKF:对于机器人状态向量 X = [ x , y , θ , v ] X=[x, y, \theta,v] X=[x,y,θ,v],生成数量为N的ensembles,每一个ensemble都是机器人状态向量加上一个方差为 Q Q Q的误差,如此一来,通过上面的(12)(13)式我们可以看出,状态向量协方差矩阵 P P P已经不需要显式地计算出来了,我们只要针对每一个ensemble做计算就好了,而在现实问题中,ensemble的数量要远小于 P P P的维度,这就是集合卡尔曼滤波的精髓。

下面我们来看参考【2】中的代码:

for ip in range(NP):x = np.array([px[:, ip]]).T#  Predict with random input sampling# 预测:给输入加上方差为Q的随机误差:ud1 = u[0, 0] + np.random.randn() * Q[0, 0] ** 0.5ud2 = u[1, 0] + np.random.randn() * Q[1, 1] ** 0.5ud = np.array([[ud1, ud2]]).Tx = motion_model(x, ud)px[:, ip] = x[:, 0]

对应公式(10),给运动模型的输入加上 q i q_i qi,计算每一个ensemble的状态向量的值。

接下来:

        # 根据状态预测值x和观测值z获取路标点加上观测方差R的空间位置z_posz_pos = observe_landmark_position(x, z)pz[:, ip] = z_pos[:, 0]

在这里我认为observe_landmark_position函数计算的其实是:

H x i f + r i Hx_i^f+r_i Hxif+ri

原因在计算(17)时会提到。

    x_ave = np.mean(px, axis=1)# 计算 x_f-x_f_barx_dif = px - np.tile(x_ave, (NP, 1)).Tz_ave = np.mean(pz, axis=1)# 计算 Hx_f-Hx_f_barz_dif = pz - np.tile(z_ave, (NP, 1)).TU = 1 / (NP - 1) * x_dif @ z_dif.TV = 1 / (NP - 1) * z_dif @ z_dif.T

接下来 U U U V V V我认为分别对应(12)(13)。在计算卡尔曼增益 K K K时,原代码直接计算:

K = U @ np.linalg.inv(V)

不知为什么好像没有体现出(14)式中的观测协方差 R R R,难道是因为我在上面提到的 p z pz pz中已经包含了观测噪声 r r r

我对原代码进行了一些改动,加入了观测方差 R R R(不能保证理论上的正确性),改动后的定位效果目测与原先没什么区别。

    # 以下是我的改动:# 观测协方差矩阵:R_a = np.diag([R[0,0]/2, R[0,0]/2] * z.shape[0])# 原代码:K = U @ np.linalg.inv(V)分母不包含观测协方差矩阵,# 这似乎与参考论文所述K的计算方法不符,不知道为什么这样处理# 在加入观测协方差R计算K后,与不加R计算K在定位结果上**目测**没有什么差别K = U @ np.linalg.inv(V + R_a)  # Kalman Gain

最后计算残差并进行更新:

    # 获取观测的真值:z_lm_pos = z[:, [2, 3]].reshape(-1, )# 更新:在这里pz包含了Hx_f和观测噪声rpx_hat = px + K @ (np.tile(z_lm_pos, (NP, 1)).T - pz)

在这里我们看到更新时直接用路标点真值减去了 p z pz pz,这也是为什么上面我提到observe_landmark_position函数计算的其实是 H x i f + r i Hx_i^f+r_i Hxif+ri

完整的包含注释的python代码如下:

"""Ensemble Kalman Filter(EnKF) localization sampleauthor: Ryohei Sasaki(rsasaki0109)modification: Junchuan ZhangRef:
Ensemble Kalman filtering
(https://rmets.onlinelibrary.wiley.com/doi/10.1256/qj.05.135)"""import mathimport matplotlib.pyplot as plt
import numpy as np
from scipy.spatial.transform import Rotation as Rot# 为了代码阅读上的方便,我在此加入了区别于simulation parameter的Q和R;
# Q和R数值上与simulation parameter一致(只是为了简化计算)
# 运动模型输入协方差:
Q = np.diag([1.0,np.deg2rad(30.0)]) ** 2  # predict state covariance
# 观测模型协方差:
R = np.diag([0.2, np.deg2rad(1.0)]) ** 2  # Observation covariance#  Simulation parameter
# 输入:
Q_sim = np.diag([1.0, np.deg2rad(30.0)]) ** 2
# 观测:
R_sim = np.diag([0.2, np.deg2rad(1.0)]) ** 2DT = 0.1  # time tick [s]
SIM_TIME = 50  # simulation time [s]
MAX_RANGE = 20.0  # maximum observation range# Ensemble Kalman filter parameter
# 很显然,增大NP,精度提高,计算量也随之提高
NP = 20  # Number of Particleshow_animation = Truedef calc_input():v = 1.0  # [m/s]yaw_rate = 0.1  # [rad/s]u = np.array([[v, yaw_rate]]).Treturn udef observation(xTrue, xd, u, RFID):xTrue = motion_model(xTrue, u)z = np.zeros((0, 4))for i in range(len(RFID[:, 0])):dx = RFID[i, 0] - xTrue[0, 0]dy = RFID[i, 1] - xTrue[1, 0]d = math.hypot(dx, dy)angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0])if d <= MAX_RANGE:dn = d + np.random.randn() * R_sim[0, 0] ** 0.5  # add noiseangle_with_noise = angle + np.random.randn() * R_sim[1, 1] ** 0.5# 在此将RFID的位置真值也一起放入了每组观测值内:zi = np.array([dn, angle_with_noise, RFID[i, 0], RFID[i, 1]])z = np.vstack((z, zi))# add noise to inputud = np.array([[u[0, 0] + np.random.randn() * Q_sim[0, 0] ** 0.5,u[1, 0] + np.random.randn() * Q_sim[1, 1] ** 0.5]]).Txd = motion_model(xd, ud)return xTrue, z, xd, uddef motion_model(x, u):F = np.array([[1.0, 0, 0, 0],[0, 1.0, 0, 0],[0, 0, 1.0, 0],[0, 0, 0, 0]])B = np.array([[DT * math.cos(x[2, 0]), 0],[DT * math.sin(x[2, 0]), 0],[0.0, DT],[1.0, 0.0]])x = F.dot(x) + B.dot(u)return xdef observe_landmark_position(x, landmarks):'''根据机器人空间位置x和路标点的观测值landmarks,计算加入观测噪声后的路标点空间值'''# landmark_pos大小为2倍路标点个数,因为将路标点x和y位置都放在了一个维度上:landmarks_pos = np.zeros((2 * landmarks.shape[0], 1))for (i, lm) in enumerate(landmarks):index = 2 * i# 在这里只包含R[0,0],因为路标点x和y只涉及距离误差:q = R[0, 0] ** 0.5# 因此每一维加上sqrt(距离协方差/2):landmarks_pos[index] = x[0, 0] + lm[0] * math.cos(x[2, 0] + lm[1]) + np.random.randn() * q / np.sqrt(2)landmarks_pos[index + 1] = x[1, 0] + lm[0] * math.sin(x[2, 0] + lm[1]) + np.random.randn() * q / np.sqrt(2)return landmarks_posdef calc_covariance(xEst, px):'''计算滤波更新后状态集合的方差'''cov = np.zeros((3, 3))for i in range(px.shape[1]):dx = (px[:, i] - xEst)[0:3]cov += dx.dot(dx.T)cov /= NPreturn covdef enkf_localization(px, z, u):"""Localization with Ensemble Kalman filterz: 前两位是观测,后两位是RFID的值u: 含有噪声的输入"""# print("________")pz = np.zeros((z.shape[0] * 2, NP))  # Particle store of zfor ip in range(NP):x = np.array([px[:, ip]]).T#  Predict with random input sampling# 预测:给输入加上方差为Q的随机误差:ud1 = u[0, 0] + np.random.randn() * Q[0, 0] ** 0.5ud2 = u[1, 0] + np.random.randn() * Q[1, 1] ** 0.5ud = np.array([[ud1, ud2]]).Tx = motion_model(x, ud)px[:, ip] = x[:, 0]# 根据状态预测值x和观测值z获取路标点加上观测方差R的空间位置z_posz_pos = observe_landmark_position(x, z)pz[:, ip] = z_pos[:, 0]x_ave = np.mean(px, axis=1)# 计算 x_f-x_f_barx_dif = px - np.tile(x_ave, (NP, 1)).Tz_ave = np.mean(pz, axis=1)# 计算 Hx_f-Hx_f_barz_dif = pz - np.tile(z_ave, (NP, 1)).TU = 1 / (NP - 1) * x_dif @ z_dif.TV = 1 / (NP - 1) * z_dif @ z_dif.T# 观测协方差矩阵:R_a = np.diag([R[0,0]/2, R[0,0]/2] * z.shape[0])# 原代码:K = U @ np.linalg.inv(V)分母不包含观测协方差矩阵,# 这似乎与参考论文所述K的计算方法不符,不知道为什么这样处理# 在加入观测协方差R计算K后,与不加R计算K在定位结果上**目测**没有什么差别K = U @ np.linalg.inv(V + R_a)  # Kalman Gain# 获取观测的真值:z_lm_pos = z[:, [2, 3]].reshape(-1, )# 更新:在这里pz包含了Hx_f和观测噪声rpx_hat = px + K @ (np.tile(z_lm_pos, (NP, 1)).T - pz)xEst = np.average(px_hat, axis=1).reshape(4, 1)# 这里的PEst只做画图之用,不参与预测-更新的迭代PEst = calc_covariance(xEst, px_hat)return xEst, PEst, px_hatdef plot_covariance_ellipse(xEst, PEst):  # pragma: no coverPxy = PEst[0:2, 0:2]eig_val, eig_vec = np.linalg.eig(Pxy)if eig_val[0] >= eig_val[1]:big_ind = 0small_ind = 1else:big_ind = 1small_ind = 0t = np.arange(0, 2 * math.pi + 0.1, 0.1)# eig_val[big_ind] or eiq_val[small_ind] were occasionally negative# numbers extremely close to 0 (~10^-20), catch these cases and set# the respective variable to 0try:a = math.sqrt(eig_val[big_ind])except ValueError:a = 0try:b = math.sqrt(eig_val[small_ind])except ValueError:b = 0x = [a * math.cos(it) for it in t]y = [b * math.sin(it) for it in t]angle = math.atan2(eig_vec[big_ind, 1], eig_vec[big_ind, 0])rot = Rot.from_euler('z', angle).as_matrix()[0:2, 0:2]fx = np.stack([x, y]).T @ rotpx = np.array(fx[:, 0] + xEst[0, 0]).flatten()py = np.array(fx[:, 1] + xEst[1, 0]).flatten()plt.plot(px, py, "--r")def pi_2_pi(angle):return (angle + math.pi) % (2 * math.pi) - math.pidef main():print(__file__ + " start!!")time = 0.0# RF_ID positions [x, y]RF_ID = np.array([[10.0, 0.0],[10.0, 10.0],[0.0, 15.0],[-5.0, 20.0]])# State Vector [x y yaw v]'xEst = np.zeros((4, 1))xTrue = np.zeros((4, 1))px = np.zeros((4, NP))  # Particle store of xxDR = np.zeros((4, 1))  # Dead reckoning# historyhxEst = xEsthxTrue = xTruehxDR = xTruewhile SIM_TIME >= time:time += DTu = calc_input()xTrue, z, xDR, ud = observation(xTrue, xDR, u, RF_ID)xEst, PEst, px = enkf_localization(px, z, ud)# store data historyhxEst = np.hstack((hxEst, xEst))hxDR = np.hstack((hxDR, xDR))hxTrue = np.hstack((hxTrue, xTrue))if show_animation:plt.cla()# for stopping simulation with the esc key.plt.gcf().canvas.mpl_connect('key_release_event',lambda event: [exit(0) if event.key == 'escape' else None])for i in range(len(z[:, 0])):plt.plot([xTrue[0, 0], z[i, 2]], [xTrue[1, 0], z[i, 3]], "-k")plt.plot(RF_ID[:, 0], RF_ID[:, 1], "*k")plt.plot(px[0, :], px[1, :], ".r")plt.plot(np.array(hxTrue[0, :]).flatten(),np.array(hxTrue[1, :]).flatten(), "-b")plt.plot(np.array(hxDR[0, :]).flatten(),np.array(hxDR[1, :]).flatten(), "-k")plt.plot(np.array(hxEst[0, :]).flatten(),np.array(hxEst[1, :]).flatten(), "-r")plot_covariance_ellipse(xEst, PEst)plt.axis("equal")plt.grid(True)plt.pause(0.001)if __name__ == '__main__':main()

http://chatgpt.dhexx.cn/article/dr0y8OXh.shtml

相关文章

Kalman滤波MATLAB实现实例——在温度测量中的应用

参考&#xff1a;《卡尔曼滤波原理及应用MATLAB仿真》 原理介绍 假设我们要研究的对象是一个房间的温度。根据经验判断,这个房间的温度大概在25℃左右,可能受空气流通、阳光等因素影响,房间内温度会小幅度地波动。我们以分钟为单位,定时测量房间温度,这里的1分钟,可以理解为采…

图解卡尔曼滤波(Kalman Filter)

背景 关于滤波 首先援引来自知乎大神的解释。 “一位专业课的教授给我们上课的时候&#xff0c;曾谈到&#xff1a;filtering is weighting&#xff08;滤波即加权&#xff09;。滤波的作用就是给不同的信号分量不同的权重。最简单的loss pass filter&#xff0c; 就是直接把低…

卡尔曼滤波(Kalman Filter)原理理解和测试

Kalman Filter学原理学习 1. Kalman Filter 历史 Kalman滤波器的历史,最早要追溯到17世纪,Roger Cotes开始研究最小均方问题。但由于缺少实际案例的支撑(那个时候哪来那么多雷达啊啥的这些信号啊),Cotes的研究让人看着显得很模糊,因此在估计理论的发展中影响很小。17世纪…

Kaplan-Meier

Kaplan-Meier 算法 Kaplan-Meier&#xff0c;是一种生存分析的常用方法&#xff0c;用于研究某一个因素对于生存时间的影响。在医学广泛使用&#xff0c;比如新药物是否有效的增加癌症病人的存活时间。 计算方法&#xff1a;假设我们已经计算出了时间t1的生存概率是0.95&#…

Kalman滤波器从原理到实现

转载请注明出处&#xff1a;http://xiahouzuoxin.github.io/notes Kalman滤波器的历史渊源 We are like dwarfs on the shoulders of giants, by whose grace we see farther than they. Our study of the works of the ancients enables us to give fresh life to their finer…

Kalman Filter 通俗讲解

引言 Kalman Filter&#xff0c;很多人刚听到这个名词时&#xff0c;总是会下意识认为这就是个滤波器。我这里想要重点声明的是&#xff0c;Kalman Filter不是滤波&#xff0c;它是一种信息融合的过程。 那么Kalman Filter到底是什么&#xff1f;它在那些方面有着应用&#xf…

卡尔曼(kalman)详解

Kalmanfliter [TOC](Kalmanfliter) kalman详解贝叶斯准则(Bayes rule)全概率定理贝叶斯卡尔曼matlab仿真 kalman详解 贝叶斯准则(Bayes rule) 全概率定理 两个随机变量 X 和 Y 的联合分布(joint distribution)如下: p (x , y) p (X x , Y y)&#xff08;Xx&#xff0c;Yy同…

kalman简单例子——初始化参数对kalman性能的影响

此篇为第⑤篇,多目标跟踪系列文章: 基础demor入门①②;公式推导③④;深入分析初始化参数的影响⑤; ① Matlab Kalman滤波例子——小球跟踪解析 :matlab官方例子,单目标跟踪。匀速模型和匀加速模型 ②Matlab Kalman Filter based Multiple object Tracking 官方例子 多目…

卡尔曼(kalman)滤波器原理

引言&#xff1a;卡尔曼滤波器适用于线性高斯系统&#xff0c;若为非线性系统&#xff0c;可以使用扩展卡尔曼滤波器。 一、状态估算器 如下图&#xff0c;如果我们需要知道火箭发射时尾部内部的实际温度&#xff0c;这个温度与火箭的燃料输入有关。但是由于里面温度过高&…

Kalman详尽原理介绍合集

目录 前言 1.线性kalman(LKF) 1.1LKF原理简介 1.2 适用场合 2.扩展kalman(EKF) 2.1EKF原理简介 2.2 适用场合 2.3 使用注意事项 3.无迹kalman&#xff08;UKF&#xff09; 3.1UKF原理简介 3.2 UT变换 3.3 适用场合 4.粒子滤波PF 4.1 PF原理简介 4.2 适用场合 前…

对Kalman(卡尔曼)滤波器的理解

分类&#xff1a; 计算机视觉 转载过来的&#xff0c;觉得不错&#xff0c;原文 http://blog.csdn.net/lanbing510/article/details/8828109 1.简介(Brief Introduction) 在学习卡尔曼滤波器之前&#xff0c;首先看看为什么叫“卡尔曼”。跟其他著名的理论&#xff08;例如傅…

Kalman滤波通俗理解+实际应用

一、Kalman用于解决什么的问题&#xff1f; 卡尔曼滤波是一种利用线性系统状态方程&#xff0c;通过系统输入输出观测数据&#xff0c;对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响&#xff0c;所以最优估计也可看作是滤波过程。 人话&#xff1…

IDEA开发及运行第一个Android项目

IDEA自动下载SDK、Gradle&#xff0c;保证能访问网络。 原来eclipse能使用的sdk&#xff0c;配到idea报错&#xff0c;就换成自动下载最新的了。 之前没成功可能是我防火墙禁用了上网。 新建项目 提示安装SDK 等待下载完成 继续建项目 选择手机或平板及目标设备API版本 选择…

怎么导入别人的android项目

到期末了好多同学都问我怎么把别人的安卓项目导进自己电脑里面&#xff0c;今天我来统一解答一下&#xff0c;希望有所帮助。 1.删除项目中原有的自动构建的文件 去到要导入项目的目录下把 .idea .gradle与build 三个文件夹&#xff0c;*.iml&#xff0c;local.properties删除…

android 开源项目:

android 开源项目&#xff1a; https://github.com/white-cat/ThinkAndroid 转自&#xff1a;http://glblong.blog.51cto.com/3058613/1354953 Android开源&#xff1a;数据库ORM框架GreenDao学习心得及使用总结 2014-01-26 23:40:01 标签&#xff1a; sqllite Android 开源…

Android项目工程结构介绍

Android项目工程结构介绍 &#xff08;1&#xff09;gradle和.idea Android Studio自动生成的文件&#xff0c;打包的时候一般会删掉再进行打包 &#xff08;2&#xff09;app 项目的代码资源都在其中&#xff0c;也是我们工作的核心目录 build &#xff1a;编译生成文件。生…

Android项目如何真机运行?

很多初学者在刚开始写Android项目的时候&#xff0c;会使用Android Studio自带的模拟器去运行项目&#xff0c;但是自带模拟器一般占内存大&#xff08;占2G都算少的&#xff09;&#xff1b;运行慢&#xff08;运行一个项目要启动半天&#xff09;&#xff1b;卡顿等等&#x…

26、Android创建项目

利用 Android Studio&#xff0c;您可以轻松地为各种类型的设备&#xff08;例如手机、平板电脑、电视和可穿戴设备&#xff09;创建 Android 应用。本页介绍了如何启动新的 Android 应用项目或导入现有项目。 如果您未打开项目&#xff0c;Android Studio 会显示欢迎屏幕&…

创建的第一个Android项目

Package name&#xff1a;项目的包名&#xff0c;一定要具有唯一性&#xff01; 静待。。。 创建模拟器 版本问题找不到具体信息&#xff0c;就选择这一个把。相似度比较高。紧跟着后边我也配置了安卓10&#xff08;Q&#xff09; 提示你的反病毒程序可能会影响你的构建性能。…

Android开源项目

阿里巴巴 项目名称功能描述freelineFreeline 是 Android 平台上的秒级编译方案&#xff0c;Instant Run 的替代品&#xff0c;也可以从 Freeline 官方主页来获取更多的信息。Freeline 由蚂蚁聚宝 Android 团队开发&#xff0c;它可以充分利用缓存文件&#xff0c;在几秒钟内迅…