-
Notifications
You must be signed in to change notification settings - Fork 0
/
ransac_fit.m
288 lines (246 loc) · 10.2 KB
/
ransac_fit.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
% used to find BoB and line segments of walls/obstacles
clc; clf; close all; clear all;
fit('data_files/scandata', 'data_files/shapedata')
function fit(input, output)
load(input) % lidar scan data (ranges, angle_increment)
% create theta from angle_increment for each data point in ranges
theta = zeros(size(ranges, 1), 1);
for i=1:size(ranges, 1)
theta(i) = i*angle_increment;
end
% clean data, remove invalid data that exceeds lidar range/room size
index=find(ranges~=0 & ranges<3);
ranges=ranges(index);
theta=theta(index);
[x,y]=pol2cart(theta,ranges); % convert to cartesian x, y
x = x - .084; % lidar offset from axle center
cart=[x y];
% graph the data points
hold on; plot(cart(:,1), cart(:,2),'k.'); hold off;
% setup variables and run targeted circle fit first
d = .0075; % maximum inlier distance
n = 1000; % number of ransac attempts
r = .25; % radius of circle
tol = .1; % percent tolerance on radius
[outliers radius center] = circlefit(cart, d, n, r, tol);
stop = 2; % how many outliers left to stop fitting at
k = .5; % maximum gap size
[outliers endpoints] = linefit_multiple(outliers, d, n, k, stop);
% save line and circle data
save(output, 'endpoints', 'radius', 'center');
end
function [outliers endpoints] = linefit_multiple(cart, d, n, k, cutoff)
% fit lines until the cutoff number of outliers has been reached
outliers = cart;
endpoints = [];
while size(outliers, 1) > cutoff
[endline inlier] = linefit(outliers, d, n, k);
outliers(inlier,:) = [];
endpoints(size(endpoints, 1)+1,:,:) = (endline);
pause(.25)
end
end
function graph_line(endline)
% graph line segments based on the given line endpoints
hold on;
plot(endline(:, 1), endline(:, 2), 'bo')
plot(endline(:,1), endline(:,2), 'g')
legend('Data points','Fit lines','Line endpoints')
axis equal;
xlim([-2, 3]);
ylim([-3, 1]);
xlabel('[m]')
ylabel('[m]')
title('Initial position scan data with line fits')
hold off;
end
function graph_circle(center, r)
% graph circle based on the given center and radius
% calculating points evenly spaced along circle perimeter
circlepts = zeros(2,360);
for angle=1:360
circlepts(:,angle) = [r*cosd(angle)+center(1), r*sind(angle)+center(2)];
end
hold on
plot(circlepts(1,:), circlepts(2,:), 'g')
legend('Data points','Fit lines')
axis equal;
xlim([-2, 3]);
ylim([-3, 1]);
xlabel('[m]')
ylabel('[m]')
title(' ')
hold off;
end
function [endline inliers] = linefit(cart, d, n, k)
% fits the line segment through 2 random points with the greatest number of
% inliers with n number of attempts, d maximum inlier distance, and k
% largest gap size in dataset cart
inliers = []; % indeces in cart of inlying points
endline = [0 0; 0 0]; % endpoints of line on line
m = 0; % slope of best line
b = 0; % intercept of best line
for i=1:n
rand_indx = randi([1,size(cart,1)],1,2); % random indeces pair
new_endpoints = [cart(rand_indx(1),1) cart(rand_indx(1),2); cart(rand_indx(2),1) cart(rand_indx(2),2)];
[new_inliers new_endline new_m new_b] = calc_inliers(cart, d, new_endpoints);
largest_gap = calc_largest_gap(cart(new_inliers,:), new_endpoints(1,:));
if largest_gap > k
new_inliers = []; % invalid solution if it has big gaps
end
if (size(new_inliers,1) > size(inliers,1)) % check if it's best yet
inliers = new_inliers;
endline = new_endline;
m = new_m;
b = new_b;
end
end
graph_line(endline);
end
function [min_dist, index] = closest_point_dist(points, point)
% calculate the minimum distance from 'point' to any other point in 'points'
min_dist = 999999999; % large number because 'Inf' wasn't working
index = 0; % closest point index
for i=1:size(points, 1)
% if it's the same point, dist is 0, save index
if points(i,1) == point(1) & points(i,2) == point(2)
min_dist = 0;
index = i;
end
% if it's not the same point, calculate distance and if it's
% closer, save dist and index
if points(i,1) ~= point(1) | points(i,2) ~= point(2)
dist = sqrt((points(i,1)-point(1))^2 + (points(i,2)-point(2))^2);
if dist < min_dist & dist ~= 0
min_dist = dist;
index = i;
end
end
end
end
function max_gap = calc_largest_gap(points, point)
% calculates the biggest gap from one point to another starting from 'point'
points_left = points; % keep track of points left
max_gap = 0; % keep track of biggest gap
[min_dist, index] = closest_point_dist(points_left, point);
if min_dist > max_gap
max_gap = min_dist;
end
while size(points_left,1) > 1
% go through all points, removing ones already used, getting the
% minimum distance from that point to any other that's left
point = points_left(index,:);
points_left(index,:) = [];
[min_dist, index] = closest_point_dist(points_left, point);
if min_dist > max_gap
max_gap = min_dist;
end
end
end
function [inliers endline m b] = calc_inliers(cart, d, endpoints)
% calculates the inliers on the line segment as well as where the segment
% ends given the two random endpoints to make a line through, datapoints
% cart, and inlier distance
m = (endpoints(1,2)-endpoints(2,2))/(endpoints(1,1)-endpoints(2,1)); % rise/run
b = endpoints(1,2)-m*endpoints(1,1); % b = y - mx
endline = endpoints; % intersection on line tangent to endpoints
inliers = [];
for i=1:size(cart, 1)
% calculate (x,y) point along line closest to cart(i) & dist between
bn = cart(i, 2) + cart(i, 1)/m; % solve b = y + x/m
x = (bn - b)/(m + 1/m); % solve mx+b = -x/m+bn for x
y = m*x + b;
dist_tan = sqrt((cart(i,1)-x)^2 + (cart(i,2)-y)^2); % sqrt(dx^2 + dy^2)
if dist_tan <= d % if it's an inlier
inliers = [inliers; i]; % add new inlier to list
% if it's the first inlier, make it the first endpoint
if size(inliers, 1) == 1
endpoints(1,:) = cart(i,:);
endline(1,:) = [x y];
end
% if it's the second inlier, assign the leftmost as endpoint 1
if size(inliers, 1) == 2
if endline(1,1) < x
endpoints(2,:) = cart(i,:);
endline(2,:) = [x y];
end
if endline(1,1) > x
endpoints(2,:) = endpoints(1,:);
endline(2,:) = endline(1,:);
endpoints(1,:) = cart(i,:);
endline(1,:) = [x y];
end
end
% if it's another inlier, figure out if it's farthest left or right so far
if size(inliers, 1) > 2
if x > endline(2,1)
endpoints(2,:) = cart(i,:);
endline(2,:) = [x y];
end
if x < endline(1,1)
endpoints(1,:) = cart(i,:);
endline(1,:) = [x y];
end
end
end
end
end
function [outliers, calc_r, center] = circlefit(cart, d, n, r, tol)
% circle fit using discriminating criteria (point distance, radius,
% near inliers) to identify the best circle in datapoints 'cart'
calc_r = r;
inliers = zeros(0,2);
center = zeros(0,2);
for k=1:n
% % Looking in a particular area on the graph instead of guessing
% % blindly. signifiantly improves recognition at the expense of
% % having to know map layout.
% xs = cart(:,1);
% ys = cart(:,2);
% indexx=find(xs > .2 & xs < .8);
% indexy=find(ys > -2.8 & ys < -2.2);
% index_circ=intersect(indexx,indexy);
% circx=xs(index_circ);
% circy=ys(index_circ);
% A = [circx circy ones(size(circx))];
% b = -circx.^2 - circy.^2;
d1 = 2*r+1; d2 = 2*r+1;
while d1 > 2*r || d2 > 2*r % check whether distance apart is too big
% Pick 3 random points
candidates = datasample(cart, 3, 'Replace', false);
d1 = sqrt((candidates(1,1)-candidates(2,1)).^2 + (candidates(1,2)-candidates(2,2)).^2);
d2 = sqrt((candidates(2,1)-candidates(3,1)).^2 + (candidates(2,2)-candidates(3,2)).^2);
end
% linear regression, solving for center and radius through candidates
A = [candidates(:,1) candidates(:,2) ones(size(candidates, 1),1)];
b = -candidates(:,1).^2 - candidates(:,2).^2;
w = A\b;
new_center = [-w(1)/2 -w(2)/2];
new_r = sqrt(new_center(1).^2 + new_center(2).^2 - w(3));
if abs(new_r - r) < tol % continue if radius is reasonably correct
% counting up the inliers and near inliers
near_inliers = [];
new_inliers = [];
for i=1:size(cart, 1)
dist = abs(sqrt((cart(i,1)-new_center(1)).^2 + (cart(i,2)-new_center(2)).^2) - new_r);
if dist < d
new_inliers = [new_inliers; i];
end
% near inliers is important bc circles don't have them, but
% circle fits over linear segments do.
if dist < 2*d && dist > d
near_inliers = [near_inliers; i];
end
end
% see if it's a better match than the previous one
if size(new_inliers,1) > size(inliers,1) && size(near_inliers, 1) == 0
inliers = new_inliers;
calc_r = new_r;
center = new_center;
end
end
end
graph_circle(center, calc_r);
outliers = cart;
outliers(inliers,:) = [];
end