# -*- coding: utf-8 -*- """ Created on Mon Oct 3 07:59:40 2016 @author: rlabbe """ from filterpy.kalman import UnscentedKalmanFilter as UKF, MerweScaledSigmaPoints from math import tan, sin, cos, sqrt, atan2 import numpy as np from numpy.random import randn import matplotlib.pyplot as plt from filterpy.stats import plot_covariance_ellipse def move(x, u, dt, wheelbase): hdg = x[2] vel = u[0] steering_angle = u[1] dist = vel * dt if abs(steering_angle) > 0.001: # is robot turning? beta = (dist / wheelbase) * tan(steering_angle) r = wheelbase / tan(steering_angle) # radius sinh, sinhb = sin(hdg), sin(hdg + beta) cosh, coshb = cos(hdg), cos(hdg + beta) return x + np.array([-r*sinh + r*sinhb, r*cosh - r*coshb, beta]) else: # moving in straight line return x + np.array([dist*cos(hdg), dist*sin(hdg), 0]) def fx(x, dt, u): return move(x, u, dt, wheelbase) def normalize_angle(x): x = x % (2 * np.pi) # force in range [0, 2 pi) if x > np.pi: # move to [-pi, pi) x -= 2 * np.pi return x def residual_h(a, b): y = a - b # data in format [dist_1, bearing_1, dist_2, bearing_2,...] for i in range(0, len(y), 2): y[i + 1] = normalize_angle(y[i + 1]) return y def residual_x(a, b): y = a - b y[2] = normalize_angle(y[2]) return y def aa(x, y): if y is not None: return x % y else: return x def bb(x,y): try: return x % y except: return x def Hx(x, landmarks): """ takes a state variable and returns the measurement that would correspond to that state. """ hx = [] for lmark in landmarks: px, py = lmark dist = sqrt((px - x[0])**2 + (py - x[1])**2) angle = atan2(py - x[1], px - x[0]) hx.extend([dist, normalize_angle(angle - x[2])]) return np.array(hx) def state_mean(sigmas, Wm): x = np.zeros(3) sum_sin = np.sum(np.dot(np.sin(sigmas[:, 2]), Wm)) sum_cos = np.sum(np.dot(np.cos(sigmas[:, 2]), Wm)) x[0] = np.sum(np.dot(sigmas[:, 0], Wm)) x[1] = np.sum(np.dot(sigmas[:, 1], Wm)) x[2] = atan2(sum_sin, sum_cos) return x def z_mean(sigmas, Wm): z_count = sigmas.shape[1] x = np.zeros(z_count) for z in range(0, z_count, 2): sum_sin = np.sum(np.dot(np.sin(sigmas[:, z+1]), Wm)) sum_cos = np.sum(np.dot(np.cos(sigmas[:, z+1]), Wm)) x[z] = np.sum(np.dot(sigmas[:,z], Wm)) x[z+1] = atan2(sum_sin, sum_cos) return x dt = 1.0 wheelbase = 0.5 def run_localization( cmds, landmarks, sigma_vel, sigma_steer, sigma_range, sigma_bearing, ellipse_step=1, step=10): plt.figure() points = MerweScaledSigmaPoints(n=3, alpha=.00001, beta=2, kappa=0, subtract=residual_x) ukf = UKF(dim_x=3, dim_z=2*len(landmarks), fx=fx, hx=Hx, dt=dt, points=points, x_mean_fn=state_mean, z_mean_fn=z_mean, residual_x=residual_x, residual_z=residual_h) ukf.x = np.array([2, 6, .3]) ukf.P = np.diag([.1, .1, .05]) ukf.R = np.diag([sigma_range**2, sigma_bearing**2]*len(landmarks)) ukf.Q = np.eye(3)*0.0001 sim_pos = ukf.x.copy() # plot landmarks if len(landmarks) > 0: plt.scatter(landmarks[:, 0], landmarks[:, 1], marker='s', s=60) track = [] for i, u in enumerate(cmds): sim_pos = move(sim_pos, u, dt/step, wheelbase) track.append(sim_pos) if i % step == 0: ukf.predict(fx_args=u) if i % ellipse_step == 0: plot_covariance_ellipse( (ukf.x[0], ukf.x[1]), ukf.P[0:2, 0:2], std=6, facecolor='k', alpha=0.3) x, y = sim_pos[0], sim_pos[1] z = [] for lmark in landmarks: dx, dy = lmark[0] - x, lmark[1] - y d = sqrt(dx**2 + dy**2) + randn()*sigma_range bearing = atan2(lmark[1] - y, lmark[0] - x) a = (normalize_angle(bearing - sim_pos[2] + randn()*sigma_bearing)) z.extend([d, a]) ukf.update(z, hx_args=(landmarks,)) if i % ellipse_step == 0: plot_covariance_ellipse( (ukf.x[0], ukf.x[1]), ukf.P[0:2, 0:2], std=6, facecolor='g', alpha=0.8) track = np.array(track) plt.plot(track[:, 0], track[:,1], color='k', lw=2) plt.axis('equal') plt.title("UKF Robot localization") plt.show() return ukf landmarks = np.array([[5, 10], [10, 5], [15, 15]]) cmds = [np.array([1.1, .01])] * 200 ukf = run_localization( cmds, landmarks, sigma_vel=0.1, sigma_steer=np.radians(1), sigma_range=0.3, sigma_bearing=0.1) print('Final P:', ukf.P.diagonal())