-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparticle_filter.cpp
More file actions
487 lines (416 loc) · 18.8 KB
/
particle_filter.cpp
File metadata and controls
487 lines (416 loc) · 18.8 KB
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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
/*
* particle_filter.cpp
*
* 2D particle filter class.
* Created on: May 02, 2020
* Author: Munir Jojo-Verge
*/
#include "particle_filter.h"
#include <math.h>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <numeric>
#include <random>
#include <sstream>
#include <string>
#define _USE_MATH_DEFINES
#define DEBUGGING false
#include <cmath>
using namespace std;
// declare a random engine to be used across multiple and various method calls
random_device rd;
static std::mt19937 randomGen(rd());
/*
The state X of the robot is comprised of:
x = position coordinate x (lower case)
y = position coordinate y (lower case)
th = heading angle
The State Space Model is:
X[k+1] = A X[k] + B u[k] + Wk
Y[k] = C x[k] + Vk
Where:
A = state matrix
X[k] = State vector at sample/time k (analogous for k+1)
B = input matrix
u[k] = measured inputs, For simplicity this is 0. We are not actuating
Wk = unmeasured forces or faults. AKA System Noise
Y[k] = measurment at sample/time k.
C = measurment matrix.
Vk = measurment noise.
*/
void ParticleFilter::init(int num_particles, const RobotState& robotState,
const RobotParams& robotParams,
const FieldLocation markerLocations[NUM_LANDMARKS]) {
// 1) Set the number of particles.
// 2) Initialize all particles to the inital Robot state and params
// 3) Initialize all their weights to 1/n.
// 4) Add random Gaussian noise to each particle based on the standard
// deviation given in params.
// 5) store the ground truth location of the landmarks.
// NOTE: Please take a look at particle_filter.h for more information about
// this method (and others in this file).
num_particles_ = (num_particles < max_num_particles_)
? num_particles
: max_num_particles_; // Number of Particles
// The optimal/correct initial weight (even though it will be normalized
// later) is 1/num_particles_
double weight_init = 1 / num_particles_;
robotParams_ = robotParams;
if (robotParams_.sensor_noise_distance <
std::numeric_limits<double>::epsilon() ||
robotParams_.sensor_noise_orientation <
std::numeric_limits<double>::epsilon()) {
cerr << "sensor_noise (distance and/or orientation is zero!" << endl;
}
robotParams_.angle_fov = robotParams_.angle_fov * M_PI / 180;
angNorm(robotParams_.angle_fov);
// Assuming that any adometry noise affecting Rotation is given in degrees
robotParams_.odom_noise_rotation_from_rotation =
robotParams_.odom_noise_rotation_from_rotation * M_PI / 180;
angNorm(robotParams_.odom_noise_rotation_from_rotation);
robotParams_.odom_noise_rotation_from_translation =
robotParams_.odom_noise_rotation_from_translation * M_PI / 180;
angNorm(robotParams_.odom_noise_rotation_from_translation);
// Create the particles and add random Sys/Model noise
// This might not be computationally efficient but its done this way for
// conceptual readability.
initialize_particles(robotState,
robotParams_.odom_noise_translation_from_translation,
robotParams_.odom_noise_translation_from_translation,
robotParams_.odom_noise_rotation_from_rotation);
// copy(begin(markerLocations), end(markerLocations),
// begin(landmarkLocations_));
for (size_t i = 0; i < NUM_LANDMARKS; i++) {
landmarkLocations_[i].markerIndex = i;
landmarkLocations_[i].x = markerLocations[i].x;
landmarkLocations_[i].y = markerLocations[i].y;
};
is_initialized_ = true;
}
void ParticleFilter::initialize_particles(const RobotState& robotState,
const double& x_std,
const double& y_std,
const double& theta_std) {
double weight_init = 1 / num_particles_;
// If we ever call this function more than once to re-generate particles, we
// want to make sure we have empty vectors.
particles_.clear();
weights_.clear();
for (int i = 0; i < num_particles_; i++) {
Particle particle;
particle.id = i;
particle.x = gen_noise(robotState.x, x_std);
particle.y = gen_noise(robotState.y, y_std);
particle.theta = gen_noise(robotState.theta, theta_std);
angNorm(particle.theta);
particle.weight = weight_init;
keep_inside_field(particle);
particles_.push_back(particle);
weights_.push_back(particle.weight);
}
}
void ParticleFilter::motionUpdate(const RobotState& delta) {
std::cout << "Robot Delta: " << delta.x << ", " << delta.y << ", "
<< delta.theta << std::endl;
for (auto& p : particles_) {
p.x += gen_noise(delta.x,
robotParams_.odom_noise_translation_from_translation);
p.y += gen_noise(delta.y,
robotParams_.odom_noise_translation_from_translation);
p.theta +=
gen_noise(delta.theta, robotParams_.odom_noise_rotation_from_rotation);
angNorm(p.theta);
// Make sure they all stay in the field. Could also use a cyclic world.
keep_inside_field(p);
}
}
// void ParticleFilter::motionUpdate(const RobotState& delta) {
// std::cout << "Robot Delta: " << delta.x << ", " << delta.y << ", "
// << delta.theta << std::endl;
// for (auto& p : particles_) {
// p.theta +=
// gen_noise(delta.theta,
// robotParams_.odom_noise_rotation_from_rotation);
// angNorm(p.theta);
// double range_noise = gen_noise(
// delta.x, robotParams_.odom_noise_translation_from_translation);
// p.x += range_noise * cos(p.theta);
// p.y += range_noise * cos(p.theta);
// // Make sure they all stay in the field. Could also use a cyclic world.
// keep_inside_field(p);
// }
// }
void ParticleFilter::keep_inside_field(Particle& p) {
double half_field_len_m = METERS_PER_PIXEL * FIELD_LENGTH / 2;
double half_field_wid_m = METERS_PER_PIXEL * FIELD_WIDTH / 2;
if (p.x < -half_field_len_m)
p.x = -half_field_len_m;
else if (p.x > half_field_len_m)
p.x = half_field_len_m;
if (p.y < -half_field_wid_m)
p.y = -half_field_wid_m;
else if (p.y > half_field_wid_m)
p.y = half_field_wid_m;
}
void ParticleFilter::updateWeights(
std::vector<MarkerObservation>& observations) {
// This is a Multivariate_normal_distribution constant that we will use below.
const double K1 = 100 * 1.0 /
(2.0 * M_PI * robotParams_.sensor_noise_distance *
robotParams_.sensor_noise_orientation);
const double sigma_distance =
robotParams_.sensor_noise_distance * robotParams_.sensor_noise_distance;
const double sigma_orientation = robotParams_.sensor_noise_orientation *
robotParams_.sensor_noise_orientation;
// Update the weights of each particle using a mult-variate Gaussian
// distribution. You can read more about this distribution here:
// https://en.wikipedia.org/wiki/Multivariate_normal_distribution
// NOTE: The observations are given in the ROBOT'S coordinate system. The
// particles are located according to the World coordinate system. We will
// need to transform between the two systems. Keep in mind that this
// transformation requires both rotation AND translation (but no scaling). The
// following is a good resource for the theory:
// https://www.willamette.edu/~gorr/classes/GeneralGraphics/Transforms/transforms2d.htm
// and the following is a good resource for the actual equation to implement
// (look at equation 3.33 http://planning.cs.uiuc.edu/node99.html
for (auto& p : particles_) {
std::vector<MarkerObservation_World> predicted_lm;
// std::map<int, int> lm2idx;
// 1) TRANSFORMATION: Particle frame to World frame
// The following vector represent the transformation of the observations
// vector w.r.t to the i - th particle. What this really means is that it
// will hold the World coordenates of the observed/measured landmarks if the
// particle was making the measurement and that's the reason I called it
// particle_observations.
std::vector<MarkerObservation_World> particle_observations;
for (auto obs : observations) {
MarkerObservation_World trans_obs;
// from polar to cartesian still in robot frame
double obs_x = obs.distance * cos(obs.orientation);
double obs_y = obs.distance * sin(obs.orientation);
// from robot coordiantes to world coordinates
trans_obs.markerIndex = obs.markerIndex;
trans_obs.x = p.x + (obs_x * cos(p.theta) - obs_y * sin(p.theta));
trans_obs.y = p.y + (obs_x * sin(p.theta) + obs_y * cos(p.theta));
// Again, these are the observations(measurements of landmark positions)
// w.r.t the particle p in World coordinates
particle_observations.push_back(trans_obs);
}
// 2) ASSOCIATION. Now we are trying to find which landmark does EACH
// observation corresponds to. We will simply associate the closest one
// (closest neighbor) This will actually give the mu(i) on the Weight Update
// Equation based on The Multivariate-Gaussian probability Let's calculate
// the CLOSEST LANDMARK TO EACH OBSERVATION
// First and for efficiency I will go through all the landmarks and make a
// list of ONLY the ones that are in sensor range (FOV). Then, after that, I
// will do the more "computantionally heavy" Eucleadian distance calculation
// using this list.
// for (int k = 0; k < NUM_LANDMARKS; k++) {
// double x_lm = landmarkLocations_[k].x;
// double y_lm = landmarkLocations_[k].y;
// int id_lm = landmarkLocations_[k].markerIndex;
// double distance = dist(x_lm, y_lm, p.x, p.y);
// double orientation = atan2((y_lm - p.y), (x_lm - p.x));
// double fov_low_limit = p.theta - (robotParams_.angle_fov / 2);
// // angNorm(fov_low_limit);
// double fov_high_limit = p.theta + (robotParams_.angle_fov / 2);
// // angNorm(fov_high_limit);
// if (orientation >= fov_low_limit & orientation <= fov_high_limit) {
// MarkerObservation_World obs = {id_lm, x_lm, y_lm};
// predicted_lm.push_back(obs);
// // lm2idx[id_lm] = k; // This, ideally should be the same, i.e. landm
// at
// // index 0 would have an id = 0
// }
// }
for (int k = 0; k < NUM_LANDMARKS; k++) {
double x_lm = landmarkLocations_[k].x;
double y_lm = landmarkLocations_[k].y;
int id_lm = landmarkLocations_[k].markerIndex;
MarkerObservation_World obs = {id_lm, x_lm, y_lm};
predicted_lm.push_back(obs);
}
landmark_association(predicted_lm, particle_observations);
/* 3) Calculating the Particle's Final Weight
Now we that we have done the measurement transformations and
associations, we have all the pieces we need to calculate the particle's
final weight. The particles final weight will be calculated as the
product of each measurement's Multivariate-Gaussian probability. The
Multivariate-Gaussian probability has two dimensions, x and y. The "Xi"
of the Multivariate-Gaussian is the i-th measurement (map coordenates):
particle_ observations. The mean (mu-i) of the Multivariate-Gaussian is
the measurement's associated landmark position (landmark associated with
the Xi measurment - map coordinates) and the Multivariate-Gaussian's
standard deviation (sigma) is described by our initial uncertainty in the
x and y ranges. The Multivariate-Gaussian is evaluated at the point of
the transformed measurement's position.
*/
/* compute bivariate-gaussian
https://en.wikipedia.org/wiki/Multivariate_normal_distribution#Bivariate_case
*/
double weight_product = 1.0;
for (auto obs : particle_observations) {
int markerIndex = obs.markerIndex;
// Error between where the particle "sees" the landmank and where the
// landmark really is (World Coordinates)
MarkerObservation_World lm;
// lm = landmarkLocations_[lm2idx[markerIndex]];
lm = landmarkLocations_[obs.markerIndex];
double distance_err = dist(obs.x, obs.y, lm.x, lm.y);
double orientation_err = atan2((lm.y - obs.y), (lm.x - obs.x));
// Calculate the Probability density for this especific
// measurment/observation and it's predicted_lm closest landmark
double weight =
K1 * exp(-0.5 * (((distance_err * distance_err) / (sigma_distance)) +
((orientation_err * orientation_err) /
(sigma_orientation))));
// multiply densities for all measurements to calculate the likelyhood
// of this particle to be in the correct/real position.
weight_product *= weight;
}
p.weight = weight_product;
}
// Normilize the weights
normalizeWeights();
}
// void ParticleFilter::resample() {
// std::vector<Particle> new_particles(num_particles_);
// int index = gen_noise(0, 1.0) * num_particles_;
// // cout << index << endl;
// double beta = 0.0;
// double mw = best_particle_.weight;
// // cout << mw;
// for (int i = 0; i < num_particles_; i++) {
// beta += gen_noise(0, 1.0) * 2.0 * mw;
// while (beta > particles_[index].weight) {
// beta -= particles_[index].weight;
// index = mod((index + 1), num_particles_);
// }
// new_particles[i] = particles_[index];
// }
// // for (int k = 0; k < num_particles_; k++) {
// // particles_[k] = new_particles[k];
// // // cout << p[k].show_pose() << endl;
// // }
// particles_ = new_particles;
// }
void ParticleFilter::resample() {
// Resample particles with replacement with probability proportional to
// their weight. NOTE: You may find helpful info on std::discrete_distribution
// here: http//en.cppreference.com/w/cpp/numeric/random/discrete_distribution
/*
What the following code specifically does is randomly, uniformaly,
sample from the cumulative distribution of the probability distribution
generated by the weights. When you sample randomly over this
distribution, you will select values based upon there statistical
probability, and thus, on average, pick values with the higher weights
(i.e. high probability of being correct given the observation z). We
will later see that we will select the particle with the highest
probability (confidence) as our best location estimate.
*/
std::vector<Particle> new_particles(num_particles_);
discrete_distribution<> distribution_weights(weights_.begin(),
weights_.end());
for (int i = 0; i < num_particles_; i++) {
// pick up a random particle based on their weight. Heavier particles get
// picketup more often
int selected_idx = distribution_weights(randomGen);
particles_[selected_idx].id = i;
new_particles[i] = (particles_[selected_idx]);
}
particles_ = new_particles;
std::cout << "n-part: " << particles_.size() << std::endl;
}
void ParticleFilter::normalizeWeights() {
std::cout << "==========NORMALIZING=========== " << std::endl;
double WeightSum = 0.0;
for (Particle p : particles_) {
WeightSum += p.weight;
}
best_particle_.weight = -1.0;
double New_WeightSum = 0.0;
for (Particle& p : particles_) {
p.weight /= WeightSum;
weights_[p.id] = p.weight;
if (p.weight > best_particle_.weight) best_particle_ = p;
std::cout << "P " << p.id << ": " << p.x << ", " << p.y << ", " << p.theta
<< ", " << p.weight << std::endl;
New_WeightSum += p.weight;
if (isnan(New_WeightSum)) break; // don't waste time
}
if (isnan(New_WeightSum)) {
std::cout << "Particle filter failing." << std::endl;
// RobotState center{best_particle.x, best_particle.y, best_particle.theta};
// set_num_particles(num_particles_ * 1.2);
// initialize_particles(center, METERS_PER_PIXEL * FIELD_LENGTH,
// METERS_PER_PIXEL * FIELD_WIDTH, M_PI);
}
std::cout << "New Weight Sum = " << New_WeightSum << std::endl;
std::cout << "Best confidence = " << best_particle_.weight << std::endl;
std::cout << "================================ " << std::endl;
}
double ParticleFilter::gen_noise(double mean, double std) {
// Generate the normal distributions for the noisy (either measurments or
// Syste model noise).
normal_distribution<double> dist(mean, std);
return dist(randomGen);
}
vector<double> ParticleFilter::gen_odom_gauss_noise() {
vector<double> result(4, 0);
double std_tt = robotParams_.odom_noise_translation_from_translation;
double std_tr = robotParams_.odom_noise_translation_from_rotation;
double std_rt = robotParams_.odom_noise_rotation_from_translation;
double std_rr = robotParams_.odom_noise_rotation_from_translation;
result[0] = gen_noise(0, std_tt);
result[1] = gen_noise(0, std_tr);
result[2] = gen_noise(0, std_rt);
result[3] = gen_noise(0, std_rr);
return result;
}
void ParticleFilter::landmark_association(
std::vector<MarkerObservation_World> predicted_lm,
std::vector<MarkerObservation_World>& observations) {
// Find the predicted_lm measurement that is closest to each observed
// measurement and assign the observed measurement to this particular
// landmark.
for (auto& obs : observations) {
int min = 1e6;
int ld_id = -1;
for (auto pred : predicted_lm) {
double distance = dist(obs.x, obs.y, pred.x, pred.y);
if (distance < min) {
min = distance;
ld_id = pred.markerIndex;
}
}
obs.markerIndex = ld_id;
}
}
Particle ParticleFilter::get_best_particle() {
// // Sort the particles in ascending order.
// std::sort(particles_.begin(), particles_.end());
// // The last particle should have the greatest weight.
// return particles_[particles_.size() - 1];
return best_particle_;
// Particle best_particle;
// long double x = 0.0;
// long double y = 0.0;
// long double theta = 0.0;
// long double Total_w = 0.0;
// for (auto p : particles_) {
// x += (p.x * p.weight);
// y += (p.y * p.weight);
// theta += (p.theta * p.weight);
// Total_w += p.weight;
// }
// best_particle.x = x / Total_w;
// best_particle.y = y / Total_w;
// best_particle.theta = theta / Total_w;
// best_particle.weight = 1.0;
// std::cout << "Best Estimate- x: " << best_particle.x
// << ", y: " << best_particle.y << ", t: " << best_particle.theta
// << std::endl;
// return best_particle;
}