Master Thesis Code
by Simon Moser
Loading...
Searching...
No Matches
EKF1.m
Go to the documentation of this file.
1% ============================================================================ %
2%> @brief EKF1 is an implementation of a state estimator for JUMP sensors
3%>
4%> This class is an implementation of an Extended Kalman Filter (EKF) based
5%> on [this derivation](@ref derivation9Ekf6D).
6%>
7%> @warning this implementation is changed from the original derivation. if you
8%> want to use the original implementation, use the version of the commit
9%> [4fac0d658d](https://github.zhaw.ch/isc/MT_moss_IMU_sensor_fusion/tree/4fac0d658de33c09c19e74a554307eb90db0d781)
10%> the changes are:
11%> - the accelerometer bias is not estimated
12%> - the accelerometer sensitivity is not estimated
13%> - the measurement update of the accelerometer is changed
14%>
15%> @code
16%> % example code
17%> ekf = EKF1();
18%>
19%> % set measurements
20%> ekf.z_acc = acc;
21%> ekf.z_gyr = gyr;
22%>
23%> % set zero velocity flag
24%> ekf.zv = zv;
25%>
26%> % run the filter
27%> [smoothed, filtered] = ekf.run();
28%> @endcode
29%>
30%> @warning all values regarding angles must be given in radians
31%>
32%> @copyright see the file @ref LICENSE in the root directory of the repository
33% ============================================================================ %
34
35classdef EKF1 < handle
36
37 properties (Constant)
38 n = 13; % number of states
39 m = 13; % number of measurements
40 end
42 properties (Access = public)
44 %> sampling time (default 1/200 s)
45 dt (1,1) double = 1/200;
46
47 %> zero velocity flag (1 x N)
48 zv (1,:) logical;
49
50 %> @name Measurement Vector
51 %> @{
52 %> attributes regarding the measurement vector
53
54 %> number of observations
55 N (1,1) double;
56 %> accelerometer measurements (3 x N) [m/s^2]
57 z_acc (3,:) double;
58
59 %> velocity measurements (3 x N)
60 %> @note these are usually all set to zero as the velocity is applied
61 %> as a pseudo measurement during ZUPTs
62 z_vel (3,:) double;
63
64 %> gyroscope measurements (3 x N) [rad/s]
65 z_gyr (3,:) double;
66
67 %> position pseudo measurements (3 x N) [m]
68 z_pos (3,:) double;
69
70 %> yaw pseudo measurements (1 x N) [rad]
71 z_yaw (1,:) double;
72
73 %> measurement vector (m x N)
74 z (:,:) double;
76 %> @} % Measurement Vector
77
78 %> @name State Vector
79 %> @{
80 %> attributes regarding the estimated state vectors
81
82 %> state vector, forward, a priori (n x N)
83 x_fm (:,:) double;
84
85 %> state vector, forward, a posteriori (n x N)
86 x_fp (:,:) double;
88 %> state vector, backward, a posteriori (n x N)
89 x_bp (:,:) double;
90
91 %> @} % State Vector
92
93 %> @name Initial State Vector
94 %> @{
95 %> attributes regarding the initial state vector
96
97 %> initial irientation quaternion
98 q_0 (4,1) double = [1,0,0,0]';
99
100 %> initial velocity
101 v_0 (3,1) double = [0,0,0]';
102
103 %> initial position
104 p_0 (3,1) double = [0,0,0]';
106 %> initial gyroscope bias
107 b_w_0 (3,1) double = [0,0,0]';
108
109 %> initial state vector (n x 1)
110 x_0 (:,1) double;
111
112 %> @} % Initial State Vector
113
114 %> @name State Covariance
115 %> @{
116 %> attributes regarding the state covariance matrix
117
118 %> state covariance matrix, forward, a priori (n x n x N)
119 P_fm (:,:,:);
120
121 %> state covariance matrix, forward, a posteriori (n x n x N)
122 P_fp (:,:,:);
124 %> state covariance matrix, backward, a posteriori (n x n x N)
125 P_bp (:,:,:);
126
127 %> @} % State Covariance
128
129 %> @name Initial State Covariance
130 %> @{
131 %> attributes regarding the initial state covariance matrix
132
133 %> initial orientation standard deviation (default 5e-4)
134 %> @remark based on experimental tuning
135 P_0_q (1,1) double = 5e-4;
136
137 %> initial velocity standard deviation (default 1e-7 m/s)
138 %> @remark based on experimental tuning
139 P_0_v (1,1) double = 1e-7;
140
141 %> initial position standard deviation (default 1e-7 m)
142 %> @remark based on experimental tuning
143 P_0_p (1,1) double = 1e-7;
144
145 %> initial gyroscope bias standard deviation (default 5e-3 rad/s)
146 %> @remark based on experimental tuning
147 P_0_b_w (1,1) double = 5e-3;
148
149 %> initial state covariance matrix (n x n)
150 %> @remark based on experimental tuning
151 P_0 (:,:) double;
152
153 %> @} % Initial State Covariance
154
155 %> @name Measurement Noise
156 %> @{
157 %> attributes regarding the measurement noise
158
159 %> accelerometer variance, x- and y-axis (default 0.045 m/s^2)
160 %> @remark based on allan variance analysis
161 var_acc_xy (1,1) double = 0.045;
162
163 %> accelerometer variance, z-axis (default 0.07 m/s^2)
164 %> @remark based on allan variance analysis
165 var_acc_z (1,1) double = 0.07;
166
167 %> variance of velocity 'measurements' during ZUPTs (default 1e-6)
168 %> @remark based on experimental tuning
169 var_vel_ZUPT (1,1) double = 1e-6;
171 %> gyroscope variance (default 1.22e-3 rad/s)
172 %> @remark based on allan variance analysis
173 var_gyr (1,1) double = 1.22e-3; % 0.07 * pi / 180;
174
175 %> variance of x-position 'measurements' during ZUPTs (default 1e-6 m)
176 %> @remark based on experimental tuning
177 var_pos_x = 1e-6;
178
179 %> variance of y-position 'measurements' during ZUPTs (default 1e-6 m)
180 %> @remark based on experimental tuning
181 var_pos_y = 1e-6;
182
183 %> variance of z-position 'measurements' during ZUPTs (default 1e-6 m)
184 %> @remark based on experimental tuning
185 var_pos_z = 1e-6;
186
187 %> variance of yaw pseudo-measurements (default TODO:)
188 %> @remark based on experimental tuning
189 var_yaw_pseudo = 0.0001;
190
191 %> measurement noise covariance matrix (m x m)
192 R (:,:) double;
193
194 %> @} % Measurement Noise
195
196 %> @name Process Noise
197 %> @{
198 %> attributes regarding the process noise
199 %> @note the process noise covariance matrix is calculated for each
200 %> iteration based on the current state vector (see @ref Q_cov)
202 %> angular velocity standard deviation (default 876e-6 rad/s)
203 %> @remark based on allan variance analysis
204 %> @note as the prediction is based on the measurement, the measurement
205 %> noise applies. but since the prediction is based on the preprocessed
206 %> angular velocity which is the mean angular velocity between two
207 %> samples, the standard deviation is divided by sqrt(2) to get the
208 %> standard deviation of the mean angular velocity.
209 sigma_w_p (1,1) double = sqrt(0.071) * pi / 180 / sqrt(2);
210
211 %> acceleration variance (default 0.045 m/s^2)
212 %> @remark based on experimental tuning
213 var_a (1,1) double = 0.045;
214
215 %> gyroscope bias variance (default 1e-9 rad/s)
216 %> @remark based on experimental tuning
217 %> @note the value from the allan variance analysis seems to be too
218 %> high. the value is set to a lower value based on experimental tuning.
219 var_b_w (1,1) double = 1e-9;
220
221 %> @} % Process Noise
222
223 end
224
225 methods (Access = public)
227 % ==================================================================== %
228 %> @brief Constructor of the EKF1 class
229 %>
230 %> constructs an instance of the EKF1 class with the default values
231 % ==================================================================== %
232 function obj = EKF1()
233 % nothing to do here
234 end
235
236 % ==================================================================== %
237 %> @brief filters and smooths the given data
238 %>
239 %> this function creates all needed vectors and matrices and also checks
240 %> the input data. it then filters the data and smoothes the data
241 %>
242 %> @param options options for the filter
243 %>
244 %> @retval smoothed smoothed state vector
245 %> @retval filtered filtered state vector
246 %>
247 %> this function creates all needed vectors and matrices and also checks
248 %> the input data. it then filters the data and smoothes the data
249 % ==================================================================== %
250 function [smoothed, filtered] = run(obj, options)
251
252 arguments
253 obj
254 options.plot (1,1) logical = false
255 end
256
257 % =============== %
258 % measurements
259 % =============== %
260 % check measurements length
261 obj.N = max(size(obj.z_acc));
262 assert(size(obj.z_vel, 2) == obj.N, "The number of velocity measurements must be equal to the number of accelerometer measurements. You provided %d velocity measurements and %d accelerometer measurements.", size(obj.z_vel, 2), obj.N);
263 assert(size(obj.z_gyr, 2) == obj.N, "The number of gyroscope measurements must be equal to the number of accelerometer measurements. You provided %d gyroscope measurements and %d accelerometer measurements.", size(obj.z_gyr, 2), obj.N);
264 assert(size(obj.z_pos, 2) == obj.N, "The number of position measurements must be equal to the number of accelerometer measurements. You provided %d position measurements and %d accelerometer measurements.", size(obj.z_pos, 2), obj.N);
265 assert(size(obj.z_yaw, 2) == obj.N, "The number of yaw measurements must be equal to the number of accelerometer measurements. You provided %d yaw measurements and %d accelerometer measurements.", size(obj.z_yaw, 2), obj.N);
266
267 % check zero velocity flag
268 assert(numel(obj.zv) == obj.N, "The number of zero velocity flags must be equal to the number of accelerometer measurements. You provided %d zero velocity flags and %d accelerometer measurements.", numel(obj.zv), obj.N);
269
270 % create measurement vector
271 obj.z = [obj.z_acc; obj.z_vel; obj.z_gyr; obj.z_pos; obj.z_yaw];
272
273 % =============== %
274 % state vectors
275 % =============== %
276 % create state vectors
277 obj.x_fm = nan(obj.n, obj.N);
278 obj.x_fp = nan(obj.n, obj.N);
279 obj.x_bp = nan(obj.n, obj.N);
280
281 % create initial state vector
282 obj.x_0 = [obj.q_0; obj.v_0; obj.p_0; obj.b_w_0];
283
284 % =============== %
285 % state covariance matrices
286 % =============== %
287 % create state covariance matrices
288 obj.P_fm = nan(obj.n, obj.n, obj.N);
289 obj.P_fp = nan(obj.n, obj.n, obj.N);
290 obj.P_bp = nan(obj.n, obj.n, obj.N);
291
292 % create initial state covariance matrix
293 obj.P_0 = diag([obj.P_0_q * ones(1,4), obj.P_0_v * ones(1,3), obj.P_0_p * ones(1,3), obj.P_0_b_w * ones(1,3)]) .^ 2;
294
295 % =============== %
296 % measurement noise
297 % =============== %
298 % create measurement noise covariance matrix
299 obj.R = diag([obj.var_acc_xy * ones(1,2), obj.var_acc_z, obj.var_vel_ZUPT * ones(1,3), obj.var_gyr * ones(1,3), obj.var_pos_x, obj.var_pos_y, obj.var_pos_z, obj.var_yaw_pseudo]);
300 assert(all(size(obj.R) == [obj.m, obj.m]), "The measurement noise covariance matrix must be of size %d x %d. You provided a matrix of size %d x %d.", obj.m, obj.m, size(obj.R, 1), size(obj.R, 2));
301
302 % =============== %
303 % process noise
304 % =============== %
305 % the process noise covariance matrix is calculated in the Q_cov function
306
307 % =============== %
308 % forward pass
309 % =============== %
310 obj.forward();
311
312 % =============== %
313 % backward pass
314 % =============== %
315 obj.backward();
316
317 % =============== %
318 % output
319 % =============== %
320 smoothed = obj.x_bp;
321 filtered = obj.x_fp;
322
323 % =============== %
324 % display results
325 % =============== %
326 if options.plot
327 obj.displayResults(filtered, smoothed, obj.zv);
328 end
329
330 end
331
332 % ==================================================================== %
333 %> @brief exports the tuning parameters of the filter to a JSON
334 %> file
335 %>
336 %> this function exports the tuning parameters of the filter to a JSON
337 %> file
338 %>
339 %> @param obj instance of the EKF1 class (implicit)
340 %> @param filename name of the JSON file (full or relative path, default
341 %> 'tuning.json')
342 %>
343 %> @retval status 1 if the file was written successfully, 0 otherwise
344 %>
345 %> @note The following parameters are exported:
346 %> - initial covariance
347 %> - measurement noise
348 %> - process noise
349 %>
350 % ==================================================================== %
351 function status = exportTuning(obj, filename)
352
353 arguments
354 obj
355 filename (1,1) string = "tuning.json"
356 end
357
358 % create struct
359 tuning = struct();
360
361 % initial covariance
362 tuning.P_0_q = obj.P_0_q;
363 tuning.P_0_v = obj.P_0_v;
364 tuning.P_0_p = obj.P_0_p;
365 tuning.P_0_b_w = obj.P_0_b_w;
366
367 % measurement noise
368 tuning.var_acc_xy = obj.var_acc_xy;
369 tuning.var_acc_z = obj.var_acc_z;
370 tuning.var_vel_ZUPT = obj.var_vel_ZUPT;
371 tuning.var_gyr = obj.var_gyr;
372 tuning.var_pos_x = obj.var_pos_x;
373 tuning.var_pos_y = obj.var_pos_y;
374 tuning.var_pos_z = obj.var_pos_z;
375 tuning.var_yaw_pseudo = obj.var_yaw_pseudo;
376
377 % process noise
378 tuning.sigma_w_p = obj.sigma_w_p;
379 tuning.var_a = obj.var_a;
380 tuning.var_b_w = obj.var_b_w;
381
382 % write to file
383 try
384 writestruct(tuning, filename, FileType="json", PrettyPrint=true);
385 status = 1;
386 catch
387 status = 0;
388 end
389
390 end
391
392 % ==================================================================== %
393 %> @brief imports the tuning parameters of the filter from a JSON
394 %> file
395 %>
396 %> this function imports the tuning parameters of the filter from a JSON
397 %> file
398 %>
399 %> @param obj instance of the EKF1 class (implicit)
400 %> @param filename name of the JSON file (full or relative path)
401 %>
402 %> @retval status 1 if the file was read successfully, 0 otherwise
403 %>
404 %> @note the function does only import the parameters that are present
405 %> in the JSON file. if a parameter is not present, the existing value
406 %> is kept. therefore, **no check** is performed if all parameters are
407 %> present.
408 % ==================================================================== %
409 function status = importTuning(obj, filename)
410
411 arguments
412 obj
413 filename (1,1) string {mustBeFile}
414 end
415
416 % read from file
417 try
418 tuning = readstruct(filename, FileType="json");
419 status = 1;
420 catch
421 status = 0;
422 return;
423 end
424
425 % get all the fields in the struct and the filter
426 fields = fieldnames(tuning);
427 field_used = false(1, numel(fields));
428 props_filter = string(properties(obj));
429
430 % loop through all fields from the struct
431 for i = 1:numel(fields)
432 field = fields{i};
433
434 % check if the field is a property of the filter, if not, skip
435 if ismember(field, props_filter)
436 obj.(field) = tuning.(field);
437 field_used(i) = true;
438 else
439 warning("The field %s from the imported tuning file is not used.", field);
440 end
441
442 end
443
444 % check if all fields from the imported struct are used
445 if ~all(field_used)
446 warning("The following fields from the imported tuning file are not used: %s", strjoin(fields(~field_used), ", "));
447 end
448
449 end
450
451 end
452
453 methods (Access = private)
455 % ==================================================================== %
456 %> @brief forward pass of the EKF1 filter
457 %>
458 %> this function performs the forward pass of the EKF1 filter
459 %>
460 %> @param obj instance of the EKF1 class
461 %>
462 % ==================================================================== %
463 function forward(obj)
464
465 % make local copies of often used variables
466 loc_x_fm = obj.x_fm;
467 loc_x_fp = obj.x_fp;
468 loc_P_fm = obj.P_fm;
469 loc_P_fp = obj.P_fp;
470 loc_z = obj.z;
471 loc_zv = obj.zv;
472
473 bad_iterations = false(1,obj.N);
474 warning("off");
475 for k = 1:obj.N
476 if k == 1
477 % initial prediction
478 [loc_x_fm(:, k), w] = obj.f_fun(obj.x_0, loc_z(:, k), loc_z(:, k), loc_zv(k));
479 F = obj.F_jac(w);
480 loc_P_fm(:, :, k) = F * obj.P_0 * F' + obj.Q_cov(obj.x_0);
481 else
482 % prediction
483 [loc_x_fm(:, k), w]= obj.f_fun(loc_x_fp(:, k-1), loc_z(:, k-1), loc_z(:, k), loc_zv(k));
484 F = obj.F_jac(w);
485 loc_P_fm(:, :, k) = F * loc_P_fp(:, :, k-1) * F' + obj.Q_cov(loc_x_fp(:, k-1));
486 end
487
488 % normalize quaternion
489 loc_x_fm(1:4, k) = loc_x_fm(1:4, k) ./ norm(loc_x_fm(1:4, k), 2);
490
491 h = obj.h_fun(loc_x_fm(:, k));
492 H = obj.H_jac(loc_x_fm(:, k));
493
494 % handle measurements
495 if ~loc_zv(k)
496 % there are no updates during movement
497 % TODO: the computing intensive part can be skipped completely instead of setting the H-Matrix to zero
498 H(:) = 0;
499 else
500 % handle measurements
501
502 % check if the x position is unavailable
503 if isnan(loc_z(10, k))
504 % set the corresponding row in the H matrix to zero
505 H(10, :) = 0;
506 end
507
508 % check if the y position is unavailable
509 if isnan(loc_z(11, k))
510 % set the corresponding row in the H matrix to zero
511 H(11, :) = 0;
512 end
513
514 % check if the z position is unavailable
515 if isnan(loc_z(12, k))
516 % set the corresponding row in the H matrix to zero
517 H(12, :) = 0;
518 end
519
520 % check if the yaw is unavailable
521 if isnan(loc_z(13, k))
522 % set the corresponding row in the H matrix to zero
523 H(13, :) = 0;
524 end
525
526 end
527
528 % fill missing values with zeros to prevent NaN values in the
529 % kalman gain. these zeros are not used in the update step
530 % since the corresponding rows in the H matrix are set to zero
531 this_z = fillmissing(loc_z(:, k), 'constant', 0);
532
533 % kalman gain
534 K = loc_P_fm(:, :, k) * H' * pinv(H * loc_P_fm(:, :, k) * H' + obj.R);
535
536 % update
537 loc_x_fp(:, k) = loc_x_fm(:, k) + K * (this_z - h);
538 loc_P_fp(:, :, k) = (eye(obj.n) - K * H) * loc_P_fm(:, :, k) * (eye(obj.n) - K * H)' + K * obj.R * K';
539
540 % normalize quaternion
541 loc_x_fp(1:4, k) = loc_x_fp(1:4, k) ./ norm(loc_x_fp(1:4, k), 2);
542
543 % check if a warning occured
544 [~, warnID] = lastwarn;
545 if strcmp(warnID, 'MATLAB:nearlySingularMatrix') || strcmp(warnID, 'MATLAB:singularMatrix')
546 bad_iterations(k) = true;
547 lastwarn("",""); % reset warning
548 end
549
550 % check for NaN values
551 if any(isnan(loc_x_fp(:,k)), "all") || any(isnan(loc_P_fp(:,:,k)), "all")
552 error("NaN detected in forward pass at iteration %d.", k);
553 end
554 end
555 warning("on");
556
557 if any(bad_iterations)
558 warning("A total of %d bad iterations in the forward pass were detected.", sum(bad_iterations));
559 end
560
561 % update object properties
562 obj.x_fm = loc_x_fm;
563 obj.x_fp = loc_x_fp;
564 obj.P_fm = loc_P_fm;
565 obj.P_fp = loc_P_fp;
566
567 end
568
569 % ==================================================================== %
570 %> @brief backward pass of the EKF1 filter
571 %>
572 %> this function performs the backward pass of the EKF1 filter
573 %>
574 %> @param obj instance of the EKF1 class
575 %>
576 % ==================================================================== %
577 function backward(obj)
578
579 % make local copies of often used variables
580 loc_x_fm = obj.x_fm;
581 loc_x_fp = obj.x_fp;
582 loc_x_bp = obj.x_bp;
583 loc_P_fm = obj.P_fm;
584 loc_P_fp = obj.P_fp;
585 loc_P_bp = obj.P_bp;
586
587 for k = obj.N-1:-1:1
588
589 if k == obj.N-1
590 % initialization
591 loc_x_bp(:, obj.N) = loc_x_fp(:, obj.N);
592 loc_P_bp(:, :, obj.N) = loc_P_fp(:, :, obj.N);
593 end
594
595 F = obj.F_jac(loc_x_fp(:, k));
596
597 I = pinv(loc_P_fm(:, :, k+1));
598 K = loc_P_fp(:, :, k) * F' * I;
599 loc_x_bp(:,k) = loc_x_fp(:,k) + K * (loc_x_bp(:,k+1) - loc_x_fm(:,k+1));
600 loc_P_bp(:,:,k) = loc_P_fp(:,:,k) - K * (loc_P_fm(:,:,k+1) - loc_P_bp(:,:,k+1)) * K';
601
602 if any(isnan(loc_x_bp(:,k)), "all") || any(isnan(loc_P_bp(:,:,k)), "all")
603 error("NaN detected in backward pass at iteration %d.", k);
604 end
605
606 end
607
608 % update object properties
609 obj.x_bp = loc_x_bp;
610 obj.P_bp = loc_P_bp;
611
612 end
613
614 % ==================================================================== %
615 %> @brief calculates the state transition function for the EKF1 filter
616 %>
617 %> this function calculates the state transition, also called the state
618 %> prediction/extrapolation.
619 %>
620 %> @param x_this state vector at time k
621 %> @param z_this measurement vector at time k
622 %> @param z_next measurement vector at time k+1
623 %> @param zv zero velocity flag at time k
624 %>
625 %> @retval x_next state vector at time k+1
626 %> @retval w preprocessed angular velocity
627 % ==================================================================== %
628 function [x_next, w] = f_fun(obj, x_this, z_this, z_next, zv)
629
630 arguments
631 obj
632 x_this (:,1) double {mustBeNumeric}
633 z_this (:,1) double {mustBeNumeric}
634 z_next (:,1) double {mustBeNumeric}
635 zv (1,1) logical
636 end
637
638 % new state vector
639 x_next = nan(size(x_this));
640
641 % new quaternion
642 epsilon = 1e-12;
643 q_this = x_this(1:4);
644 gyro_this = z_this(7:9);
645 gyro_next = z_next(7:9);
646 gyro_bias = x_this(11:13);
647 w = obj.getPreprocessedGyro(gyro_this, gyro_next, gyro_bias);
648 w_norm = norm(w,2);
649 w_matrix = [0, -w(1) -w(2) -w(3); ...
650 w(1) 0 w(3) -w(2); ...
651 w(2) -w(3) 0 w(1); ...
652 w(3) w(2) -w(1) 0];
653
654 q_next = (cos(w_norm * obj.dt / 2) * eye(4) + 1/(w_norm + epsilon) * sin(w_norm * obj.dt / 2) * w_matrix) * q_this;
655 x_next(1:4) = q_next;
656
657 % new acceleration
658 acc_this = z_this(1:3);
659 acc_next = z_next(1:3);
660 acc = obj.getPreprocessedAcc(acc_this, acc_next, q_this, q_next, zv);
661
662 % new velocity
663 x_next(5:7) = x_this(5:7) + acc * obj.dt;
664
665 % new position
666 x_next(8:10) = x_this(8:10) + x_this(5:7) * obj.dt + 0.5 * acc * obj.dt^2;
667
668 % new gyro bias
669 x_next(11:13) = x_this(11:13);
670
671 end
672
673
674 % ==================================================================== %
675 %> @brief calculates the jacobian of the state transition function
676 %>
677 %> this function calculates the jacobian of the state transition
678 %> function.
679 %>
680 %> @param w preprocessed angular velocity at time k
681 %>
682 %> @retval F jacobian of the state transition function at time k
683 % ==================================================================== %
684 function F = F_jac(obj, w)
685
686 arguments
687 obj
688 w (:,1) double {mustBeNumeric}
689 end
690
691 % F1 ( quaternion and angular velocity )
692 epsilon = 1e-12;
693 w_norm = norm(w,2);
694 alpha = cos(w_norm * obj.dt / 2) * w_norm;
695 beta = sin(w_norm * obj.dt / 2);
696
697 F_1_1 = 1/(w_norm + epsilon) * [alpha, -w(1) * beta, -w(2) * beta, -w(3) * beta; ...
698 w(1) * beta, alpha, w(3) * beta, -w(2) * beta; ...
699 w(2) * beta, -w(3) * beta, alpha, w(1) * beta; ...
700 w(3) * beta, w(2) * beta, -w(1) * beta, alpha];
701
702 F_1 = [F_1_1, zeros(4,9)];
703
704 % F2 (position, velocity, acceleration)
705
706 F_2_1 = eye(6);
707 F_2_1(4:6,1:3) = obj.dt * eye(3);
708
709 F_2 = [zeros(6,4), F_2_1, zeros(6,3)];
710
711 % F3 (gyro bias)
712 F_3 = zeros(3,13);
713 F_3(:,11:13) = eye(3);
714
715 F = [F_1; F_2; F_3];
716
717 end
718
719
720 % ==================================================================== %
721 %> @brief calculates the process noise covariance matrix
722 %>
723 %> this function calculates the process noise covariance matrix
724 %>
725 %> @param x state vector at time k
726 %>
727 %> @retval Q process noise covariance matrix
728 % ==================================================================== %
729 function Q = Q_cov(obj, x)
730
731 arguments
732 obj
733 x (:,1) double {mustBeNumeric}
734 end
735
736 % quaternion
737 q0 = x(1); q1 = x(2); q2 = x(3); q3 = x(4);
738 Q_q = (obj.dt * obj.sigma_w_p / 2)^2 * [...
739 1 - q0^2, -q0 * q1, -q0 * q2, -q0 * q3; ...
740 -q0 * q1, 1 - q1^2, -q1 * q2, -q1 * q3; ...
741 -q0 * q2, -q1 * q2, 1 - q2^2, -q2 * q3; ...
742 -q0 * q3, -q1 * q3, -q2 * q3, 1 - q3^2];
743
744 % acceleration, velocity and position
745 Q_a = zeros(6);
746 Q_a(1:3, 1:3) = obj.dt^2 * eye(3);
747 Q_a(4:6, 4:6) = obj.dt^4 / 4 * eye(3);
748 Q_a(4:6, 1:3) = obj.dt * eye(3);
749 Q_a(4:6, 1:3) = obj.dt^3 / 2 * eye(3);
750 Q_a = (triu(Q_a.',1) + tril(Q_a)) * obj.var_a;
751
752 % biases
753 Q_b_w = eye(3) * obj.var_b_w;
754
755 % combine
756 Q = blkdiag(Q_q, Q_a, Q_b_w);
757
758 % check size
759 assert(all(size(Q) == [obj.n, obj.n]), "The process noise covariance matrix must be of size %d x %d. You provided a matrix of size %d x %d.", obj.n, obj.n, size(Q, 1), size(Q, 2));
760
761 end
762
763 end
764
765 methods (Static, Access = private)
766
767 % ==================================================================== %
768 %> @brief calculates the jacobian of the measurement function from a
769 %> given state vector x
770 %>
771 %> @param x state vector at time k
772 %>
773 %> @retval H jacobian of the measurement function at time k
774 % ==================================================================== %
775 function H = H_jac(x)
776
777 arguments
778 x (:,1) double {mustBeNumeric}
779 end
780
781 g = 9.81;
782
783 % H1 (acceleration)
784 q0 = x(1); q1 = x(2); q2 = x(3); q3 = x(4);
785
786 H_1_1 = (2 * g * [ q2, -q3, q0, -q1; ...
787 -q1, -q0, -q3, -q2; ...
788 -q0, q1, q2, -q3]);
789
790 H_1 = [H_1_1, zeros(3,9)];
791
792 % H2 (ZUPT)
793 H_2 = [zeros(3,4), eye(3), zeros(3,6); ...
794 zeros(3,10), eye(3)];
795
796 % H3 (position pseudo measurement)
797 H_3 = [zeros(3,7), eye(3), zeros(3,3)];
798
799 % H4 (yaw pseudo measurement)
800 H_4_fac = 1/(2*q1*q3 - 2*q0*q2 + 1) + 1/(2*q0*q2 - 2*q1*q3 + 1);
801 H_4_1 = H_4_fac * ...
802 [2 * q3 * q1^2 - 2 * q0 * q2 * q1 - q3, ...
803 -2 * q1^2 * q2 - 2 * q0 * q1 * q3 - 2 * q2^3 - 2 * q2 * q3^2 + q2, ...
804 -2 * q1 * q3^2 + 2 * q0 * q2 * q3 + q1, ...
805 -2 * q0 * q2^2 + 2 * q1 * q3 * q2 + q0 ];
806 H_4 = [H_4_1, zeros(1,9)];
807
808 H = [H_1; H_2; H_3; H_4];
809
810 end
811
812 % ==================================================================== %
813 %> @brief calculates the measurement function from a given state vector
814 %>
815 %> @param x state vector at time k
816 %>
817 %> @retval h measurement vector at time k
818 % ==================================================================== %
819 function h=h_fun(x)
820
821 arguments
822 x (:,1) double {mustBeNumeric}
823 end
824
825 g = [0;0;-9.81]; % gravity vector in world frame
827 % acceleration
828 RotMat = quaternion(x(1:4)').rotmat("frame");
829
830 h1 = RotMat * g; % acceleration in global frame
831
832 % ZUPT
833 v_g = x(5:7); % velocity in global frame
834 b_w = x(11:13); % gyro biases
835 h2 = [v_g; b_w];
836
837 % position pseudo measurement
838 h3 = x(8:10); % position in global frame
839
840 % yaw pseudo measurement
841 q0 = x(1); q1 = x(2); q2 = x(3); q3 = x(4);
842 h4 = atan2(2 * q1 * q2 + 2 * q0 * q3, q1^2 + q0^2 - q3^2 -q2^2);
843
844 h = [h1; h2; h3; h4];
845
846 end
847
848 % ==================================================================== %
849 %> @brief calculates the preprocessed acceleration measurement
850 %>
851 %> this function calculates the preprocessed acceleration measurement as
852 %> explained in the derivation. The acceleration measurements are
853 %> preprocessed to get the bias free mean acceleration without gravity
854 %> between two samples.
855 %>
856 %> @param acc_this acceleration measurement at time k
857 %> @param acc_next acceleration measurement at time k+1
858 %> @param q_this orientation quaternion at time k
859 %> @param q_next orientation quaternion at time k+1
860 %> @param zv zero velocity flag at time k
861 %>
862 %> @retval acc preprocessed acceleration
863 % ==================================================================== %
864 function acc = getPreprocessedAcc(acc_this, acc_next, q_this, q_next, zv)
865
866 arguments
867 acc_this (3,1) double {mustBeNumeric}
868 acc_next (3,1) double {mustBeNumeric}
869 q_this (4,1) double {mustBeNumeric}
870 q_next (4,1) double {mustBeNumeric}
871 zv (1,1) logical
872 end
873
874 % if zero velocity is detected, the acceleration measurements are set to zero
875 if zv
876 acc = [0;0;0];
877 else
878 % rotate acceleration measurements to global frame
879 acc_this = quaternion(q_this').rotmat("frame")' * (acc_this);
880 acc_next = quaternion(q_next').rotmat("frame")' * (acc_next);
881
882 % calculate mean acceleration
883 acc = (acc_this + acc_next) / 2;
884
885 % remove gravity
886 acc = acc - [0;0;-9.81];
887
888 end
889
890 end
891
892 % ==================================================================== %
893 %> @brief calculates the preprocessed gyro measurement
894 %>
895 %> this function calculates the preprocessed gyro measurement as
896 %> explained in the derivation. The gyro measurements are preprocessed
897 %> to get the bias free mean angular velocity between two samples.
898 %>
899 %> @param gyro_this gyro measurement at time k
900 %> @param gyro_next gyro measurement at time k+1
901 %> @param bias bias of the gyroscope at time k
902 %> @param zv zero velocity flag at time k (not used at the moment)
903 %>
904 %> @retval w preprocessed angular velocity
905 %>
906 %> @note since a zero angular velocity leads to zeros in the F matrix
907 %> which leads to zero entries in the covariance matrix, the
908 %> differentiation cases are disabled. in some later implementations
909 %> (e.g. UKF or particle filter approach) this could be enabled again.
910 % ==================================================================== %
911 function w = getPreprocessedGyro(gyro_this, gyro_next, bias) %, zv)
912
913 arguments
914 gyro_this (3,1) double {mustBeNumeric}
915 gyro_next (3,1) double {mustBeNumeric}
916 bias (3,1) double {mustBeNumeric}
917 %zv (1,1) logical
918 end
919
920 % if zero velocity is detected, the gyro measurements are set to zero
921 %if zv
922 % w = [0;0;0];
923 %else
924 w = (gyro_this + gyro_next) / 2 - bias;
925 %end
926
927 end
928
929 % ==================================================================== %
930 %> @brief displays the results of the EKF1 filter
931 %>
932 %> this function displays the results of the EKF1 filter
933 %>
934 %> @param x_fp filtered state vector
935 %> @param x_bp smoothed state vector
936 %> @param zv zero velocity flag
937 %> @param options
938 %> @param options.monitor monitor to display the results
939 % ==================================================================== %
940 function displayResults(x_fp, x_bp, zv, options)
941
942 arguments
943 x_fp (13,:)
944 x_bp (13,:)
945 zv (1,:)
946 options.monitor = 1
947 end
948
949 % filter results
950 ori_filt = quaternion(x_fp(1:4,:)').rotvecd;
951 pos_filt = x_fp(8:10,:)';
952
953 % smoothing results
954 ori_smoo = quaternion(x_bp(1:4,:)').rotvecd;
955 pos_smoo = x_bp(8:10,:)';
956
957 disps = get(0, 'MonitorPositions');
958 f = figure;
959 f.Position = disps(options.monitor,:);
960 f.WindowState = 'maximized';
961
962 % x plot
963 pl(1) = subplot(3,3,1);
964 plot(pos_filt(:,1), 'DisplayName', 'Filter');
965 hold on;
966 plot(pos_smoo(:,1), 'DisplayName', 'Smoother', 'LineWidth',2);
967 hold off;
968 grid on;
969 xlabel("Sample");
970 ylabel("x [m]");
971
972 % y plot
973 pl(2) = subplot(3,3,2);
974 plot(pos_filt(:,2), 'DisplayName', 'Filter');
975 hold on;
976 plot(pos_smoo(:,2), 'DisplayName', 'Smoother', 'LineWidth',2);
977 hold off;
978 grid on;
979 xlabel("Sample");
980 ylabel("y");
981
982 % y,z plot
983 pl(3) = subplot(3,3,3);
984 plot(pos_filt(:,3), 'DisplayName', 'Filter');
985 hold on;
986 plot(pos_smoo(:,3), 'DisplayName', 'Smoother', 'LineWidth',2);
987 hold off;
988 grid on;
989 xlabel("Sample");
990 ylabel("z [m]");
991
992 % x,y plot (over two subplots)
993 pl(4) = subplot(3,3,[4,5]);
994
995 % filter path (thin line)
996 x = pos_filt(:,1)';
997 y = pos_filt(:,2)';
998 z = pos_filt(:,3)';
999 col = 1:width(x);
1000 surface([x;x], [y;y], [z;z], [col;col], "LineWidth",1, "EdgeColor","interp");
1001 colormap turbo;
1002 hold on;
1003
1004 % smooth path (fat line)
1005 x = pos_smoo(:,1)';
1006 y = pos_smoo(:,2)';
1007 z = pos_smoo(:,3)';
1008 col = 1:width(x);
1009 surface([x;x], [y;y], [z;z], [col;col], "LineWidth",3, "EdgeColor","interp");
1010 colormap turbo;
1011 grid on;
1012 xlabel("x position [m]")
1013 ylabel("y position [m]")
1014 axis equal;
1015
1016 % bias gyro plot
1017 pl(5) = subplot(3,3,6);
1018 yyaxis left;
1019 plot(x_fp(11:13,:)', 'DisplayName', 'Filter');
1020 ylabel("Bias [rad/s]");
1021 yyaxis right;
1022 plot(x_bp(11:13,:)', 'DisplayName', 'Smoother', 'LineWidth',2);
1023 ylabel("Bias [rad/s]");
1024 grid on;
1025 xlabel("Sample");
1026 title("Bias Gyroscope");
1027
1028 % orientation plot
1029 pl(7) = subplot(3,3,7);
1030 plot(ori_filt(:,1), 'DisplayName', 'Roll (filtered)');
1031 hold on;
1032 plot(ori_filt(:,2), 'DisplayName', 'Pitch (filtered)');
1033 plot(ori_smoo(:,1), 'LineWidth',2, 'DisplayName', 'Roll (smoothed)');
1034 plot(ori_smoo(:,2), 'LineWidth',2, 'DisplayName', 'Pitch (smoothed)');
1035 plot(ori_smoo(:,3), 'LineWidth',2, 'DisplayName', 'Yaw (smoothed)');
1036 hold off;
1037 grid on;
1038 xlabel("Sample");
1039 ylabel("Angle [°]");
1040 title("Orientation");
1041 legend("Location", "best");
1042
1043 % velocity plot
1044 pl(8) = subplot(3,3,8);
1045 plot(x_fp(5:7,:)', 'DisplayName', 'Filter');
1046 hold on;
1047 plot(x_bp(5:7,:)', 'DisplayName', 'Smoother', 'LineWidth',2);
1048 hold off;
1049 grid on;
1050 xlabel("Sample");
1051 ylabel("Velocity [m/s]");
1052 title("Velocity");
1053
1054 % zero velocity detection plot
1055 pl(9) = subplot(3,3,9);
1056 plot(zv, '.-', 'DisplayName', 'Zero Velocity Detection', 'MarkerSize', 10);
1057 ylim([-0.1, 1.1]);
1058 grid on;
1059 xlabel("Sample");
1060 ylabel("Zero Velocity Detection");
1061 title("Zero Velocity Detection");
1062
1063 % link axes
1064 linkaxes(pl([1:3,6:9]), 'x');
1065
1066 end
1067
1068 end
1069
1070end
EKF1 is an implementation of a state estimator for JUMP sensors.
Definition EKF1.m:36
function f_fun(in obj, in x_this, in z_this, in z_next, in zv)
calculates the state transition function for the EKF1 filter
function exportTuning(in obj, in filename)
exports the tuning parameters of the filter to a JSON file
function EKF1()
Constructor of the EKF1 class.
Property x_0
initial state vector (n x 1)
Definition EKF1.m:131
Property var_acc_xy
accelerometer variance, x- and y-axis (default 0.045 m/s^2)
Definition EKF1.m:191
Property P_0_p
initial position standard deviation (default 1e-7 m)
Definition EKF1.m:170
Property p_0
initial position
Definition EKF1.m:123
Property v_0
initial velocity
Definition EKF1.m:119
Property x_bp
state vector, backward, a posteriori (n x N)
Definition EKF1.m:105
Property sigma_w_p
angular velocity standard deviation (default 876e-6 rad/s)
Definition EKF1.m:248
Property q_0
initial irientation quaternion
Definition EKF1.m:115
static function getPreprocessedAcc(in acc_this, in acc_next, in q_this, in q_next, in zv)
calculates the preprocessed acceleration measurement
Property P_fp
state covariance matrix, forward, a posteriori (n x n x N)
Definition EKF1.m:145
static function getPreprocessedGyro(in gyro_this, in gyro_next, in bias)
calculates the preprocessed gyro measurement
Property x_fp
state vector, forward, a posteriori (n x N)
Definition EKF1.m:101
static function H_jac(in x)
calculates the jacobian of the measurement function from a given state vector x
function backward(in obj)
backward pass of the EKF1 filter
Constant Property m
Definition EKF1.m:43
function importTuning(in obj, in filename)
imports the tuning parameters of the filter from a JSON file
Property P_bp
state covariance matrix, backward, a posteriori (n x n x N)
Definition EKF1.m:149
Property z_gyr
gyroscope measurements (3 x N) [rad/s]
Definition EKF1.m:75
Property z_acc
accelerometer measurements (3 x N) [m/s^2]
Definition EKF1.m:65
Property P_0_v
initial velocity standard deviation (default 1e-7 m/s)
Definition EKF1.m:165
static function h_fun(in x)
calculates the measurement function from a given state vector
function run(in obj, in options)
filters and smooths the given data
Property var_pos_y
variance of y-position 'measurements' during ZUPTs (default 1e-6 m)
Definition EKF1.m:216
function forward(in obj)
forward pass of the EKF1 filter
Property var_acc_z
accelerometer variance, z-axis (default 0.07 m/s^2)
Definition EKF1.m:196
Property var_pos_x
variance of x-position 'measurements' during ZUPTs (default 1e-6 m)
Definition EKF1.m:211
Property P_0_b_w
initial gyroscope bias standard deviation (default 5e-3 rad/s)
Definition EKF1.m:175
Property zv
zero velocity flag (1 x N)
Definition EKF1.m:54
Property dt
sampling time (default 1/200 s)
Definition EKF1.m:50
Property var_a
acceleration variance (default 0.045 m/s^2)
Definition EKF1.m:253
Property var_yaw_pseudo
variance of yaw pseudo-measurements (default TODO:)
Definition EKF1.m:226
Property var_gyr
gyroscope variance (default 1.22e-3 rad/s)
Definition EKF1.m:206
Property x_fm
state vector, forward, a priori (n x N)
Definition EKF1.m:97
Property P_0_q
initial orientation standard deviation (default 5e-4)
Definition EKF1.m:160
Property P_fm
state covariance matrix, forward, a priori (n x n x N)
Definition EKF1.m:141
Property P_0
initial state covariance matrix (n x n)
Definition EKF1.m:180
Property var_vel_ZUPT
variance of velocity 'measurements' during ZUPTs (default 1e-6)
Definition EKF1.m:201
Constant Property n
Definition EKF1.m:41
Property R
measurement noise covariance matrix (m x m)
Definition EKF1.m:230
function F_jac(in obj, in w)
calculates the jacobian of the state transition function
Property z_yaw
yaw pseudo measurements (1 x N) [rad]
Definition EKF1.m:83
Property z_vel
velocity measurements (3 x N)
Definition EKF1.m:71
Property b_w_0
initial gyroscope bias
Definition EKF1.m:127
Property z
measurement vector (m x N)
Definition EKF1.m:87
Property var_pos_z
variance of z-position 'measurements' during ZUPTs (default 1e-6 m)
Definition EKF1.m:221
function Q_cov(in obj, in x)
calculates the process noise covariance matrix
Property var_b_w
gyroscope bias variance (default 1e-9 rad/s)
Definition EKF1.m:260
Property N
number of observations
Definition EKF1.m:62
Property z_pos
position pseudo measurements (3 x N) [m]
Definition EKF1.m:79
static function displayResults(in x_fp, in x_bp, in zv, in options)
displays the results of the EKF1 filter
handle class from MATLAB.
function f_fun(in x)
function F_jac(in x, in dt)
function Q_cov(in x, in dt, in zero_velocity)
function getPreprocessedGyro(in gyro_this, in gyro_next, in bias, in zv)
function displayResults(in x_fp, in x_bp, in zv, in idx_at_turn, in options)
function getPreprocessedAcc(in acc_this, in acc_next, in bias, in sens, in q_this, in q_next, in zv)