Skip to content

Commit

Permalink
add files
Browse files Browse the repository at this point in the history
  • Loading branch information
Alessio-Parmeggiani authored Mar 28, 2022
0 parents commit 4fa08fd
Show file tree
Hide file tree
Showing 75 changed files with 8,714 additions and 0 deletions.
18 changes: 18 additions & 0 deletions data/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Project 03 - Range only SLAM

The dataset is composed by a g2o file which contain poses and range observations. The file contain also odometry edges that are used to construct the initial guess
for the problem. The use of those edges is your own choice if you think it's necessary.

Hint :

- Parse the whole dataset and initialize the landmarks by using at least 3 range observations with the proper parallax
- Setup a LS optimization that involves all the poses and landmarks that you have initialized
- In the range edges the ID of the landmarkd is reported, use it to identify them for both the initialization and the global optimization

-EDGE_RANGE_SE2_XY id_pose id_landmark range

Expected output :
- Robot trajectory and Map


For any question concerning the initialization part feel free to contact me
2,442 changes: 2,442 additions & 0 deletions data/slam2d_range_only_ground_truth.g2o

Large diffs are not rendered by default.

2,381 changes: 2,381 additions & 0 deletions data/slam2d_range_only_initial_guess.g2o

Large diffs are not rendered by default.

Binary file added landmark_plot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
close all
clear
clc
clf

addpath 'tools/g2o_wrapper'
addpath 'tools/visualization'
addpath 'tools/my_visualization'
addpath 'tools/algorithms'
source 'tools/algorithms/initGuess.m'
source 'tools/algorithms/leastSquares.m'
source 'tools/algorithms/LeastSquaresUtils.m'
source 'tools/algorithms/evaluate.m'
source 'tools/algorithms/tuning.m'
source 'tools/my_visualization/myPlot.m'

#get data
data_path="data";
dataset_gt="slam2d_range_only_ground_truth.g2o";
dataset_ig="slam2d_range_only_initial_guess.g2o";

printf("\n\nLoading Data...\n")
[gt_landmarks, gt_poses, gt_transitions, gt_observations] = loadG2o(fullfile(data_path, dataset_gt));
[~,poses,transitions,observations] = loadG2o(fullfile(data_path, dataset_ig));

num_landmarks=length(gt_landmarks);
num_poses=length(poses);
num_transitions=length(transitions);
num_observations=length(observations);
printf("number of landmarks: %d\nnumber of poses: %d\nnumber of measurements: %d\n",num_landmarks,num_poses,num_observations)


#Set some parameters
first_pose_id=poses(1).id;
params.first_pose_id=first_pose_id;

params.kernel_threshold=2.0;
params.num_iterations=25;
params.damping=0.1;
printf("parameters:\n")
params

#----INITIAL GUESS----
printf("\n Initial Guess...\n")
[landmark_positions,robot_poses,land_observations,land_id_to_idx]=initGuess(observations,poses,transitions,params);
init_guess=land_observations;
printf("Error in landmark position using initial guess is %f\n",evaluate(gt_landmarks,land_observations,land_id_to_idx));


#---LEAST SQUARE OPTIMIZATION-------
printf("\n Least squares using %d iterations...\n",params.num_iterations)
[XR,XL]=leastSquares(land_observations,robot_poses,landmark_positions,poses,gt_landmarks,params,land_id_to_idx);

printf("error in landmark position after least square is %f\n",evaluate(gt_landmarks,XL,land_id_to_idx));

#tuning parameters
#this function was used to test if changing parameters of LS (kernel_threshold, damping) had some effect
#[XR,XL]=tuning(land_observations,robot_poses,landmark_positions,poses,gt_landmarks,params,land_id_to_idx);


#----PLotting----
printf("\nPlotting...\n")

#to plot estimated landmarks and ground truth with labels
#this functions however don't use traditional octave plot so the legend of the other plot will be wrong
#drawLandmarkWithLabel(XL,gt_landmarks,radius=0.1)

#get ground truth poses in matrix form, so it's easier to plot and legend work
gt_matrix_poses=zeros(3, 3, length(gt_poses));
for i=1:length(gt_poses)
gt_matrix_poses(:,:,i)=v2t([gt_poses(i).x gt_poses(i).y gt_poses(i).theta]);
endfor

#plot trajectory and landmarks
myPlot(robot_poses,XR,gt_matrix_poses,init_guess,XL,gt_landmarks)

