import numpy as np import matplotlib.dates as mdates import matplotlib.pyplot as plt from scipy.stats import norm from sympy import Symbol, symbols, Matrix, sin, cos from sympy.interactive import printing printing.init_printing() %matplotlib inline fw = 10 # figure width numstates=5 # States dt = 1.0/50.0 # Sample Rate of the Measurements is 50Hz dtGPS=1.0/10.0 # Sample Rate of GPS is 10Hz vs, psis, dpsis, dts, xs, ys, lats, lons = symbols('v \psi \dot\psi T x y lat lon') gs = Matrix([[xs+(vs/dpsis)*(sin(psis+dpsis*dts)-sin(psis))], [ys+(vs/dpsis)*(-cos(psis+dpsis*dts)+cos(psis))], [psis+dpsis*dts], [vs], [dpsis]]) state = Matrix([xs,ys,psis,vs,dpsis]) gs state gs.jacobian(state) P = np.diag([1000.0, 1000.0, 1000.0, 1000.0, 1000.0]) print(P, P.shape) fig = plt.figure(figsize=(5, 5)) im = plt.imshow(P, interpolation="none", cmap=plt.get_cmap('binary')) plt.title('Initial Covariance Matrix $P$') ylocs, ylabels = plt.yticks() # set the locations of the yticks plt.yticks(np.arange(6)) # set the locations and labels of the yticks plt.yticks(np.arange(5),('$x$', '$y$', '$\psi$', '$v$', '$\dot \psi$'), fontsize=22) xlocs, xlabels = plt.xticks() # set the locations of the yticks plt.xticks(np.arange(6)) # set the locations and labels of the yticks plt.xticks(np.arange(5),('$x$', '$y$', '$\psi$', '$v$', '$\dot \psi$'), fontsize=22) plt.xlim([-0.5,4.5]) plt.ylim([4.5, -0.5]) from mpl_toolkits.axes_grid1 import make_axes_locatable divider = make_axes_locatable(plt.gca()) cax = divider.append_axes("right", "5%", pad="3%") plt.colorbar(im, cax=cax) plt.tight_layout() sGPS = 0.5*8.8*dt**2 # assume 8.8m/s2 as maximum acceleration, forcing the vehicle sCourse = 0.1*dt # assume 0.1rad/s as maximum turn rate for the vehicle sVelocity= 8.8*dt # assume 8.8m/s2 as maximum acceleration, forcing the vehicle sYaw = 1.0*dt # assume 1.0rad/s2 as the maximum turn rate acceleration for the vehicle Q = np.diag([sGPS**2, sGPS**2, sCourse**2, sVelocity**2, sYaw**2]) print(Q, Q.shape) fig = plt.figure(figsize=(5, 5)) im = plt.imshow(Q, interpolation="none", cmap=plt.get_cmap('binary')) plt.title('Process Noise Covariance Matrix $Q$') ylocs, ylabels = plt.yticks() # set the locations of the yticks plt.yticks(np.arange(8)) # set the locations and labels of the yticks plt.yticks(np.arange(7),('$x$', '$y$', '$\psi$', '$v$', '$\dot \psi$'), fontsize=22) xlocs, xlabels = plt.xticks() # set the locations of the yticks plt.xticks(np.arange(8)) # set the locations and labels of the yticks plt.xticks(np.arange(7),('$x$', '$y$', '$\psi$', '$v$', '$\dot \psi$'), fontsize=22) plt.xlim([-0.5,4.5]) plt.ylim([4.5, -0.5]) from mpl_toolkits.axes_grid1 import make_axes_locatable divider = make_axes_locatable(plt.gca()) cax = divider.append_axes("right", "5%", pad="3%") plt.colorbar(im, cax=cax); #path = './../RaspberryPi-CarPC/TinkerDataLogger/DataLogs/2014/' datafile = '2014-03-26-000-Data.csv' date, \ time, \ millis, \ ax, \ ay, \ az, \ rollrate, \ pitchrate, \ yawrate, \ roll, \ pitch, \ yaw, \ speed, \ course, \ latitude, \ longitude, \ altitude, \ pdop, \ hdop, \ vdop, \ epe, \ fix, \ satellites_view, \ satellites_used, \ temp = np.loadtxt(datafile, delimiter=',', unpack=True, converters={1: mdates.strpdate2num('%H%M%S%f'), 0: mdates.strpdate2num('%y%m%d')}, skiprows=1) print('Read \'%s\' successfully.' % datafile) # A course of 0° means the Car is traveling north bound # and 90° means it is traveling east bound. # In the Calculation following, East is Zero and North is 90° # We need an offset. course =(-course+90.0) hs = Matrix([[xs], [ys], [vs], [dpsis]]) hs JHs=hs.jacobian(state) JHs sGPS = 5.0 # Standard Deviation of GPS Measurement sspeed = 2.0 # Standard Deviation of the speed measurement syaw = 0.01 # Standard Deviation of the yawrate measurement R = np.matrix([[sGPS**2, 0.0, 0.0, 0.0], [0.0, sGPS**2, 0.0, 0.0], [0.0, 0.0, sspeed**2, 0.0], [0.0, 0.0, 0.0, syaw**2]]) print(R, R.shape) fig = plt.figure(figsize=(4.5, 4.5)) im = plt.imshow(R, interpolation="none", cmap=plt.get_cmap('binary')) plt.title('Measurement Noise Covariance Matrix $R$') ylocs, ylabels = plt.yticks() # set the locations of the yticks plt.yticks(np.arange(5)) # set the locations and labels of the yticks plt.yticks(np.arange(4),('$x$', '$y$', '$v$', '$\dot \psi$'), fontsize=22) xlocs, xlabels = plt.xticks() # set the locations of the yticks plt.xticks(np.arange(5)) # set the locations and labels of the yticks plt.xticks(np.arange(4),('$x$', '$y$', '$v$', '$\dot \psi$'), fontsize=22) plt.xlim([-0.5,3.5]) plt.ylim([3.5, -0.5]) from mpl_toolkits.axes_grid1 import make_axes_locatable divider = make_axes_locatable(plt.gca()) cax = divider.append_axes("right", "5%", pad="3%") plt.colorbar(im, cax=cax); I = np.eye(numstates) print(I, I.shape) RadiusEarth = 6378388.0 # m arc= 2.0*np.pi*(RadiusEarth+altitude)/360.0 # m/° dx = arc * np.cos(latitude*np.pi/180.0) * np.hstack((0.0, np.diff(longitude))) # in m dy = arc * np.hstack((0.0, np.diff(latitude))) # in m mx = np.cumsum(dx) my = np.cumsum(dy) ds = np.sqrt(dx**2+dy**2) GPS=np.hstack((True, (np.diff(ds)>0.0).astype('bool'))) # GPS Trigger for Kalman Filter x = np.matrix([[mx[0], my[0], course[0]/180.0*np.pi, speed[0]/3.6+0.001, yawrate[0]/180.0*np.pi]]).T print(x, x.shape) U=float(np.cos(x[2])*x[3]) V=float(np.sin(x[2])*x[3]) plt.quiver(x[0], x[1], U, V) plt.scatter(float(x[0]), float(x[1]), s=100) plt.title('Initial Location') plt.axis('equal') measurements = np.vstack((mx, my, speed/3.6, yawrate/180.0*np.pi)) # Lenth of the measurement m = measurements.shape[1] print(measurements.shape) # Preallocation for Plotting x0 = [] x1 = [] x2 = [] x3 = [] x4 = [] x5 = [] Zx = [] Zy = [] Px = [] Py = [] Pdx= [] Pdy= [] Pddx=[] Pddy=[] Kx = [] Ky = [] Kdx= [] Kdy= [] Kddx=[] dstate=[] for filterstep in range(m): # Time Update (Prediction) # ======================== # Project the state ahead # see "Dynamic Matrix" if np.abs(yawrate[filterstep])<0.0001: # Driving straight x[0] = x[0] + x[3]*dt * np.cos(x[2]) x[1] = x[1] + x[3]*dt * np.sin(x[2]) x[2] = x[2] x[3] = x[3] x[4] = 0.0000001 # avoid numerical issues in Jacobians dstate.append(0) else: # otherwise x[0] = x[0] + (x[3]/x[4]) * (np.sin(x[4]*dt+x[2]) - np.sin(x[2])) x[1] = x[1] + (x[3]/x[4]) * (-np.cos(x[4]*dt+x[2])+ np.cos(x[2])) x[2] = (x[2] + x[4]*dt + np.pi) % (2.0*np.pi) - np.pi x[3] = x[3] x[4] = x[4] dstate.append(1) # Calculate the Jacobian of the Dynamic Matrix A # see "Calculate the Jacobian of the Dynamic Matrix with respect to the state vector" a13 = float((x[3]/x[4]) * (np.cos(x[4]*dt+x[2]) - np.cos(x[2]))) a14 = float((1.0/x[4]) * (np.sin(x[4]*dt+x[2]) - np.sin(x[2]))) a15 = float((dt*x[3]/x[4])*np.cos(x[4]*dt+x[2]) - (x[3]/x[4]**2)*(np.sin(x[4]*dt+x[2]) - np.sin(x[2]))) a23 = float((x[3]/x[4]) * (np.sin(x[4]*dt+x[2]) - np.sin(x[2]))) a24 = float((1.0/x[4]) * (-np.cos(x[4]*dt+x[2]) + np.cos(x[2]))) a25 = float((dt*x[3]/x[4])*np.sin(x[4]*dt+x[2]) - (x[3]/x[4]**2)*(-np.cos(x[4]*dt+x[2]) + np.cos(x[2]))) JA = np.matrix([[1.0, 0.0, a13, a14, a15], [0.0, 1.0, a23, a24, a25], [0.0, 0.0, 1.0, 0.0, dt], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0]]) # Project the error covariance ahead P = JA*P*JA.T + Q # Measurement Update (Correction) # =============================== # Measurement Function hx = np.matrix([[float(x[0])], [float(x[1])], [float(x[3])], [float(x[4])]]) if GPS[filterstep]: JH = np.matrix([[1.0, 0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0]]) else: JH = np.matrix([[0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0]]) S = JH*P*JH.T + R K = (P*JH.T) * np.linalg.inv(S) # Update the estimate via Z = measurements[:,filterstep].reshape(JH.shape[0],1) y = Z - (hx) # Innovation or Residual x = x + (K*y) # Update the error covariance P = (I - (K*JH))*P # Save states for Plotting x0.append(float(x[0])) x1.append(float(x[1])) x2.append(float(x[2])) x3.append(float(x[3])) x4.append(float(x[4])) Zx.append(float(Z[0])) Zy.append(float(Z[1])) Px.append(float(P[0,0])) Py.append(float(P[1,1])) Pdx.append(float(P[2,2])) Pdy.append(float(P[3,3])) Pddx.append(float(P[4,4])) Kx.append(float(K[0,0])) Ky.append(float(K[1,0])) Kdx.append(float(K[2,0])) Kdy.append(float(K[3,0])) Kddx.append(float(K[4,0])) fig = plt.figure(figsize=(fw,9)) plt.semilogy(range(m),Px, label='$x$') plt.step(range(m),Py, label='$y$') plt.step(range(m),Pdx, label='$\psi$') plt.step(range(m),Pdy, label='$v$') plt.step(range(m),Pddx, label='$\dot \psi$') plt.xlabel('Filter Step') plt.ylabel('') plt.title('Uncertainty (Elements from Matrix $P$)') plt.legend(loc='best',prop={'size':22}) fig = plt.figure(figsize=(6, 6)) im = plt.imshow(P, interpolation="none", cmap=plt.get_cmap('binary')) plt.title('Covariance Matrix $P$ (after %i Filter Steps)' % (m)) ylocs, ylabels = plt.yticks() # set the locations of the yticks plt.yticks(np.arange(6)) # set the locations and labels of the yticks plt.yticks(np.arange(5),('$x$', '$y$', '$\psi$', '$v$', '$\dot \psi$'), fontsize=22) xlocs, xlabels = plt.xticks() # set the locations of the yticks plt.xticks(np.arange(6)) # set the locations and labels of the yticks plt.xticks(np.arange(5),('$x$', '$y$', '$\psi$', '$v$', '$\dot \psi$'), fontsize=22) plt.xlim([-0.5,4.5]) plt.ylim([4.5, -0.5]) from mpl_toolkits.axes_grid1 import make_axes_locatable divider = make_axes_locatable(plt.gca()) cax = divider.append_axes("right", "5%", pad="3%") plt.colorbar(im, cax=cax) plt.tight_layout() fig = plt.figure(figsize=(fw,9)) plt.step(range(len(measurements[0])),Kx, label='$x$') plt.step(range(len(measurements[0])),Ky, label='$y$') plt.step(range(len(measurements[0])),Kdx, label='$\psi$') plt.step(range(len(measurements[0])),Kdy, label='$v$') plt.step(range(len(measurements[0])),Kddx, label='$\dot \psi$') plt.xlabel('Filter Step') plt.ylabel('') plt.title('Kalman Gain (the lower, the more the measurement fullfill the prediction)') plt.legend(prop={'size':18}) plt.ylim([-0.1,0.1]); fig = plt.figure(figsize=(fw,16)) plt.subplot(411) plt.step(range(len(measurements[0])),x0-mx[0], label='$x$') plt.step(range(len(measurements[0])),x1-my[0], label='$y$') plt.title('Extended Kalman Filter State Estimates (State Vector $x$)') plt.legend(loc='best',prop={'size':22}) plt.ylabel('Position (relative to start) [m]') plt.subplot(412) plt.step(range(len(measurements[0])),x2, label='$\psi$') plt.step(range(len(measurements[0])),(course/180.0*np.pi+np.pi)%(2.0*np.pi) - np.pi, label='$\psi$ (from GPS as reference)') plt.ylabel('Course') plt.legend(loc='best',prop={'size':16}) plt.subplot(413) plt.step(range(len(measurements[0])),x3, label='$v$') plt.step(range(len(measurements[0])),speed/3.6, label='$v$ (from GPS as reference)') plt.ylabel('Velocity') plt.ylim([0, 30]) plt.legend(loc='best',prop={'size':16}) plt.subplot(414) plt.step(range(len(measurements[0])),x4, label='$\dot \psi$') plt.step(range(len(measurements[0])),yawrate/180.0*np.pi, label='$\dot \psi$ (from IMU as reference)') plt.ylabel('Yaw Rate') plt.ylim([-0.6, 0.6]) plt.legend(loc='best',prop={'size':16}) plt.xlabel('Filter Step') plt.savefig('Extended-Kalman-Filter-CTRV-State-Estimates.png', dpi=72, transparent=True, bbox_inches='tight') #%pylab --no-import-all fig = plt.figure(figsize=(16,9)) # EKF State #plt.quiver(x0,x1,np.cos(x2), np.sin(x2), color='#94C600', units='xy', width=0.05, scale=0.5) plt.plot(x0,x1, label='EKF Position') # Measurements plt.scatter(mx[::5],my[::5], s=50, label='GPS Measurements') #cbar=plt.colorbar(ticks=np.arange(20)) #cbar.ax.set_ylabel(u'EPE', rotation=270) #cbar.ax.set_xlabel(u'm') # Start/Goal plt.scatter(x0[0],x1[0], s=60, label='Start', c='g') plt.scatter(x0[-1],x1[-1], s=60, label='Goal', c='r') plt.xlabel('X [m]') plt.ylabel('Y [m]') plt.title('Position') plt.legend(loc='best') plt.axis('equal') #plt.tight_layout() #plt.savefig('Extended-Kalman-Filter-CTRV-Position.png', dpi=72, transparent=True, bbox_inches='tight') fig = plt.figure(figsize=(9,4)) # EKF State #plt.quiver(x0,x1,np.cos(x2), np.sin(x2), color='#94C600', units='xy', width=0.01, scale=0.2, label='Driving Direction') plt.plot(x0,x1, label='EKF Position', linewidth=5, alpha=0.8) # Measurements plt.scatter(mx[::5],my[::5], s=50, label='GPS Measurements') #cbar=plt.colorbar(ticks=np.arange(20)) #cbar.ax.set_ylabel(u'EPE', rotation=270) #cbar.ax.set_xlabel(u'm') plt.xlabel('X [m]') plt.xlim(80, 120) plt.ylabel('Y [m]') plt.ylim(160, 180) plt.title('Position') plt.legend(loc='best'); plt.savefig('EKF-Position.png', dpi=150)