slam_evaluation.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 import roslib; roslib.load_manifest('stereo_slam')
00003 import pylab
00004 import numpy as np
00005 from matplotlib import pyplot
00006 from mpl_toolkits.mplot3d import Axes3D
00007 import tf.transformations as tf
00008 import scipy.optimize as optimize
00009 import collections
00010 from odometry_evaluation import utils
00011 
00012 class Error(Exception):
00013   """ Base class for exceptions in this module. """
00014   pass
00015 
00016 def rebase(base_vector, rebased_vector):
00017   min_idx_vec = []
00018   for i in range(len(base_vector)):
00019     # Search the best coincidence in time with ground truth
00020     min_dist = 99999
00021     min_idx = -1
00022     for j in range(len(rebased_vector)):
00023       dist = abs(rebased_vector[j,0] - base_vector[i,0])
00024       if dist < min_dist:
00025         min_dist = dist
00026         min_idx = j
00027     min_idx_vec.append(min_idx)
00028   min_idx_vec = np.array(min_idx_vec)
00029   return rebased_vector[min_idx_vec,:]
00030 
00031 
00032 def apply_tf_to_matrix(tf_delta, data):
00033   corrected_data = []
00034   for i in range(len(data)):
00035     point = utils.to_transform(data[i,:])
00036     point_d = tf.concatenate_matrices(point, tf_delta)
00037     t_d = tf.translation_from_matrix(point_d)
00038     q_d = tf.quaternion_from_matrix(point_d)
00039     corrected_data.append([data[i,0], t_d[0], t_d[1], t_d[2], q_d[0], q_d[1], q_d[2], q_d[3]])
00040   return np.array(corrected_data)
00041 
00042 def apply_tf_to_vector(tf_delta, data):
00043   corrected_data = []
00044   for i in range(len(data)):
00045     point = utils.to_transform(data[i,:])
00046     t_d = tf.translation_from_matrix(point)
00047     q_d = tf.quaternion_from_matrix(point)
00048     t_d = np.array([t_d[0], t_d[1], t_d[2], 1.0])
00049     point_mod = tf.concatenate_matrices(tf_delta, t_d)
00050     corrected_data.append([data[i,0], point_mod[0], point_mod[1], point_mod[2], t_d[2], q_d[0], q_d[1], q_d[2], q_d[3]])
00051   return np.array(corrected_data)
00052 
00053 def quaternion_from_rpy(roll, pitch, yaw):
00054   q = []
00055   q.append( np.cos(roll/2)*np.cos(pitch/2)*np.cos(yaw/2) + np.sin(roll/2)*np.sin(pitch/2)*np.sin(yaw/2))
00056   q.append( np.sin(roll/2)*np.cos(pitch/2)*np.cos(yaw/2) - np.cos(roll/2)*np.sin(pitch/2)*np.sin(yaw/2))
00057   q.append( -np.cos(roll/2)*np.sin(pitch/2)*np.cos(yaw/2) - np.sin(roll/2)*np.cos(pitch/2)*np.sin(yaw/2))
00058   q.append( np.cos(roll/2)*np.cos(pitch/2)*np.sin(yaw/2) - np.sin(roll/2)*np.sin(pitch/2)*np.cos(yaw/2))
00059   q = np.array(q)
00060   return q
00061 
00062 def sigmoid(p, vertices, gt_rebased):
00063   # Get the current parameter set and build the transform
00064   roll, pitch, yaw = p
00065   q = quaternion_from_rpy(roll, pitch, yaw)
00066   cur_delta = utils.to_transform([0.0, 0.0, 0.0, 0.0, q[0], q[1], q[2], q[3]])
00067   
00068   # Compute the quadratic error for the current delta transformation
00069   err = 0.0
00070   for i in range(len(vertices)):
00071     # Compute the corrected ground truth
00072     tf_gt = utils.to_transform(gt_rebased[i])
00073     tf_gt_t = tf.translation_from_matrix(tf_gt)
00074     tf_gt_t = np.array([tf_gt_t[0], tf_gt_t[1], tf_gt_t[2], 1.0])
00075     tf_gt_corrected = tf.concatenate_matrices(cur_delta, tf_gt_t)
00076     tf_gt_corr_vect = [0.0, tf_gt_corrected[0], tf_gt_corrected[1], tf_gt_corrected[2], 0.0, 0.0, 0.0, 1.0]
00077 
00078     # Compute the error
00079     err += np.power(utils.calc_dist(vertices[i], tf_gt_corr_vect), 2)
00080 
00081   return np.sqrt(err)
00082 
00083 def calc_errors(vector_1, vector_2):
00084   # Compute the errors between vectors
00085   assert(len(vector_1) == len(vector_1))
00086   output = []
00087   for i in range(len(vector_1)):
00088     output.append(utils.calc_dist(vector_1[i,:], vector_2[i,:]))
00089   return np.array(output)
00090 
00091 def calc_time_vector(data):
00092   output = []
00093   start_time = data[0,0]
00094   for i in range(len(data)):
00095     output.append((data[i,0] - start_time))
00096   return np.array(output)
00097 
00098 if __name__ == "__main__":
00099   import argparse
00100   parser = argparse.ArgumentParser(
00101           description='Plot 3D graphics of SLAM.',
00102           formatter_class=argparse.ArgumentDefaultsHelpFormatter)
00103   parser.add_argument('ground_truth_file', 
00104           help='file with ground truth')
00105   parser.add_argument('visual_odometry_file', 
00106           help='file with visual odometry')
00107   parser.add_argument('graph_vertices_file', 
00108           help='file the vertices of stereo slam')
00109   parser.add_argument('graph_edges_file', 
00110           help='file the edges of stereo slam')
00111   args = parser.parse_args()
00112   colors = ['g','r','b']
00113   angles = [-6, 6, -4, 4, -12, 12]
00114 
00115   # Log
00116   print "Adjusting curves, please wait..."
00117 
00118   # Init figure
00119   fig = pylab.figure(1)
00120   ax = Axes3D(fig)
00121   ax.grid(True)
00122   ax.set_title("Graph Viewer")
00123   ax.set_xlabel("X")
00124   ax.set_ylabel("Y")
00125   ax.set_zlabel("Z")
00126 
00127   # Load ground truth (gt) data.
00128   # Check the file type
00129   f = open(args.ground_truth_file)
00130   lines = f.readlines()
00131   f.close()
00132   size = lines[1].split(",")
00133 
00134   if (len(size) >= 12):
00135     gt = pylab.loadtxt(args.ground_truth_file, delimiter=',', skiprows=1, usecols=(0,5,6,7,8,9,10,11))
00136     gt[:,0] = gt[:,0] / 1000000000
00137   else:
00138     gt = pylab.loadtxt(args.ground_truth_file, delimiter=',', usecols=(0,1,2,3,4,5,6,7))
00139 
00140   # Load the visual odometry
00141   odom = pylab.loadtxt(args.visual_odometry_file, delimiter=',', skiprows=1, usecols=(0,5,6,7,8,9,10,11))
00142   odom[:,0] = odom[:,0] / 1000000000
00143   
00144   # Load the graph vertices
00145   vertices = pylab.loadtxt(args.graph_vertices_file, delimiter=',', skiprows=0, usecols=(0,5,6,7,8,9,10,11))
00146   ax.plot(vertices[:,1], vertices[:,2], vertices[:,3], colors[2], label='Stereo slam', marker='o')
00147 
00148   # Load the graph edges
00149   edges = pylab.loadtxt(args.graph_edges_file, delimiter=',', skiprows=0, usecols=(1,2,3,4,5,6))
00150   for i in range(len(edges)):
00151     vect = []
00152     vect.append([edges[i,0], edges[i,1], edges[i,2]])
00153     vect.append([edges[i,3], edges[i,4], edges[i,5]])
00154     vect =  np.array(vect)
00155     ax.plot(vect[:,0], vect[:,1], vect[:,2], colors[2], linestyle='--')
00156 
00157   # Get the gt indeces for all graph vertices
00158   gt_rebased = rebase(vertices, gt)
00159   odom_rebased = rebase(vertices, odom)
00160 
00161   # Compute the translation to make the same origin for all curves
00162   first_vertice = utils.to_transform(vertices[0,:])
00163   first_gt_coincidence = utils.to_transform(gt_rebased[0,:])
00164   tf_delta = tf.concatenate_matrices(tf.inverse_matrix(first_gt_coincidence), first_vertice)
00165 
00166   # Move all the gt and odometry points with the correction
00167   gt_moved = apply_tf_to_matrix(tf_delta, gt)
00168   gt_rb_moved = apply_tf_to_matrix(tf_delta, gt_rebased)
00169   odom_moved = apply_tf_to_matrix(tf_delta, odom)
00170   odom_rb_moved = apply_tf_to_matrix(tf_delta, odom_rebased)
00171 
00172   # Transform optimization
00173   Param = collections.namedtuple('Param','roll pitch yaw')
00174   rranges = ((angles[0]*np.pi/180, angles[1]*np.pi/180, 0.04), (angles[2]*np.pi/180, angles[3]*np.pi/180, 0.02), (angles[4]*np.pi/180, angles[5]*np.pi/180, 0.04))
00175   p = optimize.brute(sigmoid, rranges, args=(vertices, gt_rebased))
00176   p = Param(*p)
00177 
00178   # Build the rotation matrix
00179   roll, pitch, yaw = p
00180   q = quaternion_from_rpy(roll, pitch, yaw)
00181   tf_correction = utils.to_transform([0.0, 0.0, 0.0, 0.0, q[0], q[1], q[2], q[3]])
00182 
00183   # Correct ground truth and odometry
00184   gt_corrected = apply_tf_to_vector(tf_correction, gt_moved)
00185   gt_rb_corrected = apply_tf_to_vector(tf_correction, gt_rb_moved)
00186   odom_corrected = apply_tf_to_vector(tf_correction, odom_moved)
00187   odom_rb_corrected = apply_tf_to_vector(tf_correction, odom_rb_moved)
00188 
00189   # Compute the errors
00190   print "Computing errors, please wait..."
00191   odom_dist = utils.trajectory_distances(odom_rb_corrected)
00192   vertices_dist = utils.trajectory_distances(vertices)
00193   odom_errors = calc_errors(gt_rb_corrected, odom_rb_corrected)
00194   vertices_errors = calc_errors(gt_rb_corrected, vertices)
00195   time = calc_time_vector(gt_rb_corrected)
00196   odom_mae = np.average(np.abs(odom_errors), 0)
00197   vertices_mae = np.average(np.abs(vertices_errors), 0)
00198 
00199   rows = []
00200   rows.append(['Odometry'] + [len(odom_errors)] + [odom_dist[-1]] + [odom_mae])
00201   rows.append(['SLAM'] + [len(vertices_errors)] + [vertices_dist[-1]] + [vertices_mae])
00202 
00203   # Build the header for the output table
00204   header = [  "Input", "Data Points", "Traj. Distance (m)", "Trans. MAE (m)"]
00205   utils.toRSTtable([header] + rows)
00206 
00207   # Plot graph
00208   ax.plot(gt_corrected[:,1], gt_corrected[:,2], gt_corrected[:,3], colors[0], label='Ground Truth')
00209   ax.plot(odom_corrected[:,1], odom_corrected[:,2], odom_corrected[:,3], colors[1], label='Visual Odometry')
00210   ax.legend()
00211 
00212   # Plot errors
00213   fig1 = pylab.figure()
00214   ax1 = fig1.gca()
00215   ax1.plot(time, odom_errors, label='Odometry', marker='o')
00216   ax1.plot(time, vertices_errors, label='SLAM', marker='o')
00217   ax1.grid(True)
00218   ax1.set_title("Error: Odometry vs SLAM")
00219   ax1.set_xlabel("Time (s)")
00220   ax1.set_ylabel("Error (m)")
00221   ax1.legend(loc=2)
00222   fig2 = pylab.figure()
00223   ax2 = fig2.gca()
00224   ax2.plot(odom_dist, odom_errors, label='Odometry', marker='o')
00225   ax2.plot(vertices_dist, vertices_errors, label='SLAM', marker='o')
00226   ax2.grid(True)
00227   ax2.set_title("Error: Odometry vs SLAM")
00228   ax2.set_xlabel("Distance (m)")
00229   ax2.set_ylabel("Error (m)")
00230   ax2.legend(loc=2)
00231   
00232   pyplot.draw()
00233   pylab.show()


stereo_slam
Author(s): Pep Lluis Negre
autogenerated on Thu Aug 27 2015 15:24:22