586 lines
19 KiB
C++
586 lines
19 KiB
C++
|
#pragma once
|
||
|
|
||
|
#include <fstream>
|
||
|
#include <mutex>
|
||
|
|
||
|
#include <basalt/utils/ba_utils.h>
|
||
|
#include <Eigen/Dense>
|
||
|
#include <Eigen/Sparse>
|
||
|
#include <basalt/linearization/landmark_block.hpp>
|
||
|
#include <basalt/utils/sophus_utils.hpp>
|
||
|
|
||
|
namespace basalt {
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
class LandmarkBlockAbsDynamic : public LandmarkBlock<Scalar> {
|
||
|
public:
|
||
|
using Options = typename LandmarkBlock<Scalar>::Options;
|
||
|
using State = typename LandmarkBlock<Scalar>::State;
|
||
|
|
||
|
inline bool isNumericalFailure() const override {
|
||
|
return state == State::NumericalFailure;
|
||
|
}
|
||
|
|
||
|
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
|
||
|
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
|
||
|
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
|
||
|
|
||
|
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
|
||
|
|
||
|
using Mat36 = Eigen::Matrix<Scalar, 3, 6>;
|
||
|
|
||
|
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
|
||
|
using RowMatX =
|
||
|
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
|
||
|
|
||
|
virtual inline void allocateLandmark(
|
||
|
Keypoint<Scalar>& lm,
|
||
|
const Eigen::aligned_unordered_map<std::pair<TimeCamId, TimeCamId>,
|
||
|
RelPoseLin<Scalar>>& relative_pose_lin,
|
||
|
const Calibration<Scalar>& calib, const AbsOrderMap& aom,
|
||
|
const Options& options,
|
||
|
const std::map<TimeCamId, size_t>* rel_order = nullptr) override {
|
||
|
// some of the logic assumes the members are at their initial values
|
||
|
BASALT_ASSERT(state == State::Uninitialized);
|
||
|
|
||
|
UNUSED(rel_order);
|
||
|
|
||
|
lm_ptr = &lm;
|
||
|
options_ = &options;
|
||
|
calib_ = &calib;
|
||
|
|
||
|
// TODO: consider for VIO that we have a lot of 0 columns if we just use aom
|
||
|
// --> add option to use different AOM with reduced size and/or just
|
||
|
// involved poses --> when accumulating results, check which case we have;
|
||
|
// if both aom are identical, we don't have to do block-wise operations.
|
||
|
aom_ = &aom;
|
||
|
|
||
|
pose_lin_vec.clear();
|
||
|
pose_lin_vec.reserve(lm.obs.size());
|
||
|
pose_tcid_vec.clear();
|
||
|
pose_tcid_vec.reserve(lm.obs.size());
|
||
|
|
||
|
// LMBs without host frame should not be created
|
||
|
BASALT_ASSERT(aom.abs_order_map.count(lm.host_kf_id.frame_id) > 0);
|
||
|
|
||
|
for (const auto& [tcid_t, pos] : lm.obs) {
|
||
|
size_t i = pose_lin_vec.size();
|
||
|
|
||
|
auto it = relative_pose_lin.find(std::make_pair(lm.host_kf_id, tcid_t));
|
||
|
BASALT_ASSERT(it != relative_pose_lin.end());
|
||
|
|
||
|
if (aom.abs_order_map.count(tcid_t.frame_id) > 0) {
|
||
|
pose_lin_vec.push_back(&it->second);
|
||
|
} else {
|
||
|
// Observation droped for marginalization
|
||
|
pose_lin_vec.push_back(nullptr);
|
||
|
}
|
||
|
pose_tcid_vec.push_back(&it->first);
|
||
|
|
||
|
res_idx_by_abs_pose_[it->first.first.frame_id].insert(i); // host
|
||
|
res_idx_by_abs_pose_[it->first.second.frame_id].insert(i); // target
|
||
|
}
|
||
|
|
||
|
// number of pose-jacobian columns is determined by oam
|
||
|
padding_idx = aom_->total_size;
|
||
|
|
||
|
num_rows = pose_lin_vec.size() * 2 + 3; // residuals and lm damping
|
||
|
|
||
|
size_t pad = padding_idx % 4;
|
||
|
if (pad != 0) {
|
||
|
padding_size = 4 - pad;
|
||
|
}
|
||
|
|
||
|
lm_idx = padding_idx + padding_size;
|
||
|
res_idx = lm_idx + 3;
|
||
|
num_cols = res_idx + 1;
|
||
|
|
||
|
// number of columns should now be multiple of 4 for good memory alignment
|
||
|
// TODO: test extending this to 8 --> 32byte alignment for float?
|
||
|
BASALT_ASSERT(num_cols % 4 == 0);
|
||
|
|
||
|
storage.resize(num_rows, num_cols);
|
||
|
|
||
|
damping_rotations.clear();
|
||
|
damping_rotations.reserve(6);
|
||
|
|
||
|
state = State::Allocated;
|
||
|
}
|
||
|
|
||
|
// may set state to NumericalFailure --> linearization at this state is
|
||
|
// unusable. Numeric check is only performed for residuals that were
|
||
|
// considered to be used (valid), which depends on
|
||
|
// use_valid_projections_only setting.
|
||
|
virtual inline Scalar linearizeLandmark() override {
|
||
|
BASALT_ASSERT(state == State::Allocated ||
|
||
|
state == State::NumericalFailure ||
|
||
|
state == State::Linearized || state == State::Marginalized);
|
||
|
|
||
|
// storage.setZero(num_rows, num_cols);
|
||
|
storage.setZero();
|
||
|
damping_rotations.clear();
|
||
|
damping_rotations.reserve(6);
|
||
|
|
||
|
bool numerically_valid = true;
|
||
|
|
||
|
Scalar error_sum = 0;
|
||
|
|
||
|
size_t i = 0;
|
||
|
for (const auto& [tcid_t, obs] : lm_ptr->obs) {
|
||
|
std::visit(
|
||
|
[&, obs = obs](const auto& cam) {
|
||
|
// TODO: The pose_lin_vec[i] == nullptr is intended to deal with
|
||
|
// dropped measurements during marginalization. However, dropped
|
||
|
// measurements should only occur for the remaining frames, not for
|
||
|
// the marginalized frames. Maybe these are observations bewtween
|
||
|
// two marginalized frames, if more than one is marginalized at the
|
||
|
// same time? But those we would not have to drop... Double check if
|
||
|
// and when this happens and possibly resolve by fixing handling
|
||
|
// here, or else updating the measurements in lmdb before calling
|
||
|
// linearization. Otherwise, check where else we need a `if
|
||
|
// (pose_lin_vec[i])` check or `pose_lin_vec[i] != nullptr` assert
|
||
|
// in this class.
|
||
|
|
||
|
if (pose_lin_vec[i]) {
|
||
|
size_t obs_idx = i * 2;
|
||
|
size_t abs_h_idx =
|
||
|
aom_->abs_order_map.at(pose_tcid_vec[i]->first.frame_id)
|
||
|
.first;
|
||
|
size_t abs_t_idx =
|
||
|
aom_->abs_order_map.at(pose_tcid_vec[i]->second.frame_id)
|
||
|
.first;
|
||
|
|
||
|
Vec2 res;
|
||
|
Eigen::Matrix<Scalar, 2, POSE_SIZE> d_res_d_xi;
|
||
|
Eigen::Matrix<Scalar, 2, 3> d_res_d_p;
|
||
|
|
||
|
using CamT = std::decay_t<decltype(cam)>;
|
||
|
bool valid = linearizePoint<Scalar, CamT>(
|
||
|
obs, *lm_ptr, pose_lin_vec[i]->T_t_h, cam, res, &d_res_d_xi,
|
||
|
&d_res_d_p);
|
||
|
|
||
|
if (!options_->use_valid_projections_only || valid) {
|
||
|
numerically_valid = numerically_valid &&
|
||
|
d_res_d_xi.array().isFinite().all() &&
|
||
|
d_res_d_p.array().isFinite().all();
|
||
|
|
||
|
const Scalar res_squared = res.squaredNorm();
|
||
|
const auto [weighted_error, weight] =
|
||
|
compute_error_weight(res_squared);
|
||
|
const Scalar sqrt_weight =
|
||
|
std::sqrt(weight) / options_->obs_std_dev;
|
||
|
|
||
|
error_sum += weighted_error /
|
||
|
(options_->obs_std_dev * options_->obs_std_dev);
|
||
|
|
||
|
storage.template block<2, 3>(obs_idx, lm_idx) =
|
||
|
sqrt_weight * d_res_d_p;
|
||
|
storage.template block<2, 1>(obs_idx, res_idx) =
|
||
|
sqrt_weight * res;
|
||
|
|
||
|
d_res_d_xi *= sqrt_weight;
|
||
|
storage.template block<2, 6>(obs_idx, abs_h_idx) +=
|
||
|
d_res_d_xi * pose_lin_vec[i]->d_rel_d_h;
|
||
|
storage.template block<2, 6>(obs_idx, abs_t_idx) +=
|
||
|
d_res_d_xi * pose_lin_vec[i]->d_rel_d_t;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i++;
|
||
|
},
|
||
|
calib_->intrinsics[tcid_t.cam_id].variant);
|
||
|
}
|
||
|
|
||
|
if (numerically_valid) {
|
||
|
state = State::Linearized;
|
||
|
} else {
|
||
|
state = State::NumericalFailure;
|
||
|
}
|
||
|
|
||
|
return error_sum;
|
||
|
}
|
||
|
|
||
|
virtual inline void performQR() override {
|
||
|
BASALT_ASSERT(state == State::Linearized);
|
||
|
|
||
|
// Since we use dense matrices Householder QR might be better:
|
||
|
// https://mathoverflow.net/questions/227543/why-householder-reflection-is-better-than-givens-rotation-in-dense-linear-algebr
|
||
|
|
||
|
if (options_->use_householder) {
|
||
|
performQRHouseholder();
|
||
|
} else {
|
||
|
performQRGivens();
|
||
|
}
|
||
|
|
||
|
state = State::Marginalized;
|
||
|
}
|
||
|
|
||
|
// Sets damping and maintains upper triangular matrix for landmarks.
|
||
|
virtual inline void setLandmarkDamping(Scalar lambda) override {
|
||
|
BASALT_ASSERT(state == State::Marginalized);
|
||
|
BASALT_ASSERT(lambda >= 0);
|
||
|
|
||
|
if (hasLandmarkDamping()) {
|
||
|
BASALT_ASSERT(damping_rotations.size() == 6);
|
||
|
|
||
|
// undo dampening
|
||
|
for (int n = 2; n >= 0; n--) {
|
||
|
for (int m = n; m >= 0; m--) {
|
||
|
storage.applyOnTheLeft(num_rows - 3 + n - m, n,
|
||
|
damping_rotations.back().adjoint());
|
||
|
damping_rotations.pop_back();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (lambda == 0) {
|
||
|
storage.template block<3, 3>(num_rows - 3, lm_idx).diagonal().setZero();
|
||
|
} else {
|
||
|
BASALT_ASSERT(Jl_col_scale.array().isFinite().all());
|
||
|
|
||
|
storage.template block<3, 3>(num_rows - 3, lm_idx)
|
||
|
.diagonal()
|
||
|
.setConstant(sqrt(lambda));
|
||
|
|
||
|
BASALT_ASSERT(damping_rotations.empty());
|
||
|
|
||
|
// apply dampening and remember rotations to undo
|
||
|
for (int n = 0; n < 3; n++) {
|
||
|
for (int m = 0; m <= n; m++) {
|
||
|
damping_rotations.emplace_back();
|
||
|
damping_rotations.back().makeGivens(
|
||
|
storage(n, lm_idx + n),
|
||
|
storage(num_rows - 3 + n - m, lm_idx + n));
|
||
|
storage.applyOnTheLeft(num_rows - 3 + n - m, n,
|
||
|
damping_rotations.back());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// lambda < 0 means computing exact model cost change
|
||
|
virtual inline void backSubstitute(const VecX& pose_inc,
|
||
|
Scalar& l_diff) override {
|
||
|
BASALT_ASSERT(state == State::Marginalized);
|
||
|
|
||
|
// For now we include all columns in LMB
|
||
|
BASALT_ASSERT(pose_inc.size() == signed_cast(padding_idx));
|
||
|
|
||
|
const auto Q1Jl = storage.template block<3, 3>(0, lm_idx)
|
||
|
.template triangularView<Eigen::Upper>();
|
||
|
|
||
|
const auto Q1Jr = storage.col(res_idx).template head<3>();
|
||
|
const auto Q1Jp = storage.topLeftCorner(3, padding_idx);
|
||
|
|
||
|
Vec3 inc = -Q1Jl.solve(Q1Jr + Q1Jp * pose_inc);
|
||
|
|
||
|
// We want to compute the model cost change. The model function is
|
||
|
//
|
||
|
// L(inc) = F(x) + inc^T J^T r + 0.5 inc^T J^T J inc
|
||
|
//
|
||
|
// and thus the expected decrease in cost for the computed increment is
|
||
|
//
|
||
|
// l_diff = L(0) - L(inc)
|
||
|
// = - inc^T J^T r - 0.5 inc^T J^T J inc
|
||
|
// = - inc^T J^T (r + 0.5 J inc)
|
||
|
// = - (J inc)^T (r + 0.5 (J inc)).
|
||
|
//
|
||
|
// Here we have J = [Jp, Jl] under the orthogonal projection Q = [Q1, Q2],
|
||
|
// i.e. the linearized system (model cost) is
|
||
|
//
|
||
|
// L(inc) = 0.5 || J inc + r ||^2 = 0.5 || Q^T J inc + Q^T r ||^2
|
||
|
//
|
||
|
// and below we thus compute
|
||
|
//
|
||
|
// l_diff = - (Q^T J inc)^T (Q^T r + 0.5 (Q^T J inc)).
|
||
|
//
|
||
|
// We have
|
||
|
// | Q1^T | | Q1^T Jp Q1^T Jl |
|
||
|
// Q^T J = | | [Jp, Jl] = | |
|
||
|
// | Q2^T | | Q2^T Jp 0 |.
|
||
|
//
|
||
|
// Note that Q2 is the nullspace of Jl, and Q1^T Jl == R. So with inc =
|
||
|
// [incp^T, incl^T]^T we have
|
||
|
//
|
||
|
// | Q1^T Jp incp + Q1^T Jl incl |
|
||
|
// Q^T J inc = | |
|
||
|
// | Q2^T Jp incp |
|
||
|
//
|
||
|
|
||
|
// undo damping before we compute the model cost difference
|
||
|
setLandmarkDamping(0);
|
||
|
|
||
|
// compute "Q^T J incp"
|
||
|
VecX QJinc = storage.topLeftCorner(num_rows - 3, padding_idx) * pose_inc;
|
||
|
|
||
|
// add "Q1^T Jl incl" to the first 3 rows
|
||
|
QJinc.template head<3>() += Q1Jl * inc;
|
||
|
|
||
|
auto Qr = storage.col(res_idx).head(num_rows - 3);
|
||
|
l_diff -= QJinc.transpose() * (Scalar(0.5) * QJinc + Qr);
|
||
|
|
||
|
// TODO: detect and handle case like ceres, allowing a few iterations but
|
||
|
// stopping eventually
|
||
|
if (!inc.array().isFinite().all() ||
|
||
|
!lm_ptr->direction.array().isFinite().all() ||
|
||
|
!std::isfinite(lm_ptr->inv_dist)) {
|
||
|
std::cerr << "Numerical failure in backsubstitution\n";
|
||
|
}
|
||
|
|
||
|
// Note: scale only after computing model cost change
|
||
|
inc.array() *= Jl_col_scale.array();
|
||
|
|
||
|
lm_ptr->direction += inc.template head<2>();
|
||
|
lm_ptr->inv_dist = std::max(Scalar(0), lm_ptr->inv_dist + inc[2]);
|
||
|
}
|
||
|
|
||
|
virtual inline size_t numReducedCams() const override {
|
||
|
BASALT_LOG_FATAL("check what we mean by numReducedCams for absolute poses");
|
||
|
return pose_lin_vec.size();
|
||
|
}
|
||
|
|
||
|
inline void addQ2JpTQ2Jp_mult_x(VecX& res,
|
||
|
const VecX& x_pose) const override {
|
||
|
UNUSED(res);
|
||
|
UNUSED(x_pose);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
virtual inline void addQ2JpTQ2r(VecX& res) const override {
|
||
|
UNUSED(res);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
virtual inline void addJp_diag2(VecX& res) const override {
|
||
|
BASALT_ASSERT(state == State::Linearized);
|
||
|
|
||
|
for (const auto& [frame_id, idx_set] : res_idx_by_abs_pose_) {
|
||
|
const int pose_idx = aom_->abs_order_map.at(frame_id).first;
|
||
|
for (const int i : idx_set) {
|
||
|
const auto block = storage.block(2 * i, pose_idx, 2, POSE_SIZE);
|
||
|
|
||
|
res.template segment<POSE_SIZE>(pose_idx) +=
|
||
|
block.colwise().squaredNorm();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
virtual inline void addQ2JpTQ2Jp_blockdiag(
|
||
|
BlockDiagonalAccumulator<Scalar>& accu) const override {
|
||
|
UNUSED(accu);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
virtual inline void scaleJl_cols() override {
|
||
|
BASALT_ASSERT(state == State::Linearized);
|
||
|
|
||
|
// ceres uses 1.0 / (1.0 + sqrt(SquaredColumnNorm))
|
||
|
// we use 1.0 / (eps + sqrt(SquaredColumnNorm))
|
||
|
Jl_col_scale =
|
||
|
(options_->jacobi_scaling_eps +
|
||
|
storage.block(0, lm_idx, num_rows - 3, 3).colwise().norm().array())
|
||
|
.inverse();
|
||
|
|
||
|
storage.block(0, lm_idx, num_rows - 3, 3) *= Jl_col_scale.asDiagonal();
|
||
|
}
|
||
|
|
||
|
virtual inline void scaleJp_cols(const VecX& jacobian_scaling) override {
|
||
|
BASALT_ASSERT(state == State::Marginalized);
|
||
|
|
||
|
// we assume we apply scaling before damping (we exclude the last 3 rows)
|
||
|
BASALT_ASSERT(!hasLandmarkDamping());
|
||
|
|
||
|
storage.topLeftCorner(num_rows - 3, padding_idx) *=
|
||
|
jacobian_scaling.asDiagonal();
|
||
|
}
|
||
|
|
||
|
inline bool hasLandmarkDamping() const { return !damping_rotations.empty(); }
|
||
|
|
||
|
virtual inline void printStorage(const std::string& filename) const override {
|
||
|
std::ofstream f(filename);
|
||
|
|
||
|
Eigen::IOFormat CleanFmt(4, 0, " ", "\n", "", "");
|
||
|
|
||
|
f << "Storage (state: " << state
|
||
|
<< ", damping: " << (hasLandmarkDamping() ? "yes" : "no")
|
||
|
<< " Jl_col_scale: " << Jl_col_scale.transpose() << "):\n"
|
||
|
<< storage.format(CleanFmt) << std::endl;
|
||
|
|
||
|
f.close();
|
||
|
}
|
||
|
#if 0
|
||
|
virtual inline void stage2(
|
||
|
Scalar lambda, const VecX* jacobian_scaling, VecX* precond_diagonal2,
|
||
|
BlockDiagonalAccumulator<Scalar>* precond_block_diagonal,
|
||
|
VecX& bref) override {
|
||
|
// 1. scale jacobian
|
||
|
if (jacobian_scaling) {
|
||
|
scaleJp_cols(*jacobian_scaling);
|
||
|
}
|
||
|
|
||
|
// 2. dampen landmarks
|
||
|
setLandmarkDamping(lambda);
|
||
|
|
||
|
// 3a. compute diagonal preconditioner (SCHUR_JACOBI_DIAGONAL)
|
||
|
if (precond_diagonal2) {
|
||
|
addQ2Jp_diag2(*precond_diagonal2);
|
||
|
}
|
||
|
|
||
|
// 3b. compute block diagonal preconditioner (SCHUR_JACOBI)
|
||
|
if (precond_block_diagonal) {
|
||
|
addQ2JpTQ2Jp_blockdiag(*precond_block_diagonal);
|
||
|
}
|
||
|
|
||
|
// 4. compute rhs of reduced camera normal equations
|
||
|
addQ2JpTQ2r(bref);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
inline State getState() const override { return state; }
|
||
|
|
||
|
virtual inline size_t numQ2rows() const override { return num_rows - 3; }
|
||
|
|
||
|
protected:
|
||
|
inline void performQRGivens() {
|
||
|
// Based on "Matrix Computations 4th Edition by Golub and Van Loan"
|
||
|
// See page 252, Algorithm 5.2.4 for how these two loops work
|
||
|
Eigen::JacobiRotation<Scalar> gr;
|
||
|
for (size_t n = 0; n < 3; n++) {
|
||
|
for (size_t m = num_rows - 4; m > n; m--) {
|
||
|
gr.makeGivens(storage(m - 1, lm_idx + n), storage(m, lm_idx + n));
|
||
|
storage.applyOnTheLeft(m, m - 1, gr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void performQRHouseholder() {
|
||
|
VecX tempVector1(num_cols);
|
||
|
VecX tempVector2(num_rows - 3);
|
||
|
|
||
|
for (size_t k = 0; k < 3; ++k) {
|
||
|
size_t remainingRows = num_rows - k - 3;
|
||
|
|
||
|
Scalar beta;
|
||
|
Scalar tau;
|
||
|
storage.col(lm_idx + k)
|
||
|
.segment(k, remainingRows)
|
||
|
.makeHouseholder(tempVector2, tau, beta);
|
||
|
|
||
|
storage.block(k, 0, remainingRows, num_cols)
|
||
|
.applyHouseholderOnTheLeft(tempVector2, tau, tempVector1.data());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline std::tuple<Scalar, Scalar> compute_error_weight(
|
||
|
Scalar res_squared) const {
|
||
|
// Note: Definition of cost is 0.5 ||r(x)||^2 to be in line with ceres
|
||
|
|
||
|
if (options_->huber_parameter > 0) {
|
||
|
// use huber norm
|
||
|
const Scalar huber_weight =
|
||
|
res_squared <= options_->huber_parameter * options_->huber_parameter
|
||
|
? Scalar(1)
|
||
|
: options_->huber_parameter / std::sqrt(res_squared);
|
||
|
const Scalar error =
|
||
|
Scalar(0.5) * (2 - huber_weight) * huber_weight * res_squared;
|
||
|
return {error, huber_weight};
|
||
|
} else {
|
||
|
// use squared norm
|
||
|
return {Scalar(0.5) * res_squared, Scalar(1)};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void get_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r,
|
||
|
size_t start_idx) const override {
|
||
|
Q2r.segment(start_idx, num_rows - 3) =
|
||
|
storage.col(res_idx).tail(num_rows - 3);
|
||
|
|
||
|
BASALT_ASSERT(Q2Jp.cols() == signed_cast(padding_idx));
|
||
|
|
||
|
Q2Jp.block(start_idx, 0, num_rows - 3, padding_idx) =
|
||
|
storage.block(3, 0, num_rows - 3, padding_idx);
|
||
|
}
|
||
|
|
||
|
void get_dense_Q2Jp_Q2r_rel(
|
||
|
MatX& Q2Jp, VecX& Q2r, size_t start_idx,
|
||
|
const std::map<TimeCamId, size_t>& rel_order) const override {
|
||
|
UNUSED(Q2Jp);
|
||
|
UNUSED(Q2r);
|
||
|
UNUSED(start_idx);
|
||
|
UNUSED(rel_order);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
void add_dense_H_b(DenseAccumulator<Scalar>& accum) const override {
|
||
|
UNUSED(accum);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
void add_dense_H_b(MatX& H, VecX& b) const override {
|
||
|
const auto r = storage.col(res_idx).tail(num_rows - 3);
|
||
|
const auto J = storage.block(3, 0, num_rows - 3, padding_idx);
|
||
|
|
||
|
H.noalias() += J.transpose() * J;
|
||
|
b.noalias() += J.transpose() * r;
|
||
|
}
|
||
|
|
||
|
void add_dense_H_b_rel(
|
||
|
MatX& H_rel, VecX& b_rel,
|
||
|
const std::map<TimeCamId, size_t>& rel_order) const override {
|
||
|
UNUSED(H_rel);
|
||
|
UNUSED(b_rel);
|
||
|
UNUSED(rel_order);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
const Eigen::PermutationMatrix<Eigen::Dynamic>& get_rel_permutation()
|
||
|
const override {
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
Eigen::PermutationMatrix<Eigen::Dynamic> compute_rel_permutation(
|
||
|
const std::map<TimeCamId, size_t>& rel_order) const override {
|
||
|
UNUSED(rel_order);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
void add_dense_H_b_rel_2(MatX& H_rel, VecX& b_rel) const override {
|
||
|
UNUSED(H_rel);
|
||
|
UNUSED(b_rel);
|
||
|
BASALT_LOG_FATAL("not implemented");
|
||
|
}
|
||
|
|
||
|
virtual TimeCamId getHostKf() const override { return lm_ptr->host_kf_id; }
|
||
|
|
||
|
private:
|
||
|
// Dense storage for pose Jacobians, padding, landmark Jacobians and
|
||
|
// residuals [J_p | pad | J_l | res]
|
||
|
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
|
||
|
storage;
|
||
|
|
||
|
Vec3 Jl_col_scale = Vec3::Ones();
|
||
|
std::vector<Eigen::JacobiRotation<Scalar>> damping_rotations;
|
||
|
|
||
|
std::vector<const RelPoseLin<Scalar>*> pose_lin_vec;
|
||
|
std::vector<const std::pair<TimeCamId, TimeCamId>*> pose_tcid_vec;
|
||
|
size_t padding_idx = 0;
|
||
|
size_t padding_size = 0;
|
||
|
size_t lm_idx = 0;
|
||
|
size_t res_idx = 0;
|
||
|
|
||
|
size_t num_cols = 0;
|
||
|
size_t num_rows = 0;
|
||
|
|
||
|
const Options* options_ = nullptr;
|
||
|
|
||
|
State state = State::Uninitialized;
|
||
|
|
||
|
Keypoint<Scalar>* lm_ptr = nullptr;
|
||
|
const Calibration<Scalar>* calib_ = nullptr;
|
||
|
const AbsOrderMap* aom_ = nullptr;
|
||
|
|
||
|
std::map<int64_t, std::set<int>> res_idx_by_abs_pose_;
|
||
|
};
|
||
|
|
||
|
} // namespace basalt
|