100 changes: 100 additions & 0 deletions tools/algorithms/LeastSquaresUtils.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#size of pose and landmarks
global pose_dim=3;
global landmark_dim=2;

#matrix of measurements
#row:poses; columns:landmarks;
#so A(pose,landmark) correspond to the measure
#used also to test if a different order of the measurements change the Least squares results
function A=get_association_matrix(land_observations,num_poses,params)
landmark_num=length(land_observations);
A = ones(num_poses,landmark_num)*-1;
first_pose_id=params.first_pose_id;

#for each landmark
for l_idx=1:length(land_observations)
land_obs=land_observations(l_idx);
#for each measure from the poses
for m=1:length(land_obs.measurements)
measurement=land_obs.measurements(m);
pose_id=measurement.obs_pose_id;
pose_idx=pose_id-first_pose_id+1;
z=measurement.obs_range;
A(pose_idx,l_idx)=z;
endfor
endfor
endfunction

#retrieve index from H matrix
function v_idx=poseMatrixIndex(pose_index, num_poses, num_landmarks)
global pose_dim;
global landmark_dim;

if (pose_index>num_poses)
printf("error matrix index: pose idx > num_poses\n")
v_idx=-1;
return;
endif;
v_idx=1+(pose_index-1)*pose_dim;
endfunction;

function v_idx=landmarkMatrixIndex(landmark_index, num_poses, num_landmarks)
global pose_dim;
global landmark_dim;
if (landmark_index>num_landmarks)
printf("error matrix index: land idx > num_landmark\n")
v_idx=-1;
return;
endif;
v_idx=1 + (num_poses)*pose_dim + (landmark_index-1) * landmark_dim;
endfunction;

#boxplus operator to add perturbation in manifold space
function [XR, XL]=boxPlus(XR, XL, num_poses, num_landmarks, dx)
global pose_dim;
global landmark_dim;
for(pose_index=1:num_poses)
pose_matrix_index=poseMatrixIndex(pose_index, num_poses, num_landmarks);
dxr=dx(pose_matrix_index:pose_matrix_index+pose_dim-1);
#for poses we can't do simple addition, here is why boxplus is needed
XR(:,:,pose_index)=v2t(dxr)*XR(:,:,pose_index);

endfor;
for(landmark_index=1:num_landmarks)
#this is needed because some landmarks were not considered
if landmark_index<=length(XL) && XL(landmark_index).pos
landmark_matrix_index=landmarkMatrixIndex(landmark_index, num_poses, num_landmarks);
dxl=dx(landmark_matrix_index:landmark_matrix_index+landmark_dim-1,:);
#nothing special needed for landmark
XL(landmark_index).pos+=dxl;
endif
endfor;
endfunction;


function [e, Jr, Jl] = errorAndJacobian(Xr, Xl, z)
R = Xr(1:2,1:2);
t = Xr(1:2,3);

