|
| 1 | +""" |
| 2 | +
|
| 3 | +Graph SLAM example |
| 4 | +
|
| 5 | +author: Atsushi Sakai (@Atsushi_twi) |
| 6 | +
|
| 7 | +""" |
| 8 | + |
| 9 | +import numpy as np |
| 10 | +import math |
| 11 | +import matplotlib.pyplot as plt |
| 12 | + |
| 13 | + |
| 14 | +# EKF state covariance |
| 15 | +Cx = np.diag([0.5, 0.5, math.radians(30.0)])**2 |
| 16 | + |
| 17 | +# Simulation parameter |
| 18 | +Qsim = np.diag([0.2, math.radians(1.0)])**2 |
| 19 | +Rsim = np.diag([1.0, math.radians(10.0)])**2 |
| 20 | + |
| 21 | +DT = 0.1 # time tick [s] |
| 22 | +SIM_TIME = 50.0 # simulation time [s] |
| 23 | +MAX_RANGE = 20.0 # maximum observation range |
| 24 | +M_DIST_TH = 2.0 # Threshold of Mahalanobis distance for data association. |
| 25 | +STATE_SIZE = 3 # State size [x,y,yaw] |
| 26 | +LM_SIZE = 2 # LM srate size [x,y] |
| 27 | + |
| 28 | +show_animation = True |
| 29 | + |
| 30 | + |
| 31 | +def ekf_slam(xEst, PEst, u, z): |
| 32 | + |
| 33 | + # Predict |
| 34 | + S = STATE_SIZE |
| 35 | + xEst[0:S] = motion_model(xEst[0:S], u) |
| 36 | + G, Fx = jacob_motion(xEst[0:S], u) |
| 37 | + PEst[0:S, 0:S] = G.T * PEst[0:S, 0:S] * G + Fx.T * Cx * Fx |
| 38 | + initP = np.eye(2) |
| 39 | + |
| 40 | + # Update |
| 41 | + for iz in range(len(z[:, 0])): # for each observation |
| 42 | + minid = search_correspond_LM_ID(xEst, PEst, z[iz, 0:2]) |
| 43 | + |
| 44 | + nLM = calc_n_LM(xEst) |
| 45 | + if minid == nLM: |
| 46 | + print("New LM") |
| 47 | + # Extend state and covariance matrix |
| 48 | + xAug = np.vstack((xEst, calc_LM_Pos(xEst, z[iz, :]))) |
| 49 | + PAug = np.vstack((np.hstack((PEst, np.zeros((len(xEst), LM_SIZE)))), |
| 50 | + np.hstack((np.zeros((LM_SIZE, len(xEst))), initP)))) |
| 51 | + xEst = xAug |
| 52 | + PEst = PAug |
| 53 | + |
| 54 | + lm = get_LM_Pos_from_state(xEst, minid) |
| 55 | + y, S, H = calc_innovation(lm, xEst, PEst, z[iz, 0:2], minid) |
| 56 | + |
| 57 | + K = PEst * H.T * np.linalg.inv(S) |
| 58 | + xEst = xEst + K * y |
| 59 | + PEst = (np.eye(len(xEst)) - K * H) * PEst |
| 60 | + |
| 61 | + xEst[2] = pi_2_pi(xEst[2]) |
| 62 | + |
| 63 | + return xEst, PEst |
| 64 | + |
| 65 | + |
| 66 | +def calc_input(): |
| 67 | + v = 1.0 # [m/s] |
| 68 | + yawrate = 0.1 # [rad/s] |
| 69 | + u = np.matrix([v, yawrate]).T |
| 70 | + return u |
| 71 | + |
| 72 | + |
| 73 | +def observation(xTrue, xd, u, RFID): |
| 74 | + |
| 75 | + xTrue = motion_model(xTrue, u) |
| 76 | + |
| 77 | + # add noise to gps x-y |
| 78 | + z = np.matrix(np.zeros((0, 3))) |
| 79 | + |
| 80 | + for i in range(len(RFID[:, 0])): |
| 81 | + |
| 82 | + dx = RFID[i, 0] - xTrue[0, 0] |
| 83 | + dy = RFID[i, 1] - xTrue[1, 0] |
| 84 | + d = math.sqrt(dx**2 + dy**2) |
| 85 | + angle = pi_2_pi(math.atan2(dy, dx)) |
| 86 | + if d <= MAX_RANGE: |
| 87 | + dn = d + np.random.randn() * Qsim[0, 0] # add noise |
| 88 | + anglen = angle + np.random.randn() * Qsim[1, 1] # add noise |
| 89 | + zi = np.matrix([dn, anglen, i]) |
| 90 | + z = np.vstack((z, zi)) |
| 91 | + |
| 92 | + # add noise to input |
| 93 | + ud1 = u[0, 0] + np.random.randn() * Rsim[0, 0] |
| 94 | + ud2 = u[1, 0] + np.random.randn() * Rsim[1, 1] |
| 95 | + ud = np.matrix([ud1, ud2]).T |
| 96 | + |
| 97 | + xd = motion_model(xd, ud) |
| 98 | + |
| 99 | + return xTrue, z, xd, ud |
| 100 | + |
| 101 | + |
| 102 | +def motion_model(x, u): |
| 103 | + |
| 104 | + F = np.matrix([[1.0, 0, 0], |
| 105 | + [0, 1.0, 0], |
| 106 | + [0, 0, 1.0]]) |
| 107 | + |
| 108 | + B = np.matrix([[DT * math.cos(x[2, 0]), 0], |
| 109 | + [DT * math.sin(x[2, 0]), 0], |
| 110 | + [0.0, DT]]) |
| 111 | + |
| 112 | + x = F * x + B * u |
| 113 | + |
| 114 | + return x |
| 115 | + |
| 116 | + |
| 117 | +def calc_n_LM(x): |
| 118 | + n = int((len(x) - STATE_SIZE) / LM_SIZE) |
| 119 | + return n |
| 120 | + |
| 121 | + |
| 122 | +def jacob_motion(x, u): |
| 123 | + |
| 124 | + Fx = np.hstack((np.eye(STATE_SIZE), np.zeros( |
| 125 | + (STATE_SIZE, LM_SIZE * calc_n_LM(x))))) |
| 126 | + |
| 127 | + jF = np.matrix([[0.0, 0.0, -DT * u[0] * math.sin(x[2, 0])], |
| 128 | + [0.0, 0.0, DT * u[0] * math.cos(x[2, 0])], |
| 129 | + [0.0, 0.0, 0.0]]) |
| 130 | + |
| 131 | + G = np.eye(STATE_SIZE) + Fx.T * jF * Fx |
| 132 | + |
| 133 | + return G, Fx, |
| 134 | + |
| 135 | + |
| 136 | +def calc_LM_Pos(x, z): |
| 137 | + |
| 138 | + zp = np.zeros((2, 1)) |
| 139 | + |
| 140 | + zp[0, 0] = x[0, 0] + z[0, 0] * math.cos(x[2, 0] + z[0, 1]) |
| 141 | + zp[1, 0] = x[1, 0] + z[0, 0] * math.sin(x[2, 0] + z[0, 1]) |
| 142 | + |
| 143 | + return zp |
| 144 | + |
| 145 | + |
| 146 | +def get_LM_Pos_from_state(x, ind): |
| 147 | + |
| 148 | + lm = x[STATE_SIZE + LM_SIZE * ind: STATE_SIZE + LM_SIZE * (ind + 1), :] |
| 149 | + |
| 150 | + return lm |
| 151 | + |
| 152 | + |
| 153 | +def search_correspond_LM_ID(xAug, PAug, zi): |
| 154 | + """ |
| 155 | + Landmark association with Mahalanobis distance |
| 156 | + """ |
| 157 | + |
| 158 | + nLM = calc_n_LM(xAug) |
| 159 | + |
| 160 | + mdist = [] |
| 161 | + |
| 162 | + for i in range(nLM): |
| 163 | + lm = get_LM_Pos_from_state(xAug, i) |
| 164 | + y, S, H = calc_innovation(lm, xAug, PAug, zi, i) |
| 165 | + mdist.append(y.T * np.linalg.inv(S) * y) |
| 166 | + |
| 167 | + mdist.append(M_DIST_TH) # new landmark |
| 168 | + |
| 169 | + minid = mdist.index(min(mdist)) |
| 170 | + |
| 171 | + return minid |
| 172 | + |
| 173 | + |
| 174 | +def calc_innovation(lm, xEst, PEst, z, LMid): |
| 175 | + delta = lm - xEst[0:2] |
| 176 | + q = (delta.T * delta)[0, 0] |
| 177 | + zangle = math.atan2(delta[1], delta[0]) - xEst[2] |
| 178 | + zp = [math.sqrt(q), pi_2_pi(zangle)] |
| 179 | + y = (z - zp).T |
| 180 | + y[1] = pi_2_pi(y[1]) |
| 181 | + H = jacobH(q, delta, xEst, LMid + 1) |
| 182 | + S = H * PEst * H.T + Cx[0:2, 0:2] |
| 183 | + |
| 184 | + return y, S, H |
| 185 | + |
| 186 | + |
| 187 | +def jacobH(q, delta, x, i): |
| 188 | + sq = math.sqrt(q) |
| 189 | + G = np.matrix([[-sq * delta[0, 0], - sq * delta[1, 0], 0, sq * delta[0, 0], sq * delta[1, 0]], |
| 190 | + [delta[1, 0], - delta[0, 0], - 1.0, - delta[1, 0], delta[0, 0]]]) |
| 191 | + |
| 192 | + G = G / q |
| 193 | + nLM = calc_n_LM(x) |
| 194 | + F1 = np.hstack((np.eye(3), np.zeros((3, 2 * nLM)))) |
| 195 | + F2 = np.hstack((np.zeros((2, 3)), np.zeros((2, 2 * (i - 1))), |
| 196 | + np.eye(2), np.zeros((2, 2 * nLM - 2 * i)))) |
| 197 | + |
| 198 | + F = np.vstack((F1, F2)) |
| 199 | + |
| 200 | + H = G * F |
| 201 | + |
| 202 | + return H |
| 203 | + |
| 204 | + |
| 205 | +def pi_2_pi(angle): |
| 206 | + while(angle > math.pi): |
| 207 | + angle = angle - 2.0 * math.pi |
| 208 | + |
| 209 | + while(angle < -math.pi): |
| 210 | + angle = angle + 2.0 * math.pi |
| 211 | + |
| 212 | + return angle |
| 213 | + |
| 214 | + |
| 215 | +def main(): |
| 216 | + print(__file__ + " start!!") |
| 217 | + |
| 218 | + time = 0.0 |
| 219 | + |
| 220 | + # RFID positions [x, y] |
| 221 | + RFID = np.array([[10.0, -2.0], |
| 222 | + [15.0, 10.0], |
| 223 | + [3.0, 15.0], |
| 224 | + [-5.0, 20.0]]) |
| 225 | + |
| 226 | + # State Vector [x y yaw v]' |
| 227 | + xEst = np.matrix(np.zeros((STATE_SIZE, 1))) |
| 228 | + xTrue = np.matrix(np.zeros((STATE_SIZE, 1))) |
| 229 | + PEst = np.eye(STATE_SIZE) |
| 230 | + |
| 231 | + xDR = np.matrix(np.zeros((STATE_SIZE, 1))) # Dead reckoning |
| 232 | + |
| 233 | + # history |
| 234 | + hxEst = xEst |
| 235 | + hxTrue = xTrue |
| 236 | + hxDR = xTrue |
| 237 | + |
| 238 | + while SIM_TIME >= time: |
| 239 | + time += DT |
| 240 | + u = calc_input() |
| 241 | + |
| 242 | + xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID) |
| 243 | + |
| 244 | + xEst, PEst = ekf_slam(xEst, PEst, ud, z) |
| 245 | + |
| 246 | + x_state = xEst[0:STATE_SIZE] |
| 247 | + |
| 248 | + # store data history |
| 249 | + hxEst = np.hstack((hxEst, x_state)) |
| 250 | + hxDR = np.hstack((hxDR, xDR)) |
| 251 | + hxTrue = np.hstack((hxTrue, xTrue)) |
| 252 | + |
| 253 | + if show_animation: |
| 254 | + plt.cla() |
| 255 | + |
| 256 | + plt.plot(RFID[:, 0], RFID[:, 1], "*k") |
| 257 | + plt.plot(xEst[0], xEst[1], ".r") |
| 258 | + |
| 259 | + # plot landmark |
| 260 | + for i in range(calc_n_LM(xEst)): |
| 261 | + plt.plot(xEst[STATE_SIZE + i * 2], |
| 262 | + xEst[STATE_SIZE + i * 2 + 1], "xg") |
| 263 | + |
| 264 | + plt.plot(np.array(hxTrue[0, :]).flatten(), |
| 265 | + np.array(hxTrue[1, :]).flatten(), "-b") |
| 266 | + plt.plot(np.array(hxDR[0, :]).flatten(), |
| 267 | + np.array(hxDR[1, :]).flatten(), "-k") |
| 268 | + plt.plot(np.array(hxEst[0, :]).flatten(), |
| 269 | + np.array(hxEst[1, :]).flatten(), "-r") |
| 270 | + plt.axis("equal") |
| 271 | + plt.grid(True) |
| 272 | + plt.pause(0.001) |
| 273 | + |
| 274 | + |
| 275 | +if __name__ == '__main__': |
| 276 | + main() |
0 commit comments