粒子滤波算法笔记(一):算法概述

1 滤波方法对比

方法 优点 缺点
Discrete Bayes filter 多模,非线性 离散,单变量
Kalman filter 单模线性最优估计 线性,单变量
Unscented Kalman filter 非线性,连续,多变量 非多模,难以处理非高斯/非线性
Extended Kalman filter 非线性,连续,多变量 比UKF更难以处理非高斯/非线性

2 粒子滤波原理

粒子代表了状态方程中的状态变量,拥有位置和朝向,以及其他需要预测的量。每个粒子拥有一个权重,表明这个粒子与系统真实状态的契合度。采用有限数量的随机采样点(粒子)近似计算样本分布。当粒子的数量足够多,估计的分布就能够代表样本的真实分布。

3 粒子滤波一般步骤

a. 初始化

在不输入先验信息的情况下,粒子在初始化空间中的概率均相同,因此对所有粒子随机撒点,并分配相同权重

b. 预测

按照控制模型,更新粒子分布,类似卡尔曼滤波中的移动均值

注意控制量存在随机噪声,因此粒子分布也应添加随机噪声,类似卡尔曼滤波中的增大方差

c. 更新

按照观测模型,更新粒子权重,类似卡尔曼滤波中的似然与先验相乘得到后验。

d. 估计系统状态

最重要的一步,由粒子得到状态估计量。

对于单模系统,一般估计均值,即:

$$\mu=\frac{1}{N}\sum_{i=1}^{N}{\omega^ix^i}$$

e. 重采样

最简单的粒子滤波算法面临退化问题。随着时间推移,可用粒子越来越少(权重非常低)。因此需要重采样,将低权重粒子更换成权重高的粒子的拷贝。一种简单的重采样方法称为multinomial resampling。通过在现有粒子群中采样N次,得到新的粒子群。

我们并不会每次循环都重采样。只有当有效性N降到阈值以下,才需要重采样。

$$\hat{N}_{eff}=\frac{1}{\sum{\omega^2}}$$

4. 代码实例

将粒子滤波用于定位,这里以Atsushi Sakai的Python Robotics中的粒子滤波定位代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import numpy as np
import math
import matplotlib.pyplot as plt

# Estimation parameter of PF
Q = np.diag([0.1])**2 # range error
R = np.diag([1.0, np.deg2rad(40.0)])**2 # input error

# Simulation parameter
Qsim = np.diag([0.2])**2
Rsim = np.diag([1.0, np.deg2rad(30.0)])**2

DT = 0.1 # time tick [s]
SIM_TIME = 50.0 # simulation time [s]
MAX_RANGE = 20.0 # maximum observation range

# Particle filter parameter
NP = 100 # Number of Particle
NTh = NP / 2.0 # Number of particle for re-sampling

show_animation = True


def calc_input():
v = 1.0 # [m/s]
yawrate = 0.1 # [rad/s]
u = np.array([[v, yawrate]]).T
return u


def observation(xTrue, xd, u, RFID):

xTrue = motion_model(xTrue, u)

# add noise to gps x-y
z = np.zeros((0, 3))

for i in range(len(RFID[:, 0])):

dx = xTrue[0, 0] - RFID[i, 0]
dy = xTrue[1, 0] - RFID[i, 1]
d = math.sqrt(dx**2 + dy**2)
if d <= MAX_RANGE:
dn = d + np.random.randn() * Qsim[0, 0] # add noise
zi = np.array([[dn, RFID[i, 0], RFID[i, 1]]])
z = np.vstack((z, zi))

# add noise to input
ud1 = u[0, 0] + np.random.randn() * Rsim[0, 0]
ud2 = u[1, 0] + np.random.randn() * Rsim[1, 1]
ud = np.array([[ud1, ud2]]).T

xd = motion_model(xd, ud)

return xTrue, z, xd, ud


def 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 x


def gauss_likelihood(x, sigma):
p = 1.0 / math.sqrt(2.0 * math.pi * sigma ** 2) * \
math.exp(-x ** 2 / (2 * sigma ** 2))

return p


def calc_covariance(xEst, px, pw):
cov = np.zeros((3, 3))

