433 lines
14 KiB
C++
433 lines
14 KiB
C++
|
/**
|
||
|
BSD 3-Clause License
|
||
|
|
||
|
This file is part of the Basalt project.
|
||
|
https://gitlab.com/VladyslavUsenko/basalt.git
|
||
|
|
||
|
Copyright (c) 2021, Vladyslav Usenko and Nikolaus Demmel.
|
||
|
All rights reserved.
|
||
|
|
||
|
Redistribution and use in source and binary forms, with or without
|
||
|
modification, are permitted provided that the following conditions are met:
|
||
|
|
||
|
* Redistributions of source code must retain the above copyright notice, this
|
||
|
list of conditions and the following disclaimer.
|
||
|
|
||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||
|
this list of conditions and the following disclaimer in the documentation
|
||
|
and/or other materials provided with the distribution.
|
||
|
|
||
|
* Neither the name of the copyright holder nor the names of its
|
||
|
contributors may be used to endorse or promote products derived from
|
||
|
this software without specific prior written permission.
|
||
|
|
||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include <basalt/linearization/linearization_abs_sc.hpp>
|
||
|
|
||
|
#include <tbb/blocked_range.h>
|
||
|
#include <tbb/parallel_for.h>
|
||
|
#include <tbb/parallel_reduce.h>
|
||
|
|
||
|
#include <basalt/utils/ba_utils.h>
|
||
|
#include <basalt/vi_estimator/sc_ba_base.h>
|
||
|
#include <basalt/linearization/imu_block.hpp>
|
||
|
#include <basalt/utils/cast_utils.hpp>
|
||
|
|
||
|
namespace basalt {
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
LinearizationAbsSC<Scalar, POSE_SIZE>::LinearizationAbsSC(
|
||
|
BundleAdjustmentBase<Scalar>* estimator, const AbsOrderMap& aom,
|
||
|
const Options& options, const MargLinData<Scalar>* marg_lin_data,
|
||
|
const ImuLinData<Scalar>* imu_lin_data,
|
||
|
const std::set<FrameId>* used_frames,
|
||
|
const std::unordered_set<KeypointId>* lost_landmarks,
|
||
|
int64_t last_state_to_marg)
|
||
|
: options_(options),
|
||
|
estimator(estimator),
|
||
|
lmdb_(estimator->lmdb),
|
||
|
calib(estimator->calib),
|
||
|
aom(aom),
|
||
|
used_frames(used_frames),
|
||
|
marg_lin_data(marg_lin_data),
|
||
|
imu_lin_data(imu_lin_data),
|
||
|
lost_landmarks(lost_landmarks),
|
||
|
last_state_to_marg(last_state_to_marg),
|
||
|
pose_damping_diagonal(0),
|
||
|
pose_damping_diagonal_sqrt(0) {
|
||
|
BASALT_ASSERT_STREAM(
|
||
|
options.lb_options.huber_parameter == estimator->huber_thresh,
|
||
|
"Huber threshold should be set to the same value");
|
||
|
|
||
|
BASALT_ASSERT_STREAM(options.lb_options.obs_std_dev == estimator->obs_std_dev,
|
||
|
"obs_std_dev should be set to the same value");
|
||
|
|
||
|
if (imu_lin_data) {
|
||
|
for (const auto& kv : imu_lin_data->imu_meas) {
|
||
|
imu_blocks.emplace_back(
|
||
|
new ImuBlock<Scalar>(kv.second, imu_lin_data, aom));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// std::cout << "num_rows_Q2r " << num_rows_Q2r << " num_poses " <<
|
||
|
// num_cameras
|
||
|
// << std::endl;
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
LinearizationAbsSC<Scalar, POSE_SIZE>::~LinearizationAbsSC() = default;
|
||
|
|
||
|
template <typename Scalar_, int POSE_SIZE_>
|
||
|
void LinearizationAbsSC<Scalar_, POSE_SIZE_>::log_problem_stats(
|
||
|
ExecutionStats& stats) const {
|
||
|
UNUSED(stats);
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
Scalar LinearizationAbsSC<Scalar, POSE_SIZE>::linearizeProblem(
|
||
|
bool* numerically_valid) {
|
||
|
// reset damping and scaling (might be set from previous iteration)
|
||
|
pose_damping_diagonal = 0;
|
||
|
pose_damping_diagonal_sqrt = 0;
|
||
|
marg_scaling = VecX();
|
||
|
|
||
|
std::unordered_map<TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>
|
||
|
obs_to_lin;
|
||
|
|
||
|
if (used_frames) {
|
||
|
const auto& obs = lmdb_.getObservations();
|
||
|
|
||
|
// select all observations where the host frame is about to be
|
||
|
// marginalized
|
||
|
|
||
|
if (lost_landmarks) {
|
||
|
for (auto it = obs.cbegin(); it != obs.cend(); ++it) {
|
||
|
if (used_frames->count(it->first.frame_id) > 0) {
|
||
|
for (auto it2 = it->second.cbegin(); it2 != it->second.cend();
|
||
|
++it2) {
|
||
|
if (it2->first.frame_id <= last_state_to_marg)
|
||
|
obs_to_lin[it->first].emplace(*it2);
|
||
|
}
|
||
|
} else {
|
||
|
std::map<TimeCamId, std::set<KeypointId>> lost_obs_map;
|
||
|
for (auto it2 = it->second.cbegin(); it2 != it->second.cend();
|
||
|
++it2) {
|
||
|
for (const auto& lm_id : it2->second) {
|
||
|
if (lost_landmarks->count(lm_id)) {
|
||
|
if (it2->first.frame_id <= last_state_to_marg)
|
||
|
lost_obs_map[it2->first].emplace(lm_id);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!lost_obs_map.empty()) {
|
||
|
obs_to_lin[it->first] = lost_obs_map;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
for (auto it = obs.cbegin(); it != obs.cend(); ++it) {
|
||
|
if (used_frames->count(it->first.frame_id) > 0) {
|
||
|
for (auto it2 = it->second.cbegin(); it2 != it->second.cend();
|
||
|
++it2) {
|
||
|
if (it2->first.frame_id <= last_state_to_marg)
|
||
|
obs_to_lin[it->first].emplace(*it2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
obs_to_lin = lmdb_.getObservations();
|
||
|
}
|
||
|
|
||
|
Scalar error;
|
||
|
|
||
|
ScBundleAdjustmentBase<Scalar>::linearizeHelperAbsStatic(ald_vec, obs_to_lin,
|
||
|
estimator, error);
|
||
|
|
||
|
// TODO: Fix the computation of numarically valid points
|
||
|
if (numerically_valid) *numerically_valid = true;
|
||
|
|
||
|
if (imu_lin_data) {
|
||
|
for (auto& imu_block : imu_blocks) {
|
||
|
error += imu_block->linearizeImu(estimator->frame_states);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (marg_lin_data) {
|
||
|
Scalar marg_prior_error;
|
||
|
estimator->computeMargPriorError(*marg_lin_data, marg_prior_error);
|
||
|
error += marg_prior_error;
|
||
|
}
|
||
|
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::performQR() {}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::setPoseDamping(
|
||
|
const Scalar lambda) {
|
||
|
BASALT_ASSERT(lambda >= 0);
|
||
|
|
||
|
pose_damping_diagonal = lambda;
|
||
|
pose_damping_diagonal_sqrt = std::sqrt(lambda);
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
Scalar LinearizationAbsSC<Scalar, POSE_SIZE>::backSubstitute(
|
||
|
const VecX& pose_inc) {
|
||
|
BASALT_ASSERT(pose_inc.size() == signed_cast(aom.total_size));
|
||
|
|
||
|
// Update points
|
||
|
tbb::blocked_range<size_t> keys_range(0, ald_vec.size());
|
||
|
auto update_points_func = [&](const tbb::blocked_range<size_t>& r,
|
||
|
Scalar l_diff) {
|
||
|
for (size_t i = r.begin(); i != r.end(); ++i) {
|
||
|
const auto& ald = ald_vec[i];
|
||
|
ScBundleAdjustmentBase<Scalar>::updatePointsAbs(aom, ald, -pose_inc,
|
||
|
lmdb_, &l_diff);
|
||
|
}
|
||
|
|
||
|
return l_diff;
|
||
|
};
|
||
|
Scalar l_diff = tbb::parallel_reduce(keys_range, Scalar(0),
|
||
|
update_points_func, std::plus<Scalar>());
|
||
|
|
||
|
if (imu_lin_data) {
|
||
|
for (auto& imu_block : imu_blocks) {
|
||
|
imu_block->backSubstitute(pose_inc, l_diff);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (marg_lin_data) {
|
||
|
size_t marg_size = marg_lin_data->H.cols();
|
||
|
VecX pose_inc_marg = pose_inc.head(marg_size);
|
||
|
|
||
|
l_diff += estimator->computeMargPriorModelCostChange(
|
||
|
*marg_lin_data, marg_scaling, pose_inc_marg);
|
||
|
}
|
||
|
|
||
|
return l_diff;
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
typename LinearizationAbsSC<Scalar, POSE_SIZE>::VecX
|
||
|
LinearizationAbsSC<Scalar, POSE_SIZE>::getJp_diag2() const {
|
||
|
// TODO: group relative by host frame
|
||
|
|
||
|
BASALT_ASSERT_STREAM(false, "Not implemented");
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::scaleJl_cols() {
|
||
|
BASALT_ASSERT_STREAM(false, "Not implemented");
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::scaleJp_cols(
|
||
|
const VecX& jacobian_scaling) {
|
||
|
UNUSED(jacobian_scaling);
|
||
|
BASALT_ASSERT_STREAM(false, "Not implemented");
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::setLandmarkDamping(Scalar lambda) {
|
||
|
UNUSED(lambda);
|
||
|
BASALT_ASSERT_STREAM(false, "Not implemented");
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::get_dense_Q2Jp_Q2r(
|
||
|
MatX& Q2Jp, VecX& Q2r) const {
|
||
|
MatX H;
|
||
|
VecX b;
|
||
|
get_dense_H_b(H, b);
|
||
|
|
||
|
Eigen::LDLT<Eigen::Ref<MatX>> ldlt(H);
|
||
|
|
||
|
VecX D_sqrt = ldlt.vectorD().array().max(0).sqrt().matrix();
|
||
|
|
||
|
// After LDLT, we have
|
||
|
// marg_H == P^T*L*D*L^T*P,
|
||
|
// so we compute the square root as
|
||
|
// marg_sqrt_H = sqrt(D)*L^T*P,
|
||
|
// such that
|
||
|
// marg_sqrt_H^T marg_sqrt_H == marg_H.
|
||
|
Q2Jp.setIdentity(H.rows(), H.cols());
|
||
|
Q2Jp = ldlt.transpositionsP() * Q2Jp;
|
||
|
Q2Jp = ldlt.matrixU() * Q2Jp; // U == L^T
|
||
|
Q2Jp = D_sqrt.asDiagonal() * Q2Jp;
|
||
|
|
||
|
// For the right hand side, we want
|
||
|
// marg_b == marg_sqrt_H^T * marg_sqrt_b
|
||
|
// so we compute
|
||
|
// marg_sqrt_b = (marg_sqrt_H^T)^-1 * marg_b
|
||
|
// = (P^T*L*sqrt(D))^-1 * marg_b
|
||
|
// = sqrt(D)^-1 * L^-1 * P * marg_b
|
||
|
Q2r = ldlt.transpositionsP() * b;
|
||
|
ldlt.matrixL().solveInPlace(Q2r);
|
||
|
|
||
|
// We already clamped negative values in D_sqrt to 0 above, but for values
|
||
|
// close to 0 we set b to 0.
|
||
|
for (int i = 0; i < Q2r.size(); ++i) {
|
||
|
if (D_sqrt(i) > std::sqrt(std::numeric_limits<Scalar>::min()))
|
||
|
Q2r(i) /= D_sqrt(i);
|
||
|
else
|
||
|
Q2r(i) = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::get_dense_H_b(MatX& H,
|
||
|
VecX& b) const {
|
||
|
typename ScBundleAdjustmentBase<Scalar>::template LinearizeAbsReduce2<
|
||
|
DenseAccumulator<Scalar>>
|
||
|
lopt_abs(aom);
|
||
|
|
||
|
tbb::blocked_range<typename Eigen::aligned_vector<AbsLinData>::const_iterator>
|
||
|
range(ald_vec.cbegin(), ald_vec.cend());
|
||
|
tbb::parallel_reduce(range, lopt_abs);
|
||
|
// lopt_abs(range);
|
||
|
|
||
|
// Add imu
|
||
|
add_dense_H_b_imu(lopt_abs.accum.getH(), lopt_abs.accum.getB());
|
||
|
|
||
|
// Add damping
|
||
|
add_dense_H_b_pose_damping(lopt_abs.accum.getH());
|
||
|
|
||
|
// Add marginalization
|
||
|
add_dense_H_b_marg_prior(lopt_abs.accum.getH(), lopt_abs.accum.getB());
|
||
|
|
||
|
H = std::move(lopt_abs.accum.getH());
|
||
|
b = std::move(lopt_abs.accum.getB());
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::get_dense_Q2Jp_Q2r_pose_damping(
|
||
|
MatX& Q2Jp, size_t start_idx) const {
|
||
|
UNUSED(Q2Jp);
|
||
|
UNUSED(start_idx);
|
||
|
BASALT_ASSERT_STREAM(false, "Not implemented");
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::get_dense_Q2Jp_Q2r_marg_prior(
|
||
|
MatX& Q2Jp, VecX& Q2r, size_t start_idx) const {
|
||
|
if (!marg_lin_data) return;
|
||
|
|
||
|
size_t marg_rows = marg_lin_data->H.rows();
|
||
|
size_t marg_cols = marg_lin_data->H.cols();
|
||
|
|
||
|
VecX delta;
|
||
|
estimator->computeDelta(marg_lin_data->order, delta);
|
||
|
|
||
|
if (marg_scaling.rows() > 0) {
|
||
|
Q2Jp.template block(start_idx, 0, marg_rows, marg_cols) =
|
||
|
marg_lin_data->H * marg_scaling.asDiagonal();
|
||
|
} else {
|
||
|
Q2Jp.template block(start_idx, 0, marg_rows, marg_cols) = marg_lin_data->H;
|
||
|
}
|
||
|
|
||
|
Q2r.template segment(start_idx, marg_rows) =
|
||
|
marg_lin_data->H * delta + marg_lin_data->b;
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::add_dense_H_b_pose_damping(
|
||
|
MatX& H) const {
|
||
|
if (hasPoseDamping()) {
|
||
|
H.diagonal().array() += pose_damping_diagonal;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::add_dense_H_b_marg_prior(
|
||
|
MatX& H, VecX& b) const {
|
||
|
if (!marg_lin_data) return;
|
||
|
|
||
|
BASALT_ASSERT(marg_scaling.rows() == 0);
|
||
|
|
||
|
Scalar marg_prior_error;
|
||
|
estimator->linearizeMargPrior(*marg_lin_data, aom, H, b, marg_prior_error);
|
||
|
|
||
|
// size_t marg_size = marg_lin_data->H.cols();
|
||
|
|
||
|
// VecX delta;
|
||
|
// estimator->computeDelta(marg_lin_data->order, delta);
|
||
|
|
||
|
// Scaling not supported ATM
|
||
|
|
||
|
// if (marg_scaling.rows() > 0) {
|
||
|
// H.topLeftCorner(marg_size, marg_size) +=
|
||
|
// marg_scaling.asDiagonal() * marg_lin_data->H.transpose() *
|
||
|
// marg_lin_data->H * marg_scaling.asDiagonal();
|
||
|
|
||
|
// b.head(marg_size) += marg_scaling.asDiagonal() *
|
||
|
// marg_lin_data->H.transpose() *
|
||
|
// (marg_lin_data->H * delta + marg_lin_data->b);
|
||
|
|
||
|
// } else {
|
||
|
// H.topLeftCorner(marg_size, marg_size) +=
|
||
|
// marg_lin_data->H.transpose() * marg_lin_data->H;
|
||
|
|
||
|
// b.head(marg_size) += marg_lin_data->H.transpose() *
|
||
|
// (marg_lin_data->H * delta + marg_lin_data->b);
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::add_dense_H_b_imu(
|
||
|
DenseAccumulator<Scalar>& accum) const {
|
||
|
if (!imu_lin_data) return;
|
||
|
|
||
|
for (const auto& imu_block : imu_blocks) {
|
||
|
imu_block->add_dense_H_b(accum);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename Scalar, int POSE_SIZE>
|
||
|
void LinearizationAbsSC<Scalar, POSE_SIZE>::add_dense_H_b_imu(MatX& H,
|
||
|
VecX& b) const {
|
||
|
if (!imu_lin_data) return;
|
||
|
|
||
|
// workaround: create an accumulator here, to avoid implementing the
|
||
|
// add_dense_H_b(H, b) overload in ImuBlock
|
||
|
DenseAccumulator<Scalar> accum;
|
||
|
accum.reset(b.size());
|
||
|
|
||
|
for (const auto& imu_block : imu_blocks) {
|
||
|
imu_block->add_dense_H_b(accum);
|
||
|
}
|
||
|
|
||
|
H += accum.getH();
|
||
|
b += accum.getB();
|
||
|
}
|
||
|
|
||
|
// //////////////////////////////////////////////////////////////////
|
||
|
// instatiate templates
|
||
|
|
||
|
#ifdef BASALT_INSTANTIATIONS_DOUBLE
|
||
|
// Scalar=double, POSE_SIZE=6
|
||
|
template class LinearizationAbsSC<double, 6>;
|
||
|
#endif
|
||
|
|
||
|
#ifdef BASALT_INSTANTIATIONS_FLOAT
|
||
|
// Scalar=float, POSE_SIZE=6
|
||
|
template class LinearizationAbsSC<float, 6>;
|
||
|
#endif
|
||
|
|
||
|
} // namespace basalt
|