StateHelper.cpp
Go to the documentation of this file.
1 /*
2  * OpenVINS: An Open Platform for Visual-Inertial Research
3  * Copyright (C) 2018-2023 Patrick Geneva
4  * Copyright (C) 2018-2023 Guoquan Huang
5  * Copyright (C) 2018-2023 OpenVINS Contributors
6  * Copyright (C) 2018-2019 Kevin Eckenhoff
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "StateHelper.h"
23 
24 #include "state/State.h"
25 
26 #include "types/Landmark.h"
27 #include "utils/colors.h"
28 #include "utils/print.h"
29 
30 #include <boost/math/distributions/chi_squared.hpp>
31 
32 using namespace ov_core;
33 using namespace ov_type;
34 using namespace ov_msckf;
35 
36 void StateHelper::EKFPropagation(std::shared_ptr<State> state, const std::vector<std::shared_ptr<Type>> &order_NEW,
37  const std::vector<std::shared_ptr<Type>> &order_OLD, const Eigen::MatrixXd &Phi,
38  const Eigen::MatrixXd &Q) {
39 
40  // We need at least one old and new variable
41  if (order_NEW.empty() || order_OLD.empty()) {
42  PRINT_ERROR(RED "StateHelper::EKFPropagation() - Called with empty variable arrays!\n" RESET);
43  std::exit(EXIT_FAILURE);
44  }
45 
46  // Loop through our Phi order and ensure that they are continuous in memory
47  int size_order_NEW = order_NEW.at(0)->size();
48  for (size_t i = 0; i < order_NEW.size() - 1; i++) {
49  if (order_NEW.at(i)->id() + order_NEW.at(i)->size() != order_NEW.at(i + 1)->id()) {
50  PRINT_ERROR(RED "StateHelper::EKFPropagation() - Called with non-contiguous state elements!\n" RESET);
52  RED "StateHelper::EKFPropagation() - This code only support a state transition which is in the same order as the state\n" RESET);
53  std::exit(EXIT_FAILURE);
54  }
55  size_order_NEW += order_NEW.at(i + 1)->size();
56  }
57 
58  // Size of the old phi matrix
59  int size_order_OLD = order_OLD.at(0)->size();
60  for (size_t i = 0; i < order_OLD.size() - 1; i++) {
61  size_order_OLD += order_OLD.at(i + 1)->size();
62  }
63 
64  // Assert that we have correct sizes
65  assert(size_order_NEW == Phi.rows());
66  assert(size_order_OLD == Phi.cols());
67  assert(size_order_NEW == Q.cols());
68  assert(size_order_NEW == Q.rows());
69 
70  // Get the location in small phi for each measuring variable
71  int current_it = 0;
72  std::vector<int> Phi_id;
73  for (const auto &var : order_OLD) {
74  Phi_id.push_back(current_it);
75  current_it += var->size();
76  }
77 
78  // Loop through all our old states and get the state transition times it
79  // Cov_PhiT = [ Pxx ] [ Phi' ]'
80  Eigen::MatrixXd Cov_PhiT = Eigen::MatrixXd::Zero(state->_Cov.rows(), Phi.rows());
81  for (size_t i = 0; i < order_OLD.size(); i++) {
82  std::shared_ptr<Type> var = order_OLD.at(i);
83  Cov_PhiT.noalias() +=
84  state->_Cov.block(0, var->id(), state->_Cov.rows(), var->size()) * Phi.block(0, Phi_id[i], Phi.rows(), var->size()).transpose();
85  }
86 
87  // Get Phi_NEW*Covariance*Phi_NEW^t + Q
88  Eigen::MatrixXd Phi_Cov_PhiT = Q.selfadjointView<Eigen::Upper>();
89  for (size_t i = 0; i < order_OLD.size(); i++) {
90  std::shared_ptr<Type> var = order_OLD.at(i);
91  Phi_Cov_PhiT.noalias() += Phi.block(0, Phi_id[i], Phi.rows(), var->size()) * Cov_PhiT.block(var->id(), 0, var->size(), Phi.rows());
92  }
93 
94  // We are good to go!
95  int start_id = order_NEW.at(0)->id();
96  int phi_size = Phi.rows();
97  int total_size = state->_Cov.rows();
98  state->_Cov.block(start_id, 0, phi_size, total_size) = Cov_PhiT.transpose();
99  state->_Cov.block(0, start_id, total_size, phi_size) = Cov_PhiT;
100  state->_Cov.block(start_id, start_id, phi_size, phi_size) = Phi_Cov_PhiT;
101 
102  // We should check if we are not positive semi-definitate (i.e. negative diagionals is not s.p.d)
103  Eigen::VectorXd diags = state->_Cov.diagonal();
104  bool found_neg = false;
105  for (int i = 0; i < diags.rows(); i++) {
106  if (diags(i) < 0.0) {
107  PRINT_WARNING(RED "StateHelper::EKFPropagation() - diagonal at %d is %.2f\n" RESET, i, diags(i));
108  found_neg = true;
109  }
110  }
111  if (found_neg) {
112  std::exit(EXIT_FAILURE);
113  }
114 }
115 
116 void StateHelper::EKFUpdate(std::shared_ptr<State> state, const std::vector<std::shared_ptr<Type>> &H_order, const Eigen::MatrixXd &H,
117  const Eigen::VectorXd &res, const Eigen::MatrixXd &R) {
118 
119  //==========================================================
120  //==========================================================
121  // Part of the Kalman Gain K = (P*H^T)*S^{-1} = M*S^{-1}
122  assert(res.rows() == R.rows());
123  assert(H.rows() == res.rows());
124  Eigen::MatrixXd M_a = Eigen::MatrixXd::Zero(state->_Cov.rows(), res.rows());
125 
126  // Get the location in small jacobian for each measuring variable
127  int current_it = 0;
128  std::vector<int> H_id;
129  for (const auto &meas_var : H_order) {
130  H_id.push_back(current_it);
131  current_it += meas_var->size();
132  }
133 
134  //==========================================================
135  //==========================================================
136  // For each active variable find its M = P*H^T
137  for (const auto &var : state->_variables) {
138  // Sum up effect of each subjacobian = K_i= \sum_m (P_im Hm^T)
139  Eigen::MatrixXd M_i = Eigen::MatrixXd::Zero(var->size(), res.rows());
140  for (size_t i = 0; i < H_order.size(); i++) {
141  std::shared_ptr<Type> meas_var = H_order[i];
142  M_i.noalias() += state->_Cov.block(var->id(), meas_var->id(), var->size(), meas_var->size()) *
143  H.block(0, H_id[i], H.rows(), meas_var->size()).transpose();
144  }
145  M_a.block(var->id(), 0, var->size(), res.rows()) = M_i;
146  }
147 
148  //==========================================================
149  //==========================================================
150  // Get covariance of the involved terms
151  Eigen::MatrixXd P_small = StateHelper::get_marginal_covariance(state, H_order);
152 
153  // Residual covariance S = H*Cov*H' + R
154  Eigen::MatrixXd S(R.rows(), R.rows());
155  S.triangularView<Eigen::Upper>() = H * P_small * H.transpose();
156  S.triangularView<Eigen::Upper>() += R;
157  // Eigen::MatrixXd S = H * P_small * H.transpose() + R;
158 
159  // Invert our S (should we use a more stable method here??)
160  Eigen::MatrixXd Sinv = Eigen::MatrixXd::Identity(R.rows(), R.rows());
161  S.selfadjointView<Eigen::Upper>().llt().solveInPlace(Sinv);
162  Eigen::MatrixXd K = M_a * Sinv.selfadjointView<Eigen::Upper>();
163  // Eigen::MatrixXd K = M_a * S.inverse();
164 
165  // Update Covariance
166  state->_Cov.triangularView<Eigen::Upper>() -= K * M_a.transpose();
167  state->_Cov = state->_Cov.selfadjointView<Eigen::Upper>();
168  // Cov -= K * M_a.transpose();
169  // Cov = 0.5*(Cov+Cov.transpose());
170 
171  // We should check if we are not positive semi-definitate (i.e. negative diagionals is not s.p.d)
172  Eigen::VectorXd diags = state->_Cov.diagonal();
173  bool found_neg = false;
174  for (int i = 0; i < diags.rows(); i++) {
175  if (diags(i) < 0.0) {
176  PRINT_WARNING(RED "StateHelper::EKFUpdate() - diagonal at %d is %.2f\n" RESET, i, diags(i));
177  found_neg = true;
178  }
179  }
180  if (found_neg) {
181  std::exit(EXIT_FAILURE);
182  }
183 
184  // Calculate our delta and update all our active states
185  Eigen::VectorXd dx = K * res;
186  for (size_t i = 0; i < state->_variables.size(); i++) {
187  state->_variables.at(i)->update(dx.block(state->_variables.at(i)->id(), 0, state->_variables.at(i)->size(), 1));
188  }
189 
190  // If we are doing online intrinsic calibration we should update our camera objects
191  // NOTE: is this the best place to put this update logic??? probably..
192  if (state->_options.do_calib_camera_intrinsics) {
193  for (auto const &calib : state->_cam_intrinsics) {
194  state->_cam_intrinsics_cameras.at(calib.first)->set_value(calib.second->value());
195  }
196  }
197 }
198 
199 void StateHelper::set_initial_covariance(std::shared_ptr<State> state, const Eigen::MatrixXd &covariance,
200  const std::vector<std::shared_ptr<ov_type::Type>> &order) {
201 
202  // We need to loop through each element and overwrite the current covariance values
203  // For example consider the following:
204  // x = [ ori pos ] -> insert into -> x = [ ori bias pos ]
205  // P = [ P_oo P_op ] -> P = [ P_oo 0 P_op ]
206  // [ P_po P_pp ] [ 0 P* 0 ]
207  // [ P_po 0 P_pp ]
208  // The key assumption here is that the covariance is block diagonal (cross-terms zero with P* can be dense)
209  // This is normally the care on startup (for example between calibration and the initial state
210 
211  // For each variable, lets copy over all other variable cross terms
212  // Note: this copies over itself to when i_index=k_index
213  int i_index = 0;
214  for (size_t i = 0; i < order.size(); i++) {
215  int k_index = 0;
216  for (size_t k = 0; k < order.size(); k++) {
217  state->_Cov.block(order[i]->id(), order[k]->id(), order[i]->size(), order[k]->size()) =
218  covariance.block(i_index, k_index, order[i]->size(), order[k]->size());
219  k_index += order[k]->size();
220  }
221  i_index += order[i]->size();
222  }
223  state->_Cov = state->_Cov.selfadjointView<Eigen::Upper>();
224 }
225 
226 Eigen::MatrixXd StateHelper::get_marginal_covariance(std::shared_ptr<State> state,
227  const std::vector<std::shared_ptr<Type>> &small_variables) {
228 
229  // Calculate the marginal covariance size we need to make our matrix
230  int cov_size = 0;
231  for (size_t i = 0; i < small_variables.size(); i++) {
232  cov_size += small_variables[i]->size();
233  }
234 
235  // Construct our return covariance
236  Eigen::MatrixXd Small_cov = Eigen::MatrixXd::Zero(cov_size, cov_size);
237 
238  // For each variable, lets copy over all other variable cross terms
239  // Note: this copies over itself to when i_index=k_index
240  int i_index = 0;
241  for (size_t i = 0; i < small_variables.size(); i++) {
242  int k_index = 0;
243  for (size_t k = 0; k < small_variables.size(); k++) {
244  Small_cov.block(i_index, k_index, small_variables[i]->size(), small_variables[k]->size()) =
245  state->_Cov.block(small_variables[i]->id(), small_variables[k]->id(), small_variables[i]->size(), small_variables[k]->size());
246  k_index += small_variables[k]->size();
247  }
248  i_index += small_variables[i]->size();
249  }
250 
251  // Return the covariance
252  // Small_cov = 0.5*(Small_cov+Small_cov.transpose());
253  return Small_cov;
254 }
255 
256 Eigen::MatrixXd StateHelper::get_full_covariance(std::shared_ptr<State> state) {
257 
258  // Size of the covariance is the active
259  int cov_size = (int)state->_Cov.rows();
260 
261  // Construct our return covariance
262  Eigen::MatrixXd full_cov = Eigen::MatrixXd::Zero(cov_size, cov_size);
263 
264  // Copy in the active state elements
265  full_cov.block(0, 0, state->_Cov.rows(), state->_Cov.rows()) = state->_Cov;
266 
267  // Return the covariance
268  return full_cov;
269 }
270 
271 void StateHelper::marginalize(std::shared_ptr<State> state, std::shared_ptr<Type> marg) {
272 
273  // Check if the current state has the element we want to marginalize
274  if (std::find(state->_variables.begin(), state->_variables.end(), marg) == state->_variables.end()) {
275  PRINT_ERROR(RED "StateHelper::marginalize() - Called on variable that is not in the state\n" RESET);
276  PRINT_ERROR(RED "StateHelper::marginalize() - Marginalization, does NOT work on sub-variables yet...\n" RESET);
277  std::exit(EXIT_FAILURE);
278  }
279 
280  // Generic covariance has this form for x_1, x_m, x_2. If we want to remove x_m:
281  //
282  // P_(x_1,x_1) P(x_1,x_m) P(x_1,x_2)
283  // P_(x_m,x_1) P(x_m,x_m) P(x_m,x_2)
284  // P_(x_2,x_1) P(x_2,x_m) P(x_2,x_2)
285  //
286  // to
287  //
288  // P_(x_1,x_1) P(x_1,x_2)
289  // P_(x_2,x_1) P(x_2,x_2)
290  //
291  // i.e. x_1 goes from 0 to marg_id, x_2 goes from marg_id+marg_size to Cov.rows() in the original covariance
292 
293  int marg_size = marg->size();
294  int marg_id = marg->id();
295  int x2_size = (int)state->_Cov.rows() - marg_id - marg_size;
296 
297  Eigen::MatrixXd Cov_new(state->_Cov.rows() - marg_size, state->_Cov.rows() - marg_size);
298 
299  // P_(x_1,x_1)
300  Cov_new.block(0, 0, marg_id, marg_id) = state->_Cov.block(0, 0, marg_id, marg_id);
301 
302  // P_(x_1,x_2)
303  Cov_new.block(0, marg_id, marg_id, x2_size) = state->_Cov.block(0, marg_id + marg_size, marg_id, x2_size);
304 
305  // P_(x_2,x_1)
306  Cov_new.block(marg_id, 0, x2_size, marg_id) = Cov_new.block(0, marg_id, marg_id, x2_size).transpose();
307 
308  // P(x_2,x_2)
309  Cov_new.block(marg_id, marg_id, x2_size, x2_size) = state->_Cov.block(marg_id + marg_size, marg_id + marg_size, x2_size, x2_size);
310 
311  // Now set new covariance
312  // state->_Cov.resize(Cov_new.rows(),Cov_new.cols());
313  state->_Cov = Cov_new;
314  // state->Cov() = 0.5*(Cov_new+Cov_new.transpose());
315  assert(state->_Cov.rows() == Cov_new.rows());
316 
317  // Now we keep the remaining variables and update their ordering
318  // Note: DOES NOT SUPPORT MARGINALIZING SUBVARIABLES YET!!!!!!!
319  std::vector<std::shared_ptr<Type>> remaining_variables;
320  for (size_t i = 0; i < state->_variables.size(); i++) {
321  // Only keep non-marginal states
322  if (state->_variables.at(i) != marg) {
323  if (state->_variables.at(i)->id() > marg_id) {
324  // If the variable is "beyond" the marginal one in ordering, need to "move it forward"
325  state->_variables.at(i)->set_local_id(state->_variables.at(i)->id() - marg_size);
326  }
327  remaining_variables.push_back(state->_variables.at(i));
328  }
329  }
330 
331  // Delete the old state variable to free up its memory
332  // NOTE: we don't need to do this any more since our variable is a shared ptr
333  // NOTE: thus this is automatically managed, but this allows outside references to keep the old variable
334  // delete marg;
335  marg->set_local_id(-1);
336 
337  // Now set variables as the remaining ones
338  state->_variables = remaining_variables;
339 }
340 
341 std::shared_ptr<Type> StateHelper::clone(std::shared_ptr<State> state, std::shared_ptr<Type> variable_to_clone) {
342 
343  // Get total size of new cloned variables, and the old covariance size
344  int total_size = variable_to_clone->size();
345  int old_size = (int)state->_Cov.rows();
346  int new_loc = (int)state->_Cov.rows();
347 
348  // Resize both our covariance to the new size
349  state->_Cov.conservativeResizeLike(Eigen::MatrixXd::Zero(old_size + total_size, old_size + total_size));
350 
351  // What is the new state, and variable we inserted
352  const std::vector<std::shared_ptr<Type>> new_variables = state->_variables;
353  std::shared_ptr<Type> new_clone = nullptr;
354 
355  // Loop through all variables, and find the variable that we are going to clone
356  for (size_t k = 0; k < state->_variables.size(); k++) {
357 
358  // Skip this if it is not the same
359  // First check if the top level variable is the same, then check the sub-variables
360  std::shared_ptr<Type> type_check = state->_variables.at(k)->check_if_subvariable(variable_to_clone);
361  if (state->_variables.at(k) == variable_to_clone) {
362  type_check = state->_variables.at(k);
363  } else if (type_check != variable_to_clone) {
364  continue;
365  }
366 
367  // So we will clone this one
368  int old_loc = type_check->id();
369 
370  // Copy the covariance elements
371  state->_Cov.block(new_loc, new_loc, total_size, total_size) = state->_Cov.block(old_loc, old_loc, total_size, total_size);
372  state->_Cov.block(0, new_loc, old_size, total_size) = state->_Cov.block(0, old_loc, old_size, total_size);
373  state->_Cov.block(new_loc, 0, total_size, old_size) = state->_Cov.block(old_loc, 0, total_size, old_size);
374 
375  // Create clone from the type being cloned
376  new_clone = type_check->clone();
377  new_clone->set_local_id(new_loc);
378  break;
379  }
380 
381  // Check if the current state has this variable
382  if (new_clone == nullptr) {
383  PRINT_ERROR(RED "StateHelper::clone() - Called on variable is not in the state\n" RESET);
384  PRINT_ERROR(RED "StateHelper::clone() - Ensure that the variable specified is a variable, or sub-variable..\n" RESET);
385  std::exit(EXIT_FAILURE);
386  }
387 
388  // Add to variable list and return
389  state->_variables.push_back(new_clone);
390  return new_clone;
391 }
392 
393 bool StateHelper::initialize(std::shared_ptr<State> state, std::shared_ptr<Type> new_variable,
394  const std::vector<std::shared_ptr<Type>> &H_order, Eigen::MatrixXd &H_R, Eigen::MatrixXd &H_L,
395  Eigen::MatrixXd &R, Eigen::VectorXd &res, double chi_2_mult) {
396 
397  // Check that this new variable is not already initialized
398  if (std::find(state->_variables.begin(), state->_variables.end(), new_variable) != state->_variables.end()) {
399  PRINT_ERROR("StateHelper::initialize_invertible() - Called on variable that is already in the state\n");
400  PRINT_ERROR("StateHelper::initialize_invertible() - Found this variable at %d in covariance\n", new_variable->id());
401  std::exit(EXIT_FAILURE);
402  }
403 
404  // Check that we have isotropic noise (i.e. is diagonal and all the same value)
405  // TODO: can we simplify this so it doesn't take as much time?
406  assert(R.rows() == R.cols());
407  assert(R.rows() > 0);
408  for (int r = 0; r < R.rows(); r++) {
409  for (int c = 0; c < R.cols(); c++) {
410  if (r == c && R(0, 0) != R(r, c)) {
411  PRINT_ERROR(RED "StateHelper::initialize() - Your noise is not isotropic!\n" RESET);
412  PRINT_ERROR(RED "StateHelper::initialize() - Found a value of %.2f verses value of %.2f\n" RESET, R(r, c), R(0, 0));
413  std::exit(EXIT_FAILURE);
414  } else if (r != c && R(r, c) != 0.0) {
415  PRINT_ERROR(RED "StateHelper::initialize() - Your noise is not diagonal!\n" RESET);
416  PRINT_ERROR(RED "StateHelper::initialize() - Found a value of %.2f at row %d and column %d\n" RESET, R(r, c), r, c);
417  std::exit(EXIT_FAILURE);
418  }
419  }
420  }
421 
422  //==========================================================
423  //==========================================================
424  // First we perform QR givens to seperate the system
425  // The top will be a system that depends on the new state, while the bottom does not
426  size_t new_var_size = new_variable->size();
427  assert((int)new_var_size == H_L.cols());
428 
429  Eigen::JacobiRotation<double> tempHo_GR;
430  for (int n = 0; n < H_L.cols(); ++n) {
431  for (int m = (int)H_L.rows() - 1; m > n; m--) {
432  // Givens matrix G
433  tempHo_GR.makeGivens(H_L(m - 1, n), H_L(m, n));
434  // Multiply G to the corresponding lines (m-1,m) in each matrix
435  // Note: we only apply G to the nonzero cols [n:Ho.cols()-n-1], while
436  // it is equivalent to applying G to the entire cols [0:Ho.cols()-1].
437  (H_L.block(m - 1, n, 2, H_L.cols() - n)).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
438  (res.block(m - 1, 0, 2, 1)).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
439  (H_R.block(m - 1, 0, 2, H_R.cols())).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
440  }
441  }
442 
443  // Separate into initializing and updating portions
444  // 1. Invertible initializing system
445  Eigen::MatrixXd Hxinit = H_R.block(0, 0, new_var_size, H_R.cols());
446  Eigen::MatrixXd H_finit = H_L.block(0, 0, new_var_size, new_var_size);
447  Eigen::VectorXd resinit = res.block(0, 0, new_var_size, 1);
448  Eigen::MatrixXd Rinit = R.block(0, 0, new_var_size, new_var_size);
449 
450  // 2. Nullspace projected updating system
451  Eigen::MatrixXd Hup = H_R.block(new_var_size, 0, H_R.rows() - new_var_size, H_R.cols());
452  Eigen::VectorXd resup = res.block(new_var_size, 0, res.rows() - new_var_size, 1);
453  Eigen::MatrixXd Rup = R.block(new_var_size, new_var_size, R.rows() - new_var_size, R.rows() - new_var_size);
454 
455  //==========================================================
456  //==========================================================
457 
458  // Do mahalanobis distance testing
459  Eigen::MatrixXd P_up = get_marginal_covariance(state, H_order);
460  assert(Rup.rows() == Hup.rows());
461  assert(Hup.cols() == P_up.cols());
462  Eigen::MatrixXd S = Hup * P_up * Hup.transpose() + Rup;
463  double chi2 = resup.dot(S.llt().solve(resup));
464 
465  // Get what our threshold should be
466  boost::math::chi_squared chi_squared_dist(res.rows());
467  double chi2_check = boost::math::quantile(chi_squared_dist, 0.95);
468  if (chi2 > chi_2_mult * chi2_check) {
469  return false;
470  }
471 
472  //==========================================================
473  //==========================================================
474  // Finally, initialize it in our state
475  StateHelper::initialize_invertible(state, new_variable, H_order, Hxinit, H_finit, Rinit, resinit);
476 
477  // Update with updating portion
478  if (Hup.rows() > 0) {
479  StateHelper::EKFUpdate(state, H_order, Hup, resup, Rup);
480  }
481  return true;
482 }
483 
484 void StateHelper::initialize_invertible(std::shared_ptr<State> state, std::shared_ptr<Type> new_variable,
485  const std::vector<std::shared_ptr<Type>> &H_order, const Eigen::MatrixXd &H_R,
486  const Eigen::MatrixXd &H_L, const Eigen::MatrixXd &R, const Eigen::VectorXd &res) {
487 
488  // Check that this new variable is not already initialized
489  if (std::find(state->_variables.begin(), state->_variables.end(), new_variable) != state->_variables.end()) {
490  PRINT_ERROR("StateHelper::initialize_invertible() - Called on variable that is already in the state\n");
491  PRINT_ERROR("StateHelper::initialize_invertible() - Found this variable at %d in covariance\n", new_variable->id());
492  std::exit(EXIT_FAILURE);
493  }
494 
495  // Check that we have isotropic noise (i.e. is diagonal and all the same value)
496  // TODO: can we simplify this so it doesn't take as much time?
497  assert(R.rows() == R.cols());
498  assert(R.rows() > 0);
499  for (int r = 0; r < R.rows(); r++) {
500  for (int c = 0; c < R.cols(); c++) {
501  if (r == c && R(0, 0) != R(r, c)) {
502  PRINT_ERROR(RED "StateHelper::initialize_invertible() - Your noise is not isotropic!\n" RESET);
503  PRINT_ERROR(RED "StateHelper::initialize_invertible() - Found a value of %.2f verses value of %.2f\n" RESET, R(r, c), R(0, 0));
504  std::exit(EXIT_FAILURE);
505  } else if (r != c && R(r, c) != 0.0) {
506  PRINT_ERROR(RED "StateHelper::initialize_invertible() - Your noise is not diagonal!\n" RESET);
507  PRINT_ERROR(RED "StateHelper::initialize_invertible() - Found a value of %.2f at row %d and column %d\n" RESET, R(r, c), r, c);
508  std::exit(EXIT_FAILURE);
509  }
510  }
511  }
512 
513  //==========================================================
514  //==========================================================
515  // Part of the Kalman Gain K = (P*H^T)*S^{-1} = M*S^{-1}
516  assert(res.rows() == R.rows());
517  assert(H_L.rows() == res.rows());
518  assert(H_L.rows() == H_R.rows());
519  Eigen::MatrixXd M_a = Eigen::MatrixXd::Zero(state->_Cov.rows(), res.rows());
520 
521  // Get the location in small jacobian for each measuring variable
522  int current_it = 0;
523  std::vector<int> H_id;
524  for (const auto &meas_var : H_order) {
525  H_id.push_back(current_it);
526  current_it += meas_var->size();
527  }
528 
529  //==========================================================
530  //==========================================================
531  // For each active variable find its M = P*H^T
532  for (const auto &var : state->_variables) {
533  // Sum up effect of each subjacobian= K_i= \sum_m (P_im Hm^T)
534  Eigen::MatrixXd M_i = Eigen::MatrixXd::Zero(var->size(), res.rows());
535  for (size_t i = 0; i < H_order.size(); i++) {
536  std::shared_ptr<Type> meas_var = H_order.at(i);
537  M_i += state->_Cov.block(var->id(), meas_var->id(), var->size(), meas_var->size()) *
538  H_R.block(0, H_id[i], H_R.rows(), meas_var->size()).transpose();
539  }
540  M_a.block(var->id(), 0, var->size(), res.rows()) = M_i;
541  }
542 
543  //==========================================================
544  //==========================================================
545  // Get covariance of this small jacobian
546  Eigen::MatrixXd P_small = StateHelper::get_marginal_covariance(state, H_order);
547 
548  // M = H_R*Cov*H_R' + R
549  Eigen::MatrixXd M(H_R.rows(), H_R.rows());
550  M.triangularView<Eigen::Upper>() = H_R * P_small * H_R.transpose();
551  M.triangularView<Eigen::Upper>() += R;
552 
553  // Covariance of the variable/landmark that will be initialized
554  assert(H_L.rows() == H_L.cols());
555  assert(H_L.rows() == new_variable->size());
556  Eigen::MatrixXd H_Linv = H_L.inverse();
557  Eigen::MatrixXd P_LL = H_Linv * M.selfadjointView<Eigen::Upper>() * H_Linv.transpose();
558 
559  // Augment the covariance matrix
560  size_t oldSize = state->_Cov.rows();
561  state->_Cov.conservativeResizeLike(Eigen::MatrixXd::Zero(oldSize + new_variable->size(), oldSize + new_variable->size()));
562  state->_Cov.block(0, oldSize, oldSize, new_variable->size()).noalias() = -M_a * H_Linv.transpose();
563  state->_Cov.block(oldSize, 0, new_variable->size(), oldSize) = state->_Cov.block(0, oldSize, oldSize, new_variable->size()).transpose();
564  state->_Cov.block(oldSize, oldSize, new_variable->size(), new_variable->size()) = P_LL;
565 
566  // Update the variable that will be initialized (invertible systems can only update the new variable).
567  // However this update should be almost zero if we already used a conditional Gauss-Newton to solve for the initial estimate
568  new_variable->update(H_Linv * res);
569 
570  // Now collect results, and add it to the state variables
571  new_variable->set_local_id(oldSize);
572  state->_variables.push_back(new_variable);
573 
574  // std::stringstream ss;
575  // ss << new_variable->id() << " init dx = " << (H_Linv * res).transpose() << std::endl;
576  // PRINT_DEBUG(ss.str().c_str());
577 }
578 
579 void StateHelper::augment_clone(std::shared_ptr<State> state, Eigen::Matrix<double, 3, 1> last_w) {
580 
581  // We can't insert a clone that occured at the same timestamp!
582  if (state->_clones_IMU.find(state->_timestamp) != state->_clones_IMU.end()) {
583  PRINT_ERROR(RED "TRIED TO INSERT A CLONE AT THE SAME TIME AS AN EXISTING CLONE, EXITING!#!@#!@#\n" RESET);
584  std::exit(EXIT_FAILURE);
585  }
586 
587  // Call on our cloner and add it to our vector of types
588  // NOTE: this will clone the clone pose to the END of the covariance...
589  std::shared_ptr<Type> posetemp = StateHelper::clone(state, state->_imu->pose());
590 
591  // Cast to a JPL pose type, check if valid
592  std::shared_ptr<PoseJPL> pose = std::dynamic_pointer_cast<PoseJPL>(posetemp);
593  if (pose == nullptr) {
594  PRINT_ERROR(RED "INVALID OBJECT RETURNED FROM STATEHELPER CLONE, EXITING!#!@#!@#\n" RESET);
595  std::exit(EXIT_FAILURE);
596  }
597 
598  // Append the new clone to our clone vector
599  state->_clones_IMU[state->_timestamp] = pose;
600 
601  // If we are doing time calibration, then our clones are a function of the time offset
602  // Logic is based on Mingyang Li and Anastasios I. Mourikis paper:
603  // http://journals.sagepub.com/doi/pdf/10.1177/0278364913515286
604  if (state->_options.do_calib_camera_timeoffset) {
605  // Jacobian to augment by
606  Eigen::Matrix<double, 6, 1> dnc_dt = Eigen::MatrixXd::Zero(6, 1);
607  dnc_dt.block(0, 0, 3, 1) = last_w;
608  dnc_dt.block(3, 0, 3, 1) = state->_imu->vel();
609  // Augment covariance with time offset Jacobian
610  // TODO: replace this with a call to the EKFPropagate function instead....
611  state->_Cov.block(0, pose->id(), state->_Cov.rows(), 6) +=
612  state->_Cov.block(0, state->_calib_dt_CAMtoIMU->id(), state->_Cov.rows(), 1) * dnc_dt.transpose();
613  state->_Cov.block(pose->id(), 0, 6, state->_Cov.rows()) +=
614  dnc_dt * state->_Cov.block(state->_calib_dt_CAMtoIMU->id(), 0, 1, state->_Cov.rows());
615  }
616 }
617 
618 void StateHelper::marginalize_old_clone(std::shared_ptr<State> state) {
619  if ((int)state->_clones_IMU.size() > state->_options.max_clone_size) {
620  double marginal_time = state->margtimestep();
621  // Lock the mutex to avoid deleting any elements from _clones_IMU while accessing it from other threads
622  std::lock_guard<std::mutex> lock(state->_mutex_state);
623  assert(marginal_time != INFINITY);
624  StateHelper::marginalize(state, state->_clones_IMU.at(marginal_time));
625  // Note that the marginalizer should have already deleted the clone
626  // Thus we just need to remove the pointer to it from our state
627  state->_clones_IMU.erase(marginal_time);
628  }
629 }
630 
631 void StateHelper::marginalize_slam(std::shared_ptr<State> state) {
632  // Remove SLAM features that have their marginalization flag set
633  // We also check that we do not remove any aruoctag landmarks
634  int ct_marginalized = 0;
635  auto it0 = state->_features_SLAM.begin();
636  while (it0 != state->_features_SLAM.end()) {
637  if ((*it0).second->should_marg && (int)(*it0).first > 4 * state->_options.max_aruco_features) {
638  StateHelper::marginalize(state, (*it0).second);
639  it0 = state->_features_SLAM.erase(it0);
640  ct_marginalized++;
641  } else {
642  it0++;
643  }
644  }
645 }
ov_msckf
Extended Kalman Filter estimator.
Definition: VioManager.h:46
PRINT_ERROR
#define PRINT_ERROR(x...)
StateHelper.h
print.h
colors.h
Landmark.h
ov_type
RESET
#define RESET
State.h
ov_core
RED
RED
PRINT_WARNING
#define PRINT_WARNING(x...)


ov_msckf
Author(s): Patrick Geneva , Kevin Eckenhoff , Guoquan Huang
autogenerated on Mon Dec 16 2024 03:06:54