00001
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
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
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
00069 err = 0.0
00070 for i in range(len(vertices)):
00071
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
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
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
00116 print "Adjusting curves, please wait..."
00117
00118
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
00128
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
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
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
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
00158 gt_rebased = rebase(vertices, gt)
00159 odom_rebased = rebase(vertices, odom)
00160
00161
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
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
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
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
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
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
00204 header = [ "Input", "Data Points", "Traj. Distance (m)", "Trans. MAE (m)"]
00205 utils.toRSTtable([header] + rows)
00206
00207
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
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()