|
| 1 | +""" |
| 2 | +
|
| 3 | +Ensemble Kalman Filter localization sample |
| 4 | +
|
| 5 | +author: Ryohei Sasaki |
| 6 | +
|
| 7 | +""" |
| 8 | + |
| 9 | +import numpy as np |
| 10 | +import math |
| 11 | +import matplotlib.pyplot as plt |
| 12 | + |
| 13 | +# Simulation parameter |
| 14 | +Qsim = np.diag([0.2, np.deg2rad(1.0)])**2 |
| 15 | +Rsim = np.diag([1.0, np.deg2rad(30.0)])**2 |
| 16 | + |
| 17 | +DT = 0.1 # time tick [s] |
| 18 | +SIM_TIME = 50.0 # simulation time [s] |
| 19 | +MAX_RANGE = 20.0 # maximum observation range |
| 20 | + |
| 21 | +# Ensemble Kalman filter parameter |
| 22 | +NP = 20 # Number of Particle |
| 23 | + |
| 24 | +show_animation = True |
| 25 | + |
| 26 | + |
| 27 | +def calc_input(): |
| 28 | + v = 1.0 # [m/s] |
| 29 | + yawrate = 0.1 # [rad/s] |
| 30 | + u = np.array([[v, yawrate]]).T |
| 31 | + return u |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | +def observation(xTrue, xd, u, RFID): |
| 36 | + |
| 37 | + xTrue = motion_model(xTrue, u) |
| 38 | + |
| 39 | + z = np.zeros((0, 4)) |
| 40 | + |
| 41 | + for i in range(len(RFID[:, 0])): |
| 42 | + |
| 43 | + dx = RFID[i, 0] - xTrue[0, 0] |
| 44 | + dy = RFID[i, 1] - xTrue[1, 0] |
| 45 | + d = math.sqrt(dx**2 + dy**2) |
| 46 | + angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0]) |
| 47 | + if d <= MAX_RANGE: |
| 48 | + dn = d + np.random.randn() * Qsim[0, 0] # add noise |
| 49 | + anglen = angle + np.random.randn() * Qsim[1, 1] # add noise |
| 50 | + zi = np.array([dn, anglen,RFID[i, 0], RFID[i, 1]]) |
| 51 | + z = np.vstack((z, zi)) |
| 52 | + |
| 53 | + # add noise to input |
| 54 | + ud = np.array([[ |
| 55 | + u[0, 0] + np.random.randn() * Rsim[0, 0], |
| 56 | + u[1, 0] + np.random.randn() * Rsim[1, 1]]]).T |
| 57 | + |
| 58 | + xd = motion_model(xd, ud) |
| 59 | + return xTrue, z, xd, ud |
| 60 | + |
| 61 | + |
| 62 | +def motion_model(x, u): |
| 63 | + F = np.array([[1.0, 0, 0, 0], |
| 64 | + [0, 1.0, 0, 0], |
| 65 | + [0, 0, 1.0, 0], |
| 66 | + [0, 0, 0, 0]]) |
| 67 | + |
| 68 | + B = np.array([[DT * math.cos(x[2, 0]), 0], |
| 69 | + [DT * math.sin(x[2, 0]), 0], |
| 70 | + [0.0, DT], |
| 71 | + [1.0, 0.0]]) |
| 72 | + x = F.dot(x) + B.dot(u) |
| 73 | + |
| 74 | + return x |
| 75 | + |
| 76 | + |
| 77 | +def calc_LM_Pos(x, landmarks): |
| 78 | + landmarks_pos=np.zeros((2*landmarks.shape[0],1)) |
| 79 | + for (i,lm) in enumerate(landmarks): |
| 80 | + landmarks_pos[2*i] = x[0, 0] + lm[0] * math.cos(x[2, 0] + lm[1]) + np.random.randn() * Qsim[0, 0]/np.sqrt(2) |
| 81 | + landmarks_pos[2*i+1] = x[1, 0] + lm[0] * math.sin(x[2, 0] + lm[1]) + np.random.randn() * Qsim[0, 0]/np.sqrt(2) |
| 82 | + return landmarks_pos |
| 83 | + |
| 84 | +def calc_covariance(xEst, px): |
| 85 | + cov = np.zeros((3, 3)) |
| 86 | + |
| 87 | + for i in range(px.shape[1]): |
| 88 | + dx = (px[:, i] - xEst)[0:3] |
| 89 | + cov += dx.dot(dx.T) |
| 90 | + |
| 91 | + return cov |
| 92 | + |
| 93 | + |
| 94 | +def enkf_localization(px, xEst, PEst, z, u): |
| 95 | + """ |
| 96 | + Localization with Ensemble Kalman filter |
| 97 | + """ |
| 98 | + pz = np.zeros((z.shape[0]*2, NP)) # Particle store of z |
| 99 | + for ip in range(NP): |
| 100 | + x = np.array([px[:, ip]]).T |
| 101 | + |
| 102 | + # Predict with random input sampling |
| 103 | + ud1 = u[0, 0] + np.random.randn() * Rsim[0, 0] |
| 104 | + ud2 = u[1, 0] + np.random.randn() * Rsim[1, 1] |
| 105 | + ud = np.array([[ud1, ud2]]).T |
| 106 | + x = motion_model(x, ud) |
| 107 | + px[:, ip] = x[:, 0] |
| 108 | + z_pos=calc_LM_Pos(x, z) |
| 109 | + pz[:, ip] = z_pos[:, 0] |
| 110 | + |
| 111 | + x_ave=np.mean(px, axis=1) |
| 112 | + x_dif=px - np.tile(x_ave,(NP,1)).T |
| 113 | + |
| 114 | + z_ave=np.mean(pz, axis=1) |
| 115 | + z_dif=pz - np.tile(z_ave,(NP,1)).T |
| 116 | + |
| 117 | + U = 1/(NP-1)* x_dif @ z_dif.T |
| 118 | + V = 1/(NP-1)* z_dif @ z_dif.T |
| 119 | + |
| 120 | + K = U @ np.linalg.inv(V) # Kalman Gain |
| 121 | + |
| 122 | + z_lm_pos = z[:,[2,3]].reshape(-1,) |
| 123 | + |
| 124 | + px_hat=px + K @ (np.tile(z_lm_pos,(NP,1)).T- pz) |
| 125 | + |
| 126 | + xEst=np.average(px_hat, axis=1).reshape(4,1) |
| 127 | + pEst=calc_covariance(xEst, px_hat) |
| 128 | + |
| 129 | + return xEst, PEst, px_hat |
| 130 | + |
| 131 | + |
| 132 | +def plot_covariance_ellipse(xEst, PEst): # pragma: no cover |
| 133 | + Pxy = PEst[0:2, 0:2] |
| 134 | + eigval, eigvec = np.linalg.eig(Pxy) |
| 135 | + |
| 136 | + if eigval[0] >= eigval[1]: |
| 137 | + bigind = 0 |
| 138 | + smallind = 1 |
| 139 | + else: |
| 140 | + bigind = 1 |
| 141 | + smallind = 0 |
| 142 | + |
| 143 | + t = np.arange(0, 2 * math.pi + 0.1, 0.1) |
| 144 | + |
| 145 | + # eigval[bigind] or eiqval[smallind] were occassionally negative numbers extremely |
| 146 | + # close to 0 (~10^-20), catch these cases and set the respective variable to 0 |
| 147 | + try: |
| 148 | + a = math.sqrt(eigval[bigind]) |
| 149 | + except ValueError: |
| 150 | + a = 0 |
| 151 | + |
| 152 | + try: |
| 153 | + b = math.sqrt(eigval[smallind]) |
| 154 | + except ValueError: |
| 155 | + b = 0 |
| 156 | + |
| 157 | + x = [a * math.cos(it) for it in t] |
| 158 | + y = [b * math.sin(it) for it in t] |
| 159 | + angle = math.atan2(eigvec[bigind, 1], eigvec[bigind, 0]) |
| 160 | + R = np.array([[math.cos(angle), math.sin(angle)], |
| 161 | + [-math.sin(angle), math.cos(angle)]]) |
| 162 | + fx = R.dot(np.array([[x, y]])) |
| 163 | + px = np.array(fx[0, :] + xEst[0, 0]).flatten() |
| 164 | + py = np.array(fx[1, :] + xEst[1, 0]).flatten() |
| 165 | + plt.plot(px, py, "--r") |
| 166 | + |
| 167 | + |
| 168 | +def pi_2_pi(angle): |
| 169 | + return (angle + math.pi) % (2 * math.pi) - math.pi |
| 170 | + |
| 171 | +def main(): |
| 172 | + print(__file__ + " start!!") |
| 173 | + |
| 174 | + time = 0.0 |
| 175 | + |
| 176 | + # RFID positions [x, y] |
| 177 | + RFID = np.array([[10.0, 0.0], |
| 178 | + [10.0, 10.0], |
| 179 | + [0.0, 15.0], |
| 180 | + [-5.0, 20.0]]) |
| 181 | + |
| 182 | + # State Vector [x y yaw v]' |
| 183 | + xEst = np.zeros((4, 1)) |
| 184 | + xTrue = np.zeros((4, 1)) |
| 185 | + PEst = np.eye(4) |
| 186 | + |
| 187 | + px = np.zeros((4, NP)) # Particle store of x |
| 188 | + |
| 189 | + xDR = np.zeros((4, 1)) # Dead reckoning |
| 190 | + |
| 191 | + # history |
| 192 | + hxEst = xEst |
| 193 | + hxTrue = xTrue |
| 194 | + hxDR = xTrue |
| 195 | + |
| 196 | + while SIM_TIME >= time: |
| 197 | + time += DT |
| 198 | + u = calc_input() |
| 199 | + |
| 200 | + xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID) |
| 201 | + |
| 202 | + xEst, PEst, px = enkf_localization(px, xEst, PEst, z, ud) |
| 203 | + |
| 204 | + # store data history |
| 205 | + hxEst = np.hstack((hxEst, xEst)) |
| 206 | + hxDR = np.hstack((hxDR, xDR)) |
| 207 | + hxTrue = np.hstack((hxTrue, xTrue)) |
| 208 | + |
| 209 | + if show_animation: |
| 210 | + plt.cla() |
| 211 | + |
| 212 | + for i in range(len(z[:, 0])): |
| 213 | + plt.plot([xTrue[0, 0], z[i, 2]], [xTrue[1, 0], z[i, 3]], "-k") |
| 214 | + plt.plot(RFID[:, 0], RFID[:, 1], "*k") |
| 215 | + plt.plot(px[0, :], px[1, :], ".r") |
| 216 | + plt.plot(np.array(hxTrue[0, :]).flatten(), |
| 217 | + np.array(hxTrue[1, :]).flatten(), "-b") |
| 218 | + plt.plot(np.array(hxDR[0, :]).flatten(), |
| 219 | + np.array(hxDR[1, :]).flatten(), "-k") |
| 220 | + plt.plot(np.array(hxEst[0, :]).flatten(), |
| 221 | + np.array(hxEst[1, :]).flatten(), "-r") |
| 222 | + #plot_covariance_ellipse(xEst, PEst) |
| 223 | + plt.axis("equal") |
| 224 | + plt.grid(True) |
| 225 | + plt.pause(0.001) |
| 226 | + |
| 227 | + |
| 228 | +if __name__ == '__main__': |
| 229 | + main() |
0 commit comments