forked from udacity/FCND-Estimation-CPP
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQuadEstimatorEKF.cpp
476 lines (395 loc) · 16.5 KB
/
QuadEstimatorEKF.cpp
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
#include "Common.h"
#include "QuadEstimatorEKF.h"
#include "Utility/SimpleConfig.h"
#include "Utility/StringUtils.h"
#include "Math/Quaternion.h"
using namespace SLR;
const int QuadEstimatorEKF::QUAD_EKF_NUM_STATES;
QuadEstimatorEKF::QuadEstimatorEKF(string config, string name)
: BaseQuadEstimator(config),
Q(QUAD_EKF_NUM_STATES, QUAD_EKF_NUM_STATES),
R_GPS(6, 6),
R_Mag(1, 1),
ekfState(QUAD_EKF_NUM_STATES),
ekfCov(QUAD_EKF_NUM_STATES, QUAD_EKF_NUM_STATES),
trueError(QUAD_EKF_NUM_STATES)
{
_name = name;
Init();
}
QuadEstimatorEKF::~QuadEstimatorEKF()
{
}
void QuadEstimatorEKF::Init()
{
ParamsHandle paramSys = SimpleConfig::GetInstance();
paramSys->GetFloatVector(_config + ".InitState", ekfState);
VectorXf initStdDevs(QUAD_EKF_NUM_STATES);
paramSys->GetFloatVector(_config + ".InitStdDevs", initStdDevs);
ekfCov.setIdentity();
for (int i = 0; i < QUAD_EKF_NUM_STATES; i++)
{
ekfCov(i, i) = initStdDevs(i) * initStdDevs(i);
}
// complementary filter params
attitudeTau = paramSys->Get(_config + ".AttitudeTau", .1f);
dtIMU = paramSys->Get(_config + ".dtIMU", .002f);
pitchEst = 0;
rollEst = 0;
// GPS measurement model covariance
R_GPS.setZero();
R_GPS(0, 0) = R_GPS(1, 1) = powf(paramSys->Get(_config + ".GPSPosXYStd", 0), 2);
R_GPS(2, 2) = powf(paramSys->Get(_config + ".GPSPosZStd", 0), 2);
R_GPS(3, 3) = R_GPS(4, 4) = powf(paramSys->Get(_config + ".GPSVelXYStd", 0), 2);
R_GPS(5, 5) = powf(paramSys->Get(_config + ".GPSVelZStd", 0), 2);
// magnetometer measurement model covariance
R_Mag.setZero();
R_Mag(0, 0) = powf(paramSys->Get(_config + ".MagYawStd", 0), 2);
// load the transition model covariance
Q.setZero();
Q(0, 0) = Q(1, 1) = powf(paramSys->Get(_config + ".QPosXYStd", 0), 2);
Q(2, 2) = powf(paramSys->Get(_config + ".QPosZStd", 0), 2);
Q(3, 3) = Q(4, 4) = powf(paramSys->Get(_config + ".QVelXYStd", 0), 2);
Q(5, 5) = powf(paramSys->Get(_config + ".QVelZStd", 0), 2);
Q(6, 6) = powf(paramSys->Get(_config + ".QYawStd", 0), 2);
Q *= dtIMU;
rollErr = pitchErr = maxEuler = 0;
posErrorMag = velErrorMag = 0;
}
void QuadEstimatorEKF::UpdateFromIMU(V3F accel, V3F gyro)
{
// Improve a complementary filter-type attitude filter
//
// Currently a small-angle approximation integration method is implemented
// The integrated (predicted) value is then updated in a complementary filter style with attitude information from accelerometers
//
// Implement a better integration method that uses the current attitude estimate (rollEst, pitchEst and ekfState(6))
// to integrate the body rates into new Euler angles.
//
// HINTS:
// - there are several ways to go about this, including:
// 1) create a rotation matrix based on your current Euler angles, integrate that, convert back to Euler angles
// OR
// 2) use the Quaternion<float> class, which has a handy FromEuler123_RPY function for creating a quaternion from Euler Roll/PitchYaw
// (Quaternion<float> also has a IntegrateBodyRate function, though this uses quaternions, not Euler angles)
////////////////////////////// BEGIN STUDENT CODE ///////////////////////////
// SMALL ANGLE GYRO INTEGRATION:
// (replace the code below)
// make sure you comment it out when you add your own code -- otherwise e.g. you might integrate yaw twice
Quaternion<float> quat = Quaternion<float>::FromEuler123_RPY(rollEst, pitchEst, ekfState(6));
quat.IntegrateBodyRate(gyro, dtIMU);
float predictedPitch = quat.Pitch();
float predictedRoll = quat.Roll();
ekfState(6) = quat.Yaw(); // yaw
// normalize yaw to -pi .. pi
if (ekfState(6) > F_PI) ekfState(6) -= 2.f*F_PI;
if (ekfState(6) < -F_PI) ekfState(6) += 2.f*F_PI;
/////////////////////////////// END STUDENT CODE ////////////////////////////
// CALCULATE UPDATE
accelRoll = atan2f(accel.y, accel.z);
accelPitch = atan2f(-accel.x, 9.81f);
// FUSE INTEGRATION AND UPDATE
rollEst = attitudeTau / (attitudeTau + dtIMU) * (predictedRoll)+dtIMU / (attitudeTau + dtIMU) * accelRoll;
pitchEst = attitudeTau / (attitudeTau + dtIMU) * (predictedPitch)+dtIMU / (attitudeTau + dtIMU) * accelPitch;
lastGyro = gyro;
}
void QuadEstimatorEKF::UpdateTrueError(V3F truePos, V3F trueVel, Quaternion<float> trueAtt)
{
VectorXf trueState(QUAD_EKF_NUM_STATES);
trueState(0) = truePos.x;
trueState(1) = truePos.y;
trueState(2) = truePos.z;
trueState(3) = trueVel.x;
trueState(4) = trueVel.y;
trueState(5) = trueVel.z;
trueState(6) = trueAtt.Yaw();
trueError = ekfState - trueState;
if (trueError(6) > F_PI) trueError(6) -= 2.f*F_PI;
if (trueError(6) < -F_PI) trueError(6) += 2.f*F_PI;
pitchErr = pitchEst - trueAtt.Pitch();
rollErr = rollEst - trueAtt.Roll();
maxEuler = MAX(fabs(pitchErr), MAX(fabs(rollErr), fabs(trueError(6))));
posErrorMag = truePos.dist(V3F(ekfState(0), ekfState(1), ekfState(2)));
velErrorMag = trueVel.dist(V3F(ekfState(3), ekfState(4), ekfState(5)));
}
VectorXf QuadEstimatorEKF::PredictState(VectorXf curState, float dt, V3F accel, V3F gyro)
{
assert(curState.size() == QUAD_EKF_NUM_STATES);
VectorXf predictedState = curState;
// Predict the current state forward by time dt using current accelerations and body rates as input
// INPUTS:
// curState: starting state
// dt: time step to predict forward by [s]
// accel: acceleration of the vehicle, in body frame, *not including gravity* [m/s2]
// gyro: body rates of the vehicle, in body frame [rad/s]
//
// OUTPUT:
// return the predicted state as a vector
// HINTS
// - dt is the time duration for which you should predict. It will be very short (on the order of 1ms)
// so simplistic integration methods are fine here
// - we've created an Attitude Quaternion for you from the current state. Use
// attitude.Rotate_BtoI(<V3F>) to rotate a vector from body frame to inertial frame
// - the yaw integral is already done in the IMU update. Be sure not to integrate it again here
Quaternion<float> attitude = Quaternion<float>::FromEuler123_RPY(rollEst, pitchEst, curState(6));
////////////////////////////// BEGIN STUDENT CODE ///////////////////////////
predictedState(0) = curState(0) + dt * curState(3);
predictedState(1) = curState(1) + dt * curState(4);
predictedState(2) = curState(2) + dt * curState(5);
V3F acc_I = attitude.Rotate_BtoI(accel);
predictedState(3) = curState(3) + dt * acc_I.x;
predictedState(4) = curState(4) + dt * acc_I.y;
predictedState(5) = curState(5) + dt * acc_I.z - dt * CONST_GRAVITY;
/////////////////////////////// END STUDENT CODE ////////////////////////////
return predictedState;
}
MatrixXf QuadEstimatorEKF::GetRbgPrime(float roll, float pitch, float yaw)
{
// first, figure out the Rbg_prime
MatrixXf RbgPrime(3, 3);
RbgPrime.setZero();
// Return the partial derivative of the Rbg rotation matrix with respect to yaw. We call this RbgPrime.
// INPUTS:
// roll, pitch, yaw: Euler angles at which to calculate RbgPrime
//
// OUTPUT:
// return the 3x3 matrix representing the partial derivative at the given point
// HINTS
// - this is just a matter of putting the right sin() and cos() functions in the right place.
// make sure you write clear code and triple-check your math
// - You can also do some numerical partial derivatives in a unit test scheme to check
// that your calculations are reasonable
////////////////////////////// BEGIN STUDENT CODE ///////////////////////////
RbgPrime(0,0) = -cos(pitch) * sin (yaw);
RbgPrime(0,1) = -sin(roll)*sin(pitch)*sin(yaw) - cos(pitch)*cos(yaw);
RbgPrime(0,2) = -cos(roll)*sin(pitch)*sin(yaw) + sin(roll)*cos(yaw);
RbgPrime(1,0) = cos(pitch)*cos(yaw);
RbgPrime(1,1) = sin(roll)*sin(pitch)*cos(yaw) - cos(roll)*sin(yaw);
RbgPrime(1,2) = cos(roll)*sin(pitch)*cos(yaw) + sin(roll)*sin(yaw);
/////////////////////////////// END STUDENT CODE ////////////////////////////
return RbgPrime;
}
void QuadEstimatorEKF::Predict(float dt, V3F accel, V3F gyro)
{
// predict the state forward
VectorXf newState = PredictState(ekfState, dt, accel, gyro);
// Predict the current covariance forward by dt using the current accelerations and body rates as input.
// INPUTS:
// dt: time step to predict forward by [s]
// accel: acceleration of the vehicle, in body frame, *not including gravity* [m/s2]
// gyro: body rates of the vehicle, in body frame [rad/s]
// state (member variable): current state (state at the beginning of this prediction)
//
// OUTPUT:
// update the member variable cov to the predicted covariance
// HINTS
// - update the covariance matrix cov according to the EKF equation.
//
// - you may find the current estimated attitude in variables rollEst, pitchEst, state(6).
//
// - use the class MatrixXf for matrices. To create a 3x5 matrix A, use MatrixXf A(3,5).
//
// - the transition model covariance, Q, is loaded up from a parameter file in member variable Q
//
// - This is unfortunately a messy step. Try to split this up into clear, manageable steps:
// 1) Calculate the necessary helper matrices, building up the transition jacobian
// 2) Once all the matrices are there, write the equation to update cov.
//
// - if you want to transpose a matrix in-place, use A.transposeInPlace(), not A = A.transpose()
//
// we'll want the partial derivative of the Rbg matrix
MatrixXf RbgPrime = GetRbgPrime(rollEst, pitchEst, ekfState(6));
// we've created an empty Jacobian for you, currently simply set to identity
MatrixXf gPrime(QUAD_EKF_NUM_STATES, QUAD_EKF_NUM_STATES);
gPrime.setIdentity();
////////////////////////////// BEGIN STUDENT CODE ///////////////////////////
gPrime(0, 3) = gPrime(1, 4) = gPrime(2, 5) = dt;
VectorXf accelV(3);
accelV << accel[0], accel[1], accel[2];
VectorXf term = RbgPrime * accelV;
term *= dt;
gPrime(3, 6) = term[0];
gPrime(4, 6) = term[1];
gPrime(5, 6) = term[2];
ekfCov = gPrime * ekfCov * gPrime.transpose() + Q;
/////////////////////////////// END STUDENT CODE ////////////////////////////
ekfState = newState;
}
void QuadEstimatorEKF::UpdateFromGPS(V3F pos, V3F vel)
{
VectorXf z(6), zFromX(6);
z(0) = pos.x;
z(1) = pos.y;
z(2) = pos.z;
z(3) = vel.x;
z(4) = vel.y;
z(5) = vel.z;
MatrixXf hPrime(6, QUAD_EKF_NUM_STATES);
hPrime.setZero();
// GPS UPDATE
// Hints:
// - The GPS measurement covariance is available in member variable R_GPS
// - this is a very simple update
////////////////////////////// BEGIN STUDENT CODE ///////////////////////////
zFromX(0) = ekfState(0);
zFromX(1) = ekfState(1);
zFromX(2) = ekfState(2);
zFromX(3) = ekfState(3);
zFromX(4) = ekfState(4);
zFromX(5) = ekfState(5);
for ( int i = 0; i < 6; i++) {
hPrime(i,i) = 1;
}
/////////////////////////////// END STUDENT CODE ////////////////////////////
Update(z, hPrime, R_GPS, zFromX);
}
void QuadEstimatorEKF::UpdateFromMag(float magYaw)
{
VectorXf z(1), zFromX(1);
z(0) = magYaw;
MatrixXf hPrime(1, QUAD_EKF_NUM_STATES);
hPrime.setZero();
// MAGNETOMETER UPDATE
// Hints:
// - Your current estimated yaw can be found in the state vector: ekfState(6)
// - Make sure to normalize the difference between your measured and estimated yaw
// (you don't want to update your yaw the long way around the circle)
// - The magnetomer measurement covariance is available in member variable R_Mag
////////////////////////////// BEGIN STUDENT CODE ///////////////////////////
hPrime(6) = 1;
zFromX(0) = ekfState(6);
float diff = magYaw - ekfState(6);
if (diff > F_PI) {
zFromX(0) += 2.f*F_PI;
} else if (diff < -F_PI) {
zFromX(0) -= 2.f*F_PI;
}
/////////////////////////////// END STUDENT CODE ////////////////////////////
Update(z, hPrime, R_Mag, zFromX);
}
// Execute an EKF update step
// z: measurement
// H: Jacobian of observation function evaluated at the current estimated state
// R: observation error model covariance
// zFromX: measurement prediction based on current state
void QuadEstimatorEKF::Update(VectorXf& z, MatrixXf& H, MatrixXf& R, VectorXf& zFromX)
{
assert(z.size() == H.rows());
assert(QUAD_EKF_NUM_STATES == H.cols());
assert(z.size() == R.rows());
assert(z.size() == R.cols());
assert(z.size() == zFromX.size());
MatrixXf toInvert(z.size(), z.size());
toInvert = H*ekfCov*H.transpose() + R;
MatrixXf K = ekfCov * H.transpose() * toInvert.inverse();
ekfState = ekfState + K*(z - zFromX);
MatrixXf eye(QUAD_EKF_NUM_STATES, QUAD_EKF_NUM_STATES);
eye.setIdentity();
ekfCov = (eye - K*H)*ekfCov;
}
// Calculate the condition number of the EKF ovariance matrix (useful for numerical diagnostics)
// The condition number provides a measure of how similar the magnitudes of the error metric beliefs
// about the different states are. If the magnitudes are very far apart, numerical issues will start to come up.
float QuadEstimatorEKF::CovConditionNumber() const
{
MatrixXf m(7, 7);
for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 7; j++)
{
m(i, j) = ekfCov(i, j);
}
}
Eigen::JacobiSVD<MatrixXf> svd(m);
float cond = svd.singularValues()(0)
/ svd.singularValues()(svd.singularValues().size() - 1);
return cond;
}
// Access functions for graphing variables
bool QuadEstimatorEKF::GetData(const string& name, float& ret) const
{
if (name.find_first_of(".") == string::npos) return false;
string leftPart = LeftOf(name, '.');
string rightPart = RightOf(name, '.');
if (ToUpper(leftPart) == ToUpper(_name))
{
#define GETTER_HELPER(A,B) if (SLR::ToUpper(rightPart) == SLR::ToUpper(A)){ ret=(B); return true; }
GETTER_HELPER("Est.roll", rollEst);
GETTER_HELPER("Est.pitch", pitchEst);
GETTER_HELPER("Est.x", ekfState(0));
GETTER_HELPER("Est.y", ekfState(1));
GETTER_HELPER("Est.z", ekfState(2));
GETTER_HELPER("Est.vx", ekfState(3));
GETTER_HELPER("Est.vy", ekfState(4));
GETTER_HELPER("Est.vz", ekfState(5));
GETTER_HELPER("Est.yaw", ekfState(6));
GETTER_HELPER("Est.S.x", sqrtf(ekfCov(0, 0)));
GETTER_HELPER("Est.S.y", sqrtf(ekfCov(1, 1)));
GETTER_HELPER("Est.S.z", sqrtf(ekfCov(2, 2)));
GETTER_HELPER("Est.S.vx", sqrtf(ekfCov(3, 3)));
GETTER_HELPER("Est.S.vy", sqrtf(ekfCov(4, 4)));
GETTER_HELPER("Est.S.vz", sqrtf(ekfCov(5, 5)));
GETTER_HELPER("Est.S.yaw", sqrtf(ekfCov(6, 6)));
// diagnostic variables
GETTER_HELPER("Est.D.AccelPitch", accelPitch);
GETTER_HELPER("Est.D.AccelRoll", accelRoll);
GETTER_HELPER("Est.D.ax_g", accelG[0]);
GETTER_HELPER("Est.D.ay_g", accelG[1]);
GETTER_HELPER("Est.D.az_g", accelG[2]);
GETTER_HELPER("Est.E.x", trueError(0));
GETTER_HELPER("Est.E.y", trueError(1));
GETTER_HELPER("Est.E.z", trueError(2));
GETTER_HELPER("Est.E.vx", trueError(3));
GETTER_HELPER("Est.E.vy", trueError(4));
GETTER_HELPER("Est.E.vz", trueError(5));
GETTER_HELPER("Est.E.yaw", trueError(6));
GETTER_HELPER("Est.E.pitch", pitchErr);
GETTER_HELPER("Est.E.roll", rollErr);
GETTER_HELPER("Est.E.MaxEuler", maxEuler);
GETTER_HELPER("Est.E.pos", posErrorMag);
GETTER_HELPER("Est.E.vel", velErrorMag);
GETTER_HELPER("Est.D.covCond", CovConditionNumber());
#undef GETTER_HELPER
}
return false;
};
vector<string> QuadEstimatorEKF::GetFields() const
{
vector<string> ret = BaseQuadEstimator::GetFields();
ret.push_back(_name + ".Est.roll");
ret.push_back(_name + ".Est.pitch");
ret.push_back(_name + ".Est.x");
ret.push_back(_name + ".Est.y");
ret.push_back(_name + ".Est.z");
ret.push_back(_name + ".Est.vx");
ret.push_back(_name + ".Est.vy");
ret.push_back(_name + ".Est.vz");
ret.push_back(_name + ".Est.yaw");
ret.push_back(_name + ".Est.S.x");
ret.push_back(_name + ".Est.S.y");
ret.push_back(_name + ".Est.S.z");
ret.push_back(_name + ".Est.S.vx");
ret.push_back(_name + ".Est.S.vy");
ret.push_back(_name + ".Est.S.vz");
ret.push_back(_name + ".Est.S.yaw");
ret.push_back(_name + ".Est.E.x");
ret.push_back(_name + ".Est.E.y");
ret.push_back(_name + ".Est.E.z");
ret.push_back(_name + ".Est.E.vx");
ret.push_back(_name + ".Est.E.vy");
ret.push_back(_name + ".Est.E.vz");
ret.push_back(_name + ".Est.E.yaw");
ret.push_back(_name + ".Est.E.pitch");
ret.push_back(_name + ".Est.E.roll");
ret.push_back(_name + ".Est.E.pos");
ret.push_back(_name + ".Est.E.vel");
ret.push_back(_name + ".Est.E.maxEuler");
ret.push_back(_name + ".Est.D.covCond");
// diagnostic variables
ret.push_back(_name + ".Est.D.AccelPitch");
ret.push_back(_name + ".Est.D.AccelRoll");
ret.push_back(_name + ".Est.D.ax_g");
ret.push_back(_name + ".Est.D.ay_g");
ret.push_back(_name + ".Est.D.az_g");
return ret;
};