z_hat = norm( R' * (Xl-t));
#error
#estimated measurement - actual measurement
e = z_hat - z;

#computing jacobian
p=R' * (Xl-t);
c=Xr(1,1); s=Xr(2,1);
x=Xr(1,3); y=Xr(2,3);
xl=Xl(1); yl=Xl(2);

#derivative of error with respect to state
f1=(c*x-c*xl+s*y-s*yl); #recurrent term
f2=(-s*x+s*xl+c*y-c*yl); #other recurrent term
#Jr(3) is 0, error do not depends from angle!
#could have done with syms variables, but with this is faster and I forgot to use it before computing everything by hand

Jr=(1/norm(p)) * [f1*c+f2*-s, f1*s+f2*c, f1*( (-x*s)+(xl*s)+(y*c)+(-yl*c)) + f2*( (-x*c)+(xl*c)+(-y*s)+(yl*s)) ];
Jl=(1/norm(p)) * [f1*-c+f2*s f1*-s+f2*-c];


endfunction
38 changes: 38 additions & 0 deletions tools/algorithms/estimateLandmarkPosition.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

#lateration algorithm to estimate position fo landmark
#is a range based triangulation algorithm
#different implementation possible,
#an implementation can be found here http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.933&rep=rep1&type=pdf
#at page 504

function [p1,p2]=estimateLandmarkPosition(measurements,poses)

num_measure=length(measurements);

obs_pose_id=measurements(num_measure).obs_pose_id;
last_pose=poseFromId(obs_pose_id,poses);
last_x = last_pose.x;
last_y = last_pose.y;
last_r = measurements(num_measure).obs_range;

A = zeros(num_measure-1, 2);
b = zeros(num_measure-1, 1);

for k=1:num_measure-1

obs_pose_id=measurements(k).obs_pose_id;
pose=poseFromId(obs_pose_id,poses);
x = pose.x;
y = pose.y;
r = measurements(k).obs_range;

A(k,:) = 2 * [(x - last_x) (y - last_y)];
b(k,:) = [x^2-last_x^2 + y^2-last_y^2 + last_r^2-r^2];
endfor

landmark_position = (A'*A) \ A'*b;

p1=landmark_position(1);
p2=landmark_position(2);

end
24 changes: 24 additions & 0 deletions tools/algorithms/evaluate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
1;
function err=evaluate(gt_landmarks, est_landmarks,land_id_to_idx)
# error is computed as simply the distance between the correct
# landmark and the estimated position
err=0;
for i=1:length(gt_landmarks)
#for each landmark retrieving original index and compute error
gt_id=gt_landmarks(i).id;
est_idx=land_id_to_idx(gt_id);
if gt_id==est_landmarks(est_idx).l_id
gt_pos=[ gt_landmarks(i).x_pose; gt_landmarks(i).y_pose];
est_pos=est_landmarks(est_idx).pos;
if est_pos
err+=norm(gt_pos -est_pos);
endif
else
#should not happen
printf("ERROR! indices do not correspond")
printf(" %d %d\n",gt_id,est_landmarks(est_idx).l_id)
endif
endfor
#computing final error
err=err/length(est_landmarks);
endfunction
86 changes: 86 additions & 0 deletions tools/algorithms/initGuess.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
source 'tools/utilities/geometry_helpers_2d.m'

function [landmark_positions,robot_poses,land_observations,l_id_to_idx] = initGuess(observations,poses,transitions,params)
#array of landmark_observations
#each landmark_observations is an array fo measures (pose,range)

first_pose_id=params.first_pose_id;

land_observations=[];

#used to transform landmark id into indeces 1...n
l_id_to_idx = ones(10000, 1)*-1;

#index for the landmarks
l_idx=1;

#building a structure that contains for each landmark all the measurement related to it.
for i=1:length(observations)
pose_id=observations(i).pose_id;
landmarks_observed=observations(i).observation;
for j=1:length(landmarks_observed)
observation=landmarks_observed(j);

#pose can be retrieved by the poses array, index is pose_id-first_pose_id+1
#obs_pose=poses(pose_id-first_pose_id+1);

#i want the landmarks to be stored independently from id but accessed fast
l_id=observation.id;
if l_id_to_idx(l_id)==-1
l_id_to_idx(l_id)=l_idx;
land_observations(l_id_to_idx(l_id)).l_id=l_id;
l_idx=l_idx+1;
endif

#adding this measurement
land_observations(l_id_to_idx(l_id)).measurements(end+1).obs_pose_id=pose_id;
land_observations(l_id_to_idx(l_id)).measurements(end).obs_range=observation.range;
endfor
endfor



#for each landmark with 3 or more measurements estimate position
for i=1:length(land_observations)
measurements=land_observations(i).measurements;

if length(measurements)>=3
[px py]=estimateLandmarkPosition(measurements,poses);
land_observations(i).pos=[px; py];
landmark_positions(end+1).id=i;
landmark_positions(end).pos=[px; py];
endif

endfor

#get poses in matrix form

# no real difference in using simply the poses or computing the trajectory using the transitions
#{
#compute trajectory using poses
robot_poses=zeros(3, 3, length(poses));
for i=1:length(poses)
robot_poses(:,:,i)=v2t([poses(i).x poses(i).y poses(i).theta]);
endfor
#}

#compute trajectory using transitions
robot_poses=zeros(3, 3, length(poses));
robot_poses(:,:,1)=v2t([poses(1).x poses(1).y poses(1).theta]);
prev_pose=robot_poses(:,:,1);
for i=1:length(transitions)
v=transitions(i).v;
displacement=v2t([v(1) v(2) v(3)]);
next_pose=prev_pose*displacement;
prev_pose=next_pose;
robot_poses(:,:,i+1)=next_pose;
endfor



endfunction





Loading

0 comments on commit 4fa08fd

Please sign in to comment.