for i in range(px.shape[1]):
dx = (px[:, i] - xEst)[0:3]
cov += pw[0, i] * dx.dot(dx.T)

return cov


def pf_localization(px, pw, xEst, PEst, z, u):
"""
Localization with Particle filter
"""

for ip in range(NP):
x = np.array([px[:, ip]]).T
w = pw[0, ip]
# Predict with random input sampling
ud1 = u[0, 0] + np.random.randn() * Rsim[0, 0]
ud2 = u[1, 0] + np.random.randn() * Rsim[1, 1]
ud = np.array([[ud1, ud2]]).T
x = motion_model(x, ud)

# Calc Importance Weight
for i in range(len(z[:, 0])):
dx = x[0, 0] - z[i, 1]
dy = x[1, 0] - z[i, 2]
prez = math.sqrt(dx**2 + dy**2)
dz = prez - z[i, 0]
w = w * gauss_likelihood(dz, math.sqrt(Q[0, 0]))

px[:, ip] = x[:, 0]
pw[0, ip] = w

pw = pw / pw.sum() # normalize

xEst = px.dot(pw.T)
PEst = calc_covariance(xEst, px, pw)

px, pw = resampling(px, pw)

return xEst, PEst, px, pw


def resampling(px, pw):
"""
low variance re-sampling
"""

Neff = 1.0 / (pw.dot(pw.T))[0, 0] # Effective particle number
if Neff < NTh:
wcum = np.cumsum(pw)
base = np.cumsum(pw * 0.0 + 1 / NP) - 1 / NP
resampleid = base + np.random.rand(base.shape[0]) / NP

inds = []
ind = 0
for ip in range(NP):
while resampleid[ip] > wcum[ind]:
ind += 1
inds.append(ind)

px = px[:, inds]
pw = np.zeros((1, NP)) + 1.0 / NP # init weight

return px, pw


def plot_covariance_ellipse(xEst, PEst): # pragma: no cover
Pxy = PEst[0:2, 0:2]
eigval, eigvec = np.linalg.eig(Pxy)

if eigval[0] >= eigval[1]:
bigind = 0
smallind = 1
else:
bigind = 1
smallind = 0

t = np.arange(0, 2 * math.pi + 0.1, 0.1)

# eigval[bigind] or eiqval[smallind] were occassionally negative numbers extremely
# close to 0 (~10^-20), catch these cases and set the respective variable to 0
try:
a = math.sqrt(eigval[bigind])
except ValueError:
a = 0

try:
b = math.sqrt(eigval[smallind])
except ValueError:
b = 0

x = [a * math.cos(it) for it in t]
y = [b * math.sin(it) for it in t]
angle = math.atan2(eigvec[bigind, 1], eigvec[bigind, 0])
R = np.array([[math.cos(angle), math.sin(angle)],
[-math.sin(angle), math.cos(angle)]])
fx = R.dot(np.array([[x, y]]))
px = np.array(fx[0, :] + xEst[0, 0]).flatten()
py = np.array(fx[1, :] + xEst[1, 0]).flatten()
plt.plot(px, py, "--r")


def main():
print(__file__ + " start!!")

time = 0.0

# RFID positions [x, y]
RFID = 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))
PEst = np.eye(4)

px = np.zeros((4, NP)) # Particle store
pw = np.zeros((1, NP)) + 1.0 / NP # Particle weight
xDR = np.zeros((4, 1)) # Dead reckoning

# history
hxEst = xEst
hxTrue = xTrue
hxDR = xTrue

while SIM_TIME >= time:
time += DT
u = calc_input()

xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID)

xEst, PEst, px, pw = pf_localization(px, pw, xEst, PEst, z, ud)

# store data history
hxEst = np.hstack((hxEst, xEst))
hxDR = np.hstack((hxDR, xDR))
hxTrue = np.hstack((hxTrue, xTrue))

if show_animation:
plt.cla()

for i in range(len(z[:, 0])):
plt.plot([xTrue[0, 0], z[i, 1]], [xTrue[1, 0], z[i, 2]], "-k")
plt.plot(RFID[:, 0], RFID[:, 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()

结果如下:

参考文献

  1. https://github.com/AtsushiSakai/PythonRobotics/
  2. https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python