Kalman Filter
Table of Contents
1. Kalman Filter
https://www.kalmanfilter.net/CN/default_cn.aspx
https://stackoverflow.com/questions/43377626/how-to-use-kalman-filter-in-python-for-location-data
https://www.bilibili.com/video/BV1TW411N7Hg/
http://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/
1.1. Overview
- \(K_n=\frac{p_{n-1}}{p_{n-1}+r_{n}}\)
- \(p_n=(1-K_n)p_{n-1}\)
- \(\hat{x}_n=(1-K_n)\hat{x}_{n-1}+K_nZ_n\)
- \(\hat{x}_n = \hat{x}_{n-1}\)
- \(p_n = p_{n-1}\)
1.1.1. 初始化
kalman filter update 时需要的前一时刻的数据, 包含:
- \(p_{n-1}\) predicted variance
- \(X_{n-1}\) predicted state
1.1.2. measure
通过 measure 获得:
- \(r_n\) measured variance
- \(Z_n\) measured state
1.1.3. correct
通过 measured 来更正 predicted
- 通过公式 1 计算 \(K_n\)
- 通过公式 2 计算 \(p_n\)
- 通过公式 3 计算 \(\hat{x}_n\)
1.1.4. predict
根据系统的 dynamics 预测 x, p
若系统是 constant dynamics, 则不需要 predict, 直接 \(x_{n}=x_{n-1}\)
1.2. Example
1.2.1. constant dynamics
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(50)
position = np.ones(50)
STD_POSITION = 10
poisy_position = position + np.random.normal(0, STD_POSITION, size=(position.shape[0]))
plt.plot(t, position, label="truth position")
plt.plot(t, poisy_position, label="measured")
predicts = [poisy_position[0]]
position_predict = predicts[0]
p = 100
r = STD_POSITION ** 2
for i in range(1, 50):
k = p / (p + r)
position_predict = position_predict * (1 - k) + poisy_position[i] * k
p = (1 - k) * p
predicts.append(position_predict)
plt.plot(t, predicts, label="kalman")
plt.legend()
plt.show()
1.2.2. non-constant dynamics
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(1, 100, 100)
a = 5
position = 10 * t + 1
STD_POSITION = 100
STD_IMU = 30
poisy_position = position + np.random.normal(0, STD_POSITION, size=(t.shape[0]))
plt.plot(t, position, label="truth position")
plt.plot(t, poisy_position, label="measured")
predicts = [poisy_position[0]]
position_predict = predicts[0]
p = 0
r = STD_POSITION ** 2
for i in range(1, 100):
# ----------measure
dv = (position[i] - position[i - 1]) + np.random.normal(0, STD_IMU)
# ----------predict
# 根据 dynamics 来更新 position_predict 和 p
position_predict = position_predict + dv
# aX+bY 仍是正常分布, 且方差为 a^2*variance(X) + b^2*variance(Y)
p += STD_IMU ** 2
# ----------update
k = p / (p + r)
position_predict = position_predict * (1 - k) + poisy_position[i] * k
p = (1 - k) * p
predicts.append(position_predict)
plt.plot(t, predicts, label="kalman")
plt.legend()
plt.show()
1.3. 多维 kalman filter
以一个在一维空间里匀速运动的小车为例:
- z,x 变为 2x1 矩阵 [[x],[v]]
- 需要一个 F 描述 dynamics, 设 dynamics 为: x = x+v*dt, v=v, 则 F = [[1,dt],[0,1]]
- p,r 变为 2x2 矩阵, 是一个关于 x,v 的协方差矩阵, 例如 r=[[A,0],[0,B]], 表示 x 方差为 A, v 的方差为 B. x,v 不相关, 协方差为 0
相应的五个公式变为:
https://www.kalmanfilter.net/multiSummary.html
- \(K_n=P_nH^T(HP_nH^T+R)^{-1}\)
- \(P_n=(I-K_nH)P_n(I-K_nH)^T+K_nR_n{K_n}^T\)
- \(\hat{x}_n=\hat{x}_n+K_n(Z_n-H\hat{x}_n)\)
- \(\hat{x}_{n}=F\hat{x}_{n-1}\)
- \(P_n=FP_{n}F^T+Q\)
涉及到 P, R 的公式都是 \(APA^T\) 的形式, 因为当 X, Y 是不相关的正态分布是, \(aX+bY\) 也是正态分布, 且其方差为 \(a^2\delta^2_X+b^2\delta^2_Y\)
其中 H 为 observation matrix, 用来处理 predict 中某些数据不能 measure 的情况 跟踪鼠标
import numpy as np
import matplotlib.pyplot as plt
N = 100
t = np.linspace(1, N, N)
position = 2 * t + 1
STD_POSITION = 10
STD_IMU = 3
noisy_position = position + np.random.normal(0, STD_POSITION, size=N)
noisy_speed = (position[1:] - position[:-1]) + np.random.normal(0, STD_IMU, size=N - 1)
plt.plot(t, position, label="truth position")
plt.plot(t, noisy_position, label="measured")
predicts = [0]
# 2x1
# X=[[x],[v]]
# 2x2
# F=[[1,dt],[0,1]]
# 2x2
# P=[[1,0],[0,1]]
# 2x2
# r=[[STD_POSITION**2,0],[0,STD_IMU**2]]
#
dt = 1
X = np.array([[0], [0]])
P = [[1, 0], [0, 1]]
# F,R 是我们用 kalman filter 时必需提供的两个参数, F 代表 dynamics 参数, R 代表
# correct 参数
F = np.array([[1, dt], [0, 1]])
R = [[STD_POSITION ** 2, 0], [0, STD_IMU ** 2]]
for i in range(1, N):
# measure
Z = [
[noisy_position[i]],
[(position[i] - position[i - 1]) + np.random.normal(0, STD_IMU)],
]
# predict
X = F @ X
P = F @ P @ F.T
# update
K = P @ np.linalg.inv(P + R)
X = X + K @ (Z - X)
P = (np.eye(2) - K) @ P @ (np.eye(2) - K).T + K @ R @ K.T
predicts.append(X[0][0])
plt.plot(t, predicts, label="kalman")
plt.legend()
plt.show()
1.4. cv2.KalmanFilter
1.4.1. 匀速运动的小车
import numpy as np
import matplotlib.pyplot as plt
import cv2
N = 100
t = np.linspace(1, N, N)
position = 3 * t + 1
STD_POSITION = 10
STD_IMU = 3
noisy_position = position + np.random.normal(0, STD_POSITION, size=N)
noisy_speed = (position[1:] - position[:-1]) + np.random.normal(0, STD_IMU, size=N - 1)
plt.plot(t, position, label="truth position")
plt.plot(t, noisy_position, label="measured")
predicts = [0]
dt = 1
F = np.array([[1, dt], [0, 1]], np.float32)
R = np.array([[STD_POSITION ** 2, 0], [0, STD_IMU ** 2]], np.float32)
filter = cv2.KalmanFilter(2, 2, 0)
# F
filter.transitionMatrix = F
# R
filter.measurementNoiseCov = R
# H
filter.measurementMatrix = np.eye(2, dtype=np.float32)
for i in range(1, N):
# measure
Z = np.array(
[
noisy_position[i],
(position[i] - position[i - 1]) + np.random.normal(0, STD_IMU),
],
np.float32,
)
# predict
filter.predict()
# corrent
output = filter.correct(Z)
predicts.append(output[0])
plt.plot(t, predicts, label="kalman")
plt.legend()
plt.show()
1.4.2. 跟踪鼠标
#!/usr/bin/env python3 import numpy as np import cv2 class Stabilizer: """Using Kalman filter as a point stabilizer.""" def __init__(self, state_num=4, measure_num=2, cov_process=0.0001, cov_measure=0.1): # Store the parameters. # 隐藏状态 # X 为 [[x],[y],[v_x],[v_y]] self.state_num = state_num # 观测状态 # Z 为 [[x],[y]] self.measure_num = measure_num # The filter itself. self.filter = cv2.KalmanFilter(state_num, measure_num, 0) # 转移矩阵: FX+Q # F self.filter.transitionMatrix = np.array( [[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]], np.float32 ) # Q self.filter.processNoiseCov = np.eye(4, dtype=np.float32) * cov_process # 观测矩阵: HX+R # H, 由于 measure 信息里只有 x,y, 不包括 v_x, v_y, 所以需要用 H 把 x 中 # 的后两个数据 mask 掉 self.filter.measurementMatrix = np.eye(2, 4, dtype=np.float32) # R self.filter.measurementNoiseCov = np.eye(2, dtype=np.float32) * cov_measure def update(self, measurement): self.filter.predict() self.filter.correct(measurement) # statePost 是 correct 之后的 state # statePre 是 predict 之后, corrent 之前的 state def main(): global mp mp = np.array((2, 1), np.float32) # measurement def onmouse(k, x, y, s, p): global mp mp = np.array([[np.float32(x)], [np.float32(y)]]) cv2.namedWindow("kalman") cv2.setMouseCallback("kalman", onmouse) kalman = Stabilizer(4, 2) frame = np.zeros((768, 1024, 3), np.uint8) while True: kalman.update(mp) state = kalman.filter.statePost cv2.circle(frame, (int(mp[0]), int(mp[1])), 2, (255, 0, 0), -1) cv2.circle(frame, (int(state[0]), int(state[1])), 2, (0, 0, 255), -1) cv2.imshow("kalman", frame) k = cv2.waitKey(30) & 0xFF if k == 27: break if __name__ == "__main__": main()
1.5. HMM vs. Kalman Filter
实际上,卡尔曼滤波与隐马尔可夫类似. HMM 的离散高斯动态模型, 而 kalman filter 是连续高斯动态模型. HX+R 相当于观测矩阵, FX+Q 相当于转移矩阵。卡尔曼滤波是给定观测状态序列 Z 求概率最大的隐含状态序列 X
