This commit is contained in:
Ivan
2022-04-05 11:42:28 +03:00
commit 6dc0eb0fcf
5565 changed files with 1200500 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
struct AprilGrid {
AprilGrid(const std::string &config_path);
Eigen::aligned_vector<Eigen::Vector4d> aprilgrid_corner_pos_3d;
Eigen::aligned_vector<Eigen::Vector4d> aprilgrid_vignette_pos_3d;
inline int getTagCols() const { return tagCols; }
inline int getTagRows() const { return tagRows; }
private:
int tagCols; // number of apriltags
int tagRows; // number of apriltags
double tagSize; // size of apriltag, edge to edge [m]
double tagSpacing; // ratio of space between tags to tagSize
};
} // namespace basalt

View File

@@ -0,0 +1,131 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/calibration/aprilgrid.h>
#include <basalt/io/dataset_io.h>
#include <basalt/utils/common_types.h>
#include <basalt/calibration/calibration.hpp>
#include <tbb/concurrent_unordered_map.h>
namespace basalt {
struct CalibCornerData {
Eigen::aligned_vector<Eigen::Vector2d> corners;
std::vector<int> corner_ids;
std::vector<double> radii; //!< threshold used for maximum displacement
//! during sub-pix refinement; Search region is
//! slightly larger.
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct ProjectedCornerData {
Eigen::aligned_vector<Eigen::Vector2d> corners_proj;
std::vector<bool> corners_proj_success;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct CalibInitPoseData {
Sophus::SE3d T_a_c;
size_t num_inliers;
Eigen::aligned_vector<Eigen::Vector2d> reprojected_corners;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
using CalibCornerMap = tbb::concurrent_unordered_map<TimeCamId, CalibCornerData,
std::hash<TimeCamId>>;
using CalibInitPoseMap =
tbb::concurrent_unordered_map<TimeCamId, CalibInitPoseData,
std::hash<TimeCamId>>;
class CalibHelper {
public:
static void detectCorners(const VioDatasetPtr& vio_data,
CalibCornerMap& calib_corners,
CalibCornerMap& calib_corners_rejected);
static void initCamPoses(
const Calibration<double>::Ptr& calib,
const Eigen::aligned_vector<Eigen::Vector4d>& aprilgrid_corner_pos_3d,
CalibCornerMap& calib_corners, CalibInitPoseMap& calib_init_poses);
static bool initializeIntrinsics(
const Eigen::aligned_vector<Eigen::Vector2d>& corners,
const std::vector<int>& corner_ids, const AprilGrid& aprilgrid, int cols,
int rows, Eigen::Vector4d& init_intr);
static bool initializeIntrinsicsPinhole(
const std::vector<CalibCornerData*> pinhole_corners,
const AprilGrid& aprilgrid, int cols, int rows,
Eigen::Vector4d& init_intr);
private:
inline static double square(double x) { return x * x; }
inline static double hypot(double a, double b) {
return sqrt(square(a) + square(b));
}
static void computeInitialPose(
const Calibration<double>::Ptr& calib, size_t cam_id,
const Eigen::aligned_vector<Eigen::Vector4d>& aprilgrid_corner_pos_3d,
const basalt::CalibCornerData& cd, basalt::CalibInitPoseData& cp);
static size_t computeReprojectionError(
const UnifiedCamera<double>& cam_calib,
const Eigen::aligned_vector<Eigen::Vector2d>& corners,
const std::vector<int>& corner_ids,
const Eigen::aligned_vector<Eigen::Vector4d>& aprilgrid_corner_pos_3d,
const Sophus::SE3d& T_target_camera, double& error);
};
} // namespace basalt
namespace cereal {
template <class Archive>
void serialize(Archive& ar, basalt::CalibCornerData& c) {
ar(c.corners, c.corner_ids, c.radii);
}
template <class Archive>
void serialize(Archive& ar, basalt::CalibInitPoseData& c) {
ar(c.T_a_c, c.num_inliers, c.reprojected_corners);
}
} // namespace cereal

View File

@@ -0,0 +1,176 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <pangolin/display/image_view.h>
#include <pangolin/gl/gldraw.h>
#include <pangolin/image/image.h>
#include <pangolin/image/image_io.h>
#include <pangolin/image/typed_image.h>
#include <pangolin/pangolin.h>
#include <Eigen/Dense>
#include <iostream>
#include <limits>
#include <thread>
#include <basalt/calibration/aprilgrid.h>
#include <basalt/calibration/calibration_helper.h>
#include <basalt/image/image.h>
#include <basalt/utils/test_utils.h>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
class PosesOptimization;
class CamCalib {
public:
CamCalib(const std::string &dataset_path, const std::string &dataset_type,
const std::string &aprilgrid_path, const std::string &cache_path,
const std::string &cache_dataset_name, int skip_images,
const std::vector<std::string> &cam_types, bool show_gui = true);
~CamCalib();
void initGui();
void computeVign();
void setNumCameras(size_t n);
void renderingLoop();
void computeProjections();
void detectCorners();
void initCamIntrinsics();
void initCamPoses();
void initCamExtrinsics();
void initOptimization();
void loadDataset();
void optimize();
bool optimizeWithParam(bool print_info,
std::map<std::string, double> *stats = nullptr);
void saveCalib();
void drawImageOverlay(pangolin::View &v, size_t cam_id);
bool hasCorners() const;
void setOptIntrinsics(bool opt) { opt_intr = opt; }
private:
static constexpr int UI_WIDTH = 300;
static constexpr size_t RANSAC_THRESHOLD = 10;
// typedef Calibration::Ptr CalibrationPtr;
VioDatasetPtr vio_dataset;
// CalibrationPtr calib;
CalibCornerMap calib_corners;
CalibCornerMap calib_corners_rejected;
CalibInitPoseMap calib_init_poses;
std::shared_ptr<std::thread> processing_thread;
std::shared_ptr<PosesOptimization> calib_opt;
std::map<TimeCamId, ProjectedCornerData> reprojected_corners;
std::map<TimeCamId, ProjectedCornerData> reprojected_vignette;
std::map<TimeCamId, std::vector<double>> reprojected_vignette_error;
std::string dataset_path;
std::string dataset_type;
AprilGrid april_grid;
std::string cache_path;
std::string cache_dataset_name;
int skip_images;
std::vector<std::string> cam_types;
bool show_gui;
const size_t MIN_CORNERS = 15;
//////////////////////
pangolin::Var<int> show_frame;
pangolin::Var<bool> show_corners;
pangolin::Var<bool> show_corners_rejected;
pangolin::Var<bool> show_init_reproj;
pangolin::Var<bool> show_opt;
pangolin::Var<bool> show_vign;
pangolin::Var<bool> show_ids;
pangolin::Var<double> huber_thresh;
pangolin::Var<bool> opt_intr;
pangolin::Var<bool> opt_until_convg;
pangolin::Var<double> stop_thresh;
std::shared_ptr<pangolin::Plotter> vign_plotter;
std::shared_ptr<pangolin::Plotter> polar_plotter;
std::shared_ptr<pangolin::Plotter> azimuth_plotter;
std::vector<pangolin::Colour> cam_colors;
pangolin::View *img_view_display;
std::vector<std::shared_ptr<pangolin::ImageView>> img_view;
pangolin::DataLog vign_data_log;
std::vector<std::shared_ptr<pangolin::DataLog>> polar_data_log,
azimuth_data_log;
};
} // namespace basalt

View File

@@ -0,0 +1,182 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <pangolin/display/image_view.h>
#include <pangolin/gl/gldraw.h>
#include <pangolin/image/image.h>
#include <pangolin/image/image_io.h>
#include <pangolin/image/typed_image.h>
#include <pangolin/pangolin.h>
#include <Eigen/Dense>
#include <iostream>
#include <limits>
#include <thread>
#include <basalt/calibration/aprilgrid.h>
#include <basalt/calibration/calibration_helper.h>
#include <basalt/utils/test_utils.h>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
template <int N, typename Scalar>
class SplineOptimization;
class CamImuCalib {
public:
CamImuCalib(const std::string &dataset_path, const std::string &dataset_type,
const std::string &aprilgrid_path, const std::string &cache_path,
const std::string &cache_dataset_name, int skip_images,
const std::vector<double> &imu_noise, bool show_gui = true);
~CamImuCalib();
void initGui();
void setNumCameras(size_t n);
void renderingLoop();
void computeProjections();
void detectCorners();
void initCamPoses();
void initCamImuTransform();
void initOptimization();
void initMocap();
void loadDataset();
void optimize();
bool optimizeWithParam(bool print_info,
std::map<std::string, double> *stats = nullptr);
void saveCalib();
void saveMocapCalib();
void drawImageOverlay(pangolin::View &v, size_t cam_id);
void recomputeDataLog();
void drawPlots();
bool hasCorners() const;
void setOptIntrinsics(bool opt) { opt_intr = opt; }
private:
static constexpr int UI_WIDTH = 300;
VioDatasetPtr vio_dataset;
CalibCornerMap calib_corners;
CalibCornerMap calib_corners_rejected;
CalibInitPoseMap calib_init_poses;
std::shared_ptr<std::thread> processing_thread;
std::shared_ptr<SplineOptimization<5, double>> calib_opt;
std::map<TimeCamId, ProjectedCornerData> reprojected_corners;
std::string dataset_path;
std::string dataset_type;
AprilGrid april_grid;
std::string cache_path;
std::string cache_dataset_name;
int skip_images;
bool show_gui;
const size_t MIN_CORNERS = 15;
std::vector<double> imu_noise;
//////////////////////
pangolin::Var<int> show_frame;
pangolin::Var<bool> show_corners;
pangolin::Var<bool> show_corners_rejected;
pangolin::Var<bool> show_init_reproj;
pangolin::Var<bool> show_opt;
pangolin::Var<bool> show_ids;
pangolin::Var<bool> show_accel;
pangolin::Var<bool> show_gyro;
pangolin::Var<bool> show_pos;
pangolin::Var<bool> show_rot_error;
pangolin::Var<bool> show_mocap;
pangolin::Var<bool> show_mocap_rot_error;
pangolin::Var<bool> show_mocap_rot_vel;
pangolin::Var<bool> show_spline;
pangolin::Var<bool> show_data;
pangolin::Var<bool> opt_intr;
pangolin::Var<bool> opt_poses;
pangolin::Var<bool> opt_corners;
pangolin::Var<bool> opt_cam_time_offset;
pangolin::Var<bool> opt_imu_scale;
pangolin::Var<bool> opt_mocap;
pangolin::Var<double> huber_thresh;
pangolin::Var<bool> opt_until_convg;
pangolin::Var<double> stop_thresh;
pangolin::Plotter *plotter;
pangolin::View *img_view_display;
std::vector<std::shared_ptr<pangolin::ImageView>> img_view;
pangolin::DataLog imu_data_log, pose_data_log, mocap_data_log, vign_data_log;
};
} // namespace basalt

View File

@@ -0,0 +1,89 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/calibration/aprilgrid.h>
#include <basalt/calibration/calibration_helper.h>
#include <basalt/spline/rd_spline.h>
namespace basalt {
class VignetteEstimator {
public:
static const int SPLINE_N = 4;
static const int64_t knot_spacing = 1e10;
static const int border_size = 2;
VignetteEstimator(
const VioDatasetPtr &vio_dataset,
const Eigen::aligned_vector<Eigen::Vector2d> &optical_centers,
const Eigen::aligned_vector<Eigen::Vector2i> &resolutions,
const std::map<TimeCamId, Eigen::aligned_vector<Eigen::Vector3d>>
&reprojected_vignette,
const AprilGrid &april_grid);
void compute_error(std::map<TimeCamId, std::vector<double>>
*reprojected_vignette_error = nullptr);
void opt_irradience();
void opt_vign();
void optimize();
void compute_data_log(std::vector<std::vector<float>> &vign_data_log);
void save_vign_png(const std::string &path);
inline const std::vector<basalt::RdSpline<1, SPLINE_N>> &get_vign_param() {
return vign_param;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
const VioDatasetPtr vio_dataset;
Eigen::aligned_vector<Eigen::Vector2d> optical_centers;
Eigen::aligned_vector<Eigen::Vector2i> resolutions;
std::map<TimeCamId, Eigen::aligned_vector<Eigen::Vector3d>>
reprojected_vignette;
const AprilGrid &april_grid;
size_t vign_size;
std::vector<double> irradiance;
std::vector<basalt::RdSpline<1, SPLINE_N>> vign_param;
};
} // namespace basalt

View File

@@ -0,0 +1,118 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, Vladyslav Usenko, Michael Loipführer 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.
*/
#pragma once
#include <math.h>
#include <atomic>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <thread>
#include <librealsense2/rs.hpp>
#include <pangolin/image/typed_image.h>
#include <pangolin/pangolin.h>
#include <tbb/concurrent_queue.h>
#include <basalt/imu/imu_types.h>
#include <basalt/optical_flow/optical_flow.h>
#include <basalt/calibration/calibration.hpp>
namespace basalt {
struct RsIMUData {
double timestamp;
Eigen::Vector3d data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct RsPoseData {
int64_t t_ns;
Sophus::SE3d data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
class RsT265Device {
public:
using Ptr = std::shared_ptr<RsT265Device>;
static constexpr int IMU_RATE = 200;
static constexpr int NUM_CAMS = 2;
RsT265Device(bool manual_exposure, int skip_frames, int webp_quality,
double exposure_value = 10.0);
void start();
void stop();
bool setExposure(double exposure); // in milliseconds
void setSkipFrames(int skip);
void setWebpQuality(int quality);
std::shared_ptr<basalt::Calibration<double>> exportCalibration();
OpticalFlowInput::Ptr last_img_data;
tbb::concurrent_bounded_queue<OpticalFlowInput::Ptr>* image_data_queue =
nullptr;
tbb::concurrent_bounded_queue<ImuData<double>::Ptr>* imu_data_queue = nullptr;
tbb::concurrent_bounded_queue<RsPoseData>* pose_data_queue = nullptr;
private:
bool manual_exposure;
int skip_frames;
int webp_quality;
int frame_counter = 0;
Eigen::aligned_deque<RsIMUData> gyro_data_queue;
std::shared_ptr<RsIMUData> prev_accel_data;
std::shared_ptr<basalt::Calibration<double>> calib;
rs2::context context;
rs2::config config;
rs2::pipeline pipe;
rs2::sensor sensor;
rs2::pipeline_profile profile;
};
} // namespace basalt

View File

@@ -0,0 +1,167 @@
#pragma once
#include <array>
#include <bitset>
#include <iostream>
#include <unordered_map>
#include <vector>
#include <basalt/utils/common_types.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
namespace basalt {
template <size_t N>
class HashBow {
public:
HashBow(size_t num_bits) : num_bits(num_bits < 32 ? num_bits : 32) {
static_assert(N < 512,
"This implementation of HashBow only supports the descriptor "
"length below 512.");
}
inline FeatureHash compute_hash(const std::bitset<N>& descriptor) const {
FeatureHash res;
for (size_t i = 0; i < num_bits; ++i) {
res[i] = descriptor[word_bit_permutation[i]];
}
return res;
}
inline void compute_bow(const std::vector<std::bitset<N>>& descriptors,
std::vector<FeatureHash>& hashes,
HashBowVector& bow_vector) const {
size_t descriptors_size = descriptors.size();
hashes.resize(descriptors_size);
std::unordered_map<FeatureHash, double> bow_map;
bow_map.clear();
bow_map.reserve(descriptors_size);
for (size_t i = 0; i < descriptors_size; i++) {
hashes[i] = compute_hash(descriptors[i]);
bow_map[hashes[i]] += 1.0;
}
bow_vector.clear();
double l1_sum = 0;
for (const auto& kv : bow_map) {
bow_vector.emplace_back(kv);
l1_sum += std::abs(kv.second);
}
for (auto& kv : bow_vector) {
kv.second /= l1_sum;
}
}
inline void add_to_database(const TimeCamId& tcid,
const HashBowVector& bow_vector) {
for (const auto& kv : bow_vector) {
// std::pair<TimeCamId, double> p = std::make_pair(tcid, kv.second);
inverted_index[kv.first].emplace_back(tcid, kv.second);
}
}
inline void querry_database(
const HashBowVector& bow_vector, size_t num_results,
std::vector<std::pair<TimeCamId, double>>& results,
const int64_t* max_t_ns = nullptr) const {
results.clear();
std::unordered_map<TimeCamId, double> scores;
for (const auto& kv : bow_vector) {
const auto range_it = inverted_index.find(kv.first);
if (range_it != inverted_index.end())
for (const auto& v : range_it->second) {
// if there is a maximum query time select only the frames that have
// timestamp below max_t_ns
if (!max_t_ns || v.first.frame_id < (*max_t_ns))
scores[v.first] += std::abs(kv.second - v.second) -
std::abs(kv.second) - std::abs(v.second);
}
}
results.reserve(scores.size());
for (const auto& kv : scores)
results.emplace_back(kv.first, -kv.second / 2.0);
if (results.size() > num_results) {
std::partial_sort(
results.begin(), results.begin() + num_results, results.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
results.resize(num_results);
}
}
protected:
constexpr static const size_t random_bit_permutation[512] = {
484, 458, 288, 170, 215, 424, 41, 38, 293, 96, 172, 428, 508, 52, 370,
1, 182, 472, 89, 339, 273, 234, 98, 217, 73, 195, 307, 306, 113, 429,
161, 443, 364, 439, 301, 247, 325, 24, 490, 366, 75, 7, 464, 232, 49,
196, 144, 69, 470, 387, 3, 86, 361, 313, 396, 356, 94, 201, 291, 360,
107, 251, 413, 393, 296, 124, 308, 146, 298, 160, 121, 302, 151, 345, 336,
26, 63, 238, 79, 267, 262, 437, 433, 350, 53, 134, 194, 452, 114, 54,
82, 214, 191, 242, 482, 37, 432, 311, 130, 460, 422, 221, 271, 192, 474,
46, 289, 34, 20, 95, 463, 499, 159, 272, 481, 129, 448, 173, 323, 258,
416, 229, 334, 510, 461, 263, 362, 346, 39, 500, 381, 401, 492, 299, 33,
169, 241, 11, 254, 449, 199, 486, 400, 365, 70, 436, 108, 19, 233, 505,
152, 6, 480, 468, 278, 426, 253, 471, 328, 327, 139, 29, 27, 488, 332,
290, 412, 164, 259, 352, 222, 186, 32, 319, 410, 211, 405, 187, 213, 507,
205, 395, 62, 178, 36, 140, 87, 491, 351, 450, 314, 77, 342, 132, 133,
477, 103, 389, 206, 197, 324, 485, 425, 297, 231, 123, 447, 126, 9, 64,
181, 40, 14, 5, 261, 431, 333, 223, 4, 138, 220, 76, 44, 300, 331,
78, 193, 497, 403, 435, 275, 147, 66, 368, 141, 451, 225, 250, 61, 18,
444, 208, 380, 109, 255, 337, 372, 212, 359, 457, 31, 398, 354, 219, 117,
248, 392, 203, 88, 479, 509, 149, 120, 145, 51, 15, 367, 190, 163, 417,
454, 329, 183, 390, 83, 404, 249, 81, 264, 445, 317, 179, 244, 473, 71,
111, 118, 209, 171, 224, 459, 446, 104, 13, 377, 200, 414, 198, 420, 226,
153, 384, 25, 441, 305, 338, 316, 483, 184, 402, 48, 131, 502, 252, 469,
12, 167, 243, 373, 35, 127, 341, 455, 379, 210, 340, 128, 430, 57, 434,
330, 415, 494, 142, 355, 282, 322, 65, 105, 421, 68, 409, 466, 245, 59,
269, 112, 386, 257, 256, 93, 174, 16, 60, 143, 343, 115, 506, 276, 10,
496, 489, 235, 47, 136, 22, 165, 204, 42, 465, 440, 498, 312, 504, 116,
419, 185, 303, 218, 353, 283, 374, 2, 177, 137, 240, 102, 309, 292, 85,
453, 388, 397, 438, 281, 279, 442, 110, 55, 101, 100, 150, 375, 406, 157,
23, 0, 237, 376, 236, 216, 8, 154, 91, 456, 423, 176, 427, 284, 30,
84, 349, 335, 56, 270, 227, 286, 168, 239, 122, 478, 162, 475, 166, 17,
348, 285, 175, 155, 266, 382, 304, 268, 180, 295, 125, 371, 467, 277, 294,
58, 347, 72, 280, 50, 287, 511, 80, 260, 326, 495, 45, 106, 399, 369,
503, 357, 315, 418, 487, 99, 43, 320, 188, 407, 246, 501, 119, 158, 274,
408, 230, 358, 90, 148, 363, 207, 344, 265, 462, 189, 310, 385, 67, 28,
383, 378, 156, 394, 97, 476, 493, 321, 411, 228, 21, 391, 202, 92, 318,
74, 135};
constexpr static std::array<size_t, FEATURE_HASH_MAX_SIZE>
compute_permutation() {
std::array<size_t, FEATURE_HASH_MAX_SIZE> res{};
size_t j = 0;
for (size_t i = 0; i < 512 && j < FEATURE_HASH_MAX_SIZE; ++i) {
if (random_bit_permutation[i] < N) {
res[j] = random_bit_permutation[i];
j++;
}
}
return res;
}
constexpr static const std::array<size_t, FEATURE_HASH_MAX_SIZE>
word_bit_permutation = compute_permutation();
size_t num_bits;
tbb::concurrent_unordered_map<
FeatureHash, tbb::concurrent_vector<std::pair<TimeCamId, double>>,
std::hash<FeatureHash>>
inverted_index;
};
} // namespace basalt

View File

@@ -0,0 +1,178 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <array>
#include <fstream>
#include <iomanip>
#include <memory>
#include <string>
#include <vector>
#include <cereal/archives/binary.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/types/bitset.hpp>
#include <cereal/types/deque.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/set.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/unordered_map.hpp>
#include <cereal/types/utility.hpp>
#include <cereal/types/vector.hpp>
#include <Eigen/Dense>
#include <basalt/utils/sophus_utils.hpp>
#include <basalt/image/image.h>
#include <basalt/utils/assert.h>
#include <basalt/camera/generic_camera.hpp>
#include <basalt/camera/stereographic_param.hpp>
#include <opencv2/highgui/highgui.hpp>
namespace basalt {
struct ImageData {
ImageData() : exposure(0) {}
ManagedImage<uint16_t>::Ptr img;
double exposure;
};
struct Observations {
Eigen::aligned_vector<Eigen::Vector2d> pos;
std::vector<int> id;
};
struct GyroData {
int64_t timestamp_ns;
Eigen::Vector3d data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct AccelData {
int64_t timestamp_ns;
Eigen::Vector3d data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct PoseData {
int64_t timestamp_ns;
Sophus::SE3d data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct MocapPoseData {
int64_t timestamp_ns;
Sophus::SE3d data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct AprilgridCornersData {
int64_t timestamp_ns;
int cam_id;
Eigen::aligned_vector<Eigen::Vector2d> corner_pos;
std::vector<int> corner_id;
};
class VioDataset {
public:
virtual ~VioDataset(){};
virtual size_t get_num_cams() const = 0;
virtual std::vector<int64_t> &get_image_timestamps() = 0;
virtual const Eigen::aligned_vector<AccelData> &get_accel_data() const = 0;
virtual const Eigen::aligned_vector<GyroData> &get_gyro_data() const = 0;
virtual const std::vector<int64_t> &get_gt_timestamps() const = 0;
virtual const Eigen::aligned_vector<Sophus::SE3d> &get_gt_pose_data()
const = 0;
virtual int64_t get_mocap_to_imu_offset_ns() const = 0;
virtual std::vector<ImageData> get_image_data(int64_t t_ns) = 0;
virtual std::vector<cv::Mat> get_image_data_cv_mat(int64_t t_ns) = 0;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
typedef std::shared_ptr<VioDataset> VioDatasetPtr;
class DatasetIoInterface {
public:
virtual void read(const std::string &path) = 0;
virtual void reset() = 0;
virtual VioDatasetPtr get_data() = 0;
virtual ~DatasetIoInterface(){};
};
typedef std::shared_ptr<DatasetIoInterface> DatasetIoInterfacePtr;
class DatasetIoFactory {
public:
static DatasetIoInterfacePtr getDatasetIo(const std::string &dataset_type,
bool load_mocap_as_gt = false);
};
} // namespace basalt
namespace cereal {
template <class Archive>
void serialize(Archive &archive, basalt::ManagedImage<uint8_t> &m) {
archive(m.w);
archive(m.h);
m.Reinitialise(m.w, m.h);
archive(binary_data(m.ptr, m.size()));
}
template <class Archive>
void serialize(Archive &ar, basalt::GyroData &c) {
ar(c.timestamp_ns, c.data);
}
template <class Archive>
void serialize(Archive &ar, basalt::AccelData &c) {
ar(c.timestamp_ns, c.data);
}
} // namespace cereal

View File

@@ -0,0 +1,336 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef DATASET_IO_EUROC_H
#define DATASET_IO_EUROC_H
#include <basalt/io/dataset_io.h>
#include <basalt/utils/filesystem.h>
#include <opencv2/highgui/highgui.hpp>
namespace basalt {
class EurocVioDataset : public VioDataset {
size_t num_cams;
std::string path;
std::vector<int64_t> image_timestamps;
std::unordered_map<int64_t, std::string> image_path;
// vector of images for every timestamp
// assumes vectors size is num_cams for every timestamp with null pointers for
// missing frames
// std::unordered_map<int64_t, std::vector<ImageData>> image_data;
Eigen::aligned_vector<AccelData> accel_data;
Eigen::aligned_vector<GyroData> gyro_data;
std::vector<int64_t> gt_timestamps; // ordered gt timestamps
Eigen::aligned_vector<Sophus::SE3d>
gt_pose_data; // TODO: change to eigen aligned
int64_t mocap_to_imu_offset_ns = 0;
std::vector<std::unordered_map<int64_t, double>> exposure_times;
public:
~EurocVioDataset(){};
size_t get_num_cams() const { return num_cams; }
std::vector<int64_t> &get_image_timestamps() { return image_timestamps; }
const Eigen::aligned_vector<AccelData> &get_accel_data() const {
return accel_data;
}
const Eigen::aligned_vector<GyroData> &get_gyro_data() const {
return gyro_data;
}
const std::vector<int64_t> &get_gt_timestamps() const {
return gt_timestamps;
}
const Eigen::aligned_vector<Sophus::SE3d> &get_gt_pose_data() const {
return gt_pose_data;
}
int64_t get_mocap_to_imu_offset_ns() const { return mocap_to_imu_offset_ns; }
std::vector<ImageData> get_image_data(int64_t t_ns) {
std::vector<ImageData> res(num_cams);
const std::vector<std::string> folder = {"/mav0/cam0/", "/mav0/cam1/"};
for (size_t i = 0; i < num_cams; i++) {
std::string full_image_path =
path + folder[i] + "data/" + image_path[t_ns];
if (fs::exists(full_image_path)) {
cv::Mat img = cv::imread(full_image_path, cv::IMREAD_UNCHANGED);
if (img.type() == CV_8UC1) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
const uint8_t *data_in = img.ptr();
uint16_t *data_out = res[i].img->ptr;
size_t full_size = img.cols * img.rows;
for (size_t i = 0; i < full_size; i++) {
int val = data_in[i];
val = val << 8;
data_out[i] = val;
}
} else if (img.type() == CV_8UC3) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
const uint8_t *data_in = img.ptr();
uint16_t *data_out = res[i].img->ptr;
size_t full_size = img.cols * img.rows;
for (size_t i = 0; i < full_size; i++) {
int val = data_in[i * 3];
val = val << 8;
data_out[i] = val;
}
} else if (img.type() == CV_16UC1) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
std::memcpy(res[i].img->ptr, img.ptr(),
img.cols * img.rows * sizeof(uint16_t));
} else {
std::cerr << "img.fmt.bpp " << img.type() << std::endl;
std::abort();
}
auto exp_it = exposure_times[i].find(t_ns);
if (exp_it != exposure_times[i].end()) {
res[i].exposure = exp_it->second;
}
}
}
return res;
}
// returns a vector of 2 images: for the first and the second camera at the defined timestamp.
std::vector<cv::Mat> get_image_data_cv_mat(int64_t t_ns) {
std::vector<cv::Mat> res(num_cams);
const std::vector<std::string> folder = {"/mav0/cam0/", "/mav0/cam1/"};
for (size_t i = 0; i < num_cams; i++) {
std::string full_image_path =
path + folder[i] + "data/" + image_path[t_ns];
if (fs::exists(full_image_path)) {
cv::Mat img = cv::imread(full_image_path, cv::IMREAD_COLOR);
res.push_back(img);
// The rest of the body from get_image_data() was deleted.
}
}
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
friend class EurocIO;
};
class EurocIO : public DatasetIoInterface {
public:
EurocIO(bool load_mocap_as_gt) : load_mocap_as_gt(load_mocap_as_gt) {}
void read(const std::string &path) {
if (!fs::exists(path))
std::cerr << "No dataset found in " << path << std::endl;
data.reset(new EurocVioDataset);
data->num_cams = 2;
data->path = path;
read_image_timestamps(path + "/mav0/cam0/");
read_imu_data(path + "/mav0/imu0/");
if (!load_mocap_as_gt &&
fs::exists(path + "/mav0/state_groundtruth_estimate0/data.csv")) {
read_gt_data_state(path + "/mav0/state_groundtruth_estimate0/");
} else if (!load_mocap_as_gt && fs::exists(path + "/mav0/gt/data.csv")) {
read_gt_data_pose(path + "/mav0/gt/");
} else if (fs::exists(path + "/mav0/mocap0/data.csv")) {
read_gt_data_pose(path + "/mav0/mocap0/");
}
data->exposure_times.resize(data->num_cams);
if (fs::exists(path + "/mav0/cam0/exposure.csv")) {
std::cout << "Loading exposure times for cam0" << std::endl;
read_exposure(path + "/mav0/cam0/", data->exposure_times[0]);
}
if (fs::exists(path + "/mav0/cam1/exposure.csv")) {
std::cout << "Loading exposure times for cam1" << std::endl;
read_exposure(path + "/mav0/cam1/", data->exposure_times[1]);
}
}
void reset() { data.reset(); }
VioDatasetPtr get_data() { return data; }
private:
void read_exposure(const std::string &path,
std::unordered_map<int64_t, double> &exposure_data) {
exposure_data.clear();
std::ifstream f(path + "exposure.csv");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
char tmp;
int64_t timestamp, exposure_int;
Eigen::Vector3d gyro, accel;
ss >> timestamp >> tmp >> exposure_int;
exposure_data[timestamp] = exposure_int * 1e-9;
}
}
void read_image_timestamps(const std::string &path) {
std::ifstream f(path + "data.csv");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
char tmp;
int64_t t_ns;
std::string path;
ss >> t_ns >> tmp >> path;
data->image_timestamps.emplace_back(t_ns);
data->image_path[t_ns] = path;
}
}
void read_imu_data(const std::string &path) {
data->accel_data.clear();
data->gyro_data.clear();
std::ifstream f(path + "data.csv");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
char tmp;
uint64_t timestamp;
Eigen::Vector3d gyro, accel;
ss >> timestamp >> tmp >> gyro[0] >> tmp >> gyro[1] >> tmp >> gyro[2] >>
tmp >> accel[0] >> tmp >> accel[1] >> tmp >> accel[2];
data->accel_data.emplace_back();
data->accel_data.back().timestamp_ns = timestamp;
data->accel_data.back().data = accel;
data->gyro_data.emplace_back();
data->gyro_data.back().timestamp_ns = timestamp;
data->gyro_data.back().data = gyro;
}
}
void read_gt_data_state(const std::string &path) {
data->gt_timestamps.clear();
data->gt_pose_data.clear();
std::ifstream f(path + "data.csv");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
char tmp;
uint64_t timestamp;
Eigen::Quaterniond q;
Eigen::Vector3d pos, vel, accel_bias, gyro_bias;
ss >> timestamp >> tmp >> pos[0] >> tmp >> pos[1] >> tmp >> pos[2] >>
tmp >> q.w() >> tmp >> q.x() >> tmp >> q.y() >> tmp >> q.z() >> tmp >>
vel[0] >> tmp >> vel[1] >> tmp >> vel[2] >> tmp >> accel_bias[0] >>
tmp >> accel_bias[1] >> tmp >> accel_bias[2] >> tmp >> gyro_bias[0] >>
tmp >> gyro_bias[1] >> tmp >> gyro_bias[2];
data->gt_timestamps.emplace_back(timestamp);
data->gt_pose_data.emplace_back(q, pos);
}
}
void read_gt_data_pose(const std::string &path) {
data->gt_timestamps.clear();
data->gt_pose_data.clear();
std::ifstream f(path + "data.csv");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
char tmp;
uint64_t timestamp;
Eigen::Quaterniond q;
Eigen::Vector3d pos;
ss >> timestamp >> tmp >> pos[0] >> tmp >> pos[1] >> tmp >> pos[2] >>
tmp >> q.w() >> tmp >> q.x() >> tmp >> q.y() >> tmp >> q.z();
data->gt_timestamps.emplace_back(timestamp);
data->gt_pose_data.emplace_back(q, pos);
}
}
std::shared_ptr<EurocVioDataset> data;
bool load_mocap_as_gt;
}; // namespace basalt
} // namespace basalt
#endif // DATASET_IO_H

View File

@@ -0,0 +1,220 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/io/dataset_io.h>
#include <basalt/utils/filesystem.h>
#include <opencv2/highgui/highgui.hpp>
namespace basalt {
class KittiVioDataset : public VioDataset {
size_t num_cams;
std::string path;
std::vector<int64_t> image_timestamps;
std::unordered_map<int64_t, std::string> image_path;
// vector of images for every timestamp
// assumes vectors size is num_cams for every timestamp with null pointers for
// missing frames
// std::unordered_map<int64_t, std::vector<ImageData>> image_data;
Eigen::aligned_vector<AccelData> accel_data;
Eigen::aligned_vector<GyroData> gyro_data;
std::vector<int64_t> gt_timestamps; // ordered gt timestamps
Eigen::aligned_vector<Sophus::SE3d>
gt_pose_data; // TODO: change to eigen aligned
int64_t mocap_to_imu_offset_ns;
public:
~KittiVioDataset(){};
size_t get_num_cams() const { return num_cams; }
std::vector<int64_t> &get_image_timestamps() { return image_timestamps; }
const Eigen::aligned_vector<AccelData> &get_accel_data() const {
return accel_data;
}
const Eigen::aligned_vector<GyroData> &get_gyro_data() const {
return gyro_data;
}
const std::vector<int64_t> &get_gt_timestamps() const {
return gt_timestamps;
}
const Eigen::aligned_vector<Sophus::SE3d> &get_gt_pose_data() const {
return gt_pose_data;
}
int64_t get_mocap_to_imu_offset_ns() const { return mocap_to_imu_offset_ns; }
std::vector<ImageData> get_image_data(int64_t t_ns) {
std::vector<ImageData> res(num_cams);
const std::vector<std::string> folder = {"/image_0/", "/image_1/"};
for (size_t i = 0; i < num_cams; i++) {
std::string full_image_path = path + folder[i] + image_path[t_ns];
if (fs::exists(full_image_path)) {
cv::Mat img = cv::imread(full_image_path, cv::IMREAD_UNCHANGED);
if (img.type() == CV_8UC1) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
const uint8_t *data_in = img.ptr();
uint16_t *data_out = res[i].img->ptr;
size_t full_size = img.cols * img.rows;
for (size_t i = 0; i < full_size; i++) {
int val = data_in[i];
val = val << 8;
data_out[i] = val;
}
} else {
std::cerr << "img.fmt.bpp " << img.type() << std::endl;
std::abort();
}
}
}
return res;
}
std::vector<cv::Mat> get_image_data_cv_mat(int64_t t_ns) {
std::vector<cv::Mat> res(num_cams);
const std::vector<std::string> folder = {"/image_0/", "/image_1/"};
for (size_t i = 0; i < num_cams; i++) {
std::string full_image_path =
path + folder[i] + image_path[t_ns];
if (fs::exists(full_image_path)) {
cv::Mat img = cv::imread(full_image_path, cv::IMREAD_GRAYSCALE);
res.push_back(img);
// The rest of the body from get_image_data() was deleted.
}
}
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
friend class KittiIO;
};
class KittiIO : public DatasetIoInterface {
public:
KittiIO() {}
void read(const std::string &path) {
if (!fs::exists(path))
std::cerr << "No dataset found in " << path << std::endl;
data.reset(new KittiVioDataset);
data->num_cams = 2;
data->path = path;
read_image_timestamps(path + "/times.txt");
if (fs::exists(path + "/poses.txt")) {
read_gt_data_pose(path + "/poses.txt");
}
}
void reset() { data.reset(); }
VioDatasetPtr get_data() { return data; }
private:
void read_image_timestamps(const std::string &path) {
std::ifstream f(path);
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
double t_s;
ss >> t_s;
int64_t t_ns = t_s * 1e9;
std::stringstream ss1;
ss1 << std::setfill('0') << std::setw(6) << data->image_timestamps.size()
<< ".png";
data->image_timestamps.emplace_back(t_ns);
data->image_path[t_ns] = ss1.str();
}
}
void read_gt_data_pose(const std::string &path) {
data->gt_timestamps.clear();
data->gt_pose_data.clear();
int i = 0;
std::ifstream f(path);
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
Eigen::Matrix3d rot;
Eigen::Vector3d pos;
ss >> rot(0, 0) >> rot(0, 1) >> rot(0, 2) >> pos[0] >> rot(1, 0) >>
rot(1, 1) >> rot(1, 2) >> pos[1] >> rot(2, 0) >> rot(2, 1) >>
rot(2, 2) >> pos[2];
data->gt_timestamps.emplace_back(data->image_timestamps[i]);
data->gt_pose_data.emplace_back(Eigen::Quaterniond(rot), pos);
i++;
}
}
std::shared_ptr<KittiVioDataset> data;
};
} // namespace basalt

View File

@@ -0,0 +1,406 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef DATASET_IO_ROSBAG_H
#define DATASET_IO_ROSBAG_H
#include <mutex>
#include <optional>
#include <basalt/io/dataset_io.h>
// Hack to access private functions
#define private public
#include <rosbag/bag.h>
#include <rosbag/view.h>
#undef private
#include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/TransformStamped.h>
#include <sensor_msgs/Image.h>
#include <sensor_msgs/Imu.h>
#include <basalt/utils/filesystem.h>
namespace basalt {
class RosbagVioDataset : public VioDataset {
std::shared_ptr<rosbag::Bag> bag;
std::mutex m;
size_t num_cams;
std::vector<int64_t> image_timestamps;
// vector of images for every timestamp
// assumes vectors size is num_cams for every timestamp with null pointers for
// missing frames
std::unordered_map<int64_t, std::vector<std::optional<rosbag::IndexEntry>>>
image_data_idx;
Eigen::aligned_vector<AccelData> accel_data;
Eigen::aligned_vector<GyroData> gyro_data;
std::vector<int64_t> gt_timestamps; // ordered gt timestamps
Eigen::aligned_vector<Sophus::SE3d>
gt_pose_data; // TODO: change to eigen aligned
int64_t mocap_to_imu_offset_ns;
public:
~RosbagVioDataset() {}
size_t get_num_cams() const { return num_cams; }
std::vector<int64_t> &get_image_timestamps() { return image_timestamps; }
const Eigen::aligned_vector<AccelData> &get_accel_data() const {
return accel_data;
}
const Eigen::aligned_vector<GyroData> &get_gyro_data() const {
return gyro_data;
}
const std::vector<int64_t> &get_gt_timestamps() const {
return gt_timestamps;
}
const Eigen::aligned_vector<Sophus::SE3d> &get_gt_pose_data() const {
return gt_pose_data;
}
int64_t get_mocap_to_imu_offset_ns() const { return mocap_to_imu_offset_ns; }
std::vector<ImageData> get_image_data(int64_t t_ns) {
std::vector<ImageData> res(num_cams);
auto it = image_data_idx.find(t_ns);
if (it != image_data_idx.end())
for (size_t i = 0; i < num_cams; i++) {
ImageData &id = res[i];
if (!it->second[i].has_value()) continue;
m.lock();
sensor_msgs::ImageConstPtr img_msg =
bag->instantiateBuffer<sensor_msgs::Image>(*it->second[i]);
m.unlock();
// std::cerr << "img_msg->width " << img_msg->width << "
// img_msg->height "
// << img_msg->height << std::endl;
id.img.reset(
new ManagedImage<uint16_t>(img_msg->width, img_msg->height));
if (!img_msg->header.frame_id.empty() &&
std::isdigit(img_msg->header.frame_id[0])) {
id.exposure = std::stol(img_msg->header.frame_id) * 1e-9;
} else {
id.exposure = -1;
}
if (img_msg->encoding == "mono8") {
const uint8_t *data_in = img_msg->data.data();
uint16_t *data_out = id.img->ptr;
for (size_t i = 0; i < img_msg->data.size(); i++) {
int val = data_in[i];
val = val << 8;
data_out[i] = val;
}
} else if (img_msg->encoding == "mono16") {
std::memcpy(id.img->ptr, img_msg->data.data(), img_msg->data.size());
} else {
std::cerr << "Encoding " << img_msg->encoding << " is not supported."
<< std::endl;
std::abort();
}
}
return res;
}
std::vector<cv::Mat> get_image_data_cv_mat(int64_t t_ns) {
cv::Mat mat = cv::Mat(1, 10, CV_32F, 1);
return mat;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
friend class RosbagIO;
};
class RosbagIO : public DatasetIoInterface {
public:
RosbagIO() {}
void read(const std::string &path) {
if (!fs::exists(path))
std::cerr << "No dataset found in " << path << std::endl;
data.reset(new RosbagVioDataset);
data->bag.reset(new rosbag::Bag);
data->bag->open(path, rosbag::bagmode::Read);
rosbag::View view(*data->bag);
// get topics
std::vector<const rosbag::ConnectionInfo *> connection_infos =
view.getConnections();
std::set<std::string> cam_topics;
std::string imu_topic;
std::string mocap_topic;
std::string point_topic;
for (const rosbag::ConnectionInfo *info : connection_infos) {
// if (info->topic.substr(0, 4) == std::string("/cam")) {
// cam_topics.insert(info->topic);
// } else if (info->topic.substr(0, 4) == std::string("/imu")) {
// imu_topic = info->topic;
// } else if (info->topic.substr(0, 5) == std::string("/vrpn") ||
// info->topic.substr(0, 6) == std::string("/vicon")) {
// mocap_topic = info->topic;
// }
if (info->datatype == std::string("sensor_msgs/Image")) {
cam_topics.insert(info->topic);
} else if (info->datatype == std::string("sensor_msgs/Imu") &&
info->topic.rfind("/fcu", 0) != 0) {
imu_topic = info->topic;
} else if (info->datatype ==
std::string("geometry_msgs/TransformStamped") ||
info->datatype == std::string("geometry_msgs/PoseStamped")) {
mocap_topic = info->topic;
} else if (info->datatype == std::string("geometry_msgs/PointStamped")) {
point_topic = info->topic;
}
}
std::cout << "imu_topic: " << imu_topic << std::endl;
std::cout << "mocap_topic: " << mocap_topic << std::endl;
std::cout << "cam_topics: ";
for (const std::string &s : cam_topics) std::cout << s << " ";
std::cout << std::endl;
std::map<std::string, int> topic_to_id;
int idx = 0;
for (const std::string &s : cam_topics) {
topic_to_id[s] = idx;
idx++;
}
data->num_cams = cam_topics.size();
int num_msgs = 0;
int64_t min_time = std::numeric_limits<int64_t>::max();
int64_t max_time = std::numeric_limits<int64_t>::min();
std::vector<geometry_msgs::TransformStampedConstPtr> mocap_msgs;
std::vector<geometry_msgs::PointStampedConstPtr> point_msgs;
std::vector<int64_t>
system_to_imu_offset_vec; // t_imu = t_system + system_to_imu_offset
std::vector<int64_t> system_to_mocap_offset_vec; // t_mocap = t_system +
// system_to_mocap_offset
std::set<int64_t> image_timestamps;
for (const rosbag::MessageInstance &m : view) {
const std::string &topic = m.getTopic();
if (cam_topics.find(topic) != cam_topics.end()) {
sensor_msgs::ImageConstPtr img_msg =
m.instantiate<sensor_msgs::Image>();
int64_t timestamp_ns = img_msg->header.stamp.toNSec();
auto &img_vec = data->image_data_idx[timestamp_ns];
if (img_vec.size() == 0) img_vec.resize(data->num_cams);
img_vec[topic_to_id.at(topic)] = m.index_entry_;
image_timestamps.insert(timestamp_ns);
min_time = std::min(min_time, timestamp_ns);
max_time = std::max(max_time, timestamp_ns);
}
if (imu_topic == topic) {
sensor_msgs::ImuConstPtr imu_msg = m.instantiate<sensor_msgs::Imu>();
int64_t time = imu_msg->header.stamp.toNSec();
data->accel_data.emplace_back();
data->accel_data.back().timestamp_ns = time;
data->accel_data.back().data = Eigen::Vector3d(
imu_msg->linear_acceleration.x, imu_msg->linear_acceleration.y,
imu_msg->linear_acceleration.z);
data->gyro_data.emplace_back();
data->gyro_data.back().timestamp_ns = time;
data->gyro_data.back().data = Eigen::Vector3d(
imu_msg->angular_velocity.x, imu_msg->angular_velocity.y,
imu_msg->angular_velocity.z);
min_time = std::min(min_time, time);
max_time = std::max(max_time, time);
int64_t msg_arrival_time = m.getTime().toNSec();
system_to_imu_offset_vec.push_back(time - msg_arrival_time);
}
if (mocap_topic == topic) {
geometry_msgs::TransformStampedConstPtr mocap_msg =
m.instantiate<geometry_msgs::TransformStamped>();
// Try different message type if instantiate did not work
if (!mocap_msg) {
geometry_msgs::PoseStampedConstPtr mocap_pose_msg =
m.instantiate<geometry_msgs::PoseStamped>();
geometry_msgs::TransformStampedPtr mocap_new_msg(
new geometry_msgs::TransformStamped);
mocap_new_msg->header = mocap_pose_msg->header;
mocap_new_msg->transform.rotation = mocap_pose_msg->pose.orientation;
mocap_new_msg->transform.translation.x =
mocap_pose_msg->pose.position.x;
mocap_new_msg->transform.translation.y =
mocap_pose_msg->pose.position.y;
mocap_new_msg->transform.translation.z =
mocap_pose_msg->pose.position.z;
mocap_msg = mocap_new_msg;
}
int64_t time = mocap_msg->header.stamp.toNSec();
mocap_msgs.push_back(mocap_msg);
int64_t msg_arrival_time = m.getTime().toNSec();
system_to_mocap_offset_vec.push_back(time - msg_arrival_time);
}
if (point_topic == topic) {
geometry_msgs::PointStampedConstPtr mocap_msg =
m.instantiate<geometry_msgs::PointStamped>();
int64_t time = mocap_msg->header.stamp.toNSec();
point_msgs.push_back(mocap_msg);
int64_t msg_arrival_time = m.getTime().toNSec();
system_to_mocap_offset_vec.push_back(time - msg_arrival_time);
}
num_msgs++;
}
data->image_timestamps.clear();
data->image_timestamps.insert(data->image_timestamps.begin(),
image_timestamps.begin(),
image_timestamps.end());
if (system_to_mocap_offset_vec.size() > 0) {
int64_t system_to_imu_offset =
system_to_imu_offset_vec[system_to_imu_offset_vec.size() / 2];
int64_t system_to_mocap_offset =
system_to_mocap_offset_vec[system_to_mocap_offset_vec.size() / 2];
data->mocap_to_imu_offset_ns =
system_to_imu_offset - system_to_mocap_offset;
}
data->gt_pose_data.clear();
data->gt_timestamps.clear();
if (!mocap_msgs.empty())
for (size_t i = 0; i < mocap_msgs.size() - 1; i++) {
auto mocap_msg = mocap_msgs[i];
int64_t time = mocap_msg->header.stamp.toNSec();
Eigen::Quaterniond q(
mocap_msg->transform.rotation.w, mocap_msg->transform.rotation.x,
mocap_msg->transform.rotation.y, mocap_msg->transform.rotation.z);
Eigen::Vector3d t(mocap_msg->transform.translation.x,
mocap_msg->transform.translation.y,
mocap_msg->transform.translation.z);
int64_t timestamp_ns = time + data->mocap_to_imu_offset_ns;
data->gt_timestamps.emplace_back(timestamp_ns);
data->gt_pose_data.emplace_back(q, t);
}
if (!point_msgs.empty())
for (size_t i = 0; i < point_msgs.size() - 1; i++) {
auto point_msg = point_msgs[i];
int64_t time = point_msg->header.stamp.toNSec();
Eigen::Vector3d t(point_msg->point.x, point_msg->point.y,
point_msg->point.z);
int64_t timestamp_ns = time; // + data->mocap_to_imu_offset_ns;
data->gt_timestamps.emplace_back(timestamp_ns);
data->gt_pose_data.emplace_back(Sophus::SO3d(), t);
}
std::cout << "Total number of messages: " << num_msgs << std::endl;
std::cout << "Image size: " << data->image_data_idx.size() << std::endl;
std::cout << "Min time: " << min_time << " max time: " << max_time
<< " mocap to imu offset: " << data->mocap_to_imu_offset_ns
<< std::endl;
std::cout << "Number of mocap poses: " << data->gt_timestamps.size()
<< std::endl;
}
void reset() { data.reset(); }
VioDatasetPtr get_data() {
// return std::dynamic_pointer_cast<VioDataset>(data);
return data;
}
private:
std::shared_ptr<RosbagVioDataset> data;
};
} // namespace basalt
#endif // DATASET_IO_ROSBAG_H

View File

@@ -0,0 +1,316 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/io/dataset_io.h>
#include <basalt/utils/filesystem.h>
#include <opencv2/highgui/highgui.hpp>
namespace basalt {
class UzhVioDataset : public VioDataset {
size_t num_cams;
std::string path;
std::vector<int64_t> image_timestamps;
std::unordered_map<int64_t, std::string> left_image_path;
std::unordered_map<int64_t, std::string> right_image_path;
// vector of images for every timestamp
// assumes vectors size is num_cams for every timestamp with null pointers for
// missing frames
// std::unordered_map<int64_t, std::vector<ImageData>> image_data;
Eigen::aligned_vector<AccelData> accel_data;
Eigen::aligned_vector<GyroData> gyro_data;
std::vector<int64_t> gt_timestamps; // ordered gt timestamps
Eigen::aligned_vector<Sophus::SE3d>
gt_pose_data; // TODO: change to eigen aligned
int64_t mocap_to_imu_offset_ns = 0;
std::vector<std::unordered_map<int64_t, double>> exposure_times;
public:
~UzhVioDataset(){};
size_t get_num_cams() const { return num_cams; }
std::vector<int64_t> &get_image_timestamps() { return image_timestamps; }
const Eigen::aligned_vector<AccelData> &get_accel_data() const {
return accel_data;
}
const Eigen::aligned_vector<GyroData> &get_gyro_data() const {
return gyro_data;
}
const std::vector<int64_t> &get_gt_timestamps() const {
return gt_timestamps;
}
const Eigen::aligned_vector<Sophus::SE3d> &get_gt_pose_data() const {
return gt_pose_data;
}
int64_t get_mocap_to_imu_offset_ns() const { return mocap_to_imu_offset_ns; }
std::vector<ImageData> get_image_data(int64_t t_ns) {
std::vector<ImageData> res(num_cams);
for (size_t i = 0; i < num_cams; i++) {
std::string full_image_path =
path + "/" +
(i == 0 ? left_image_path.at(t_ns) : right_image_path.at(t_ns));
if (fs::exists(full_image_path)) {
cv::Mat img = cv::imread(full_image_path, cv::IMREAD_UNCHANGED);
if (img.type() == CV_8UC1) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
const uint8_t *data_in = img.ptr();
uint16_t *data_out = res[i].img->ptr;
size_t full_size = img.cols * img.rows;
for (size_t i = 0; i < full_size; i++) {
int val = data_in[i];
val = val << 8;
data_out[i] = val;
}
} else if (img.type() == CV_8UC3) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
const uint8_t *data_in = img.ptr();
uint16_t *data_out = res[i].img->ptr;
size_t full_size = img.cols * img.rows;
for (size_t i = 0; i < full_size; i++) {
int val = data_in[i * 3];
val = val << 8;
data_out[i] = val;
}
} else if (img.type() == CV_16UC1) {
res[i].img.reset(new ManagedImage<uint16_t>(img.cols, img.rows));
std::memcpy(res[i].img->ptr, img.ptr(),
img.cols * img.rows * sizeof(uint16_t));
} else {
std::cerr << "img.fmt.bpp " << img.type() << std::endl;
std::abort();
}
auto exp_it = exposure_times[i].find(t_ns);
if (exp_it != exposure_times[i].end()) {
res[i].exposure = exp_it->second;
}
}
}
return res;
}
std::vector<cv::Mat> get_image_data_cv_mat(int64_t t_ns) {
cv::Mat mat = cv::Mat(1, 10, CV_32F, 1);
return mat;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
friend class UzhIO;
};
class UzhIO : public DatasetIoInterface {
public:
UzhIO() {}
void read(const std::string &path) {
if (!fs::exists(path))
std::cerr << "No dataset found in " << path << std::endl;
data.reset(new UzhVioDataset);
data->num_cams = 2;
data->path = path;
read_image_timestamps(path);
std::cout << "Loaded " << data->get_image_timestamps().size()
<< " timestamps, " << data->left_image_path.size()
<< " left images and " << data->right_image_path.size()
<< std::endl;
// {
// int64_t t_ns = data->get_image_timestamps()[0];
// std::cout << t_ns << " " << data->left_image_path.at(t_ns) << " "
// << data->right_image_path.at(t_ns) << std::endl;
// }
read_imu_data(path + "/imu.txt");
std::cout << "Loaded " << data->get_gyro_data().size() << " imu msgs."
<< std::endl;
if (fs::exists(path + "/groundtruth.txt")) {
read_gt_data_pose(path + "/groundtruth.txt");
}
data->exposure_times.resize(data->num_cams);
}
void reset() { data.reset(); }
VioDatasetPtr get_data() { return data; }
private:
void read_exposure(const std::string &path,
std::unordered_map<int64_t, double> &exposure_data) {
exposure_data.clear();
std::ifstream f(path + "exposure.csv");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
char tmp;
int64_t timestamp, exposure_int;
Eigen::Vector3d gyro, accel;
ss >> timestamp >> tmp >> exposure_int;
exposure_data[timestamp] = exposure_int * 1e-9;
}
}
void read_image_timestamps(const std::string &path) {
{
std::ifstream f(path + "/left_images.txt");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
int tmp;
double t_s;
std::string path;
ss >> tmp >> t_s >> path;
int64_t t_ns = t_s * 1e9;
data->image_timestamps.emplace_back(t_ns);
data->left_image_path[t_ns] = path;
}
}
{
std::ifstream f(path + "/right_images.txt");
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
int tmp;
double t_s;
std::string path;
ss >> tmp >> t_s >> path;
int64_t t_ns = t_s * 1e9;
data->right_image_path[t_ns] = path;
}
}
}
void read_imu_data(const std::string &path) {
data->accel_data.clear();
data->gyro_data.clear();
std::ifstream f(path);
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
int tmp;
double timestamp;
Eigen::Vector3d gyro, accel;
ss >> tmp >> timestamp >> gyro[0] >> gyro[1] >> gyro[2] >> accel[0] >>
accel[1] >> accel[2];
int64_t t_ns = timestamp * 1e9;
data->accel_data.emplace_back();
data->accel_data.back().timestamp_ns = t_ns;
data->accel_data.back().data = accel;
data->gyro_data.emplace_back();
data->gyro_data.back().timestamp_ns = t_ns;
data->gyro_data.back().data = gyro;
}
}
void read_gt_data_pose(const std::string &path) {
data->gt_timestamps.clear();
data->gt_pose_data.clear();
std::ifstream f(path);
std::string line;
while (std::getline(f, line)) {
if (line[0] == '#') continue;
std::stringstream ss(line);
int tmp;
double timestamp;
Eigen::Quaterniond q;
Eigen::Vector3d pos;
ss >> tmp >> timestamp >> pos[0] >> pos[1] >> pos[2] >> q.x() >> q.y() >>
q.z() >> q.w();
int64_t t_ns = timestamp * 1e9;
data->gt_timestamps.emplace_back(t_ns);
data->gt_pose_data.emplace_back(q, pos);
}
}
std::shared_ptr<UzhVioDataset> data;
};
} // namespace basalt

View File

@@ -0,0 +1,78 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <memory>
#include <thread>
#include <basalt/utils/imu_types.h>
namespace basalt {
class MargDataSaver {
public:
using Ptr = std::shared_ptr<MargDataSaver>;
MargDataSaver(const std::string& path);
~MargDataSaver() {
saving_thread->join();
saving_img_thread->join();
}
tbb::concurrent_bounded_queue<MargData::Ptr> in_marg_queue;
private:
std::shared_ptr<std::thread> saving_thread;
std::shared_ptr<std::thread> saving_img_thread;
tbb::concurrent_bounded_queue<OpticalFlowResult::Ptr> save_image_queue;
};
class MargDataLoader {
public:
using Ptr = std::shared_ptr<MargDataLoader>;
MargDataLoader();
void start(const std::string& path);
~MargDataLoader() {
if (processing_thread) processing_thread->join();
}
tbb::concurrent_bounded_queue<MargData::Ptr>* out_marg_queue;
private:
std::shared_ptr<std::thread> processing_thread;
};
} // namespace basalt

View File

@@ -0,0 +1,89 @@
#pragma once
#include <unordered_map>
#include <Eigen/Dense>
#include <basalt/utils/cast_utils.hpp>
namespace basalt {
// TODO: expand IndexedBlocks to small class / struct that also holds info on
// block size and number of blocks, so we don't have to pass it around all the
// time and we can directly implement things link adding diagonal and matrix
// vector products in this sparse block diagonal matrix.
// map of camera index to block; used to represent sparse block diagonal matrix
template <typename Scalar>
using IndexedBlocks =
std::unordered_map<size_t,
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>>;
// scale dimensions of JTJ as you would do for jacobian scaling of J beforehand,
// with diagonal scaling matrix D: For jacobain we would use JD, so for JTJ we
// use DJTJD.
template <typename Scalar>
void scale_jacobians(
IndexedBlocks<Scalar>& block_diagonal, size_t num_blocks, size_t block_size,
const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& scaling_vector) {
BASALT_ASSERT(num_blocks * block_size ==
unsigned_cast(scaling_vector.size()));
for (auto& [cam_idx, block] : block_diagonal) {
auto D =
scaling_vector.segment(block_size * cam_idx, block_size).asDiagonal();
block = D * block * D;
}
}
// add diagonal to block-diagonal matrix; missing blocks are assumed to be 0
template <typename Scalar>
void add_diagonal(IndexedBlocks<Scalar>& block_diagonal, size_t num_blocks,
size_t block_size,
const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& diagonal) {
BASALT_ASSERT(num_blocks * block_size == unsigned_cast(diagonal.size()));
for (size_t idx = 0; idx < num_blocks; ++idx) {
auto [it, is_new] = block_diagonal.try_emplace(idx);
if (is_new) {
it->second = diagonal.segment(block_size * idx, block_size).asDiagonal();
} else {
it->second += diagonal.segment(block_size * idx, block_size).asDiagonal();
}
}
}
// sum up diagonal blocks in hash map for parallel reduction
template <typename Scalar>
class BlockDiagonalAccumulator {
public:
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
inline void add(size_t idx, MatX&& block) {
auto [it, is_new] = block_diagonal_.try_emplace(idx);
if (is_new) {
it->second = std::move(block);
} else {
it->second += block;
}
}
inline void add_diag(size_t num_blocks, size_t block_size,
const VecX& diagonal) {
add_diagonal(block_diagonal_, num_blocks, block_size, diagonal);
}
inline void join(BlockDiagonalAccumulator& b) {
for (auto& [k, v] : b.block_diagonal_) {
auto [it, is_new] = block_diagonal_.try_emplace(k);
if (is_new) {
it->second = std::move(v);
} else {
it->second += v;
}
}
}
IndexedBlocks<Scalar> block_diagonal_;
};
} // namespace basalt

View File

@@ -0,0 +1,235 @@
#pragma once
#include <basalt/imu/preintegration.h>
#include <basalt/optimization/accumulator.h>
#include <basalt/utils/imu_types.h>
namespace basalt {
template <class Scalar_>
class ImuBlock {
public:
using Scalar = Scalar_;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
ImuBlock(const IntegratedImuMeasurement<Scalar>* meas,
const ImuLinData<Scalar>* imu_lin_data, const AbsOrderMap& aom)
: imu_meas(meas), imu_lin_data(imu_lin_data), aom(aom) {
Jp.resize(POSE_VEL_BIAS_SIZE, 2 * POSE_VEL_BIAS_SIZE);
r.resize(POSE_VEL_BIAS_SIZE);
}
Scalar linearizeImu(
const Eigen::aligned_map<int64_t, PoseVelBiasStateWithLin<Scalar>>&
frame_states) {
Jp.setZero();
r.setZero();
const int64_t start_t = imu_meas->get_start_t_ns();
const int64_t end_t = imu_meas->get_start_t_ns() + imu_meas->get_dt_ns();
const size_t start_idx = 0;
const size_t end_idx = POSE_VEL_BIAS_SIZE;
PoseVelBiasStateWithLin<Scalar> start_state = frame_states.at(start_t);
PoseVelBiasStateWithLin<Scalar> end_state = frame_states.at(end_t);
typename IntegratedImuMeasurement<Scalar>::MatNN d_res_d_start, d_res_d_end;
typename IntegratedImuMeasurement<Scalar>::MatN3 d_res_d_bg, d_res_d_ba;
typename PoseVelState<Scalar>::VecN res = imu_meas->residual(
start_state.getStateLin(), imu_lin_data->g, end_state.getStateLin(),
start_state.getStateLin().bias_gyro,
start_state.getStateLin().bias_accel, &d_res_d_start, &d_res_d_end,
&d_res_d_bg, &d_res_d_ba);
if (start_state.isLinearized() || end_state.isLinearized()) {
res = imu_meas->residual(
start_state.getState(), imu_lin_data->g, end_state.getState(),
start_state.getState().bias_gyro, start_state.getState().bias_accel);
}
// error
Scalar imu_error =
Scalar(0.5) * (imu_meas->get_sqrt_cov_inv() * res).squaredNorm();
// imu residual linearization
Jp.template block<9, 9>(0, start_idx) =
imu_meas->get_sqrt_cov_inv() * d_res_d_start;
Jp.template block<9, 9>(0, end_idx) =
imu_meas->get_sqrt_cov_inv() * d_res_d_end;
Jp.template block<9, 3>(0, start_idx + 9) =
imu_meas->get_sqrt_cov_inv() * d_res_d_bg;
Jp.template block<9, 3>(0, start_idx + 12) =
imu_meas->get_sqrt_cov_inv() * d_res_d_ba;
r.template segment<9>(0) = imu_meas->get_sqrt_cov_inv() * res;
// difference between biases
Scalar dt = imu_meas->get_dt_ns() * Scalar(1e-9);
Vec3 gyro_bias_weight_dt =
imu_lin_data->gyro_bias_weight_sqrt / std::sqrt(dt);
Vec3 res_bg =
start_state.getState().bias_gyro - end_state.getState().bias_gyro;
Jp.template block<3, 3>(9, start_idx + 9) =
gyro_bias_weight_dt.asDiagonal();
Jp.template block<3, 3>(9, end_idx + 9) =
(-gyro_bias_weight_dt).asDiagonal();
r.template segment<3>(9) += gyro_bias_weight_dt.asDiagonal() * res_bg;
Scalar bg_error =
Scalar(0.5) * (gyro_bias_weight_dt.asDiagonal() * res_bg).squaredNorm();
Vec3 accel_bias_weight_dt =
imu_lin_data->accel_bias_weight_sqrt / std::sqrt(dt);
Vec3 res_ba =
start_state.getState().bias_accel - end_state.getState().bias_accel;
Jp.template block<3, 3>(12, start_idx + 12) =
accel_bias_weight_dt.asDiagonal();
Jp.template block<3, 3>(12, end_idx + 12) =
(-accel_bias_weight_dt).asDiagonal();
r.template segment<3>(12) += accel_bias_weight_dt.asDiagonal() * res_ba;
Scalar ba_error =
Scalar(0.5) *
(accel_bias_weight_dt.asDiagonal() * res_ba).squaredNorm();
return imu_error + bg_error + ba_error;
}
void add_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r, size_t row_start_idx) const {
int64_t start_t = imu_meas->get_start_t_ns();
int64_t end_t = imu_meas->get_start_t_ns() + imu_meas->get_dt_ns();
const size_t start_idx = aom.abs_order_map.at(start_t).first;
const size_t end_idx = aom.abs_order_map.at(end_t).first;
Q2Jp.template block<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(row_start_idx,
start_idx) +=
Jp.template topLeftCorner<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>();
Q2Jp.template block<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(row_start_idx,
end_idx) +=
Jp.template topRightCorner<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>();
Q2r.template segment<POSE_VEL_BIAS_SIZE>(row_start_idx) += r;
}
void add_dense_H_b(DenseAccumulator<Scalar>& accum) const {
int64_t start_t = imu_meas->get_start_t_ns();
int64_t end_t = imu_meas->get_start_t_ns() + imu_meas->get_dt_ns();
const size_t start_idx = aom.abs_order_map.at(start_t).first;
const size_t end_idx = aom.abs_order_map.at(end_t).first;
const MatX H = Jp.transpose() * Jp;
const VecX b = Jp.transpose() * r;
accum.template addH<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
start_idx, start_idx,
H.template block<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(0, 0));
accum.template addH<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
end_idx, start_idx,
H.template block<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
POSE_VEL_BIAS_SIZE, 0));
accum.template addH<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
start_idx, end_idx,
H.template block<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
0, POSE_VEL_BIAS_SIZE));
accum.template addH<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
end_idx, end_idx,
H.template block<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>(
POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE));
accum.template addB<POSE_VEL_BIAS_SIZE>(
start_idx, b.template segment<POSE_VEL_BIAS_SIZE>(0));
accum.template addB<POSE_VEL_BIAS_SIZE>(
end_idx, b.template segment<POSE_VEL_BIAS_SIZE>(POSE_VEL_BIAS_SIZE));
}
void scaleJp_cols(const VecX& jacobian_scaling) {
int64_t start_t = imu_meas->get_start_t_ns();
int64_t end_t = imu_meas->get_start_t_ns() + imu_meas->get_dt_ns();
const size_t start_idx = aom.abs_order_map.at(start_t).first;
const size_t end_idx = aom.abs_order_map.at(end_t).first;
Jp.template topLeftCorner<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>() *=
jacobian_scaling.template segment<POSE_VEL_BIAS_SIZE>(start_idx)
.asDiagonal();
Jp.template topRightCorner<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>() *=
jacobian_scaling.template segment<POSE_VEL_BIAS_SIZE>(end_idx)
.asDiagonal();
}
void addJp_diag2(VecX& res) const {
int64_t start_t = imu_meas->get_start_t_ns();
int64_t end_t = imu_meas->get_start_t_ns() + imu_meas->get_dt_ns();
const size_t start_idx = aom.abs_order_map.at(start_t).first;
const size_t end_idx = aom.abs_order_map.at(end_t).first;
res.template segment<POSE_VEL_BIAS_SIZE>(start_idx) +=
Jp.template topLeftCorner<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>()
.colwise()
.squaredNorm();
res.template segment<POSE_VEL_BIAS_SIZE>(end_idx) +=
Jp.template topRightCorner<POSE_VEL_BIAS_SIZE, POSE_VEL_BIAS_SIZE>()
.colwise()
.squaredNorm();
}
void backSubstitute(const VecX& pose_inc, Scalar& l_diff) {
int64_t start_t = imu_meas->get_start_t_ns();
int64_t end_t = imu_meas->get_start_t_ns() + imu_meas->get_dt_ns();
const size_t start_idx = aom.abs_order_map.at(start_t).first;
const size_t end_idx = aom.abs_order_map.at(end_t).first;
VecX pose_inc_reduced(2 * POSE_VEL_BIAS_SIZE);
pose_inc_reduced.template head<POSE_VEL_BIAS_SIZE>() =
pose_inc.template segment<POSE_VEL_BIAS_SIZE>(start_idx);
pose_inc_reduced.template tail<POSE_VEL_BIAS_SIZE>() =
pose_inc.template segment<POSE_VEL_BIAS_SIZE>(end_idx);
// We want to compute the model cost change. The model function is
//
// L(inc) = F(x) + incT JT r + 0.5 incT JT J inc
//
// and thus the expect decrease in cost for the computed increment is
//
// l_diff = L(0) - L(inc)
// = - incT JT r - 0.5 incT JT J inc.
// = - incT JT (r + 0.5 J inc)
// = - (J inc)T (r + 0.5 (J inc))
VecX Jinc = Jp * pose_inc_reduced;
l_diff -= Jinc.transpose() * (Scalar(0.5) * Jinc + r);
}
protected:
std::array<FrameId, 2> frame_ids;
MatX Jp;
VecX r;
const IntegratedImuMeasurement<Scalar>* imu_meas;
const ImuLinData<Scalar>* imu_lin_data;
const AbsOrderMap& aom;
}; // namespace basalt
} // namespace basalt

View File

@@ -0,0 +1,140 @@
#pragma once
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <basalt/optimization/accumulator.h>
#include <basalt/vi_estimator/landmark_database.h>
#include <basalt/linearization/block_diagonal.hpp>
namespace basalt {
template <class Scalar>
struct RelPoseLin {
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using Mat6 = Eigen::Matrix<Scalar, 6, 6>;
Mat4 T_t_h;
Mat6 d_rel_d_h;
Mat6 d_rel_d_t;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
template <typename Scalar>
class LandmarkBlock {
public:
struct Options {
// use Householder instead of Givens for marginalization
bool use_householder = true;
// use_valid_projections_only: if true, set invalid projection's
// residual and jacobian to 0; invalid means z <= 0
bool use_valid_projections_only = true;
// if > 0, use huber norm with given threshold, else squared norm
Scalar huber_parameter = 0;
// Standard deviation of reprojection error to weight visual measurements
Scalar obs_std_dev = 1;
// ceres uses 1.0 / (1.0 + sqrt(SquaredColumnNorm))
// we use 1.0 / (eps + sqrt(SquaredColumnNorm))
Scalar jacobi_scaling_eps = 1e-6;
};
enum State {
Uninitialized = 0,
Allocated,
NumericalFailure,
Linearized,
Marginalized
};
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>;
using RowMat3 = Eigen::Matrix<Scalar, 3, 3, Eigen::RowMajor>;
virtual ~LandmarkBlock(){};
virtual bool isNumericalFailure() const = 0;
virtual 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) = 0;
// 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 Scalar linearizeLandmark() = 0;
virtual void performQR() = 0;
// Sets damping and maintains upper triangular matrix for landmarks.
virtual void setLandmarkDamping(Scalar lambda) = 0;
// lambda < 0 means computing exact model cost change
virtual void backSubstitute(const VecX& pose_inc, Scalar& l_diff) = 0;
virtual void addQ2JpTQ2Jp_mult_x(VecX& res, const VecX& x_pose) const = 0;
virtual void addQ2JpTQ2r(VecX& res) const = 0;
virtual void addJp_diag2(VecX& res) const = 0;
virtual void addQ2JpTQ2Jp_blockdiag(
BlockDiagonalAccumulator<Scalar>& accu) const = 0;
virtual void scaleJl_cols() = 0;
virtual void scaleJp_cols(const VecX& jacobian_scaling) = 0;
virtual void printStorage(const std::string& filename) const = 0;
virtual State getState() const = 0;
virtual size_t numReducedCams() const = 0;
virtual void get_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r,
size_t start_idx) const = 0;
virtual void get_dense_Q2Jp_Q2r_rel(
MatX& Q2Jp, VecX& Q2r, size_t start_idx,
const std::map<TimeCamId, size_t>& rel_order) const = 0;
virtual void add_dense_H_b(DenseAccumulator<Scalar>& accum) const = 0;
virtual void add_dense_H_b(MatX& H, VecX& b) const = 0;
virtual void add_dense_H_b_rel(
MatX& H_rel, VecX& b_rel,
const std::map<TimeCamId, size_t>& rel_order) const = 0;
virtual const Eigen::PermutationMatrix<Eigen::Dynamic>& get_rel_permutation()
const = 0;
virtual Eigen::PermutationMatrix<Eigen::Dynamic> compute_rel_permutation(
const std::map<TimeCamId, size_t>& rel_order) const = 0;
virtual void add_dense_H_b_rel_2(MatX& H_rel, VecX& b_rel) const = 0;
virtual TimeCamId getHostKf() const = 0;
virtual size_t numQ2rows() const = 0;
// factory method
template <int POSE_SIZE>
static std::unique_ptr<LandmarkBlock<Scalar>> createLandmarkBlock();
};
} // namespace basalt

View File

@@ -0,0 +1,585 @@
#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

View File

@@ -0,0 +1,141 @@
#pragma once
#include <basalt/linearization/linearization_base.hpp>
#include <basalt/optimization/accumulator.h>
#include <basalt/vi_estimator/landmark_database.h>
#include <basalt/linearization/landmark_block.hpp>
#include <basalt/utils/cast_utils.hpp>
#include <basalt/utils/time_utils.hpp>
namespace basalt {
template <class Scalar>
class ImuBlock;
template <typename Scalar_, int POSE_SIZE_>
class LinearizationAbsQR : public LinearizationBase<Scalar_, POSE_SIZE_> {
public:
using Scalar = Scalar_;
static constexpr int POSE_SIZE = POSE_SIZE_;
using Base = LinearizationBase<Scalar, POSE_SIZE>;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecP = Eigen::Matrix<Scalar, POSE_SIZE, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat36 = Eigen::Matrix<Scalar, 3, 6>;
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using Mat6 = Eigen::Matrix<Scalar, 6, 6>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using LandmarkBlockPtr = std::unique_ptr<LandmarkBlock<Scalar>>;
using ImuBlockPtr = std::unique_ptr<ImuBlock<Scalar>>;
using typename Base::Options;
LinearizationAbsQR(
BundleAdjustmentBase<Scalar>* estimator, const AbsOrderMap& aom,
const Options& options,
const MargLinData<Scalar>* marg_lin_data = nullptr,
const ImuLinData<Scalar>* imu_lin_data = nullptr,
const std::set<FrameId>* used_frames = nullptr,
const std::unordered_set<KeypointId>* lost_landmarks = nullptr,
int64_t last_state_to_marg = std::numeric_limits<int64_t>::max());
// destructor defined in cpp b/c of unique_ptr destructor (ImuBlock)
// copy/move constructor/assignment-operator for rule-of-five
~LinearizationAbsQR();
LinearizationAbsQR(const LinearizationAbsQR&) = default;
LinearizationAbsQR(LinearizationAbsQR&&) = default;
LinearizationAbsQR& operator=(const LinearizationAbsQR&) = default;
LinearizationAbsQR& operator=(LinearizationAbsQR&&) = default;
void log_problem_stats(ExecutionStats& stats) const override;
Scalar linearizeProblem(bool* numerically_valid = nullptr) override;
void performQR() override;
void setPoseDamping(const Scalar lambda);
bool hasPoseDamping() const { return pose_damping_diagonal > 0; }
Scalar backSubstitute(const VecX& pose_inc) override;
VecX getJp_diag2() const;
void scaleJl_cols();
void scaleJp_cols(const VecX& jacobian_scaling);
void setLandmarkDamping(Scalar lambda);
void get_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r) const override;
void get_dense_H_b(MatX& H, VecX& b) const override;
protected: // types
using PoseLinMapType =
Eigen::aligned_unordered_map<std::pair<TimeCamId, TimeCamId>,
RelPoseLin<Scalar>>;
using PoseLinMapTypeConstIter = typename PoseLinMapType::const_iterator;
using HostLandmarkMapType =
std::unordered_map<TimeCamId, std::vector<const LandmarkBlock<Scalar>*>>;
protected: // helper
void get_dense_Q2Jp_Q2r_pose_damping(MatX& Q2Jp, size_t start_idx) const;
void get_dense_Q2Jp_Q2r_marg_prior(MatX& Q2Jp, VecX& Q2r,
size_t start_idx) const;
void add_dense_H_b_pose_damping(MatX& H) const;
void add_dense_H_b_marg_prior(MatX& H, VecX& b) const;
void add_dense_H_b_imu(DenseAccumulator<Scalar>& accum) const;
void add_dense_H_b_imu(MatX& H, VecX& b) const;
protected:
Options options_;
std::vector<KeypointId> landmark_ids;
std::vector<LandmarkBlockPtr> landmark_blocks;
std::vector<ImuBlockPtr> imu_blocks;
std::unordered_map<TimeCamId, size_t> host_to_idx_;
HostLandmarkMapType host_to_landmark_block;
std::vector<size_t> landmark_block_idx;
const BundleAdjustmentBase<Scalar>* estimator;
LandmarkDatabase<Scalar>& lmdb_;
const Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>>& frame_poses;
const Calibration<Scalar>& calib;
const AbsOrderMap& aom;
const std::set<FrameId>* used_frames;
const MargLinData<Scalar>* marg_lin_data;
const ImuLinData<Scalar>* imu_lin_data;
PoseLinMapType relative_pose_lin;
std::vector<std::vector<PoseLinMapTypeConstIter>> relative_pose_per_host;
Scalar pose_damping_diagonal;
Scalar pose_damping_diagonal_sqrt;
VecX marg_scaling;
size_t num_cameras;
size_t num_rows_Q2r;
};
} // namespace basalt

View File

@@ -0,0 +1,142 @@
#pragma once
#include <basalt/linearization/linearization_base.hpp>
#include <basalt/optimization/accumulator.h>
#include <basalt/vi_estimator/landmark_database.h>
#include <basalt/vi_estimator/sqrt_ba_base.h>
#include <basalt/linearization/landmark_block.hpp>
#include <basalt/utils/cast_utils.hpp>
#include <basalt/utils/time_utils.hpp>
namespace basalt {
template <class Scalar>
class ImuBlock;
template <typename Scalar_, int POSE_SIZE_>
class LinearizationAbsSC : public LinearizationBase<Scalar_, POSE_SIZE_> {
public:
using Scalar = Scalar_;
static constexpr int POSE_SIZE = POSE_SIZE_;
using Base = LinearizationBase<Scalar, POSE_SIZE>;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecP = Eigen::Matrix<Scalar, POSE_SIZE, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat36 = Eigen::Matrix<Scalar, 3, 6>;
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using Mat6 = Eigen::Matrix<Scalar, 6, 6>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using LandmarkBlockPtr = std::unique_ptr<LandmarkBlock<Scalar>>;
using ImuBlockPtr = std::unique_ptr<ImuBlock<Scalar>>;
using AbsLinData = typename ScBundleAdjustmentBase<Scalar>::AbsLinData;
using typename Base::Options;
LinearizationAbsSC(
BundleAdjustmentBase<Scalar>* estimator, const AbsOrderMap& aom,
const Options& options,
const MargLinData<Scalar>* marg_lin_data = nullptr,
const ImuLinData<Scalar>* imu_lin_data = nullptr,
const std::set<FrameId>* used_frames = nullptr,
const std::unordered_set<KeypointId>* lost_landmarks = nullptr,
int64_t last_state_to_marg = std::numeric_limits<int64_t>::max());
// destructor defined in cpp b/c of unique_ptr destructor (ImuBlock)
// copy/move constructor/assignment-operator for rule-of-five
~LinearizationAbsSC();
LinearizationAbsSC(const LinearizationAbsSC&) = default;
LinearizationAbsSC(LinearizationAbsSC&&) = default;
LinearizationAbsSC& operator=(const LinearizationAbsSC&) = default;
LinearizationAbsSC& operator=(LinearizationAbsSC&&) = default;
void log_problem_stats(ExecutionStats& stats) const override;
Scalar linearizeProblem(bool* numerically_valid = nullptr) override;
void performQR() override;
void setPoseDamping(const Scalar lambda);
bool hasPoseDamping() const { return pose_damping_diagonal > 0; }
Scalar backSubstitute(const VecX& pose_inc) override;
VecX getJp_diag2() const;
void scaleJl_cols();
void scaleJp_cols(const VecX& jacobian_scaling);
void setLandmarkDamping(Scalar lambda);
void get_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r) const override;
void get_dense_H_b(MatX& H, VecX& b) const override;
protected: // types
using PoseLinMapType =
Eigen::aligned_unordered_map<std::pair<TimeCamId, TimeCamId>,
RelPoseLin<Scalar>>;
using PoseLinMapTypeConstIter = typename PoseLinMapType::const_iterator;
using HostLandmarkMapType =
std::unordered_map<TimeCamId, std::vector<const LandmarkBlock<Scalar>*>>;
protected: // helper
void get_dense_Q2Jp_Q2r_pose_damping(MatX& Q2Jp, size_t start_idx) const;
void get_dense_Q2Jp_Q2r_marg_prior(MatX& Q2Jp, VecX& Q2r,
size_t start_idx) const;
void add_dense_H_b_pose_damping(MatX& H) const;
void add_dense_H_b_marg_prior(MatX& H, VecX& b) const;
void add_dense_H_b_imu(DenseAccumulator<Scalar>& accum) const;
void add_dense_H_b_imu(MatX& H, VecX& b) const;
// Transform to abs
static void accumulate_dense_H_b_rel_to_abs(
const MatX& rel_H, const VecX& rel_b,
const std::vector<PoseLinMapTypeConstIter>& rpph, const AbsOrderMap& aom,
DenseAccumulator<Scalar>& accum);
protected:
Options options_;
std::vector<ImuBlockPtr> imu_blocks;
const BundleAdjustmentBase<Scalar>* estimator;
LandmarkDatabase<Scalar>& lmdb_;
const Calibration<Scalar>& calib;
const AbsOrderMap& aom;
const std::set<FrameId>* used_frames;
const MargLinData<Scalar>* marg_lin_data;
const ImuLinData<Scalar>* imu_lin_data;
const std::unordered_set<KeypointId>* lost_landmarks;
int64_t last_state_to_marg;
Eigen::aligned_vector<AbsLinData> ald_vec;
Scalar pose_damping_diagonal;
Scalar pose_damping_diagonal_sqrt;
VecX marg_scaling;
size_t num_rows_Q2r;
};
} // namespace basalt

View File

@@ -0,0 +1,63 @@
#pragma once
#include <Eigen/Dense>
#include <basalt/vi_estimator/ba_base.h>
#include <basalt/linearization/landmark_block.hpp>
#include <basalt/utils/time_utils.hpp>
namespace basalt {
template <typename Scalar_, int POSE_SIZE_>
class LinearizationBase {
public:
using Scalar = Scalar_;
static constexpr int POSE_SIZE = POSE_SIZE_;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
struct Options {
typename LandmarkBlock<Scalar>::Options lb_options;
LinearizationType linearization_type;
};
virtual ~LinearizationBase() = default;
virtual void log_problem_stats(ExecutionStats& stats) const = 0;
virtual Scalar linearizeProblem(bool* numerically_valid = nullptr) = 0;
virtual void performQR() = 0;
// virtual void setPoseDamping(const Scalar lambda) = 0;
// virtual bool hasPoseDamping() const = 0;
virtual Scalar backSubstitute(const VecX& pose_inc) = 0;
// virtual VecX getJp_diag2() const = 0;
// virtual void scaleJl_cols() = 0;
// virtual void scaleJp_cols(const VecX& jacobian_scaling) = 0;
// virtual void setLandmarkDamping(Scalar lambda) = 0;
virtual void get_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r) const = 0;
virtual void get_dense_H_b(MatX& H, VecX& b) const = 0;
static std::unique_ptr<LinearizationBase> create(
BundleAdjustmentBase<Scalar>* estimator, const AbsOrderMap& aom,
const Options& options,
const MargLinData<Scalar>* marg_lin_data = nullptr,
const ImuLinData<Scalar>* imu_lin_data = nullptr,
const std::set<FrameId>* used_frames = nullptr,
const std::unordered_set<KeypointId>* lost_landmarks = nullptr,
int64_t last_state_to_marg = std::numeric_limits<int64_t>::max());
};
bool isLinearizationSqrt(const LinearizationType& type);
} // namespace basalt

View File

@@ -0,0 +1,143 @@
#pragma once
#include <basalt/linearization/linearization_base.hpp>
#include <basalt/optimization/accumulator.h>
#include <basalt/vi_estimator/landmark_database.h>
#include <basalt/vi_estimator/sqrt_ba_base.h>
#include <basalt/linearization/landmark_block.hpp>
#include <basalt/utils/cast_utils.hpp>
#include <basalt/utils/time_utils.hpp>
namespace basalt {
template <class Scalar>
class ImuBlock;
template <typename Scalar_, int POSE_SIZE_>
class LinearizationRelSC : public LinearizationBase<Scalar_, POSE_SIZE_> {
public:
using Scalar = Scalar_;
static constexpr int POSE_SIZE = POSE_SIZE_;
using Base = LinearizationBase<Scalar, POSE_SIZE>;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecP = Eigen::Matrix<Scalar, POSE_SIZE, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat36 = Eigen::Matrix<Scalar, 3, 6>;
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using Mat6 = Eigen::Matrix<Scalar, 6, 6>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using LandmarkBlockPtr = std::unique_ptr<LandmarkBlock<Scalar>>;
using ImuBlockPtr = std::unique_ptr<ImuBlock<Scalar>>;
using RelLinData = typename ScBundleAdjustmentBase<Scalar>::RelLinData;
using typename Base::Options;
LinearizationRelSC(
BundleAdjustmentBase<Scalar>* estimator, const AbsOrderMap& aom,
const Options& options,
const MargLinData<Scalar>* marg_lin_data = nullptr,
const ImuLinData<Scalar>* imu_lin_data = nullptr,
const std::set<FrameId>* used_frames = nullptr,
const std::unordered_set<KeypointId>* lost_landmarks = nullptr,
int64_t last_state_to_marg = std::numeric_limits<int64_t>::max());
// destructor defined in cpp b/c of unique_ptr destructor (ImuBlock)
// copy/move constructor/assignment-operator for rule-of-five
~LinearizationRelSC();
LinearizationRelSC(const LinearizationRelSC&) = default;
LinearizationRelSC(LinearizationRelSC&&) = default;
LinearizationRelSC& operator=(const LinearizationRelSC&) = default;
LinearizationRelSC& operator=(LinearizationRelSC&&) = default;
void log_problem_stats(ExecutionStats& stats) const override;
Scalar linearizeProblem(bool* numerically_valid = nullptr) override;
void performQR() override;
void setPoseDamping(const Scalar lambda);
bool hasPoseDamping() const { return pose_damping_diagonal > 0; }
Scalar backSubstitute(const VecX& pose_inc) override;
VecX getJp_diag2() const;
void scaleJl_cols();
void scaleJp_cols(const VecX& jacobian_scaling);
void setLandmarkDamping(Scalar lambda);
void get_dense_Q2Jp_Q2r(MatX& Q2Jp, VecX& Q2r) const override;
void get_dense_H_b(MatX& H, VecX& b) const override;
protected: // types
using PoseLinMapType =
Eigen::aligned_unordered_map<std::pair<TimeCamId, TimeCamId>,
RelPoseLin<Scalar>>;
using PoseLinMapTypeConstIter = typename PoseLinMapType::const_iterator;
using HostLandmarkMapType =
std::unordered_map<TimeCamId, std::vector<const LandmarkBlock<Scalar>*>>;
protected: // helper
void get_dense_Q2Jp_Q2r_pose_damping(MatX& Q2Jp, size_t start_idx) const;
void get_dense_Q2Jp_Q2r_marg_prior(MatX& Q2Jp, VecX& Q2r,
size_t start_idx) const;
void add_dense_H_b_pose_damping(MatX& H) const;
void add_dense_H_b_marg_prior(MatX& H, VecX& b) const;
void add_dense_H_b_imu(DenseAccumulator<Scalar>& accum) const;
void add_dense_H_b_imu(MatX& H, VecX& b) const;
// Transform to abs
static void accumulate_dense_H_b_rel_to_abs(
const MatX& rel_H, const VecX& rel_b,
const std::vector<PoseLinMapTypeConstIter>& rpph, const AbsOrderMap& aom,
DenseAccumulator<Scalar>& accum);
protected:
Options options_;
std::vector<ImuBlockPtr> imu_blocks;
const BundleAdjustmentBase<Scalar>* estimator;
LandmarkDatabase<Scalar>& lmdb_;
const Calibration<Scalar>& calib;
const AbsOrderMap& aom;
const std::set<FrameId>* used_frames;
const MargLinData<Scalar>* marg_lin_data;
const ImuLinData<Scalar>* imu_lin_data;
const std::unordered_set<KeypointId>* lost_landmarks;
int64_t last_state_to_marg;
Eigen::aligned_vector<RelLinData> rld_vec;
Scalar pose_damping_diagonal;
Scalar pose_damping_diagonal_sqrt;
VecX marg_scaling;
size_t num_rows_Q2r;
};
} // namespace basalt

View File

@@ -0,0 +1,405 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <thread>
#include <sophus/se2.hpp>
#include <tbb/blocked_range.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/parallel_for.h>
#include <basalt/optical_flow/optical_flow.h>
#include <basalt/optical_flow/patch.h>
#include <basalt/image/image_pyr.h>
#include <basalt/utils/keypoints.h>
namespace basalt {
/// Unlike PatchOpticalFlow, FrameToFrameOpticalFlow always tracks patches
/// against the previous frame, not the initial frame where a track was created.
/// While it might cause more drift of the patch location, it leads to longer
/// tracks in practice.
template <typename Scalar, template <typename> typename Pattern>
class FrameToFrameOpticalFlow : public OpticalFlowBase {
public:
typedef OpticalFlowPatch<Scalar, Pattern<Scalar>> PatchT;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 2, 2> Matrix2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 4, 4> Matrix4;
typedef Sophus::SE2<Scalar> SE2;
FrameToFrameOpticalFlow(const VioConfig& config,
const basalt::Calibration<double>& calib)
: t_ns(-1), frame_counter(0), last_keypoint_id(0), config(config) {
input_queue.set_capacity(10);
this->calib = calib.cast<Scalar>();
patch_coord = PatchT::pattern2.template cast<float>();
if (calib.intrinsics.size() > 1) {
Eigen::Matrix4d Ed;
Sophus::SE3d T_i_j = calib.T_i_c[0].inverse() * calib.T_i_c[1];
computeEssential(T_i_j, Ed);
E = Ed.cast<Scalar>();
}
processing_thread.reset(
new std::thread(&FrameToFrameOpticalFlow::processingLoop, this));
}
~FrameToFrameOpticalFlow() { processing_thread->join(); }
void processingLoop() {
OpticalFlowInput::Ptr input_ptr;
while (true) {
input_queue.pop(input_ptr);
if (!input_ptr.get()) {
if (output_queue) output_queue->push(nullptr);
break;
}
processFrame(input_ptr->t_ns, input_ptr);
}
}
void processFrame(int64_t curr_t_ns, OpticalFlowInput::Ptr& new_img_vec) {
for (const auto& v : new_img_vec->img_data) {
if (!v.img.get()) return;
}
if (t_ns < 0) {
t_ns = curr_t_ns;
transforms.reset(new OpticalFlowResult);
transforms->observations.resize(calib.intrinsics.size());
transforms->t_ns = t_ns;
pyramid.reset(new std::vector<basalt::ManagedImagePyr<uint16_t>>);
pyramid->resize(calib.intrinsics.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, calib.intrinsics.size()),
[&](const tbb::blocked_range<size_t>& r) {
for (size_t i = r.begin(); i != r.end(); ++i) {
pyramid->at(i).setFromImage(
*new_img_vec->img_data[i].img,
config.optical_flow_levels);
}
});
transforms->input_images = new_img_vec;
addPoints();
filterPoints();
} else {
t_ns = curr_t_ns;
old_pyramid = pyramid;
pyramid.reset(new std::vector<basalt::ManagedImagePyr<uint16_t>>);
pyramid->resize(calib.intrinsics.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, calib.intrinsics.size()),
[&](const tbb::blocked_range<size_t>& r) {
for (size_t i = r.begin(); i != r.end(); ++i) {
pyramid->at(i).setFromImage(
*new_img_vec->img_data[i].img,
config.optical_flow_levels);
}
});
OpticalFlowResult::Ptr new_transforms;
new_transforms.reset(new OpticalFlowResult);
new_transforms->observations.resize(calib.intrinsics.size());
new_transforms->t_ns = t_ns;
for (size_t i = 0; i < calib.intrinsics.size(); i++) {
trackPoints(old_pyramid->at(i), pyramid->at(i),
transforms->observations[i],
new_transforms->observations[i]);
}
transforms = new_transforms;
transforms->input_images = new_img_vec;
addPoints();
filterPoints();
}
if (output_queue && frame_counter % config.optical_flow_skip_frames == 0) {
output_queue->push(transforms);
}
frame_counter++;
}
void trackPoints(const basalt::ManagedImagePyr<uint16_t>& pyr_1,
const basalt::ManagedImagePyr<uint16_t>& pyr_2,
const Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>&
transform_map_1,
Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>&
transform_map_2) const {
size_t num_points = transform_map_1.size();
std::vector<KeypointId> ids;
Eigen::aligned_vector<Eigen::AffineCompact2f> init_vec;
ids.reserve(num_points);
init_vec.reserve(num_points);
for (const auto& kv : transform_map_1) {
ids.push_back(kv.first);
init_vec.push_back(kv.second);
}
tbb::concurrent_unordered_map<KeypointId, Eigen::AffineCompact2f,
std::hash<KeypointId>>
result;
auto compute_func = [&](const tbb::blocked_range<size_t>& range) {
for (size_t r = range.begin(); r != range.end(); ++r) {
const KeypointId id = ids[r];
const Eigen::AffineCompact2f& transform_1 = init_vec[r];
Eigen::AffineCompact2f transform_2 = transform_1;
bool valid = trackPoint(pyr_1, pyr_2, transform_1, transform_2);
if (valid) {
Eigen::AffineCompact2f transform_1_recovered = transform_2;
valid = trackPoint(pyr_2, pyr_1, transform_2, transform_1_recovered);
if (valid) {
Scalar dist2 = (transform_1.translation() -
transform_1_recovered.translation())
.squaredNorm();
if (dist2 < config.optical_flow_max_recovered_dist2) {
result[id] = transform_2;
}
}
}
}
};
tbb::blocked_range<size_t> range(0, num_points);
tbb::parallel_for(range, compute_func);
// compute_func(range);
transform_map_2.clear();
transform_map_2.insert(result.begin(), result.end());
}
inline bool trackPoint(const basalt::ManagedImagePyr<uint16_t>& old_pyr,
const basalt::ManagedImagePyr<uint16_t>& pyr,
const Eigen::AffineCompact2f& old_transform,
Eigen::AffineCompact2f& transform) const {
bool patch_valid = true;
transform.linear().setIdentity();
for (int level = config.optical_flow_levels; level >= 0 && patch_valid;
level--) {
const Scalar scale = 1 << level;
transform.translation() /= scale;
PatchT p(old_pyr.lvl(level), old_transform.translation() / scale);
patch_valid &= p.valid;
if (patch_valid) {
// Perform tracking on current level
patch_valid &= trackPointAtLevel(pyr.lvl(level), p, transform);
}
transform.translation() *= scale;
}
transform.linear() = old_transform.linear() * transform.linear();
return patch_valid;
}
inline bool trackPointAtLevel(const Image<const uint16_t>& img_2,
const PatchT& dp,
Eigen::AffineCompact2f& transform) const {
bool patch_valid = true;
for (int iteration = 0;
patch_valid && iteration < config.optical_flow_max_iterations;
iteration++) {
typename PatchT::VectorP res;
typename PatchT::Matrix2P transformed_pat =
transform.linear().matrix() * PatchT::pattern2;
transformed_pat.colwise() += transform.translation();
patch_valid &= dp.residual(img_2, transformed_pat, res);
if (patch_valid) {
const Vector3 inc = -dp.H_se2_inv_J_se2_T * res;
// avoid NaN in increment (leads to SE2::exp crashing)
patch_valid &= inc.array().isFinite().all();
// avoid very large increment
patch_valid &= inc.template lpNorm<Eigen::Infinity>() < 1e6;
if (patch_valid) {
transform *= SE2::exp(inc).matrix();
const int filter_margin = 2;
patch_valid &= img_2.InBounds(transform.translation(), filter_margin);
}
}
}
return patch_valid;
}
void addPoints() {
Eigen::aligned_vector<Eigen::Vector2d> pts0;
for (const auto& kv : transforms->observations.at(0)) {
pts0.emplace_back(kv.second.translation().cast<double>());
}
KeypointsData kd;
detectKeypoints(pyramid->at(0).lvl(0), kd,
config.optical_flow_detection_grid_size, 1, pts0);
Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f> new_poses0,
new_poses1;
for (size_t i = 0; i < kd.corners.size(); i++) {
Eigen::AffineCompact2f transform;
transform.setIdentity();
transform.translation() = kd.corners[i].cast<Scalar>();
transforms->observations.at(0)[last_keypoint_id] = transform;
new_poses0[last_keypoint_id] = transform;
last_keypoint_id++;
}
if (calib.intrinsics.size() > 1) {
trackPoints(pyramid->at(0), pyramid->at(1), new_poses0, new_poses1);
for (const auto& kv : new_poses1) {
transforms->observations.at(1).emplace(kv);
}
}
}
void filterPoints() {
if (calib.intrinsics.size() < 2) return;
std::set<KeypointId> lm_to_remove;
std::vector<KeypointId> kpid;
Eigen::aligned_vector<Eigen::Vector2f> proj0, proj1;
for (const auto& kv : transforms->observations.at(1)) {
auto it = transforms->observations.at(0).find(kv.first);
if (it != transforms->observations.at(0).end()) {
proj0.emplace_back(it->second.translation());
proj1.emplace_back(kv.second.translation());
kpid.emplace_back(kv.first);
}
}
Eigen::aligned_vector<Eigen::Vector4f> p3d0, p3d1;
std::vector<bool> p3d0_success, p3d1_success;
calib.intrinsics[0].unproject(proj0, p3d0, p3d0_success);
calib.intrinsics[1].unproject(proj1, p3d1, p3d1_success);
for (size_t i = 0; i < p3d0_success.size(); i++) {
if (p3d0_success[i] && p3d1_success[i]) {
const double epipolar_error =
std::abs(p3d0[i].transpose() * E * p3d1[i]);
if (epipolar_error > config.optical_flow_epipolar_error) {
lm_to_remove.emplace(kpid[i]);
}
} else {
lm_to_remove.emplace(kpid[i]);
}
}
for (int id : lm_to_remove) {
transforms->observations.at(1).erase(id);
}
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
int64_t t_ns;
size_t frame_counter;
KeypointId last_keypoint_id;
VioConfig config;
basalt::Calibration<Scalar> calib;
OpticalFlowResult::Ptr transforms;
std::shared_ptr<std::vector<basalt::ManagedImagePyr<uint16_t>>> old_pyramid,
pyramid;
Matrix4 E;
std::shared_ptr<std::thread> processing_thread;
};
} // namespace basalt

View File

@@ -0,0 +1,468 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
// Original source for multi-scale implementation:
// https://github.com/DLR-RM/granite (MIT license)
#pragma once
#include <thread>
#include <sophus/se2.hpp>
#include <tbb/blocked_range.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/parallel_for.h>
#include <basalt/optical_flow/optical_flow.h>
#include <basalt/optical_flow/patch.h>
#include <basalt/image/image_pyr.h>
#include <basalt/utils/keypoints.h>
namespace basalt {
/// MultiscaleFrameToFrameOpticalFlow is the same as FrameToFrameOpticalFlow,
/// but patches can be created at all pyramid levels, not just the lowest
/// pyramid.
template <typename Scalar, template <typename> typename Pattern>
class MultiscaleFrameToFrameOpticalFlow : public OpticalFlowBase {
public:
typedef OpticalFlowPatch<Scalar, Pattern<Scalar>> PatchT;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 2, 2> Matrix2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 4, 4> Matrix4;
typedef Sophus::SE2<Scalar> SE2;
MultiscaleFrameToFrameOpticalFlow(const VioConfig& config,
const basalt::Calibration<double>& calib)
: t_ns(-1), frame_counter(0), last_keypoint_id(0), config(config) {
input_queue.set_capacity(10);
this->calib = calib.cast<Scalar>();
patch_coord = PatchT::pattern2.template cast<float>();
if (calib.intrinsics.size() > 1) {
Eigen::Matrix4d Ed;
Sophus::SE3d T_i_j = calib.T_i_c[0].inverse() * calib.T_i_c[1];
computeEssential(T_i_j, Ed);
E = Ed.cast<Scalar>();
}
processing_thread.reset(new std::thread(
&MultiscaleFrameToFrameOpticalFlow::processingLoop, this));
}
~MultiscaleFrameToFrameOpticalFlow() { processing_thread->join(); }
void processingLoop() {
OpticalFlowInput::Ptr input_ptr;
while (true) {
input_queue.pop(input_ptr);
if (!input_ptr.get()) {
if (output_queue) output_queue->push(nullptr);
break;
}
processFrame(input_ptr->t_ns, input_ptr);
}
}
bool processFrame(int64_t curr_t_ns, OpticalFlowInput::Ptr& new_img_vec) {
for (const auto& v : new_img_vec->img_data) {
if (!v.img.get()) {
std::cout << "Image for " << curr_t_ns << " not present!" << std::endl;
return true;
}
}
if (t_ns < 0) {
t_ns = curr_t_ns;
transforms.reset(new OpticalFlowResult);
transforms->observations.resize(calib.intrinsics.size());
transforms->pyramid_levels.resize(calib.intrinsics.size());
transforms->t_ns = t_ns;
pyramid.reset(new std::vector<basalt::ManagedImagePyr<uint16_t>>);
pyramid->resize(calib.intrinsics.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, calib.intrinsics.size()),
[&](const tbb::blocked_range<size_t>& r) {
for (size_t i = r.begin(); i != r.end(); ++i) {
pyramid->at(i).setFromImage(
*new_img_vec->img_data[i].img,
config.optical_flow_levels);
}
});
transforms->input_images = new_img_vec;
addPoints();
filterPoints();
} else {
t_ns = curr_t_ns;
old_pyramid = pyramid;
pyramid.reset(new std::vector<basalt::ManagedImagePyr<uint16_t>>);
pyramid->resize(calib.intrinsics.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, calib.intrinsics.size()),
[&](const tbb::blocked_range<size_t>& r) {
for (size_t i = r.begin(); i != r.end(); ++i) {
pyramid->at(i).setFromImage(
*new_img_vec->img_data[i].img,
config.optical_flow_levels);
}
});
OpticalFlowResult::Ptr new_transforms;
new_transforms.reset(new OpticalFlowResult);
new_transforms->observations.resize(calib.intrinsics.size());
new_transforms->pyramid_levels.resize(calib.intrinsics.size());
new_transforms->t_ns = t_ns;
for (size_t i = 0; i < calib.intrinsics.size(); i++) {
trackPoints(old_pyramid->at(i), pyramid->at(i),
transforms->observations[i], transforms->pyramid_levels[i],
new_transforms->observations[i],
new_transforms->pyramid_levels[i]);
}
// std::cout << t_ns << ": Could track "
// << new_transforms->observations.at(0).size() << " points."
// << std::endl;
transforms = new_transforms;
transforms->input_images = new_img_vec;
addPoints();
filterPoints();
}
if (frame_counter % config.optical_flow_skip_frames == 0) {
try {
output_queue->push(transforms);
} catch (const tbb::user_abort&) {
return false;
};
}
frame_counter++;
return true;
}
void trackPoints(
const basalt::ManagedImagePyr<uint16_t>& pyr_1,
const basalt::ManagedImagePyr<uint16_t>& pyr_2,
const Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>&
transform_map_1,
const std::map<KeypointId, size_t>& pyramid_levels_1,
Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>& transform_map_2,
std::map<KeypointId, size_t>& pyramid_levels_2) const {
size_t num_points = transform_map_1.size();
std::vector<KeypointId> ids;
Eigen::aligned_vector<Eigen::AffineCompact2f> init_vec;
std::vector<size_t> pyramid_level;
ids.reserve(num_points);
init_vec.reserve(num_points);
pyramid_level.reserve(num_points);
for (const auto& kv : transform_map_1) {
ids.push_back(kv.first);
init_vec.push_back(kv.second);
pyramid_level.push_back(pyramid_levels_1.at(kv.first));
}
tbb::concurrent_unordered_map<KeypointId, Eigen::AffineCompact2f,
std::hash<KeypointId>>
result_transforms;
tbb::concurrent_unordered_map<KeypointId, size_t, std::hash<KeypointId>>
result_pyramid_level;
auto compute_func = [&](const tbb::blocked_range<size_t>& range) {
for (size_t r = range.begin(); r != range.end(); ++r) {
const KeypointId id = ids[r];
const Eigen::AffineCompact2f& transform_1 = init_vec[r];
Eigen::AffineCompact2f transform_2 = transform_1;
bool valid = trackPoint(pyr_1, pyr_2, transform_1, pyramid_level[r],
transform_2);
if (valid) {
Eigen::AffineCompact2f transform_1_recovered = transform_2;
valid = trackPoint(pyr_2, pyr_1, transform_2, pyramid_level[r],
transform_1_recovered);
if (valid) {
const Scalar scale = 1 << pyramid_level[r];
Scalar dist2 = (transform_1.translation() / scale -
transform_1_recovered.translation() / scale)
.squaredNorm();
if (dist2 < config.optical_flow_max_recovered_dist2) {
result_transforms[id] = transform_2;
result_pyramid_level[id] = pyramid_level[r];
}
}
}
}
};
tbb::blocked_range<size_t> range(0, num_points);
tbb::parallel_for(range, compute_func);
// compute_func(range);
transform_map_2.clear();
transform_map_2.insert(result_transforms.begin(), result_transforms.end());
pyramid_levels_2.clear();
pyramid_levels_2.insert(result_pyramid_level.begin(),
result_pyramid_level.end());
}
inline bool trackPoint(const basalt::ManagedImagePyr<uint16_t>& old_pyr,
const basalt::ManagedImagePyr<uint16_t>& pyr,
const Eigen::AffineCompact2f& old_transform,
const size_t pyramid_level,
Eigen::AffineCompact2f& transform) const {
bool patch_valid = true;
transform.linear().setIdentity();
for (ssize_t level = config.optical_flow_levels;
level >= static_cast<ssize_t>(pyramid_level); level--) {
const Scalar scale = 1 << level;
Eigen::AffineCompact2f transform_tmp = transform;
transform_tmp.translation() /= scale;
PatchT p(old_pyr.lvl(level), old_transform.translation() / scale);
patch_valid &= p.valid;
if (patch_valid) {
// Perform tracking on current level
patch_valid &= trackPointAtLevel(pyr.lvl(level), p, transform_tmp);
}
if (level == static_cast<ssize_t>(pyramid_level) + 1 && !patch_valid) {
return false;
}
transform_tmp.translation() *= scale;
if (patch_valid) {
transform = transform_tmp;
}
}
transform.linear() = old_transform.linear() * transform.linear();
return patch_valid;
}
inline bool trackPointAtLevel(const Image<const uint16_t>& img_2,
const PatchT& dp,
Eigen::AffineCompact2f& transform) const {
bool patch_valid = true;
for (int iteration = 0;
patch_valid && iteration < config.optical_flow_max_iterations;
iteration++) {
typename PatchT::VectorP res;
typename PatchT::Matrix2P transformed_pat =
transform.linear().matrix() * PatchT::pattern2;
transformed_pat.colwise() += transform.translation();
patch_valid &= dp.residual(img_2, transformed_pat, res);
if (patch_valid) {
const Vector3 inc = -dp.H_se2_inv_J_se2_T * res;
// avoid NaN in increment (leads to SE2::exp crashing)
patch_valid &= inc.array().isFinite().all();
// avoid very large increment
patch_valid &= inc.template lpNorm<Eigen::Infinity>() < 1e6;
if (patch_valid) {
transform *= SE2::exp(inc).matrix();
const int filter_margin = 2;
patch_valid &= img_2.InBounds(transform.translation(), filter_margin);
}
}
}
return patch_valid;
}
void addPoints() {
KeypointsData kd;
Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f> new_poses_main,
new_poses_stereo;
std::map<KeypointId, size_t> new_pyramid_levels_main,
new_pyramid_levels_stereo;
for (ssize_t level = 0;
level < static_cast<ssize_t>(config.optical_flow_levels) - 1;
level++) {
Eigen::aligned_vector<Eigen::Vector2d> pts;
for (const auto& kv : transforms->observations.at(0)) {
const ssize_t point_level =
transforms->pyramid_levels.at(0).at(kv.first);
// do not create points were already points at similar levels are
if (point_level <= level + 1 && point_level >= level - 1) {
// if (point_level == level) {
const Scalar scale = 1 << point_level;
pts.emplace_back(
(kv.second.translation() / scale).template cast<double>());
}
}
detectKeypoints(pyramid->at(0).lvl(level), kd,
config.optical_flow_detection_grid_size, 1, pts);
const Scalar scale = 1 << level;
for (size_t i = 0; i < kd.corners.size(); i++) {
Eigen::AffineCompact2f transform;
transform.setIdentity();
transform.translation() =
kd.corners[i].cast<Scalar>() * scale; // TODO cast float?
transforms->observations.at(0)[last_keypoint_id] = transform;
transforms->pyramid_levels.at(0)[last_keypoint_id] = level;
new_poses_main[last_keypoint_id] = transform;
new_pyramid_levels_main[last_keypoint_id] = level;
last_keypoint_id++;
}
trackPoints(pyramid->at(0), pyramid->at(1), new_poses_main,
new_pyramid_levels_main, new_poses_stereo,
new_pyramid_levels_stereo);
for (const auto& kv : new_poses_stereo) {
transforms->observations.at(1).emplace(kv);
transforms->pyramid_levels.at(1)[kv.first] =
new_pyramid_levels_stereo.at(kv.first);
}
}
}
void filterPoints() {
std::set<KeypointId> lm_to_remove;
std::vector<KeypointId> kpid;
Eigen::aligned_vector<Eigen::Vector2f> proj0, proj1;
for (const auto& kv : transforms->observations.at(1)) {
auto it = transforms->observations.at(0).find(kv.first);
if (it != transforms->observations.at(0).end()) {
proj0.emplace_back(it->second.translation());
proj1.emplace_back(kv.second.translation());
kpid.emplace_back(kv.first);
}
}
Eigen::aligned_vector<Eigen::Vector4f> p3d_main, p3d_stereo;
std::vector<bool> p3d_main_success, p3d_stereo_success;
calib.intrinsics[0].unproject(proj0, p3d_main, p3d_main_success);
calib.intrinsics[1].unproject(proj1, p3d_stereo, p3d_stereo_success);
for (size_t i = 0; i < p3d_main_success.size(); i++) {
if (p3d_main_success[i] && p3d_stereo_success[i]) {
const double epipolar_error =
std::abs(p3d_main[i].transpose() * E * p3d_stereo[i]);
const Scalar scale = 1 << transforms->pyramid_levels.at(0).at(kpid[i]);
if (epipolar_error > config.optical_flow_epipolar_error * scale) {
lm_to_remove.emplace(kpid[i]);
}
} else {
lm_to_remove.emplace(kpid[i]);
}
}
for (int id : lm_to_remove) {
transforms->observations.at(1).erase(id);
}
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
int64_t t_ns;
size_t frame_counter;
KeypointId last_keypoint_id;
VioConfig config;
basalt::Calibration<Scalar> calib;
OpticalFlowResult::Ptr transforms;
std::shared_ptr<std::vector<basalt::ManagedImagePyr<uint16_t>>> old_pyramid,
pyramid;
// map from stereo pair -> essential matrix
Matrix4 E;
std::shared_ptr<std::thread> processing_thread;
};
} // namespace basalt

View File

@@ -0,0 +1,91 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <memory>
#include <Eigen/Geometry>
#include <basalt/utils/vio_config.h>
#include <basalt/io/dataset_io.h>
#include <basalt/calibration/calibration.hpp>
#include <basalt/camera/stereographic_param.hpp>
#include <basalt/utils/sophus_utils.hpp>
#include <tbb/concurrent_queue.h>
#include <opencv2/highgui/highgui.hpp>
namespace basalt {
using KeypointId = size_t;
struct OpticalFlowInput {
using Ptr = std::shared_ptr<OpticalFlowInput>;
int64_t t_ns;
std::vector<ImageData> img_data;
std::vector<cv::Mat> img_cv_data;
};
struct OpticalFlowResult {
using Ptr = std::shared_ptr<OpticalFlowResult>;
int64_t t_ns;
std::vector<Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>>
observations;
std::vector<std::map<KeypointId, size_t>> pyramid_levels;
OpticalFlowInput::Ptr input_images;
};
class OpticalFlowBase {
public:
using Ptr = std::shared_ptr<OpticalFlowBase>;
tbb::concurrent_bounded_queue<OpticalFlowInput::Ptr> input_queue;
tbb::concurrent_bounded_queue<OpticalFlowResult::Ptr>* output_queue = nullptr;
Eigen::MatrixXf patch_coord;
};
class OpticalFlowFactory {
public:
static OpticalFlowBase::Ptr getOpticalFlow(const VioConfig& config,
const Calibration<double>& cam);
};
} // namespace basalt

View File

@@ -0,0 +1,226 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <Eigen/Dense>
#include <sophus/se2.hpp>
#include <basalt/image/image.h>
#include <basalt/optical_flow/patterns.h>
namespace basalt {
template <typename Scalar, typename Pattern>
struct OpticalFlowPatch {
static constexpr int PATTERN_SIZE = Pattern::PATTERN_SIZE;
typedef Eigen::Matrix<int, 2, 1> Vector2i;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 1, 2> Vector2T;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 4, 4> Matrix4;
typedef Eigen::Matrix<Scalar, PATTERN_SIZE, 1> VectorP;
typedef Eigen::Matrix<Scalar, 2, PATTERN_SIZE> Matrix2P;
typedef Eigen::Matrix<Scalar, PATTERN_SIZE, 2> MatrixP2;
typedef Eigen::Matrix<Scalar, PATTERN_SIZE, 3> MatrixP3;
typedef Eigen::Matrix<Scalar, 3, PATTERN_SIZE> Matrix3P;
typedef Eigen::Matrix<Scalar, PATTERN_SIZE, 4> MatrixP4;
typedef Eigen::Matrix<int, 2, PATTERN_SIZE> Matrix2Pi;
static const Matrix2P pattern2;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
OpticalFlowPatch() = default;
OpticalFlowPatch(const Image<const uint16_t> &img, const Vector2 &pos) {
setFromImage(img, pos);
}
template <typename ImgT>
static void setData(const ImgT &img, const Vector2 &pos, Scalar &mean,
VectorP &data, const Sophus::SE2<Scalar> *se2 = nullptr) {
int num_valid_points = 0;
Scalar sum = 0;
for (int i = 0; i < PATTERN_SIZE; i++) {
Vector2 p;
if (se2) {
p = pos + (*se2) * pattern2.col(i);
} else {
p = pos + pattern2.col(i);
};
if (img.InBounds(p, 2)) {
Scalar val = img.template interp<Scalar>(p);
data[i] = val;
sum += val;
num_valid_points++;
} else {
data[i] = -1;
}
}
mean = sum / num_valid_points;
data /= mean;
}
template <typename ImgT>
static void setDataJacSe2(const ImgT &img, const Vector2 &pos, Scalar &mean,
VectorP &data, MatrixP3 &J_se2) {
int num_valid_points = 0;
Scalar sum = 0;
Vector3 grad_sum_se2(0, 0, 0);
Eigen::Matrix<Scalar, 2, 3> Jw_se2;
Jw_se2.template topLeftCorner<2, 2>().setIdentity();
for (int i = 0; i < PATTERN_SIZE; i++) {
Vector2 p = pos + pattern2.col(i);
// Fill jacobians with respect to SE2 warp
Jw_se2(0, 2) = -pattern2(1, i);
Jw_se2(1, 2) = pattern2(0, i);
if (img.InBounds(p, 2)) {
Vector3 valGrad = img.template interpGrad<Scalar>(p);
data[i] = valGrad[0];
sum += valGrad[0];
J_se2.row(i) = valGrad.template tail<2>().transpose() * Jw_se2;
grad_sum_se2 += J_se2.row(i);
num_valid_points++;
} else {
data[i] = -1;
}
}
mean = sum / num_valid_points;
const Scalar mean_inv = num_valid_points / sum;
for (int i = 0; i < PATTERN_SIZE; i++) {
if (data[i] >= 0) {
J_se2.row(i) -= grad_sum_se2.transpose() * data[i] / sum;
data[i] *= mean_inv;
} else {
J_se2.row(i).setZero();
}
}
J_se2 *= mean_inv;
}
void setFromImage(const Image<const uint16_t> &img, const Vector2 &pos) {
this->pos = pos;
MatrixP3 J_se2;
setDataJacSe2(img, pos, mean, data, J_se2);
Matrix3 H_se2 = J_se2.transpose() * J_se2;
Matrix3 H_se2_inv;
H_se2_inv.setIdentity();
H_se2.ldlt().solveInPlace(H_se2_inv);
H_se2_inv_J_se2_T = H_se2_inv * J_se2.transpose();
// NOTE: while it's very unlikely we get a source patch with all black
// pixels, since points are usually selected at corners, it doesn't cost
// much to be safe here.
// all-black patch cannot be normalized; will result in mean of "zero" and
// H_se2_inv_J_se2_T will contain "NaN" and data will contain "inf"
valid = mean > std::numeric_limits<Scalar>::epsilon() &&
H_se2_inv_J_se2_T.array().isFinite().all() &&
data.array().isFinite().all();
}
inline bool residual(const Image<const uint16_t> &img,
const Matrix2P &transformed_pattern,
VectorP &residual) const {
Scalar sum = 0;
int num_valid_points = 0;
for (int i = 0; i < PATTERN_SIZE; i++) {
if (img.InBounds(transformed_pattern.col(i), 2)) {
residual[i] = img.interp<Scalar>(transformed_pattern.col(i));
sum += residual[i];
num_valid_points++;
} else {
residual[i] = -1;
}
}
// all-black patch cannot be normalized
if (sum < std::numeric_limits<Scalar>::epsilon()) {
residual.setZero();
return false;
}
int num_residuals = 0;
for (int i = 0; i < PATTERN_SIZE; i++) {
if (residual[i] >= 0 && data[i] >= 0) {
const Scalar val = residual[i];
residual[i] = num_valid_points * val / sum - data[i];
num_residuals++;
} else {
residual[i] = 0;
}
}
return num_residuals > PATTERN_SIZE / 2;
}
Vector2 pos = Vector2::Zero();
VectorP data = VectorP::Zero(); // negative if the point is not valid
// MatrixP3 J_se2; // total jacobian with respect to se2 warp
// Matrix3 H_se2_inv;
Matrix3P H_se2_inv_J_se2_T = Matrix3P::Zero();
Scalar mean = 0;
bool valid = false;
};
template <typename Scalar, typename Pattern>
const typename OpticalFlowPatch<Scalar, Pattern>::Matrix2P
OpticalFlowPatch<Scalar, Pattern>::pattern2 = Pattern::pattern2;
} // namespace basalt

View File

@@ -0,0 +1,410 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <thread>
#include <sophus/se2.hpp>
#include <tbb/blocked_range.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/parallel_for.h>
#include <basalt/optical_flow/optical_flow.h>
#include <basalt/optical_flow/patch.h>
#include <basalt/image/image_pyr.h>
#include <basalt/utils/keypoints.h>
namespace basalt {
// TODO: patches are currently never erased, so over time memory consumption
// increases
// TODO: some changes from FrameToFrameOpticalFlow could be back-ported
// (adjustments to Scalar=float, tbb parallelization, ...).
/// PatchOpticalFlow keeps reference patches from the frame where the point was
/// initially created. Should result in more consistent tracks (less drift over
/// time) than frame-to-frame tracking, but it results in shorter tracks in
/// practice.
template <typename Scalar, template <typename> typename Pattern>
class PatchOpticalFlow : public OpticalFlowBase {
public:
typedef OpticalFlowPatch<Scalar, Pattern<Scalar>> PatchT;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 2, 2> Matrix2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 4, 4> Matrix4;
typedef Sophus::SE2<Scalar> SE2;
PatchOpticalFlow(const VioConfig& config,
const basalt::Calibration<double>& calib)
: t_ns(-1),
frame_counter(0),
last_keypoint_id(0),
config(config),
calib(calib) {
patches.reserve(3000);
input_queue.set_capacity(10);
patch_coord = PatchT::pattern2.template cast<float>();
if (calib.intrinsics.size() > 1) {
Sophus::SE3d T_i_j = calib.T_i_c[0].inverse() * calib.T_i_c[1];
computeEssential(T_i_j, E);
}
processing_thread.reset(
new std::thread(&PatchOpticalFlow::processingLoop, this));
}
~PatchOpticalFlow() { processing_thread->join(); }
void processingLoop() {
OpticalFlowInput::Ptr input_ptr;
while (true) {
input_queue.pop(input_ptr);
if (!input_ptr.get()) {
output_queue->push(nullptr);
break;
}
processFrame(input_ptr->t_ns, input_ptr);
}
}
void processFrame(int64_t curr_t_ns, OpticalFlowInput::Ptr& new_img_vec) {
for (const auto& v : new_img_vec->img_data) {
if (!v.img.get()) return;
}
if (t_ns < 0) {
t_ns = curr_t_ns;
transforms.reset(new OpticalFlowResult);
transforms->observations.resize(calib.intrinsics.size());
transforms->t_ns = t_ns;
pyramid.reset(new std::vector<basalt::ManagedImagePyr<uint16_t>>);
pyramid->resize(calib.intrinsics.size());
for (size_t i = 0; i < calib.intrinsics.size(); i++) {
pyramid->at(i).setFromImage(*new_img_vec->img_data[i].img,
config.optical_flow_levels);
}
transforms->input_images = new_img_vec;
addPoints();
filterPoints();
} else {
t_ns = curr_t_ns;
old_pyramid = pyramid;
pyramid.reset(new std::vector<basalt::ManagedImagePyr<uint16_t>>);
pyramid->resize(calib.intrinsics.size());
for (size_t i = 0; i < calib.intrinsics.size(); i++) {
pyramid->at(i).setFromImage(*new_img_vec->img_data[i].img,
config.optical_flow_levels);
}
OpticalFlowResult::Ptr new_transforms;
new_transforms.reset(new OpticalFlowResult);
new_transforms->observations.resize(new_img_vec->img_data.size());
new_transforms->t_ns = t_ns;
for (size_t i = 0; i < calib.intrinsics.size(); i++) {
trackPoints(old_pyramid->at(i), pyramid->at(i),
transforms->observations[i],
new_transforms->observations[i]);
}
transforms = new_transforms;
transforms->input_images = new_img_vec;
addPoints();
filterPoints();
}
if (output_queue && frame_counter % config.optical_flow_skip_frames == 0) {
output_queue->push(transforms);
}
frame_counter++;
}
void trackPoints(const basalt::ManagedImagePyr<uint16_t>& pyr_1,
const basalt::ManagedImagePyr<uint16_t>& pyr_2,
const Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>&
transform_map_1,
Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f>&
transform_map_2) const {
size_t num_points = transform_map_1.size();
std::vector<KeypointId> ids;
Eigen::aligned_vector<Eigen::AffineCompact2f> init_vec;
ids.reserve(num_points);
init_vec.reserve(num_points);
for (const auto& kv : transform_map_1) {
ids.push_back(kv.first);
init_vec.push_back(kv.second);
}
tbb::concurrent_unordered_map<KeypointId, Eigen::AffineCompact2f,
std::hash<KeypointId>>
result;
auto compute_func = [&](const tbb::blocked_range<size_t>& range) {
for (size_t r = range.begin(); r != range.end(); ++r) {
const KeypointId id = ids[r];
const Eigen::AffineCompact2f& transform_1 = init_vec[r];
Eigen::AffineCompact2f transform_2 = transform_1;
const Eigen::aligned_vector<PatchT>& patch_vec = patches.at(id);
bool valid = trackPoint(pyr_2, patch_vec, transform_2);
if (valid) {
Eigen::AffineCompact2f transform_1_recovered = transform_2;
valid = trackPoint(pyr_1, patch_vec, transform_1_recovered);
if (valid) {
Scalar dist2 = (transform_1.translation() -
transform_1_recovered.translation())
.squaredNorm();
if (dist2 < config.optical_flow_max_recovered_dist2) {
result[id] = transform_2;
}
}
}
}
};
tbb::blocked_range<size_t> range(0, num_points);
tbb::parallel_for(range, compute_func);
// compute_func(range);
transform_map_2.clear();
transform_map_2.insert(result.begin(), result.end());
}
inline bool trackPoint(const basalt::ManagedImagePyr<uint16_t>& pyr,
const Eigen::aligned_vector<PatchT>& patch_vec,
Eigen::AffineCompact2f& transform) const {
bool patch_valid = true;
for (int level = config.optical_flow_levels; level >= 0 && patch_valid;
level--) {
const Scalar scale = 1 << level;
transform.translation() /= scale;
// TODO: maybe we should better check patch validity when creating points
const auto& p = patch_vec[level];
patch_valid &= p.valid;
if (patch_valid) {
// Perform tracking on current level
patch_valid &= trackPointAtLevel(pyr.lvl(level), p, transform);
}
transform.translation() *= scale;
}
return patch_valid;
}
inline bool trackPointAtLevel(const Image<const uint16_t>& img_2,
const PatchT& dp,
Eigen::AffineCompact2f& transform) const {
bool patch_valid = true;
for (int iteration = 0;
patch_valid && iteration < config.optical_flow_max_iterations;
iteration++) {
typename PatchT::VectorP res;
typename PatchT::Matrix2P transformed_pat =
transform.linear().matrix() * PatchT::pattern2;
transformed_pat.colwise() += transform.translation();
patch_valid &= dp.residual(img_2, transformed_pat, res);
if (patch_valid) {
const Vector3 inc = -dp.H_se2_inv_J_se2_T * res;
// avoid NaN in increment (leads to SE2::exp crashing)
patch_valid &= inc.array().isFinite().all();
// avoid very large increment
patch_valid &= inc.template lpNorm<Eigen::Infinity>() < 1e6;
if (patch_valid) {
transform *= SE2::exp(inc).matrix();
const int filter_margin = 2;
patch_valid &= img_2.InBounds(transform.translation(), filter_margin);
}
}
}
return patch_valid;
}
void addPoints() {
Eigen::aligned_vector<Eigen::Vector2d> pts0;
for (const auto& kv : transforms->observations.at(0)) {
pts0.emplace_back(kv.second.translation().cast<double>());
}
KeypointsData kd;
detectKeypoints(pyramid->at(0).lvl(0), kd,
config.optical_flow_detection_grid_size, 1, pts0);
Eigen::aligned_map<KeypointId, Eigen::AffineCompact2f> new_poses0,
new_poses1;
for (size_t i = 0; i < kd.corners.size(); i++) {
Eigen::aligned_vector<PatchT>& p = patches[last_keypoint_id];
Vector2 pos = kd.corners[i].cast<Scalar>();
for (int l = 0; l <= config.optical_flow_levels; l++) {
Scalar scale = 1 << l;
Vector2 pos_scaled = pos / scale;
p.emplace_back(pyramid->at(0).lvl(l), pos_scaled);
}
Eigen::AffineCompact2f transform;
transform.setIdentity();
transform.translation() = kd.corners[i].cast<Scalar>();
transforms->observations.at(0)[last_keypoint_id] = transform;
new_poses0[last_keypoint_id] = transform;
last_keypoint_id++;
}
if (calib.intrinsics.size() > 1) {
trackPoints(pyramid->at(0), pyramid->at(1), new_poses0, new_poses1);
for (const auto& kv : new_poses1) {
transforms->observations.at(1).emplace(kv);
}
}
}
void filterPoints() {
if (calib.intrinsics.size() < 2) return;
std::set<KeypointId> lm_to_remove;
std::vector<KeypointId> kpid;
Eigen::aligned_vector<Eigen::Vector2d> proj0, proj1;
for (const auto& kv : transforms->observations.at(1)) {
auto it = transforms->observations.at(0).find(kv.first);
if (it != transforms->observations.at(0).end()) {
proj0.emplace_back(it->second.translation().cast<double>());
proj1.emplace_back(kv.second.translation().cast<double>());
kpid.emplace_back(kv.first);
}
}
Eigen::aligned_vector<Eigen::Vector4d> p3d0, p3d1;
std::vector<bool> p3d0_success, p3d1_success;
calib.intrinsics[0].unproject(proj0, p3d0, p3d0_success);
calib.intrinsics[1].unproject(proj1, p3d1, p3d1_success);
for (size_t i = 0; i < p3d0_success.size(); i++) {
if (p3d0_success[i] && p3d1_success[i]) {
const double epipolar_error =
std::abs(p3d0[i].transpose() * E * p3d1[i]);
if (epipolar_error > config.optical_flow_epipolar_error) {
lm_to_remove.emplace(kpid[i]);
}
} else {
lm_to_remove.emplace(kpid[i]);
}
}
for (int id : lm_to_remove) {
transforms->observations.at(1).erase(id);
}
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
int64_t t_ns;
size_t frame_counter;
KeypointId last_keypoint_id;
VioConfig config;
basalt::Calibration<double> calib;
Eigen::aligned_unordered_map<KeypointId, Eigen::aligned_vector<PatchT>>
patches;
OpticalFlowResult::Ptr transforms;
std::shared_ptr<std::vector<basalt::ManagedImagePyr<uint16_t>>> old_pyramid,
pyramid;
Eigen::Matrix4d E;
std::shared_ptr<std::thread> processing_thread;
};
} // namespace basalt

View File

@@ -0,0 +1,171 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <Eigen/Dense>
namespace basalt {
template <class Scalar>
struct Pattern24 {
// 00 01
//
// 02 03 04 05
//
// 06 07 08 09 10 11
//
// 12 13 14 15 16 17
//
// 18 19 20 21
//
// 22 23
//
// -----> x
// |
// |
// y
static constexpr Scalar pattern_raw[][2] = {
{-1, 5}, {1, 5},
{-3, 3}, {-1, 3}, {1, 3}, {3, 3},
{-5, 1}, {-3, 1}, {-1, 1}, {1, 1}, {3, 1}, {5, 1},
{-5, -1}, {-3, -1}, {-1, -1}, {1, -1}, {3, -1}, {5, -1},
{-3, -3}, {-1, -3}, {1, -3}, {3, -3},
{-1, -5}, {1, -5}
};
static constexpr int PATTERN_SIZE =
sizeof(pattern_raw) / (2 * sizeof(Scalar));
typedef Eigen::Matrix<Scalar, 2, PATTERN_SIZE> Matrix2P;
static const Matrix2P pattern2;
};
template <class Scalar>
const typename Pattern24<Scalar>::Matrix2P Pattern24<Scalar>::pattern2 =
Eigen::Map<Pattern24<Scalar>::Matrix2P>((Scalar *)
Pattern24<Scalar>::pattern_raw);
template <class Scalar>
struct Pattern52 {
// 00 01 02 03
//
// 04 05 06 07 08 09
//
// 10 11 12 13 14 15 16 17
//
// 18 19 20 21 22 23 24 25
//
// 26 27 28 29 30 31 32 33
//
// 34 35 36 37 38 39 40 41
//
// 42 43 44 45 46 47
//
// 48 49 50 51
//
// -----> x
// |
// |
// y
static constexpr Scalar pattern_raw[][2] = {
{-3, 7}, {-1, 7}, {1, 7}, {3, 7},
{-5, 5}, {-3, 5}, {-1, 5}, {1, 5}, {3, 5}, {5, 5},
{-7, 3}, {-5, 3}, {-3, 3}, {-1, 3}, {1, 3}, {3, 3},
{5, 3}, {7, 3},
{-7, 1}, {-5, 1}, {-3, 1}, {-1, 1}, {1, 1}, {3, 1},
{5, 1}, {7, 1},
{-7, -1}, {-5, -1}, {-3, -1}, {-1, -1}, {1, -1}, {3, -1},
{5, -1}, {7, -1},
{-7, -3}, {-5, -3}, {-3, -3}, {-1, -3}, {1, -3}, {3, -3},
{5, -3}, {7, -3},
{-5, -5}, {-3, -5}, {-1, -5}, {1, -5}, {3, -5}, {5, -5},
{-3, -7}, {-1, -7}, {1, -7}, {3, -7}
};
static constexpr int PATTERN_SIZE =
sizeof(pattern_raw) / (2 * sizeof(Scalar));
typedef Eigen::Matrix<Scalar, 2, PATTERN_SIZE> Matrix2P;
static const Matrix2P pattern2;
};
template <class Scalar>
const typename Pattern52<Scalar>::Matrix2P Pattern52<Scalar>::pattern2 =
Eigen::Map<Pattern52<Scalar>::Matrix2P>((Scalar *)
Pattern52<Scalar>::pattern_raw);
// Same as Pattern52 but twice smaller
template <class Scalar>
struct Pattern51 {
static constexpr int PATTERN_SIZE = Pattern52<Scalar>::PATTERN_SIZE;
typedef Eigen::Matrix<Scalar, 2, PATTERN_SIZE> Matrix2P;
static const Matrix2P pattern2;
};
template <class Scalar>
const typename Pattern51<Scalar>::Matrix2P Pattern51<Scalar>::pattern2 =
0.5 * Pattern52<Scalar>::pattern2;
// Same as Pattern52 but 0.75 smaller
template <class Scalar>
struct Pattern50 {
static constexpr int PATTERN_SIZE = Pattern52<Scalar>::PATTERN_SIZE;
typedef Eigen::Matrix<Scalar, 2, PATTERN_SIZE> Matrix2P;
static const Matrix2P pattern2;
};
template <class Scalar>
const typename Pattern50<Scalar>::Matrix2P Pattern50<Scalar>::pattern2 =
0.75 * Pattern52<Scalar>::pattern2;
} // namespace basalt

View File

@@ -0,0 +1,281 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <array>
#include <chrono>
#include <unordered_map>
#include <basalt/utils/assert.h>
#include <basalt/utils/hash.h>
#if defined(BASALT_USE_CHOLMOD)
#include <Eigen/CholmodSupport>
template <class T>
using SparseLLT = Eigen::CholmodSupernodalLLT<T>;
#else
template <class T>
using SparseLLT = Eigen::SimplicialLDLT<T>;
#endif
namespace basalt {
template <typename Scalar_ = double>
class DenseAccumulator {
public:
using Scalar = Scalar_;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
template <int ROWS, int COLS, typename Derived>
inline void addH(int i, int j, const Eigen::MatrixBase<Derived>& data) {
BASALT_ASSERT_STREAM(i >= 0, "i " << i);
BASALT_ASSERT_STREAM(j >= 0, "j " << j);
BASALT_ASSERT_STREAM(i + ROWS <= H.cols(), "i " << i << " ROWS " << ROWS
<< " H.rows() "
<< H.rows());
BASALT_ASSERT_STREAM(j + COLS <= H.rows(), "j " << j << " COLS " << COLS
<< " H.cols() "
<< H.cols());
H.template block<ROWS, COLS>(i, j) += data;
}
template <int ROWS, typename Derived>
inline void addB(int i, const Eigen::MatrixBase<Derived>& data) {
BASALT_ASSERT_STREAM(i >= 0, "i " << i);
BASALT_ASSERT_STREAM(i + ROWS <= H.cols(), "i " << i << " ROWS " << ROWS
<< " H.rows() "
<< H.rows());
b.template segment<ROWS>(i) += data;
}
// inline VectorX solve() const { return H.ldlt().solve(b); }
inline VectorX solve(const VectorX* diagonal) const {
if (diagonal == nullptr) {
return H.ldlt().solve(b);
} else {
MatrixX HH = H;
HH.diagonal() += *diagonal;
return HH.ldlt().solve(b);
}
}
inline void reset(int opt_size) {
H.setZero(opt_size, opt_size);
b.setZero(opt_size);
}
inline void join(const DenseAccumulator<Scalar>& other) {
H += other.H;
b += other.b;
}
inline void print() {
Eigen::IOFormat CleanFmt(2);
std::cerr << "H\n" << H.format(CleanFmt) << std::endl;
std::cerr << "b\n" << b.transpose().format(CleanFmt) << std::endl;
}
inline void setup_solver(){};
inline VectorX Hdiagonal() const { return H.diagonal(); }
inline const MatrixX& getH() const { return H; }
inline const VectorX& getB() const { return b; }
inline MatrixX& getH() { return H; }
inline VectorX& getB() { return b; }
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
MatrixX H;
VectorX b;
};
template <typename Scalar_ = double>
class SparseHashAccumulator {
public:
using Scalar = Scalar_;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef Eigen::Triplet<Scalar> T;
typedef Eigen::SparseMatrix<Scalar> SparseMatrix;
template <int ROWS, int COLS, typename Derived>
inline void addH(int si, int sj, const Eigen::MatrixBase<Derived>& data) {
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(Derived, ROWS, COLS);
KeyT id;
id[0] = si;
id[1] = sj;
id[2] = ROWS;
id[3] = COLS;
auto it = hash_map.find(id);
if (it == hash_map.end()) {
hash_map.emplace(id, data);
} else {
it->second += data;
}
}
template <int ROWS, typename Derived>
inline void addB(int i, const Eigen::MatrixBase<Derived>& data) {
b.template segment<ROWS>(i) += data;
}
inline void setup_solver() {
std::vector<T> triplets;
triplets.reserve(hash_map.size() * 36 + b.rows());
for (const auto& kv : hash_map) {
for (int i = 0; i < kv.second.rows(); i++) {
for (int j = 0; j < kv.second.cols(); j++) {
triplets.emplace_back(kv.first[0] + i, kv.first[1] + j,
kv.second(i, j));
}
}
}
for (int i = 0; i < b.rows(); i++) {
triplets.emplace_back(i, i, std::numeric_limits<double>::min());
}
smm = SparseMatrix(b.rows(), b.rows());
smm.setFromTriplets(triplets.begin(), triplets.end());
}
inline VectorX Hdiagonal() const { return smm.diagonal(); }
inline VectorX& getB() { return b; }
inline VectorX solve(const VectorX* diagonal) const {
auto t2 = std::chrono::high_resolution_clock::now();
SparseMatrix sm = smm;
if (diagonal) sm.diagonal() += *diagonal;
VectorX res;
if (iterative_solver) {
// NOTE: since we have to disable Eigen's parallelization with OpenMP
// (interferes with TBB), the current CG is single-threaded, and we
// can expect a substantial speedup by switching to a parallel
// implementation of CG.
Eigen::ConjugateGradient<Eigen::SparseMatrix<double>,
Eigen::Lower | Eigen::Upper>
cg;
cg.setTolerance(tolerance);
cg.compute(sm);
res = cg.solve(b);
} else {
SparseLLT<SparseMatrix> chol(sm);
res = chol.solve(b);
}
auto t3 = std::chrono::high_resolution_clock::now();
auto elapsed2 =
std::chrono::duration_cast<std::chrono::microseconds>(t3 - t2);
if (print_info) {
std::cout << "Solving linear system: " << elapsed2.count() * 1e-6 << "s."
<< std::endl;
}
return res;
}
inline void reset(int opt_size) {
hash_map.clear();
b.setZero(opt_size);
}
inline void join(const SparseHashAccumulator<Scalar>& other) {
for (const auto& kv : other.hash_map) {
auto it = hash_map.find(kv.first);
if (it == hash_map.end()) {
hash_map.emplace(kv.first, kv.second);
} else {
it->second += kv.second;
}
}
b += other.b;
}
double tolerance = 1e-4;
bool iterative_solver = false;
bool print_info = false;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
using KeyT = std::array<int, 4>;
struct KeyHash {
inline size_t operator()(const KeyT& c) const {
size_t seed = 0;
for (int i = 0; i < 4; i++) {
hash_combine(seed, c[i]);
}
return seed;
}
};
std::unordered_map<KeyT, MatrixX, KeyHash> hash_map;
VectorX b;
SparseMatrix smm;
};
} // namespace basalt

View File

@@ -0,0 +1,195 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef BASALT_LINEARIZE_H
#define BASALT_LINEARIZE_H
#include <basalt/io/dataset_io.h>
#include <basalt/spline/se3_spline.h>
#include <basalt/calibration/calibration.hpp>
#include <basalt/camera/stereographic_param.hpp>
namespace basalt {
template <typename Scalar>
struct LinearizeBase {
static const int POSE_SIZE = 6;
static const int POS_SIZE = 3;
static const int POS_OFFSET = 0;
static const int ROT_SIZE = 3;
static const int ROT_OFFSET = 3;
static const int ACCEL_BIAS_SIZE = 9;
static const int GYRO_BIAS_SIZE = 12;
static const int G_SIZE = 3;
static const int TIME_SIZE = 1;
static const int ACCEL_BIAS_OFFSET = 0;
static const int GYRO_BIAS_OFFSET = ACCEL_BIAS_SIZE;
static const int G_OFFSET = GYRO_BIAS_OFFSET + GYRO_BIAS_SIZE;
typedef Sophus::SE3<Scalar> SE3;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 6, 1> Vector6;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 6, 6> Matrix6;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef typename Eigen::aligned_vector<PoseData>::const_iterator PoseDataIter;
typedef typename Eigen::aligned_vector<GyroData>::const_iterator GyroDataIter;
typedef
typename Eigen::aligned_vector<AccelData>::const_iterator AccelDataIter;
typedef typename Eigen::aligned_vector<AprilgridCornersData>::const_iterator
AprilgridCornersDataIter;
template <int INTRINSICS_SIZE>
struct PoseCalibH {
Sophus::Matrix6d H_pose_accum;
Sophus::Vector6d b_pose_accum;
Eigen::Matrix<double, INTRINSICS_SIZE, 6> H_intr_pose_accum;
Eigen::Matrix<double, INTRINSICS_SIZE, INTRINSICS_SIZE> H_intr_accum;
Eigen::Matrix<double, INTRINSICS_SIZE, 1> b_intr_accum;
PoseCalibH() {
H_pose_accum.setZero();
b_pose_accum.setZero();
H_intr_pose_accum.setZero();
H_intr_accum.setZero();
b_intr_accum.setZero();
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct CalibCommonData {
const Calibration<Scalar>* calibration = nullptr;
const MocapCalibration<Scalar>* mocap_calibration = nullptr;
const Eigen::aligned_vector<Eigen::Vector4d>* aprilgrid_corner_pos_3d =
nullptr;
// Calib data
const std::vector<size_t>* offset_T_i_c = nullptr;
const std::vector<size_t>* offset_intrinsics = nullptr;
// Cam data
size_t mocap_block_offset;
size_t bias_block_offset;
const std::unordered_map<int64_t, size_t>* offset_poses = nullptr;
// Cam-IMU data
const Vector3* g = nullptr;
bool opt_intrinsics;
// Cam-IMU options
bool opt_cam_time_offset;
bool opt_g;
bool opt_imu_scale;
Scalar pose_var_inv;
Vector3 gyro_var_inv;
Vector3 accel_var_inv;
Scalar mocap_var_inv;
Scalar huber_thresh;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
template <class CamT>
inline void linearize_point(const Eigen::Vector2d& corner_pos, int corner_id,
const Eigen::Matrix4d& T_c_w, const CamT& cam,
PoseCalibH<CamT::N>* cph, double& error,
int& num_points, double& reproj_err) const {
Eigen::Matrix<double, 2, 4> d_r_d_p;
Eigen::Matrix<double, 2, CamT::N> d_r_d_param;
BASALT_ASSERT_STREAM(
corner_id < int(common_data.aprilgrid_corner_pos_3d->size()),
"corner_id " << corner_id);
Eigen::Vector4d point3d =
T_c_w * common_data.aprilgrid_corner_pos_3d->at(corner_id);
Eigen::Vector2d proj;
bool valid;
if (cph) {
valid = cam.project(point3d, proj, &d_r_d_p, &d_r_d_param);
} else {
valid = cam.project(point3d, proj);
}
if (!valid || !proj.array().isFinite().all()) return;
Eigen::Vector2d residual = proj - corner_pos;
double e = residual.norm();
double huber_weight =
e < common_data.huber_thresh ? 1.0 : common_data.huber_thresh / e;
if (cph) {
Eigen::Matrix<double, 4, 6> d_point_d_xi;
d_point_d_xi.topLeftCorner<3, 3>().setIdentity();
d_point_d_xi.topRightCorner<3, 3>() =
-Sophus::SO3d::hat(point3d.head<3>());
d_point_d_xi.row(3).setZero();
Eigen::Matrix<double, 2, 6> d_r_d_xi = d_r_d_p * d_point_d_xi;
cph->H_pose_accum += huber_weight * d_r_d_xi.transpose() * d_r_d_xi;
cph->b_pose_accum += huber_weight * d_r_d_xi.transpose() * residual;
cph->H_intr_pose_accum +=
huber_weight * d_r_d_param.transpose() * d_r_d_xi;
cph->H_intr_accum += huber_weight * d_r_d_param.transpose() * d_r_d_param;
cph->b_intr_accum += huber_weight * d_r_d_param.transpose() * residual;
}
error += huber_weight * e * e * (2 - huber_weight);
reproj_err += e;
num_points++;
}
CalibCommonData common_data;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
} // namespace basalt
#endif

View File

@@ -0,0 +1,264 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef BASALT_POSES_LINEARIZE_H
#define BASALT_POSES_LINEARIZE_H
#include <basalt/io/dataset_io.h>
#include <basalt/spline/se3_spline.h>
#include <basalt/calibration/calibration.hpp>
#include <basalt/optimization/linearize.h>
#include <basalt/optimization/accumulator.h>
#include <tbb/blocked_range.h>
namespace basalt {
template <typename Scalar, typename AccumT>
struct LinearizePosesOpt : public LinearizeBase<Scalar> {
static const int POSE_SIZE = LinearizeBase<Scalar>::POSE_SIZE;
typedef Sophus::SE3<Scalar> SE3;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 6, 1> Vector6;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 6, 6> Matrix6;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef typename Eigen::aligned_vector<AprilgridCornersData>::const_iterator
AprilgridCornersDataIter;
typedef typename LinearizeBase<Scalar>::CalibCommonData CalibCommonData;
AccumT accum;
Scalar error;
Scalar reprojection_error;
int num_points;
size_t opt_size;
const Eigen::aligned_unordered_map<int64_t, SE3>& timestam_to_pose;
LinearizePosesOpt(
size_t opt_size,
const Eigen::aligned_unordered_map<int64_t, SE3>& timestam_to_pose,
const CalibCommonData& common_data)
: opt_size(opt_size), timestam_to_pose(timestam_to_pose) {
this->common_data = common_data;
accum.reset(opt_size);
error = 0;
reprojection_error = 0;
num_points = 0;
}
LinearizePosesOpt(const LinearizePosesOpt& other, tbb::split)
: opt_size(other.opt_size), timestam_to_pose(other.timestam_to_pose) {
this->common_data = other.common_data;
accum.reset(opt_size);
error = 0;
reprojection_error = 0;
num_points = 0;
}
void operator()(const tbb::blocked_range<AprilgridCornersDataIter>& r) {
for (const AprilgridCornersData& acd : r) {
std::visit(
[&](const auto& cam) {
constexpr int INTRINSICS_SIZE =
std::remove_reference<decltype(cam)>::type::N;
typename LinearizeBase<Scalar>::template PoseCalibH<INTRINSICS_SIZE>
cph;
SE3 T_w_i = timestam_to_pose.at(acd.timestamp_ns);
SE3 T_w_c =
T_w_i * this->common_data.calibration->T_i_c[acd.cam_id];
SE3 T_c_w = T_w_c.inverse();
Eigen::Matrix4d T_c_w_m = T_c_w.matrix();
double err = 0;
double reproj_err = 0;
int num_inliers = 0;
for (size_t i = 0; i < acd.corner_pos.size(); i++) {
this->linearize_point(acd.corner_pos[i], acd.corner_id[i],
T_c_w_m, cam, &cph, err, num_inliers,
reproj_err);
}
error += err;
reprojection_error += reproj_err;
num_points += num_inliers;
const Matrix6 Adj =
-this->common_data.calibration->T_i_c[acd.cam_id]
.inverse()
.Adj();
const size_t po =
this->common_data.offset_poses->at(acd.timestamp_ns);
const size_t co = this->common_data.offset_T_i_c->at(acd.cam_id);
const size_t io =
this->common_data.offset_intrinsics->at(acd.cam_id);
accum.template addH<POSE_SIZE, POSE_SIZE>(
po, po, Adj.transpose() * cph.H_pose_accum * Adj);
accum.template addB<POSE_SIZE>(po,
Adj.transpose() * cph.b_pose_accum);
if (acd.cam_id > 0) {
accum.template addH<POSE_SIZE, POSE_SIZE>(
co, po, -cph.H_pose_accum * Adj);
accum.template addH<POSE_SIZE, POSE_SIZE>(co, co,
cph.H_pose_accum);
accum.template addB<POSE_SIZE>(co, -cph.b_pose_accum);
}
if (this->common_data.opt_intrinsics) {
accum.template addH<INTRINSICS_SIZE, POSE_SIZE>(
io, po, cph.H_intr_pose_accum * Adj);
if (acd.cam_id > 0)
accum.template addH<INTRINSICS_SIZE, POSE_SIZE>(
io, co, -cph.H_intr_pose_accum);
accum.template addH<INTRINSICS_SIZE, INTRINSICS_SIZE>(
io, io, cph.H_intr_accum);
accum.template addB<INTRINSICS_SIZE>(io, cph.b_intr_accum);
}
},
this->common_data.calibration->intrinsics[acd.cam_id].variant);
}
}
void join(LinearizePosesOpt& rhs) {
accum.join(rhs.accum);
error += rhs.error;
reprojection_error += rhs.reprojection_error;
num_points += rhs.num_points;
}
};
template <typename Scalar>
struct ComputeErrorPosesOpt : public LinearizeBase<Scalar> {
static const int POSE_SIZE = LinearizeBase<Scalar>::POSE_SIZE;
typedef Sophus::SE3<Scalar> SE3;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 6, 1> Vector6;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 6, 6> Matrix6;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef typename Eigen::aligned_vector<AprilgridCornersData>::const_iterator
AprilgridCornersDataIter;
typedef typename LinearizeBase<Scalar>::CalibCommonData CalibCommonData;
Scalar error;
Scalar reprojection_error;
int num_points;
size_t opt_size;
const Eigen::aligned_unordered_map<int64_t, SE3>& timestam_to_pose;
ComputeErrorPosesOpt(
size_t opt_size,
const Eigen::aligned_unordered_map<int64_t, SE3>& timestam_to_pose,
const CalibCommonData& common_data)
: opt_size(opt_size), timestam_to_pose(timestam_to_pose) {
this->common_data = common_data;
error = 0;
reprojection_error = 0;
num_points = 0;
}
ComputeErrorPosesOpt(const ComputeErrorPosesOpt& other, tbb::split)
: opt_size(other.opt_size), timestam_to_pose(other.timestam_to_pose) {
this->common_data = other.common_data;
error = 0;
reprojection_error = 0;
num_points = 0;
}
void operator()(const tbb::blocked_range<AprilgridCornersDataIter>& r) {
for (const AprilgridCornersData& acd : r) {
std::visit(
[&](const auto& cam) {
SE3 T_w_i = timestam_to_pose.at(acd.timestamp_ns);
SE3 T_w_c =
T_w_i * this->common_data.calibration->T_i_c[acd.cam_id];
SE3 T_c_w = T_w_c.inverse();
Eigen::Matrix4d T_c_w_m = T_c_w.matrix();
double err = 0;
double reproj_err = 0;
int num_inliers = 0;
for (size_t i = 0; i < acd.corner_pos.size(); i++) {
this->linearize_point(acd.corner_pos[i], acd.corner_id[i],
T_c_w_m, cam, nullptr, err, num_inliers,
reproj_err);
}
error += err;
reprojection_error += reproj_err;
num_points += num_inliers;
},
this->common_data.calibration->intrinsics[acd.cam_id].variant);
}
}
void join(ComputeErrorPosesOpt& rhs) {
error += rhs.error;
reprojection_error += rhs.reprojection_error;
num_points += rhs.num_points;
}
}; // namespace basalt
} // namespace basalt
#endif

View File

@@ -0,0 +1,315 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/calibration/aprilgrid.h>
#include <basalt/calibration/calibration_helper.h>
#include <basalt/optimization/poses_linearize.h>
#include <tbb/parallel_reduce.h>
namespace basalt {
class PosesOptimization {
static constexpr size_t POSE_SIZE = 6;
using Scalar = double;
typedef LinearizePosesOpt<Scalar, SparseHashAccumulator<Scalar>> LinearizeT;
using SE3 = typename LinearizeT::SE3;
using Vector2 = typename LinearizeT::Vector2;
using Vector3 = typename LinearizeT::Vector3;
using Vector4 = typename LinearizeT::Vector4;
using VectorX = typename LinearizeT::VectorX;
using AprilgridCornersDataIter =
typename Eigen::aligned_vector<AprilgridCornersData>::const_iterator;
public:
PosesOptimization()
: lambda(1e-6), min_lambda(1e-12), max_lambda(100), lambda_vee(2) {}
Vector2 getOpticalCenter(size_t i) {
return calib->intrinsics[i].getParam().template segment<2>(2);
}
void resetCalib(size_t num_cams, const std::vector<std::string> &cam_types) {
BASALT_ASSERT(cam_types.size() == num_cams);
calib.reset(new Calibration<Scalar>);
for (size_t i = 0; i < cam_types.size(); i++) {
calib->intrinsics.emplace_back(
GenericCamera<Scalar>::fromString(cam_types[i]));
if (calib->intrinsics.back().getName() != cam_types[i]) {
std::cerr << "Unknown camera type " << cam_types[i] << " default to "
<< calib->intrinsics.back().getName() << std::endl;
}
}
calib->T_i_c.resize(num_cams);
}
void loadCalib(const std::string &p) {
std::string path = p + "calibration.json";
std::ifstream is(path);
if (is.good()) {
cereal::JSONInputArchive archive(is);
calib.reset(new Calibration<Scalar>);
archive(*calib);
std::cout << "Loaded calibration from: " << path << std::endl;
} else {
std::cout << "No calibration found" << std::endl;
}
}
void saveCalib(const std::string &path) const {
if (calib) {
std::ofstream os(path + "calibration.json");
cereal::JSONOutputArchive archive(os);
archive(*calib);
}
}
bool calibInitialized() const { return calib != nullptr; }
bool initialized() const { return true; }
// Returns true when converged
bool optimize(bool opt_intrinsics, double huber_thresh, double stop_thresh,
double &error, int &num_points, double &reprojection_error) {
error = 0;
num_points = 0;
ccd.opt_intrinsics = opt_intrinsics;
ccd.huber_thresh = huber_thresh;
LinearizePosesOpt<double, SparseHashAccumulator<double>> lopt(
problem_size, timestam_to_pose, ccd);
tbb::blocked_range<AprilgridCornersDataIter> april_range(
aprilgrid_corners_measurements.begin(),
aprilgrid_corners_measurements.end());
tbb::parallel_reduce(april_range, lopt);
error = lopt.error;
num_points = lopt.num_points;
reprojection_error = lopt.reprojection_error;
std::cout << "[LINEARIZE] Error: " << lopt.error << " num points "
<< lopt.num_points << std::endl;
lopt.accum.setup_solver();
Eigen::VectorXd Hdiag = lopt.accum.Hdiagonal();
bool converged = false;
bool step = false;
int max_iter = 10;
while (!step && max_iter > 0 && !converged) {
Eigen::aligned_unordered_map<int64_t, Sophus::SE3d>
timestam_to_pose_backup = timestam_to_pose;
Calibration<Scalar> calib_backup = *calib;
Eigen::VectorXd Hdiag_lambda = Hdiag * lambda;
for (int i = 0; i < Hdiag_lambda.size(); i++)
Hdiag_lambda[i] = std::max(Hdiag_lambda[i], min_lambda);
Eigen::VectorXd inc = -lopt.accum.solve(&Hdiag_lambda);
double max_inc = inc.array().abs().maxCoeff();
if (max_inc < stop_thresh) converged = true;
for (auto &kv : timestam_to_pose) {
kv.second *=
Sophus::se3_expd(inc.segment<POSE_SIZE>(offset_poses[kv.first]));
}
for (size_t i = 0; i < calib->T_i_c.size(); i++) {
calib->T_i_c[i] *=
Sophus::se3_expd(inc.segment<POSE_SIZE>(offset_T_i_c[i]));
}
for (size_t i = 0; i < calib->intrinsics.size(); i++) {
auto &c = calib->intrinsics[i];
c.applyInc(inc.segment(offset_cam_intrinsics[i], c.getN()));
}
ComputeErrorPosesOpt<double> eopt(problem_size, timestam_to_pose, ccd);
tbb::parallel_reduce(april_range, eopt);
double f_diff = (lopt.error - eopt.error);
double l_diff = 0.5 * inc.dot(inc * lambda - lopt.accum.getB());
// std::cout << "f_diff " << f_diff << " l_diff " << l_diff << std::endl;
double step_quality = f_diff / l_diff;
if (step_quality < 0) {
std::cout << "\t[REJECTED] lambda:" << lambda
<< " step_quality: " << step_quality
<< " max_inc: " << max_inc << " Error: " << eopt.error
<< " num points " << eopt.num_points << std::endl;
lambda = std::min(max_lambda, lambda_vee * lambda);
lambda_vee *= 2;
timestam_to_pose = timestam_to_pose_backup;
*calib = calib_backup;
} else {
std::cout << "\t[ACCEPTED] lambda:" << lambda
<< " step_quality: " << step_quality
<< " max_inc: " << max_inc << " Error: " << eopt.error
<< " num points " << eopt.num_points << std::endl;
lambda = std::max(
min_lambda,
lambda *
std::max(1.0 / 3, 1 - std::pow(2 * step_quality - 1, 3.0)));
lambda_vee = 2;
error = eopt.error;
num_points = eopt.num_points;
reprojection_error = eopt.reprojection_error;
step = true;
}
max_iter--;
}
if (converged) {
std::cout << "[CONVERGED]" << std::endl;
}
return converged;
}
void recompute_size() {
offset_poses.clear();
offset_T_i_c.clear();
offset_cam_intrinsics.clear();
size_t curr_offset = 0;
for (const auto &kv : timestam_to_pose) {
offset_poses[kv.first] = curr_offset;
curr_offset += POSE_SIZE;
}
offset_T_i_c.emplace_back(curr_offset);
for (size_t i = 0; i < calib->T_i_c.size(); i++)
offset_T_i_c.emplace_back(offset_T_i_c.back() + POSE_SIZE);
offset_cam_intrinsics.emplace_back(offset_T_i_c.back());
for (size_t i = 0; i < calib->intrinsics.size(); i++)
offset_cam_intrinsics.emplace_back(offset_cam_intrinsics.back() +
calib->intrinsics[i].getN());
problem_size = offset_cam_intrinsics.back();
}
Sophus::SE3d getT_w_i(int64_t i) {
auto it = timestam_to_pose.find(i);
if (it == timestam_to_pose.end())
return Sophus::SE3d();
else
return it->second;
}
void setAprilgridCorners3d(const Eigen::aligned_vector<Eigen::Vector4d> &v) {
aprilgrid_corner_pos_3d = v;
}
void init() {
recompute_size();
ccd.calibration = calib.get();
ccd.aprilgrid_corner_pos_3d = &aprilgrid_corner_pos_3d;
ccd.offset_poses = &offset_poses;
ccd.offset_T_i_c = &offset_T_i_c;
ccd.offset_intrinsics = &offset_cam_intrinsics;
}
void addAprilgridMeasurement(
int64_t t_ns, int cam_id,
const Eigen::aligned_vector<Eigen::Vector2d> &corners_pos,
const std::vector<int> &corner_id) {
aprilgrid_corners_measurements.emplace_back();
aprilgrid_corners_measurements.back().timestamp_ns = t_ns;
aprilgrid_corners_measurements.back().cam_id = cam_id;
aprilgrid_corners_measurements.back().corner_pos = corners_pos;
aprilgrid_corners_measurements.back().corner_id = corner_id;
}
void addPoseMeasurement(int64_t t_ns, const Sophus::SE3d &pose) {
timestam_to_pose[t_ns] = pose;
}
void setVignette(const std::vector<basalt::RdSpline<1, 4>> &vign) {
calib->vignette = vign;
}
void setResolution(const Eigen::aligned_vector<Eigen::Vector2i> &resolution) {
calib->resolution = resolution;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
std::shared_ptr<Calibration<Scalar>> calib;
private:
typename LinearizePosesOpt<
Scalar, SparseHashAccumulator<Scalar>>::CalibCommonData ccd;
Scalar lambda, min_lambda, max_lambda, lambda_vee;
size_t problem_size;
std::unordered_map<int64_t, size_t> offset_poses;
std::vector<size_t> offset_cam_intrinsics;
std::vector<size_t> offset_T_i_c;
// frame poses
Eigen::aligned_unordered_map<int64_t, Sophus::SE3d> timestam_to_pose;
Eigen::aligned_vector<AprilgridCornersData> aprilgrid_corners_measurements;
Eigen::aligned_vector<Eigen::Vector4d> aprilgrid_corner_pos_3d;
};
} // namespace basalt

View File

@@ -0,0 +1,846 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef BASALT_SPLINE_LINEARIZE_H
#define BASALT_SPLINE_LINEARIZE_H
#include <basalt/io/dataset_io.h>
#include <basalt/spline/se3_spline.h>
#include <basalt/camera/generic_camera.hpp>
#include <basalt/camera/stereographic_param.hpp>
#include <basalt/optimization/linearize.h>
#include <basalt/utils/test_utils.h>
#include <tbb/blocked_range.h>
namespace basalt {
template <int N, typename Scalar, typename AccumT>
struct LinearizeSplineOpt : public LinearizeBase<Scalar> {
static const int POSE_SIZE = LinearizeBase<Scalar>::POSE_SIZE;
static const int POS_SIZE = LinearizeBase<Scalar>::POS_SIZE;
static const int POS_OFFSET = LinearizeBase<Scalar>::POS_OFFSET;
static const int ROT_SIZE = LinearizeBase<Scalar>::ROT_SIZE;
static const int ROT_OFFSET = LinearizeBase<Scalar>::ROT_OFFSET;
static const int ACCEL_BIAS_SIZE = LinearizeBase<Scalar>::ACCEL_BIAS_SIZE;
static const int GYRO_BIAS_SIZE = LinearizeBase<Scalar>::GYRO_BIAS_SIZE;
static const int G_SIZE = LinearizeBase<Scalar>::G_SIZE;
static const int TIME_SIZE = LinearizeBase<Scalar>::TIME_SIZE;
static const int ACCEL_BIAS_OFFSET = LinearizeBase<Scalar>::ACCEL_BIAS_OFFSET;
static const int GYRO_BIAS_OFFSET = LinearizeBase<Scalar>::GYRO_BIAS_OFFSET;
static const int G_OFFSET = LinearizeBase<Scalar>::G_OFFSET;
typedef Sophus::SE3<Scalar> SE3;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 6, 1> Vector6;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 6, 6> Matrix6;
typedef Eigen::Matrix<Scalar, 2, 4> Matrix24;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef Se3Spline<N, Scalar> SplineT;
typedef typename Eigen::aligned_deque<PoseData>::const_iterator PoseDataIter;
typedef typename Eigen::aligned_deque<GyroData>::const_iterator GyroDataIter;
typedef
typename Eigen::aligned_deque<AccelData>::const_iterator AccelDataIter;
typedef typename Eigen::aligned_deque<AprilgridCornersData>::const_iterator
AprilgridCornersDataIter;
typedef typename Eigen::aligned_deque<MocapPoseData>::const_iterator
MocapPoseDataIter;
// typedef typename LinearizeBase<Scalar>::PoseCalibH PoseCalibH;
typedef typename LinearizeBase<Scalar>::CalibCommonData CalibCommonData;
AccumT accum;
Scalar error;
Scalar reprojection_error;
int num_points;
size_t opt_size;
const SplineT* spline;
LinearizeSplineOpt(size_t opt_size, const SplineT* spl,
const CalibCommonData& common_data)
: opt_size(opt_size), spline(spl) {
this->common_data = common_data;
accum.reset(opt_size);
error = 0;
reprojection_error = 0;
num_points = 0;
BASALT_ASSERT(spline);
}
LinearizeSplineOpt(const LinearizeSplineOpt& other, tbb::split)
: opt_size(other.opt_size), spline(other.spline) {
this->common_data = other.common_data;
accum.reset(opt_size);
error = 0;
reprojection_error = 0;
num_points = 0;
}
void operator()(const tbb::blocked_range<PoseDataIter>& r) {
for (const PoseData& pm : r) {
int64_t start_idx;
typename SplineT::SO3JacobianStruct J_rot;
typename SplineT::PosJacobianStruct J_pos;
int64_t time_ns = pm.timestamp_ns;
// std::cout << "time " << time << std::endl;
// std::cout << "sline.minTime() " << spline.minTime() << std::endl;
BASALT_ASSERT_STREAM(
time_ns >= spline->minTimeNs(),
"time " << time_ns << " spline.minTimeNs() " << spline->minTimeNs());
// Residual from current value of spline
Vector3 residual_pos =
spline->positionResidual(time_ns, pm.data.translation(), &J_pos);
Vector3 residual_rot =
spline->orientationResidual(time_ns, pm.data.so3(), &J_rot);
// std::cerr << "residual_pos " << residual_pos.transpose() << std::endl;
// std::cerr << "residual_rot " << residual_rot.transpose() << std::endl;
BASALT_ASSERT(J_pos.start_idx == J_rot.start_idx);
start_idx = J_pos.start_idx;
// std::cout << "J_pos.start_idx " << J_pos.start_idx << std::endl;
const Scalar& pose_var_inv = this->common_data.pose_var_inv;
error += pose_var_inv *
(residual_pos.squaredNorm() + residual_rot.squaredNorm());
for (size_t i = 0; i < N; i++) {
size_t start_i = (start_idx + i) * POSE_SIZE;
// std::cout << "start_idx " << start_idx << std::endl;
// std::cout << "start_i " << start_i << std::endl;
BASALT_ASSERT(start_i < opt_size);
for (size_t j = 0; j <= i; j++) {
size_t start_j = (start_idx + j) * POSE_SIZE;
BASALT_ASSERT(start_j < opt_size);
accum.template addH<POS_SIZE, POS_SIZE>(
start_i + POS_OFFSET, start_j + POS_OFFSET,
this->common_data.pose_var_inv * J_pos.d_val_d_knot[i] *
J_pos.d_val_d_knot[j] * Matrix3::Identity());
accum.template addH<ROT_SIZE, ROT_SIZE>(
start_i + ROT_OFFSET, start_j + ROT_OFFSET,
this->common_data.pose_var_inv *
J_rot.d_val_d_knot[i].transpose() * J_rot.d_val_d_knot[j]);
}
accum.template addB<POS_SIZE>(
start_i + POS_OFFSET,
pose_var_inv * J_pos.d_val_d_knot[i] * residual_pos);
accum.template addB<ROT_SIZE>(
start_i + ROT_OFFSET,
pose_var_inv * J_rot.d_val_d_knot[i].transpose() * residual_rot);
}
}
}
void operator()(const tbb::blocked_range<AccelDataIter>& r) {
// size_t num_knots = spline.numKnots();
// size_t bias_block_offset = POSE_SIZE * num_knots;
for (const AccelData& pm : r) {
typename SplineT::AccelPosSO3JacobianStruct J;
typename SplineT::Mat39 J_bias;
Matrix3 J_g;
int64_t t = pm.timestamp_ns;
// std::cout << "time " << t << std::endl;
// std::cout << "sline.minTime() " << spline.minTime() << std::endl;
BASALT_ASSERT_STREAM(
t >= spline->minTimeNs(),
"t " << t << " spline.minTime() " << spline->minTimeNs());
BASALT_ASSERT_STREAM(
t <= spline->maxTimeNs(),
"t " << t << " spline.maxTime() " << spline->maxTimeNs());
Vector3 residual = spline->accelResidual(
t, pm.data, this->common_data.calibration->calib_accel_bias,
*(this->common_data.g), &J, &J_bias, &J_g);
if (!this->common_data.opt_imu_scale) {
J_bias.template block<3, 6>(0, 3).setZero();
}
// std::cerr << "================================" << std::endl;
// std::cerr << "accel res: " << residual.transpose() << std::endl;
// std::cerr << "accel_bias.transpose(): "
// << this->common_data.calibration->accel_bias.transpose()
// << std::endl;
// std::cerr << "*(this->common_data.g): "
// << this->common_data.g->transpose() << std::endl;
// std::cerr << "pm.data " << pm.data.transpose() << std::endl;
// std::cerr << "time " << t << std::endl;
// std::cerr << "sline.minTime() " << spline.minTime() << std::endl;
// std::cerr << "sline.maxTime() " << spline.maxTime() << std::endl;
// std::cerr << "================================" << std::endl;
const Vector3& accel_var_inv = this->common_data.accel_var_inv;
error += residual.transpose() * accel_var_inv.asDiagonal() * residual;
size_t start_bias =
this->common_data.bias_block_offset + ACCEL_BIAS_OFFSET;
size_t start_g = this->common_data.bias_block_offset + G_OFFSET;
for (size_t i = 0; i < N; i++) {
size_t start_i = (J.start_idx + i) * POSE_SIZE;
BASALT_ASSERT(start_i < opt_size);
for (size_t j = 0; j <= i; j++) {
size_t start_j = (J.start_idx + j) * POSE_SIZE;
BASALT_ASSERT(start_j < opt_size);
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_i, start_j,
J.d_val_d_knot[i].transpose() * accel_var_inv.asDiagonal() *
J.d_val_d_knot[j]);
}
accum.template addH<ACCEL_BIAS_SIZE, POSE_SIZE>(
start_bias, start_i,
J_bias.transpose() * accel_var_inv.asDiagonal() *
J.d_val_d_knot[i]);
if (this->common_data.opt_g) {
accum.template addH<G_SIZE, POSE_SIZE>(
start_g, start_i,
J_g.transpose() * accel_var_inv.asDiagonal() * J.d_val_d_knot[i]);
}
accum.template addB<POSE_SIZE>(start_i, J.d_val_d_knot[i].transpose() *
accel_var_inv.asDiagonal() *
residual);
}
accum.template addH<ACCEL_BIAS_SIZE, ACCEL_BIAS_SIZE>(
start_bias, start_bias,
J_bias.transpose() * accel_var_inv.asDiagonal() * J_bias);
if (this->common_data.opt_g) {
accum.template addH<G_SIZE, ACCEL_BIAS_SIZE>(
start_g, start_bias,
J_g.transpose() * accel_var_inv.asDiagonal() * J_bias);
accum.template addH<G_SIZE, G_SIZE>(
start_g, start_g,
J_g.transpose() * accel_var_inv.asDiagonal() * J_g);
}
accum.template addB<ACCEL_BIAS_SIZE>(
start_bias,
J_bias.transpose() * accel_var_inv.asDiagonal() * residual);
if (this->common_data.opt_g) {
accum.template addB<G_SIZE>(
start_g, J_g.transpose() * accel_var_inv.asDiagonal() * residual);
}
}
}
void operator()(const tbb::blocked_range<GyroDataIter>& r) {
// size_t num_knots = spline.numKnots();
// size_t bias_block_offset = POSE_SIZE * num_knots;
for (const GyroData& pm : r) {
typename SplineT::SO3JacobianStruct J;
typename SplineT::Mat312 J_bias;
int64_t t_ns = pm.timestamp_ns;
BASALT_ASSERT(t_ns >= spline->minTimeNs());
BASALT_ASSERT(t_ns <= spline->maxTimeNs());
const Vector3& gyro_var_inv = this->common_data.gyro_var_inv;
Vector3 residual = spline->gyroResidual(
t_ns, pm.data, this->common_data.calibration->calib_gyro_bias, &J,
&J_bias);
if (!this->common_data.opt_imu_scale) {
J_bias.template block<3, 9>(0, 3).setZero();
}
// std::cerr << "==============================" << std::endl;
// std::cerr << "residual " << residual.transpose() << std::endl;
// std::cerr << "gyro_bias "
// << this->common_data.calibration->gyro_bias.transpose()
// << std::endl;
// std::cerr << "pm.data " << pm.data.transpose() << std::endl;
// std::cerr << "t_ns " << t_ns << std::endl;
error += residual.transpose() * gyro_var_inv.asDiagonal() * residual;
size_t start_bias =
this->common_data.bias_block_offset + GYRO_BIAS_OFFSET;
for (size_t i = 0; i < N; i++) {
size_t start_i = (J.start_idx + i) * POSE_SIZE + ROT_OFFSET;
// std::cout << "start_idx " << J.start_idx << std::endl;
// std::cout << "start_i " << start_i << std::endl;
BASALT_ASSERT(start_i < opt_size);
for (size_t j = 0; j <= i; j++) {
size_t start_j = (J.start_idx + j) * POSE_SIZE + ROT_OFFSET;
// std::cout << "start_j " << start_j << std::endl;
BASALT_ASSERT(start_i < opt_size);
accum.template addH<ROT_SIZE, ROT_SIZE>(
start_i, start_j,
J.d_val_d_knot[i].transpose() * gyro_var_inv.asDiagonal() *
J.d_val_d_knot[j]);
}
accum.template addH<GYRO_BIAS_SIZE, ROT_SIZE>(
start_bias, start_i,
J_bias.transpose() * gyro_var_inv.asDiagonal() * J.d_val_d_knot[i]);
accum.template addB<ROT_SIZE>(start_i, J.d_val_d_knot[i].transpose() *
gyro_var_inv.asDiagonal() *
residual);
}
accum.template addH<GYRO_BIAS_SIZE, GYRO_BIAS_SIZE>(
start_bias, start_bias,
J_bias.transpose() * gyro_var_inv.asDiagonal() * J_bias);
accum.template addB<GYRO_BIAS_SIZE>(
start_bias,
J_bias.transpose() * gyro_var_inv.asDiagonal() * residual);
}
}
void operator()(const tbb::blocked_range<AprilgridCornersDataIter>& r) {
for (const AprilgridCornersData& acd : r) {
std::visit(
[&](const auto& cam) {
constexpr int INTRINSICS_SIZE =
std::remove_reference<decltype(cam)>::type::N;
typename SplineT::PosePosSO3JacobianStruct J;
int64_t time_ns = acd.timestamp_ns +
this->common_data.calibration->cam_time_offset_ns;
if (time_ns < spline->minTimeNs() || time_ns >= spline->maxTimeNs())
return;
SE3 T_w_i = spline->pose(time_ns, &J);
Vector6 d_T_wi_d_time;
spline->d_pose_d_t(time_ns, d_T_wi_d_time);
SE3 T_w_c =
T_w_i * this->common_data.calibration->T_i_c[acd.cam_id];
SE3 T_c_w = T_w_c.inverse();
Eigen::Matrix4d T_c_w_m = T_c_w.matrix();
typename LinearizeBase<Scalar>::template PoseCalibH<INTRINSICS_SIZE>
cph;
double err = 0;
double reproj_err = 0;
int num_inliers = 0;
for (size_t i = 0; i < acd.corner_pos.size(); i++) {
this->linearize_point(acd.corner_pos[i], acd.corner_id[i],
T_c_w_m, cam, &cph, err, num_inliers,
reproj_err);
}
error += err;
reprojection_error += reproj_err;
num_points += num_inliers;
Matrix6 Adj = -this->common_data.calibration->T_i_c[acd.cam_id]
.inverse()
.Adj();
Matrix6 A_H_p_A = Adj.transpose() * cph.H_pose_accum * Adj;
size_t T_i_c_start = this->common_data.offset_T_i_c->at(acd.cam_id);
size_t calib_start =
this->common_data.offset_intrinsics->at(acd.cam_id);
size_t start_cam_time_offset =
this->common_data.mocap_block_offset + 2 * POSE_SIZE + 1;
for (int i = 0; i < N; i++) {
int start_i = (J.start_idx + i) * POSE_SIZE;
Matrix6 Ji_A_H_p_A = J.d_val_d_knot[i].transpose() * A_H_p_A;
for (int j = 0; j <= i; j++) {
int start_j = (J.start_idx + j) * POSE_SIZE;
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_i, start_j, Ji_A_H_p_A * J.d_val_d_knot[j]);
}
accum.template addH<POSE_SIZE, POSE_SIZE>(
T_i_c_start, start_i,
-cph.H_pose_accum * Adj * J.d_val_d_knot[i]);
if (this->common_data.opt_intrinsics)
accum.template addH<INTRINSICS_SIZE, POSE_SIZE>(
calib_start, start_i,
cph.H_intr_pose_accum * Adj * J.d_val_d_knot[i]);
if (this->common_data.opt_cam_time_offset)
accum.template addH<TIME_SIZE, POSE_SIZE>(
start_cam_time_offset, start_i,
d_T_wi_d_time.transpose() * A_H_p_A * J.d_val_d_knot[i]);
accum.template addB<POSE_SIZE>(
start_i, J.d_val_d_knot[i].transpose() * Adj.transpose() *
cph.b_pose_accum);
}
accum.template addH<POSE_SIZE, POSE_SIZE>(T_i_c_start, T_i_c_start,
cph.H_pose_accum);
accum.template addB<POSE_SIZE>(T_i_c_start, -cph.b_pose_accum);
if (this->common_data.opt_intrinsics) {
accum.template addH<INTRINSICS_SIZE, POSE_SIZE>(
calib_start, T_i_c_start, -cph.H_intr_pose_accum);
accum.template addH<INTRINSICS_SIZE, INTRINSICS_SIZE>(
calib_start, calib_start, cph.H_intr_accum);
accum.template addB<INTRINSICS_SIZE>(calib_start,
cph.b_intr_accum);
}
if (this->common_data.opt_cam_time_offset) {
accum.template addH<TIME_SIZE, POSE_SIZE>(
start_cam_time_offset, T_i_c_start,
-d_T_wi_d_time.transpose() * Adj.transpose() *
cph.H_pose_accum);
if (this->common_data.opt_intrinsics)
accum.template addH<TIME_SIZE, INTRINSICS_SIZE>(
start_cam_time_offset, calib_start,
d_T_wi_d_time.transpose() * Adj.transpose() *
cph.H_intr_pose_accum.transpose());
accum.template addH<TIME_SIZE, TIME_SIZE>(
start_cam_time_offset, start_cam_time_offset,
d_T_wi_d_time.transpose() * A_H_p_A * d_T_wi_d_time);
accum.template addB<TIME_SIZE>(start_cam_time_offset,
d_T_wi_d_time.transpose() *
Adj.transpose() *
cph.b_pose_accum);
}
},
this->common_data.calibration->intrinsics[acd.cam_id].variant);
}
}
void operator()(const tbb::blocked_range<MocapPoseDataIter>& r) {
for (const MocapPoseData& pm : r) {
typename SplineT::PosePosSO3JacobianStruct J_pose;
int64_t time_ns =
pm.timestamp_ns +
this->common_data.mocap_calibration->mocap_time_offset_ns;
// std::cout << "time " << time << std::endl;
// std::cout << "sline.minTime() " << spline.minTime() << std::endl;
if (time_ns < spline->minTimeNs() || time_ns >= spline->maxTimeNs())
continue;
BASALT_ASSERT_STREAM(
time_ns >= spline->minTimeNs(),
"time " << time_ns << " spline.minTimeNs() " << spline->minTimeNs());
const SE3 T_moc_w = this->common_data.mocap_calibration->T_moc_w;
const SE3 T_i_mark = this->common_data.mocap_calibration->T_i_mark;
const SE3 T_w_i = spline->pose(time_ns, &J_pose);
const SE3 T_moc_mark = T_moc_w * T_w_i * T_i_mark;
const SE3 T_mark_moc_meas = pm.data.inverse();
Vector6 residual = Sophus::se3_logd(T_mark_moc_meas * T_moc_mark);
// TODO: check derivatives
Matrix6 d_res_d_T_i_mark;
Sophus::rightJacobianInvSE3Decoupled(residual, d_res_d_T_i_mark);
Matrix6 d_res_d_T_w_i = d_res_d_T_i_mark * T_i_mark.inverse().Adj();
Matrix6 d_res_d_T_moc_w =
d_res_d_T_i_mark * (T_w_i * T_i_mark).inverse().Adj();
Vector6 d_T_wi_d_time;
spline->d_pose_d_t(time_ns, d_T_wi_d_time);
Vector6 d_res_d_time = d_res_d_T_w_i * d_T_wi_d_time;
size_t start_idx = J_pose.start_idx;
size_t start_T_moc_w = this->common_data.mocap_block_offset;
size_t start_T_i_mark = this->common_data.mocap_block_offset + POSE_SIZE;
size_t start_mocap_time_offset =
this->common_data.mocap_block_offset + 2 * POSE_SIZE;
// std::cout << "J_pos.start_idx " << J_pos.start_idx << std::endl;
const Scalar& mocap_var_inv = this->common_data.mocap_var_inv;
error += mocap_var_inv * residual.squaredNorm();
Matrix6 H_T_w_i = d_res_d_T_w_i.transpose() * d_res_d_T_w_i;
for (size_t i = 0; i < N; i++) {
size_t start_i = (start_idx + i) * POSE_SIZE;
// std::cout << "start_idx " << start_idx << std::endl;
// std::cout << "start_i " << start_i << std::endl;
BASALT_ASSERT(start_i < opt_size);
Matrix6 Ji_H_T_w_i = J_pose.d_val_d_knot[i].transpose() * H_T_w_i;
for (size_t j = 0; j <= i; j++) {
size_t start_j = (start_idx + j) * POSE_SIZE;
BASALT_ASSERT(start_j < opt_size);
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_i, start_j,
mocap_var_inv * Ji_H_T_w_i * J_pose.d_val_d_knot[j]);
}
accum.template addB<POSE_SIZE>(
start_i, mocap_var_inv * J_pose.d_val_d_knot[i].transpose() *
d_res_d_T_w_i.transpose() * residual);
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_T_moc_w, start_i,
mocap_var_inv * d_res_d_T_moc_w.transpose() * d_res_d_T_w_i *
J_pose.d_val_d_knot[i]);
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_T_i_mark, start_i,
mocap_var_inv * d_res_d_T_i_mark.transpose() * d_res_d_T_w_i *
J_pose.d_val_d_knot[i]);
accum.template addH<TIME_SIZE, POSE_SIZE>(
start_mocap_time_offset, start_i,
mocap_var_inv * d_res_d_time.transpose() * d_res_d_T_w_i *
J_pose.d_val_d_knot[i]);
}
// start_T_moc_w
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_T_moc_w, start_T_moc_w,
mocap_var_inv * d_res_d_T_moc_w.transpose() * d_res_d_T_moc_w);
// start_T_i_mark
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_T_i_mark, start_T_moc_w,
mocap_var_inv * d_res_d_T_i_mark.transpose() * d_res_d_T_moc_w);
accum.template addH<POSE_SIZE, POSE_SIZE>(
start_T_i_mark, start_T_i_mark,
mocap_var_inv * d_res_d_T_i_mark.transpose() * d_res_d_T_i_mark);
// start_mocap_time_offset
accum.template addH<TIME_SIZE, POSE_SIZE>(
start_mocap_time_offset, start_T_moc_w,
mocap_var_inv * d_res_d_time.transpose() * d_res_d_T_moc_w);
accum.template addH<TIME_SIZE, POSE_SIZE>(
start_mocap_time_offset, start_T_i_mark,
mocap_var_inv * d_res_d_time.transpose() * d_res_d_T_i_mark);
accum.template addH<TIME_SIZE, TIME_SIZE>(
start_mocap_time_offset, start_mocap_time_offset,
mocap_var_inv * d_res_d_time.transpose() * d_res_d_time);
// B
accum.template addB<POSE_SIZE>(
start_T_moc_w,
mocap_var_inv * d_res_d_T_moc_w.transpose() * residual);
accum.template addB<POSE_SIZE>(
start_T_i_mark,
mocap_var_inv * d_res_d_T_i_mark.transpose() * residual);
accum.template addB<TIME_SIZE>(
start_mocap_time_offset,
mocap_var_inv * d_res_d_time.transpose() * residual);
}
}
void join(LinearizeSplineOpt& rhs) {
accum.join(rhs.accum);
error += rhs.error;
reprojection_error += rhs.reprojection_error;
num_points += rhs.num_points;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
template <int N, typename Scalar>
struct ComputeErrorSplineOpt : public LinearizeBase<Scalar> {
typedef Sophus::SE3<Scalar> SE3;
typedef Eigen::Matrix<Scalar, 2, 1> Vector2;
typedef Eigen::Matrix<Scalar, 3, 1> Vector3;
typedef Eigen::Matrix<Scalar, 4, 1> Vector4;
typedef Eigen::Matrix<Scalar, 6, 1> Vector6;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 6, 6> Matrix6;
typedef Eigen::Matrix<Scalar, 2, 4> Matrix24;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef Se3Spline<N, Scalar> SplineT;
typedef typename Eigen::aligned_deque<PoseData>::const_iterator PoseDataIter;
typedef typename Eigen::aligned_deque<GyroData>::const_iterator GyroDataIter;
typedef
typename Eigen::aligned_deque<AccelData>::const_iterator AccelDataIter;
typedef typename Eigen::aligned_deque<AprilgridCornersData>::const_iterator
AprilgridCornersDataIter;
typedef typename Eigen::aligned_deque<MocapPoseData>::const_iterator
MocapPoseDataIter;
// typedef typename LinearizeBase<Scalar>::PoseCalibH PoseCalibH;
typedef typename LinearizeBase<Scalar>::CalibCommonData CalibCommonData;
Scalar error;
Scalar reprojection_error;
int num_points;
size_t opt_size;
const SplineT* spline;
ComputeErrorSplineOpt(size_t opt_size, const SplineT* spl,
const CalibCommonData& common_data)
: opt_size(opt_size), spline(spl) {
this->common_data = common_data;
error = 0;
reprojection_error = 0;
num_points = 0;
BASALT_ASSERT(spline);
}
ComputeErrorSplineOpt(const ComputeErrorSplineOpt& other, tbb::split)
: opt_size(other.opt_size), spline(other.spline) {
this->common_data = other.common_data;
error = 0;
reprojection_error = 0;
num_points = 0;
}
void operator()(const tbb::blocked_range<PoseDataIter>& r) {
for (const PoseData& pm : r) {
int64_t time_ns = pm.timestamp_ns;
BASALT_ASSERT_STREAM(
time_ns >= spline->minTimeNs(),
"time " << time_ns << " spline.minTimeNs() " << spline->minTimeNs());
// Residual from current value of spline
Vector3 residual_pos =
spline->positionResidual(time_ns, pm.data.translation());
Vector3 residual_rot =
spline->orientationResidual(time_ns, pm.data.so3());
// std::cout << "J_pos.start_idx " << J_pos.start_idx << std::endl;
const Scalar& pose_var_inv = this->common_data.pose_var_inv;
error += pose_var_inv *
(residual_pos.squaredNorm() + residual_rot.squaredNorm());
}
}
void operator()(const tbb::blocked_range<AccelDataIter>& r) {
// size_t num_knots = spline.numKnots();
// size_t bias_block_offset = POSE_SIZE * num_knots;
for (const AccelData& pm : r) {
int64_t t = pm.timestamp_ns;
// std::cout << "time " << t << std::endl;
// std::cout << "sline.minTime() " << spline.minTime() << std::endl;
BASALT_ASSERT_STREAM(
t >= spline->minTimeNs(),
"t " << t << " spline.minTime() " << spline->minTimeNs());
BASALT_ASSERT_STREAM(
t <= spline->maxTimeNs(),
"t " << t << " spline.maxTime() " << spline->maxTimeNs());
Vector3 residual = spline->accelResidual(
t, pm.data, this->common_data.calibration->calib_accel_bias,
*(this->common_data.g));
const Vector3& accel_var_inv = this->common_data.accel_var_inv;
error += residual.transpose() * accel_var_inv.asDiagonal() * residual;
}
}
void operator()(const tbb::blocked_range<GyroDataIter>& r) {
// size_t num_knots = spline.numKnots();
// size_t bias_block_offset = POSE_SIZE * num_knots;
for (const GyroData& pm : r) {
int64_t t_ns = pm.timestamp_ns;
BASALT_ASSERT(t_ns >= spline->minTimeNs());
BASALT_ASSERT(t_ns <= spline->maxTimeNs());
const Vector3& gyro_var_inv = this->common_data.gyro_var_inv;
Vector3 residual = spline->gyroResidual(
t_ns, pm.data, this->common_data.calibration->calib_gyro_bias);
error += residual.transpose() * gyro_var_inv.asDiagonal() * residual;
}
}
void operator()(const tbb::blocked_range<AprilgridCornersDataIter>& r) {
for (const AprilgridCornersData& acd : r) {
std::visit(
[&](const auto& cam) {
int64_t time_ns = acd.timestamp_ns +
this->common_data.calibration->cam_time_offset_ns;
if (time_ns < spline->minTimeNs() || time_ns >= spline->maxTimeNs())
return;
SE3 T_w_i = spline->pose(time_ns);
SE3 T_w_c =
T_w_i * this->common_data.calibration->T_i_c[acd.cam_id];
SE3 T_c_w = T_w_c.inverse();
Eigen::Matrix4d T_c_w_m = T_c_w.matrix();
double err = 0;
double reproj_err = 0;
int num_inliers = 0;
for (size_t i = 0; i < acd.corner_pos.size(); i++) {
this->linearize_point(acd.corner_pos[i], acd.corner_id[i],
T_c_w_m, cam, nullptr, err, num_inliers,
reproj_err);
}
error += err;
reprojection_error += reproj_err;
num_points += num_inliers;
},
this->common_data.calibration->intrinsics[acd.cam_id].variant);
}
}
void operator()(const tbb::blocked_range<MocapPoseDataIter>& r) {
for (const MocapPoseData& pm : r) {
int64_t time_ns =
pm.timestamp_ns +
this->common_data.mocap_calibration->mocap_time_offset_ns;
if (time_ns < spline->minTimeNs() || time_ns >= spline->maxTimeNs())
continue;
BASALT_ASSERT_STREAM(
time_ns >= spline->minTimeNs(),
"time " << time_ns << " spline.minTimeNs() " << spline->minTimeNs());
const SE3 T_moc_w = this->common_data.mocap_calibration->T_moc_w;
const SE3 T_i_mark = this->common_data.mocap_calibration->T_i_mark;
const SE3 T_w_i = spline->pose(time_ns);
const SE3 T_moc_mark = T_moc_w * T_w_i * T_i_mark;
const SE3 T_mark_moc_meas = pm.data.inverse();
Vector6 residual = Sophus::se3_logd(T_mark_moc_meas * T_moc_mark);
const Scalar& mocap_var_inv = this->common_data.mocap_var_inv;
error += mocap_var_inv * residual.squaredNorm();
}
}
void join(ComputeErrorSplineOpt& rhs) {
error += rhs.error;
reprojection_error += rhs.reprojection_error;
num_points += rhs.num_points;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
} // namespace basalt
#endif

View File

@@ -0,0 +1,612 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/optimization/accumulator.h>
#include <basalt/optimization/spline_linearize.h>
#include <basalt/calibration/calibration.hpp>
#include <basalt/camera/unified_camera.hpp>
#include <basalt/image/image.h>
#include <basalt/serialization/headers_serialization.h>
#include <tbb/parallel_reduce.h>
#include <chrono>
namespace basalt {
template <int N, typename Scalar>
class SplineOptimization {
public:
typedef LinearizeSplineOpt<N, Scalar, SparseHashAccumulator<Scalar>>
LinearizeT;
typedef typename LinearizeT::SE3 SE3;
typedef typename LinearizeT::Vector2 Vector2;
typedef typename LinearizeT::Vector3 Vector3;
typedef typename LinearizeT::Vector4 Vector4;
typedef typename LinearizeT::VectorX VectorX;
typedef typename LinearizeT::Matrix24 Matrix24;
static const int POSE_SIZE = LinearizeT::POSE_SIZE;
static const int POS_SIZE = LinearizeT::POS_SIZE;
static const int POS_OFFSET = LinearizeT::POS_OFFSET;
static const int ROT_SIZE = LinearizeT::ROT_SIZE;
static const int ROT_OFFSET = LinearizeT::ROT_OFFSET;
static const int ACCEL_BIAS_SIZE = LinearizeT::ACCEL_BIAS_SIZE;
static const int GYRO_BIAS_SIZE = LinearizeT::GYRO_BIAS_SIZE;
static const int G_SIZE = LinearizeT::G_SIZE;
static const int ACCEL_BIAS_OFFSET = LinearizeT::ACCEL_BIAS_OFFSET;
static const int GYRO_BIAS_OFFSET = LinearizeT::GYRO_BIAS_OFFSET;
static const int G_OFFSET = LinearizeT::G_OFFSET;
const Scalar pose_var;
Scalar pose_var_inv;
typedef Eigen::Matrix<Scalar, 3, 3> Matrix3;
typedef Eigen::Matrix<Scalar, 6, 1> Vector6;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;
typedef Se3Spline<N, Scalar> SplineT;
SplineOptimization(int64_t dt_ns = 1e7, double init_lambda = 1e-12)
: pose_var(1e-4),
mocap_initialized(false),
lambda(init_lambda),
min_lambda(1e-18),
max_lambda(100),
lambda_vee(2),
spline(dt_ns),
dt_ns(dt_ns) {
pose_var_inv = 1.0 / pose_var;
// reasonable default values
// calib.accelerometer_noise_var = 2e-2;
// calib.gyroscope_noise_var = 1e-4;
g.setZero();
min_time_us = std::numeric_limits<int64_t>::max();
max_time_us = std::numeric_limits<int64_t>::min();
}
int64_t getCamTimeOffsetNs() const { return calib->cam_time_offset_ns; }
int64_t getMocapTimeOffsetNs() const {
return mocap_calib->mocap_time_offset_ns;
}
const SE3& getCamT_i_c(size_t i) const { return calib->T_i_c[i]; }
SE3& getCamT_i_c(size_t i) { return calib->T_i_c[i]; }
VectorX getIntrinsics(size_t i) const {
return calib->intrinsics[i].getParam();
}
const CalibAccelBias<Scalar>& getAccelBias() const {
return calib->calib_accel_bias;
}
const CalibGyroBias<Scalar>& getGyroBias() const {
return calib->calib_gyro_bias;
}
void resetCalib(size_t num_cams, const std::vector<std::string>& cam_types) {
BASALT_ASSERT(cam_types.size() == num_cams);
calib.reset(new Calibration<Scalar>);
calib->intrinsics.resize(num_cams);
calib->T_i_c.resize(num_cams);
mocap_calib.reset(new MocapCalibration<Scalar>);
}
void resetMocapCalib() { mocap_calib.reset(new MocapCalibration<Scalar>); }
void loadCalib(const std::string& p) {
std::string path = p + "calibration.json";
std::ifstream is(path);
if (is.good()) {
cereal::JSONInputArchive archive(is);
calib.reset(new Calibration<Scalar>);
archive(*calib);
std::cout << "Loaded calibration from: " << path << std::endl;
} else {
std::cerr << "No calibration found. Run camera calibration first!!!"
<< std::endl;
}
}
void saveCalib(const std::string& path) const {
if (calib) {
std::ofstream os(path + "calibration.json");
cereal::JSONOutputArchive archive(os);
archive(*calib);
}
}
void saveMocapCalib(const std::string& path,
int64_t mocap_to_imu_offset_ns = 0) const {
if (calib) {
std::ofstream os(path + "mocap_calibration.json");
cereal::JSONOutputArchive archive(os);
mocap_calib->mocap_to_imu_offset_ns = mocap_to_imu_offset_ns;
archive(*mocap_calib);
}
}
bool calibInitialized() const { return calib != nullptr; }
bool initialized() const { return spline.numKnots() > 0; }
void initSpline(const SE3& pose, int num_knots) {
spline.setKnots(pose, num_knots);
}
void initSpline(const SplineT& other) {
spline.setKnots(other);
// std::cerr << "spline.numKnots() " << spline.numKnots() << std::endl;
// std::cerr << "other.numKnots() " << other.numKnots() << std::endl;
size_t num_knots = spline.numKnots();
for (size_t i = 0; i < num_knots; i++) {
Eigen::Vector3d rot_rand_inc = Eigen::Vector3d::Random() / 20;
Eigen::Vector3d trans_rand_inc = Eigen::Vector3d::Random();
// std::cerr << "rot_rand_inc " << rot_rand_inc.transpose() << std::endl;
// std::cerr << "trans_rand_inc " << trans_rand_inc.transpose() <<
// std::endl;
spline.getKnotSO3(i) *= Sophus::SO3d::exp(rot_rand_inc);
spline.getKnotPos(i) += trans_rand_inc;
}
}
const SplineT& getSpline() const { return spline; }
Vector3 getG() const { return g; }
void setG(const Vector3& g_a) { g = g_a; }
// const Calibration& getCalib() const { return calib; }
// void setCalib(const Calibration& c) { calib = c; }
SE3 getT_w_moc() const { return mocap_calib->T_moc_w.inverse(); }
void setT_w_moc(const SE3& val) { mocap_calib->T_moc_w = val.inverse(); }
SE3 getT_mark_i() const { return mocap_calib->T_i_mark.inverse(); }
void setT_mark_i(const SE3& val) { mocap_calib->T_i_mark = val.inverse(); }
Eigen::Vector3d getTransAccelWorld(int64_t t_ns) const {
return spline.transAccelWorld(t_ns);
}
Eigen::Vector3d getRotVelBody(int64_t t_ns) const {
return spline.rotVelBody(t_ns);
}
SE3 getT_w_i(int64_t t_ns) { return spline.pose(t_ns); }
void setAprilgridCorners3d(const Eigen::aligned_vector<Eigen::Vector4d>& v) {
aprilgrid_corner_pos_3d = v;
}
void addPoseMeasurement(int64_t t_ns, const SE3& pose) {
min_time_us = std::min(min_time_us, t_ns);
max_time_us = std::max(max_time_us, t_ns);
pose_measurements.emplace_back();
pose_measurements.back().timestamp_ns = t_ns;
pose_measurements.back().data = pose;
}
void addMocapMeasurement(int64_t t_ns, const SE3& pose) {
mocap_measurements.emplace_back();
mocap_measurements.back().timestamp_ns = t_ns;
mocap_measurements.back().data = pose;
}
void addAccelMeasurement(int64_t t_ns, const Vector3& meas) {
min_time_us = std::min(min_time_us, t_ns);
max_time_us = std::max(max_time_us, t_ns);
accel_measurements.emplace_back();
accel_measurements.back().timestamp_ns = t_ns;
accel_measurements.back().data = meas;
}
void addGyroMeasurement(int64_t t_ns, const Vector3& meas) {
min_time_us = std::min(min_time_us, t_ns);
max_time_us = std::max(max_time_us, t_ns);
gyro_measurements.emplace_back();
gyro_measurements.back().timestamp_ns = t_ns;
gyro_measurements.back().data = meas;
}
void addAprilgridMeasurement(
int64_t t_ns, int cam_id,
const Eigen::aligned_vector<Eigen::Vector2d>& corners_pos,
const std::vector<int>& corner_id) {
min_time_us = std::min(min_time_us, t_ns);
max_time_us = std::max(max_time_us, t_ns);
aprilgrid_corners_measurements.emplace_back();
aprilgrid_corners_measurements.back().timestamp_ns = t_ns;
aprilgrid_corners_measurements.back().cam_id = cam_id;
aprilgrid_corners_measurements.back().corner_pos = corners_pos;
aprilgrid_corners_measurements.back().corner_id = corner_id;
}
Scalar getMinTime() const { return min_time_us * 1e-9; }
Scalar getMaxTime() const { return max_time_us * 1e-9; }
int64_t getMinTimeNs() const { return min_time_us; }
int64_t getMaxTimeNs() const { return max_time_us; }
void init() {
int64_t time_interval_us = max_time_us - min_time_us;
if (spline.numKnots() == 0) {
spline.setStartTimeNs(min_time_us);
spline.setKnots(pose_measurements.front().data,
time_interval_us / dt_ns + N + 1);
}
recompute_size();
// std::cout << "spline.minTimeNs() " << spline.minTimeNs() << std::endl;
// std::cout << "spline.maxTimeNs() " << spline.maxTimeNs() << std::endl;
while (!mocap_measurements.empty() &&
mocap_measurements.front().timestamp_ns <=
spline.minTimeNs() + spline.getDtNs())
mocap_measurements.pop_front();
while (!mocap_measurements.empty() &&
mocap_measurements.back().timestamp_ns >=
spline.maxTimeNs() - spline.getDtNs())
mocap_measurements.pop_back();
ccd.calibration = calib.get();
ccd.mocap_calibration = mocap_calib.get();
ccd.aprilgrid_corner_pos_3d = &aprilgrid_corner_pos_3d;
ccd.g = &g;
ccd.offset_intrinsics = &offset_cam_intrinsics;
ccd.offset_T_i_c = &offset_T_i_c;
ccd.bias_block_offset = bias_block_offset;
ccd.mocap_block_offset = mocap_block_offset;
ccd.opt_g = true;
ccd.pose_var_inv = pose_var_inv;
ccd.gyro_var_inv =
calib->dicrete_time_gyro_noise_std().array().square().inverse();
ccd.accel_var_inv =
calib->dicrete_time_accel_noise_std().array().square().inverse();
ccd.mocap_var_inv = pose_var_inv;
}
void recompute_size() {
offset_cam_intrinsics.clear();
size_t num_knots = spline.numKnots();
bias_block_offset = POSE_SIZE * num_knots;
size_t T_i_c_block_offset =
bias_block_offset + ACCEL_BIAS_SIZE + GYRO_BIAS_SIZE + G_SIZE;
offset_T_i_c.emplace_back(T_i_c_block_offset);
for (size_t i = 0; i < calib->T_i_c.size(); i++)
offset_T_i_c.emplace_back(offset_T_i_c.back() + POSE_SIZE);
offset_cam_intrinsics.emplace_back(offset_T_i_c.back());
for (size_t i = 0; i < calib->intrinsics.size(); i++)
offset_cam_intrinsics.emplace_back(offset_cam_intrinsics.back() +
calib->intrinsics[i].getN());
mocap_block_offset = offset_cam_intrinsics.back();
opt_size = mocap_block_offset + 2 * POSE_SIZE + 2;
// std::cerr << "bias_block_offset " << bias_block_offset << std::endl;
// std::cerr << "mocap_block_offset " << mocap_block_offset << std::endl;
// std::cerr << "opt_size " << opt_size << std::endl;
// std::cerr << "offset_T_i_c.back() " << offset_T_i_c.back() <<
// std::endl; std::cerr << "offset_cam_intrinsics.back() " <<
// offset_cam_intrinsics.back()
// << std::endl;
}
// Returns true when converged
bool optimize(bool use_intr, bool use_poses, bool use_april_corners,
bool opt_cam_time_offset, bool opt_imu_scale, bool use_mocap,
double huber_thresh, double stop_thresh, double& error,
int& num_points, double& reprojection_error,
bool print_info = true) {
// std::cerr << "optimize num_knots " << num_knots << std::endl;
ccd.opt_intrinsics = use_intr;
ccd.opt_cam_time_offset = opt_cam_time_offset;
ccd.opt_imu_scale = opt_imu_scale;
ccd.huber_thresh = huber_thresh;
LinearizeT lopt(opt_size, &spline, ccd);
// auto t1 = std::chrono::high_resolution_clock::now();
tbb::blocked_range<PoseDataIter> pose_range(pose_measurements.begin(),
pose_measurements.end());
tbb::blocked_range<AprilgridCornersDataIter> april_range(
aprilgrid_corners_measurements.begin(),
aprilgrid_corners_measurements.end());
tbb::blocked_range<MocapPoseDataIter> mocap_pose_range(
mocap_measurements.begin(), mocap_measurements.end());
tbb::blocked_range<AccelDataIter> accel_range(accel_measurements.begin(),
accel_measurements.end());
tbb::blocked_range<GyroDataIter> gyro_range(gyro_measurements.begin(),
gyro_measurements.end());
if (use_poses) {
tbb::parallel_reduce(pose_range, lopt);
// lopt(pose_range);
}
if (use_april_corners) {
tbb::parallel_reduce(april_range, lopt);
// lopt(april_range);
}
if (use_mocap && mocap_initialized) {
tbb::parallel_reduce(mocap_pose_range, lopt);
// lopt(mocap_pose_range);
} else if (use_mocap && !mocap_initialized) {
std::cout << "Mocap residuals are not used. Initialize Mocap first!"
<< std::endl;
}
tbb::parallel_reduce(accel_range, lopt);
tbb::parallel_reduce(gyro_range, lopt);
error = lopt.error;
num_points = lopt.num_points;
reprojection_error = lopt.reprojection_error;
if (print_info)
std::cout << "[LINEARIZE] Error: " << lopt.error << " num points "
<< lopt.num_points << std::endl;
lopt.accum.setup_solver();
Eigen::VectorXd Hdiag = lopt.accum.Hdiagonal();
bool converged = false;
bool step = false;
int max_iter = 10;
while (!step && max_iter > 0 && !converged) {
Eigen::VectorXd Hdiag_lambda = Hdiag * lambda;
for (int i = 0; i < Hdiag_lambda.size(); i++)
Hdiag_lambda[i] = std::max(Hdiag_lambda[i], min_lambda);
VectorX inc_full = -lopt.accum.solve(&Hdiag_lambda);
double max_inc = inc_full.array().abs().maxCoeff();
if (max_inc < stop_thresh) converged = true;
Calibration<Scalar> calib_backup = *calib;
MocapCalibration<Scalar> mocap_calib_backup = *mocap_calib;
SplineT spline_backup = spline;
Vector3 g_backup = g;
applyInc(inc_full, offset_cam_intrinsics);
ComputeErrorSplineOpt eopt(opt_size, &spline, ccd);
if (use_poses) {
tbb::parallel_reduce(pose_range, eopt);
}
if (use_april_corners) {
tbb::parallel_reduce(april_range, eopt);
}
if (use_mocap && mocap_initialized) {
tbb::parallel_reduce(mocap_pose_range, eopt);
} else if (use_mocap && !mocap_initialized) {
std::cout << "Mocap residuals are not used. Initialize Mocap first!"
<< std::endl;
}
tbb::parallel_reduce(accel_range, eopt);
tbb::parallel_reduce(gyro_range, eopt);
double f_diff = (lopt.error - eopt.error);
double l_diff = 0.5 * inc_full.dot(inc_full * lambda - lopt.accum.getB());
// std::cout << "f_diff " << f_diff << " l_diff " << l_diff << std::endl;
double step_quality = f_diff / l_diff;
if (step_quality < 0) {
if (print_info)
std::cout << "\t[REJECTED] lambda:" << lambda
<< " step_quality: " << step_quality
<< " max_inc: " << max_inc << " Error: " << eopt.error
<< " num points " << eopt.num_points << std::endl;
lambda = std::min(max_lambda, lambda_vee * lambda);
lambda_vee *= 2;
spline = spline_backup;
*calib = calib_backup;
*mocap_calib = mocap_calib_backup;
g = g_backup;
} else {
if (print_info)
std::cout << "\t[ACCEPTED] lambda:" << lambda
<< " step_quality: " << step_quality
<< " max_inc: " << max_inc << " Error: " << eopt.error
<< " num points " << eopt.num_points << std::endl;
lambda = std::max(
min_lambda,
lambda *
std::max(1.0 / 3, 1 - std::pow(2 * step_quality - 1, 3.0)));
lambda_vee = 2;
error = eopt.error;
num_points = eopt.num_points;
reprojection_error = eopt.reprojection_error;
step = true;
}
max_iter--;
}
if (converged && print_info) {
std::cout << "[CONVERGED]" << std::endl;
}
return converged;
}
typename Calibration<Scalar>::Ptr calib;
typename MocapCalibration<Scalar>::Ptr mocap_calib;
bool mocap_initialized;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
typedef typename Eigen::aligned_deque<PoseData>::const_iterator PoseDataIter;
typedef typename Eigen::aligned_deque<GyroData>::const_iterator GyroDataIter;
typedef
typename Eigen::aligned_deque<AccelData>::const_iterator AccelDataIter;
typedef typename Eigen::aligned_deque<AprilgridCornersData>::const_iterator
AprilgridCornersDataIter;
typedef typename Eigen::aligned_deque<MocapPoseData>::const_iterator
MocapPoseDataIter;
void applyInc(VectorX& inc_full,
const std::vector<size_t>& offset_cam_intrinsics) {
size_t num_knots = spline.numKnots();
for (size_t i = 0; i < num_knots; i++) {
Vector6 inc = inc_full.template segment<POSE_SIZE>(POSE_SIZE * i);
// std::cerr << "i: " << i << " inc: " << inc.transpose() << std::endl;
spline.applyInc(i, inc);
}
size_t bias_block_offset = POSE_SIZE * num_knots;
calib->calib_accel_bias += inc_full.template segment<ACCEL_BIAS_SIZE>(
bias_block_offset + ACCEL_BIAS_OFFSET);
calib->calib_gyro_bias += inc_full.template segment<GYRO_BIAS_SIZE>(
bias_block_offset + GYRO_BIAS_OFFSET);
g += inc_full.template segment<G_SIZE>(bias_block_offset + G_OFFSET);
size_t T_i_c_block_offset =
bias_block_offset + ACCEL_BIAS_SIZE + GYRO_BIAS_SIZE + G_SIZE;
for (size_t i = 0; i < calib->T_i_c.size(); i++) {
calib->T_i_c[i] *= Sophus::se3_expd(inc_full.template segment<POSE_SIZE>(
T_i_c_block_offset + i * POSE_SIZE));
}
for (size_t i = 0; i < calib->intrinsics.size(); i++) {
calib->intrinsics[i].applyInc(inc_full.segment(
offset_cam_intrinsics[i], calib->intrinsics[i].getN()));
}
size_t mocap_block_offset = offset_cam_intrinsics.back();
mocap_calib->T_moc_w *= Sophus::se3_expd(
inc_full.template segment<POSE_SIZE>(mocap_block_offset));
mocap_calib->T_i_mark *= Sophus::se3_expd(
inc_full.template segment<POSE_SIZE>(mocap_block_offset + POSE_SIZE));
mocap_calib->mocap_time_offset_ns +=
1e9 * inc_full[mocap_block_offset + 2 * POSE_SIZE];
calib->cam_time_offset_ns +=
1e9 * inc_full[mocap_block_offset + 2 * POSE_SIZE + 1];
// std::cout << "bias_block_offset " << bias_block_offset << std::endl;
// std::cout << "mocap_block_offset " << mocap_block_offset << std::endl;
}
Scalar lambda, min_lambda, max_lambda, lambda_vee;
int64_t min_time_us, max_time_us;
Eigen::aligned_deque<PoseData> pose_measurements;
Eigen::aligned_deque<GyroData> gyro_measurements;
Eigen::aligned_deque<AccelData> accel_measurements;
Eigen::aligned_deque<AprilgridCornersData> aprilgrid_corners_measurements;
Eigen::aligned_deque<MocapPoseData> mocap_measurements;
typename LinearizeT::CalibCommonData ccd;
std::vector<size_t> offset_cam_intrinsics;
std::vector<size_t> offset_T_i_c;
size_t mocap_block_offset;
size_t bias_block_offset;
size_t opt_size;
SplineT spline;
Vector3 g;
Eigen::aligned_vector<Eigen::Vector4d> aprilgrid_corner_pos_3d;
int64_t dt_ns;
}; // namespace basalt
} // namespace basalt

View File

@@ -0,0 +1,146 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/vi_estimator/landmark_database.h>
namespace basalt {
template <class Scalar>
Sophus::SE3<Scalar> computeRelPose(
const Sophus::SE3<Scalar>& T_w_i_h, const Sophus::SE3<Scalar>& T_i_c_h,
const Sophus::SE3<Scalar>& T_w_i_t, const Sophus::SE3<Scalar>& T_i_c_t,
Sophus::Matrix6<Scalar>* d_rel_d_h = nullptr,
Sophus::Matrix6<Scalar>* d_rel_d_t = nullptr) {
Sophus::SE3<Scalar> tmp2 = (T_i_c_t).inverse();
Sophus::SE3<Scalar> T_t_i_h_i;
T_t_i_h_i.so3() = T_w_i_t.so3().inverse() * T_w_i_h.so3();
T_t_i_h_i.translation() =
T_w_i_t.so3().inverse() * (T_w_i_h.translation() - T_w_i_t.translation());
Sophus::SE3<Scalar> tmp = tmp2 * T_t_i_h_i;
Sophus::SE3<Scalar> res = tmp * T_i_c_h;
if (d_rel_d_h) {
Sophus::Matrix3<Scalar> R = T_w_i_h.so3().inverse().matrix();
Sophus::Matrix6<Scalar> RR;
RR.setZero();
RR.template topLeftCorner<3, 3>() = R;
RR.template bottomRightCorner<3, 3>() = R;
*d_rel_d_h = tmp.Adj() * RR;
}
if (d_rel_d_t) {
Sophus::Matrix3<Scalar> R = T_w_i_t.so3().inverse().matrix();
Sophus::Matrix6<Scalar> RR;
RR.setZero();
RR.template topLeftCorner<3, 3>() = R;
RR.template bottomRightCorner<3, 3>() = R;
*d_rel_d_t = -tmp2.Adj() * RR;
}
return res;
}
template <class Scalar, class CamT>
inline bool linearizePoint(
const Eigen::Matrix<Scalar, 2, 1>& kpt_obs, const Keypoint<Scalar>& kpt_pos,
const Eigen::Matrix<Scalar, 4, 4>& T_t_h, const CamT& cam,
Eigen::Matrix<Scalar, 2, 1>& res,
Eigen::Matrix<Scalar, 2, POSE_SIZE>* d_res_d_xi = nullptr,
Eigen::Matrix<Scalar, 2, 3>* d_res_d_p = nullptr,
Eigen::Matrix<Scalar, 4, 1>* proj = nullptr) {
static_assert(std::is_same_v<typename CamT::Scalar, Scalar>);
// Todo implement without jacobians
Eigen::Matrix<Scalar, 4, 2> Jup;
Eigen::Matrix<Scalar, 4, 1> p_h_3d;
p_h_3d = StereographicParam<Scalar>::unproject(kpt_pos.direction, &Jup);
p_h_3d[3] = kpt_pos.inv_dist;
const Eigen::Matrix<Scalar, 4, 1> p_t_3d = T_t_h * p_h_3d;
Eigen::Matrix<Scalar, 2, 4> Jp;
bool valid = cam.project(p_t_3d, res, &Jp);
valid &= res.array().isFinite().all();
if (!valid) {
// std::cerr << " Invalid projection! kpt_pos.dir "
// << kpt_pos.dir.transpose() << " kpt_pos.id " <<
// kpt_pos.id
// << " idx " << kpt_obs.kpt_id << std::endl;
// std::cerr << "T_t_h\n" << T_t_h << std::endl;
// std::cerr << "p_h_3d\n" << p_h_3d.transpose() << std::endl;
// std::cerr << "p_t_3d\n" << p_t_3d.transpose() << std::endl;
return false;
}
if (proj) {
proj->template head<2>() = res;
(*proj)[2] = p_t_3d[3] / p_t_3d.template head<3>().norm();
}
res -= kpt_obs;
if (d_res_d_xi) {
Eigen::Matrix<Scalar, 4, POSE_SIZE> d_point_d_xi;
d_point_d_xi.template topLeftCorner<3, 3>() =
Eigen::Matrix<Scalar, 3, 3>::Identity() * kpt_pos.inv_dist;
d_point_d_xi.template topRightCorner<3, 3>() =
-Sophus::SO3<Scalar>::hat(p_t_3d.template head<3>());
d_point_d_xi.row(3).setZero();
*d_res_d_xi = Jp * d_point_d_xi;
}
if (d_res_d_p) {
Eigen::Matrix<Scalar, 4, 3> Jpp;
Jpp.setZero();
Jpp.template block<3, 2>(0, 0) = T_t_h.template topLeftCorner<3, 4>() * Jup;
Jpp.col(2) = T_t_h.col(3);
*d_res_d_p = Jp * Jpp;
}
return true;
}
} // namespace basalt

View File

@@ -0,0 +1,64 @@
/**
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.
*/
#pragma once
#include <type_traits>
namespace basalt {
template <class I>
typename std::make_signed_t<I> signed_cast(I v) {
static_assert(std::is_unsigned_v<I>, "no unsigned");
return static_cast<typename std::make_signed_t<I>>(v);
}
template <class I>
typename std::make_unsigned_t<I> unsigned_cast(I v) {
static_assert(std::is_signed_v<I>, "no signed");
return static_cast<typename std::make_unsigned_t<I>>(v);
}
// copy-assign map while casting Scalar type of values
template <class M1, class M2>
void assign_cast_map_values(M1& a, const M2& b) {
using Scalar = typename M1::mapped_type::Scalar;
a.clear();
for (const auto& [k, v] : b) {
a.try_emplace(k, v.template cast<Scalar>());
}
}
} // namespace basalt

View File

@@ -0,0 +1,319 @@
/**
BSD 3-Clause License
Copyright (c) 2018, 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.
*/
#pragma once
#include <bitset>
#include <cstdint>
#include <map>
#include <unordered_map>
#include <vector>
#include <tbb/concurrent_unordered_map.h>
#include <Eigen/Core>
#include <Eigen/StdVector>
#include <sophus/se3.hpp>
#include <basalt/utils/hash.h>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
/// ids for 2D features detected in images
using FeatureId = int;
/// identifies a frame of multiple images (stereo pair)
using FrameId = int64_t;
/// identifies the camera (left or right)
using CamId = std::size_t;
/// pair of image timestamp and camera id identifies an image (imageId)
struct TimeCamId {
TimeCamId() : frame_id(0), cam_id(0) {}
TimeCamId(const FrameId& frame_id, const CamId& cam_id)
: frame_id(frame_id), cam_id(cam_id) {}
FrameId frame_id;
CamId cam_id;
};
inline std::ostream& operator<<(std::ostream& os, const TimeCamId& tcid) {
os << tcid.frame_id << "_" << tcid.cam_id;
return os;
}
inline bool operator<(const TimeCamId& o1, const TimeCamId& o2) {
if (o1.frame_id == o2.frame_id) return o1.cam_id < o2.cam_id;
return o1.frame_id < o2.frame_id;
}
inline bool operator==(const TimeCamId& o1, const TimeCamId& o2) {
return o1.frame_id == o2.frame_id && o1.cam_id == o2.cam_id;
}
inline bool operator!=(const TimeCamId& o1, const TimeCamId& o2) {
return o1.frame_id != o2.frame_id || o1.cam_id != o2.cam_id;
}
constexpr static const size_t FEATURE_HASH_MAX_SIZE = 32;
using FeatureHash = std::bitset<FEATURE_HASH_MAX_SIZE>;
using HashBowVector = std::vector<std::pair<FeatureHash, double>>;
/// keypoint positions and descriptors for an image
struct KeypointsData {
/// collection of 2d corner points (indexed by FeatureId)
std::vector<Eigen::Vector2d, Eigen::aligned_allocator<Eigen::Vector2d>>
corners;
/// collection of feature orientation (in radian) with same index as `corners`
/// (indexed by FeatureId)
std::vector<double> corner_angles;
/// collection of feature descriptors with same index as `corners` (indexed by
/// FeatureId)
std::vector<std::bitset<256>> corner_descriptors;
Eigen::aligned_vector<Eigen::Vector4d> corners_3d;
std::vector<FeatureHash> hashes;
HashBowVector bow_vector;
};
/// feature corners is a collection of { imageId => KeypointsData }
using Corners = tbb::concurrent_unordered_map<TimeCamId, KeypointsData,
std::hash<TimeCamId>>;
/// feature matches for an image pair
struct MatchData {
/// estimated transformation (based on inliers or calibration) from the second
/// image's coordinate system to the first image's corrdinate system
Sophus::SE3d T_i_j;
/// collection of {featureId_i, featureId_j} pairs of all matches
std::vector<std::pair<FeatureId, FeatureId>> matches;
/// collection of {featureId_i, featureId_j} pairs of inlier matches
std::vector<std::pair<FeatureId, FeatureId>> inliers;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
/// feature matches is a collection of { (imageId, imageId) => MatchData }
using Matches = tbb::concurrent_unordered_map<
std::pair<TimeCamId, TimeCamId>, MatchData,
std::hash<std::pair<TimeCamId, TimeCamId>>,
std::equal_to<std::pair<TimeCamId, TimeCamId>>,
Eigen::aligned_allocator<
std::pair<const std::pair<TimeCamId, TimeCamId>, MatchData>>>;
/// pair of image and feature indices
using ImageFeaturePair = std::pair<TimeCamId, FeatureId>;
/// Feature tracks are collections of {ImageId => FeatureId}.
/// I.e. a collection of all images that observed this feature and the
/// corresponding feature index in that image.
using FeatureTrack = std::map<TimeCamId, FeatureId>;
/// Ids for feature tracks; also used for landmarks created from (some of) the
/// tracks;
using TrackId = int64_t;
/// FeatureTracks is a collection {TrackId => FeatureTrack}
using FeatureTracks = std::unordered_map<TrackId, FeatureTrack>;
/// cameras in the map
struct Camera {
// camera pose (transforms from camera to world)
Sophus::SE3d T_w_c;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
/// landmarks in the map
struct Landmark {
/// 3d position in world coordinates
Eigen::Vector3d p;
/// Inlier observations in the current map.
/// This is a subset of the original feature track.
FeatureTrack obs;
/// Outlier observations in the current map.
/// This is a subset of the original feature track.
FeatureTrack outlier_obs;
};
/// collection {imageId => Camera} for all cameras in the map
using Cameras =
std::map<TimeCamId, Camera, std::less<TimeCamId>,
Eigen::aligned_allocator<std::pair<const TimeCamId, Camera>>>;
/// collection {trackId => Landmark} for all landmarks in the map.
/// trackIds correspond to feature_tracks
using Landmarks = std::unordered_map<TrackId, Landmark>;
/// camera candidate to be added to map
struct CameraCandidate {
TimeCamId tcid;
std::vector<TrackId> shared_tracks;
// keep track of different stages of adding a set of candidate cameras and its
// landmarks to the map
bool tried = false; //!< tried to add to map
bool camera_added = false; //!< succeeded to add to map
bool landmarks_added = false; //!< added new landmarks to map
};
/// list of current candidates and some book keeping for the different stages
struct CameraCandidates {
enum Stage {
ComputeCandidates,
AddCameras,
AddLandmarks,
Optimize,
RemoveOutliers,
Done
};
std::vector<CameraCandidate> cameras;
Stage current_stage = ComputeCandidates;
int min_localization_inliers = 0;
int max_cameras_to_add = 0;
int num_cameras_added() {
int num_added = 0;
for (const auto& c : cameras) {
if (c.camera_added) {
++num_added;
}
}
return num_added;
}
int num_landmarks_added() {
int num_added = 0;
for (const auto& c : cameras) {
if (c.landmarks_added) {
++num_added;
}
}
return num_added;
}
};
/// Flags for different landmark outlier criteria
enum OutlierFlags {
OutlierNone = 0,
// reprojection error much too large
OutlierReprojectionErrorHuge = 1 << 0,
// reprojection error too large
OutlierReprojectionErrorNormal = 1 << 1,
// distance to a camera too small
OutlierCameraDistance = 1 << 2,
// z-coord in some camera frame too small
OutlierZCoordinate = 1 << 3
};
/// info on a single projected landmark
struct ProjectedLandmark {
Eigen::Vector2d point_measured; //!< detected feature location
Eigen::Vector2d point_reprojected; //!< landmark projected into image
Eigen::Vector3d point_3d_c; //!< 3d point in camera coordinates
TrackId track_id = -1; //!< corresponding track_id
double reprojection_error = 0; //!< current reprojection error
unsigned int outlier_flags = OutlierNone; //!< flags for outlier
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
using ProjectedLandmarkPtr = std::shared_ptr<ProjectedLandmark>;
using ProjectedLandmarkConstPtr = std::shared_ptr<const ProjectedLandmark>;
/// all landmark projections for inlier and outlier observations for a single
/// image
struct ImageProjection {
std::vector<ProjectedLandmarkConstPtr> obs;
std::vector<ProjectedLandmarkConstPtr> outlier_obs;
};
/// projections for all images
using ImageProjections = std::map<TimeCamId, ImageProjection>;
/// inlier projections indexed per track
using TrackProjections =
std::unordered_map<TrackId, std::map<TimeCamId, ProjectedLandmarkConstPtr>>;
} // namespace basalt
namespace cereal {
template <class Archive>
void serialize(Archive& ar, basalt::TimeCamId& c) {
ar(c.frame_id, c.cam_id);
}
template <class Archive>
void serialize(Archive& ar, basalt::KeypointsData& c) {
ar(c.corners, c.corner_angles, c.corner_descriptors);
}
template <class Archive>
void serialize(Archive& ar, basalt::MatchData& c) {
ar(c.T_i_j, c.matches, c.inliers);
}
} // namespace cereal
namespace std {
template <>
struct hash<basalt::TimeCamId> {
size_t operator()(const basalt::TimeCamId& x) const {
size_t seed = 0;
basalt::hash_combine(seed, x.frame_id);
basalt::hash_combine(seed, x.cam_id);
return seed;
}
};
template <>
struct hash<std::pair<basalt::TimeCamId, basalt::TimeCamId>> {
size_t operator()(
const std::pair<basalt::TimeCamId, basalt::TimeCamId>& x) const {
size_t seed = 0;
basalt::hash_combine(seed, x.first.frame_id);
basalt::hash_combine(seed, x.first.cam_id);
basalt::hash_combine(seed, x.second.frame_id);
basalt::hash_combine(seed, x.second.cam_id);
return seed;
}
};
} // namespace std

View File

@@ -0,0 +1,70 @@
#pragma once
// Check for feature test macro for <filesystem>
#if defined(__cpp_lib_filesystem)
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
// Check for feature test macro for <experimental/filesystem>
#elif defined(__cpp_lib_experimental_filesystem)
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
// We can't check if headers exist...
// Let's assume experimental to be safe
#elif !defined(__has_include)
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
// Check if the header "<filesystem>" exists
#elif __has_include(<filesystem>)
// If we're compiling on Visual Studio and are not compiling with C++17, we need
// to use experimental
#ifdef _MSC_VER
// Check and include header that defines "_HAS_CXX17"
#if __has_include(<yvals_core.h>)
#include <yvals_core.h>
// Check for enabled C++17 support
#if defined(_HAS_CXX17) && _HAS_CXX17
// We're using C++17, so let's use the normal version
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
#endif
#endif
// If the marco isn't defined yet, that means any of the other VS specific
// checks failed, so we need to use experimental
#ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
#endif
// Not on Visual Studio. Let's use the normal version
#else // #ifdef _MSC_VER
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
#endif
// Check if the header "<filesystem>" exists
#elif __has_include(<experimental/filesystem>)
#define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
// Fail if neither header is available with a nice error message
#else
#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
#endif
// We priously determined that we need the exprimental version
#if INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
#include <experimental/filesystem>
namespace basalt {
namespace fs = std::experimental::filesystem;
}
// We have a decent compiler and can use the normal version
#else
#include <filesystem>
namespace basalt {
namespace fs = std::filesystem;
}
#endif

View File

@@ -0,0 +1,385 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <atomic>
#include <map>
#include <memory>
#include <set>
#include <Eigen/Dense>
#include <sophus/se3.hpp>
#include <basalt/imu/imu_types.h>
#include <basalt/optical_flow/optical_flow.h>
#include <basalt/utils/common_types.h>
#include <basalt/utils/sophus_utils.hpp>
#include <cereal/cereal.hpp>
namespace basalt {
template <class Scalar_>
class IntegratedImuMeasurement;
template <class Scalar_>
struct PoseStateWithLin;
namespace constants {
static const Eigen::Vector3d g(0, 0, -9.81);
static const Eigen::Vector3d g_dir(0, 0, -1);
} // namespace constants
template <class Scalar_>
struct PoseVelBiasStateWithLin {
using Scalar = Scalar_;
using VecN = typename PoseVelBiasState<Scalar>::VecN;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using SE3 = Sophus::SE3<Scalar>;
PoseVelBiasStateWithLin() {
linearized = false;
delta.setZero();
};
PoseVelBiasStateWithLin(int64_t t_ns, const SE3& T_w_i, const Vec3& vel_w_i,
const Vec3& bias_gyro, const Vec3& bias_accel,
bool linearized)
: linearized(linearized),
state_linearized(t_ns, T_w_i, vel_w_i, bias_gyro, bias_accel) {
delta.setZero();
state_current = state_linearized;
}
explicit PoseVelBiasStateWithLin(const PoseVelBiasState<Scalar>& other)
: linearized(false), state_linearized(other) {
delta.setZero();
state_current = other;
}
template <class Scalar2>
PoseVelBiasStateWithLin<Scalar2> cast() const {
PoseVelBiasStateWithLin<Scalar2> a;
a.linearized = linearized;
a.delta = delta.template cast<Scalar2>();
a.state_linearized = state_linearized.template cast<Scalar2>();
a.state_current = state_current.template cast<Scalar2>();
return a;
}
void setLinFalse() {
linearized = false;
delta.setZero();
}
void setLinTrue() {
linearized = true;
BASALT_ASSERT(delta.isApproxToConstant(0));
state_current = state_linearized;
}
void applyInc(const VecN& inc) {
if (!linearized) {
state_linearized.applyInc(inc);
} else {
delta += inc;
state_current = state_linearized;
state_current.applyInc(delta);
}
}
inline const PoseVelBiasState<Scalar>& getState() const {
if (!linearized) {
return state_linearized;
} else {
return state_current;
}
}
inline const PoseVelBiasState<Scalar>& getStateLin() const {
return state_linearized;
}
inline bool isLinearized() const { return linearized; }
inline const VecN& getDelta() const { return delta; }
inline int64_t getT_ns() const { return state_linearized.t_ns; }
inline void backup() {
backup_delta = delta;
backup_state_linearized = state_linearized;
backup_state_current = state_current;
}
inline void restore() {
delta = backup_delta;
state_linearized = backup_state_linearized;
state_current = backup_state_current;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
bool linearized;
VecN delta;
PoseVelBiasState<Scalar> state_linearized, state_current;
VecN backup_delta;
PoseVelBiasState<Scalar> backup_state_linearized, backup_state_current;
// give access to private members for constructor of PoseStateWithLin
friend struct PoseStateWithLin<Scalar>;
// give access to private members for cast() implementation
template <class>
friend struct PoseVelBiasStateWithLin;
friend class cereal::access;
template <class Archive>
void serialize(Archive& ar) {
ar(state_linearized.T_w_i);
ar(state_linearized.vel_w_i);
ar(state_linearized.bias_gyro);
ar(state_linearized.bias_accel);
ar(state_current.T_w_i);
ar(state_current.vel_w_i);
ar(state_current.bias_gyro);
ar(state_current.bias_accel);
ar(delta);
ar(linearized);
ar(state_linearized.t_ns);
}
};
template <class Scalar_>
struct PoseStateWithLin {
using Scalar = Scalar_;
using VecN = typename PoseState<Scalar>::VecN;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using SE3 = Sophus::SE3<Scalar>;
PoseStateWithLin() {
linearized = false;
delta.setZero();
};
PoseStateWithLin(int64_t t_ns, const SE3& T_w_i, bool linearized = false)
: linearized(linearized), pose_linearized(t_ns, T_w_i) {
delta.setZero();
T_w_i_current = T_w_i;
}
explicit PoseStateWithLin(const PoseVelBiasStateWithLin<Scalar>& other)
: linearized(other.linearized),
delta(other.delta.template head<6>()),
pose_linearized(other.state_linearized.t_ns,
other.state_linearized.T_w_i) {
T_w_i_current = pose_linearized.T_w_i;
PoseState<Scalar>::incPose(delta, T_w_i_current);
}
template <class Scalar2>
PoseStateWithLin<Scalar2> cast() const {
PoseStateWithLin<Scalar2> a;
a.linearized = linearized;
a.delta = delta.template cast<Scalar2>();
a.pose_linearized = pose_linearized.template cast<Scalar2>();
a.T_w_i_current = T_w_i_current.template cast<Scalar2>();
return a;
}
void setLinTrue() {
linearized = true;
BASALT_ASSERT(delta.isApproxToConstant(0));
T_w_i_current = pose_linearized.T_w_i;
}
inline const SE3& getPose() const {
if (!linearized) {
return pose_linearized.T_w_i;
} else {
return T_w_i_current;
}
}
inline const SE3& getPoseLin() const { return pose_linearized.T_w_i; }
inline void applyInc(const VecN& inc) {
if (!linearized) {
PoseState<Scalar>::incPose(inc, pose_linearized.T_w_i);
} else {
delta += inc;
T_w_i_current = pose_linearized.T_w_i;
PoseState<Scalar>::incPose(delta, T_w_i_current);
}
}
inline bool isLinearized() const { return linearized; }
inline const VecN& getDelta() const { return delta; }
inline int64_t getT_ns() const { return pose_linearized.t_ns; }
inline void backup() {
backup_delta = delta;
backup_pose_linearized = pose_linearized;
backup_T_w_i_current = T_w_i_current;
}
inline void restore() {
delta = backup_delta;
pose_linearized = backup_pose_linearized;
T_w_i_current = backup_T_w_i_current;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
bool linearized;
VecN delta;
PoseState<Scalar> pose_linearized;
SE3 T_w_i_current;
VecN backup_delta;
PoseState<Scalar> backup_pose_linearized;
SE3 backup_T_w_i_current;
// give access to private members for cast() implementation
template <class>
friend struct PoseStateWithLin;
friend class cereal::access;
template <class Archive>
void serialize(Archive& ar) {
ar(pose_linearized.T_w_i);
ar(T_w_i_current);
ar(delta);
ar(linearized);
ar(pose_linearized.t_ns);
}
};
struct AbsOrderMap {
std::map<int64_t, std::pair<int, int>> abs_order_map;
size_t items = 0;
size_t total_size = 0;
void print_order() {
for (const auto& kv : abs_order_map) {
std::cout << kv.first << " (" << kv.second.first << ","
<< kv.second.second << ")" << std::endl;
}
std::cout << std::endl;
}
};
template <class Scalar_>
struct ImuLinData {
using Scalar = Scalar_;
const Eigen::Matrix<Scalar, 3, 1>& g;
const Eigen::Matrix<Scalar, 3, 1>& gyro_bias_weight_sqrt;
const Eigen::Matrix<Scalar, 3, 1>& accel_bias_weight_sqrt;
std::map<int64_t, const IntegratedImuMeasurement<Scalar>*> imu_meas;
};
template <class Scalar_>
struct MargLinData {
using Scalar = Scalar_;
bool is_sqrt = true;
AbsOrderMap order;
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> H;
Eigen::Matrix<Scalar, Eigen::Dynamic, 1> b;
};
struct MargData {
typedef std::shared_ptr<MargData> Ptr;
AbsOrderMap aom;
Eigen::MatrixXd abs_H;
Eigen::VectorXd abs_b;
Eigen::aligned_map<int64_t, PoseVelBiasStateWithLin<double>> frame_states;
Eigen::aligned_map<int64_t, PoseStateWithLin<double>> frame_poses;
std::set<int64_t> kfs_all;
std::set<int64_t> kfs_to_marg;
bool use_imu;
std::vector<OpticalFlowResult::Ptr> opt_flow_res;
};
struct RelPoseFactor {
int64_t t_i_ns, t_j_ns;
Sophus::SE3d T_i_j;
Sophus::Matrix6d cov_inv;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct RollPitchFactor {
int64_t t_ns;
Sophus::SO3d R_w_i_meas;
Eigen::Matrix2d cov_inv;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
} // namespace basalt
namespace cereal {
template <class Archive>
void serialize(Archive& ar, basalt::AbsOrderMap& a) {
ar(a.total_size);
ar(a.items);
ar(a.abs_order_map);
}
template <class Archive>
void serialize(Archive& ar, basalt::MargData& m) {
ar(m.aom);
ar(m.abs_H);
ar(m.abs_b);
ar(m.frame_poses);
ar(m.frame_states);
ar(m.kfs_all);
ar(m.kfs_to_marg);
ar(m.use_imu);
}
} // namespace cereal

View File

@@ -0,0 +1,104 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <bitset>
#include <set>
#include <Eigen/Dense>
#include <sophus/se3.hpp>
#include <basalt/image/image.h>
#include <basalt/utils/sophus_utils.hpp>
#include <basalt/utils/common_types.h>
#include <basalt/camera/generic_camera.hpp>
namespace basalt {
typedef std::bitset<256> Descriptor;
void detectKeypointsMapping(const basalt::Image<const uint16_t>& img_raw,
KeypointsData& kd, int num_features);
void detectKeypoints(
const basalt::Image<const uint16_t>& img_raw, KeypointsData& kd,
int PATCH_SIZE = 32, int num_points_cell = 1,
const Eigen::aligned_vector<Eigen::Vector2d>& current_points =
Eigen::aligned_vector<Eigen::Vector2d>());
void computeAngles(const basalt::Image<const uint16_t>& img_raw,
KeypointsData& kd, bool rotate_features);
void computeDescriptors(const basalt::Image<const uint16_t>& img_raw,
KeypointsData& kd);
void matchDescriptors(const std::vector<std::bitset<256>>& corner_descriptors_1,
const std::vector<std::bitset<256>>& corner_descriptors_2,
std::vector<std::pair<int, int>>& matches, int threshold,
double dist_2_best);
inline void computeEssential(const Sophus::SE3d& T_0_1, Eigen::Matrix4d& E) {
E.setZero();
const Eigen::Vector3d t_0_1 = T_0_1.translation();
const Eigen::Matrix3d R_0_1 = T_0_1.rotationMatrix();
E.topLeftCorner<3, 3>() = Sophus::SO3d::hat(t_0_1.normalized()) * R_0_1;
}
inline void findInliersEssential(const KeypointsData& kd1,
const KeypointsData& kd2,
const Eigen::Matrix4d& E,
double epipolar_error_threshold,
MatchData& md) {
md.inliers.clear();
for (size_t j = 0; j < md.matches.size(); j++) {
const Eigen::Vector4d p0_3d = kd1.corners_3d[md.matches[j].first];
const Eigen::Vector4d p1_3d = kd2.corners_3d[md.matches[j].second];
const double epipolar_error = std::abs(p0_3d.transpose() * E * p1_3d);
if (epipolar_error < epipolar_error_threshold) {
md.inliers.push_back(md.matches[j]);
}
}
}
void findInliersRansac(const KeypointsData& kd1, const KeypointsData& kd2,
const double ransac_thresh, const int ransac_min_inliers,
MatchData& md);
} // namespace basalt

120
include/basalt/utils/nfr.h Normal file
View File

@@ -0,0 +1,120 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <sophus/se3.hpp>
#include <basalt/utils/imu_types.h>
namespace basalt {
inline Sophus::Vector6d relPoseError(
const Sophus::SE3d& T_i_j, const Sophus::SE3d& T_w_i,
const Sophus::SE3d& T_w_j, Sophus::Matrix6d* d_res_d_T_w_i = nullptr,
Sophus::Matrix6d* d_res_d_T_w_j = nullptr) {
Sophus::SE3d T_j_i = T_w_j.inverse() * T_w_i;
Sophus::Vector6d res = Sophus::se3_logd(T_i_j * T_j_i);
if (d_res_d_T_w_i || d_res_d_T_w_j) {
Sophus::Matrix6d J;
Sophus::rightJacobianInvSE3Decoupled(res, J);
Eigen::Matrix3d R = T_w_i.so3().inverse().matrix();
Sophus::Matrix6d Adj;
Adj.setZero();
Adj.topLeftCorner<3, 3>() = R;
Adj.bottomRightCorner<3, 3>() = R;
if (d_res_d_T_w_i) {
*d_res_d_T_w_i = J * Adj;
}
if (d_res_d_T_w_j) {
Adj.topRightCorner<3, 3>() =
Sophus::SO3d::hat(T_j_i.inverse().translation()) * R;
*d_res_d_T_w_j = -J * Adj;
}
}
return res;
}
inline Sophus::Vector3d absPositionError(
const Sophus::SE3d& T_w_i, const Eigen::Vector3d pos,
Eigen::Matrix<double, 3, 6>* d_res_d_T_w_i = nullptr) {
if (d_res_d_T_w_i) {
d_res_d_T_w_i->topLeftCorner<3, 3>().setIdentity();
d_res_d_T_w_i->topRightCorner<3, 3>().setZero();
}
return T_w_i.translation() - pos;
}
inline double yawError(const Sophus::SE3d& T_w_i,
const Eigen::Vector3d yaw_dir_body,
Eigen::Matrix<double, 1, 6>* d_res_d_T_w_i = nullptr) {
Eigen::Matrix3d curr_R_w_i = T_w_i.so3().matrix();
Eigen::Vector3d tmp = curr_R_w_i * yaw_dir_body;
double res_yaw = tmp[1];
if (d_res_d_T_w_i) {
d_res_d_T_w_i->setZero();
(*d_res_d_T_w_i)[3] = -tmp[2];
(*d_res_d_T_w_i)[5] = tmp[0];
}
return res_yaw;
}
inline Sophus::Vector2d rollPitchError(
const Sophus::SE3d& T_w_i, const Sophus::SO3d& R_w_i_meas,
Eigen::Matrix<double, 2, 6>* d_res_d_T_w_i = nullptr) {
// Assumes g direction is negative Z in world coordinate frame
Eigen::Matrix3d R = (R_w_i_meas * T_w_i.so3().inverse()).matrix();
Eigen::Vector3d res = R * (-Eigen::Vector3d::UnitZ());
if (d_res_d_T_w_i) {
d_res_d_T_w_i->setZero();
(*d_res_d_T_w_i)(0, 3) = -R(0, 1);
(*d_res_d_T_w_i)(1, 3) = -R(1, 1);
(*d_res_d_T_w_i)(0, 4) = R(0, 0);
(*d_res_d_T_w_i)(1, 4) = R(1, 0);
}
return res.head<2>();
}
} // namespace basalt

View File

@@ -0,0 +1,53 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
struct SimObservations {
Eigen::aligned_vector<Eigen::Vector2d> pos;
std::vector<int> id;
};
} // namespace basalt
namespace cereal {
template <class Archive>
void serialize(Archive& ar, basalt::SimObservations& c) {
ar(c.pos, c.id);
}
} // namespace cereal

View File

@@ -0,0 +1,60 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef BASALT_SYSTEM_UTILS_H
#define BASALT_SYSTEM_UTILS_H
#include <cstdint>
#include <string>
namespace basalt {
inline std::string ensure_trailing_slash(const std::string& path) {
if (!path.empty() && path[path.size() - 1] != '/') {
return path + "/";
} else {
return path;
}
}
struct MemoryInfo {
uint64_t resident_memory = 0; //!< in bytes
uint64_t resident_memory_peak = 0; //!< in bytes
};
bool get_memory_info(MemoryInfo& info);
} // namespace basalt
#endif // include guard

View File

@@ -0,0 +1,77 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#ifndef BASALT_TEST_UTILS_H
#define BASALT_TEST_UTILS_H
#include <Eigen/Dense>
template <typename Derived1, typename Derived2, typename F>
void test_jacobian_code(const std::string& name,
const Eigen::MatrixBase<Derived1>& Ja, F func,
const Eigen::MatrixBase<Derived2>& x0,
double eps = 1e-8, double max_norm = 1e-4) {
typedef typename Derived1::Scalar Scalar;
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> Jn = Ja;
Jn.setZero();
Eigen::Matrix<Scalar, Eigen::Dynamic, 1> inc = x0;
for (int i = 0; i < Jn.cols(); i++) {
inc.setZero();
inc[i] += eps;
Eigen::Matrix<Scalar, Eigen::Dynamic, 1> fpe = func(x0 + inc);
Eigen::Matrix<Scalar, Eigen::Dynamic, 1> fme = func(x0 - inc);
Jn.col(i) = (fpe - fme) / (2 * eps);
}
Scalar diff = (Ja - Jn).norm();
if (diff > max_norm || !Ja.allFinite()) {
std::cerr << name << std::endl;
std::cerr << "Numeric Jacobian is different from analytic. Norm difference "
<< diff << std::endl;
std::cerr << "Ja\n" << Ja << std::endl;
std::cerr << "Jn\n" << Jn << std::endl;
} else {
// std::cout << name << std::endl;
// std::cout << "Success" << std::endl;
// std::cout << "Ja\n" << Ja << std::endl;
// std::cout << "Jn\n" << Jn << std::endl;
}
}
#endif // BASALT_TEST_UTILS_H

View File

@@ -0,0 +1,141 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2020-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.
*/
#pragma once
#include <chrono>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include <Eigen/Dense>
namespace basalt {
template <class Clock = std::chrono::high_resolution_clock>
class Timer {
public:
/// start timer
Timer() : start_(Clock::now()) {}
/// return elapsed time in seconds
double elapsed() const {
return std::chrono::duration<double>(Clock::now() - start_).count();
}
/// return elapsed time in seconds and reset timer
double reset() {
const auto now = Clock::now();
const double elapsed = std::chrono::duration<double>(now - start_).count();
start_ = now;
return elapsed;
}
private:
std::chrono::time_point<Clock> start_;
};
template <class Scalar = double>
struct assign_op {
void operator()(Scalar& lhs, const Scalar& rhs) { lhs = rhs; }
};
template <class Scalar = double>
struct plus_assign_op {
void operator()(Scalar& lhs, const Scalar& rhs) { lhs += rhs; }
};
template <class T = Timer<>, class Assign = assign_op<>>
class ScopedTimer {
public:
explicit ScopedTimer(double& dest) : dest_(dest) {}
~ScopedTimer() { Assign()(dest_, timer_.elapsed()); }
private:
double& dest_;
T timer_;
};
using ScopedTimerAdd = ScopedTimer<Timer<>, plus_assign_op<>>;
template <class F>
void log_timing(double& time, F fun) {
Timer timer;
fun();
time = timer.elapsed();
}
template <class F>
void log_timing_add(double& time, F fun) {
Timer timer;
fun();
time += timer.elapsed();
}
class ExecutionStats {
public:
struct Meta {
inline Meta& format(const std::string& s) {
format_ = s;
return *this;
}
// overwrite the meta data from another object
void set_meta(const Meta& other) { format_ = other.format_; }
std::variant<std::vector<double>, std::vector<Eigen::VectorXd>> data_;
std::string format_;
};
Meta& add(const std::string& name, double value);
Meta& add(const std::string& name, const Eigen::VectorXd& value);
Meta& add(const std::string& name, const Eigen::VectorXf& value);
void merge_all(const ExecutionStats& other);
void merge_sums(const ExecutionStats& other);
void print() const;
bool save_json(const std::string& path) const;
private:
std::unordered_map<std::string, Meta> stats_;
std::vector<std::string> order_;
};
} // namespace basalt

View File

@@ -0,0 +1,222 @@
// Adapted from OpenMVG
// Copyright (c) 2012, 2013 Pierre MOULON
// 2018 Nikolaus DEMMEL
// This file was originally part of OpenMVG, an Open Multiple View Geometry C++
// library.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Implementation of [1] an efficient algorithm to compute track from pairwise
// correspondences.
//
// [1] Pierre Moulon and Pascal Monasse,
// "Unordered feature tracking made fast and easy" CVMP 2012.
//
// It tracks the position of features along the series of image from pairwise
// correspondences.
//
// From map<[imageI,ImageJ], [indexed matches array] > it builds tracks.
//
// Usage :
// //---------------------------------------
// // Compute tracks from matches
// //---------------------------------------
// TrackBuilder trackBuilder;
// FeatureTracks tracks;
// trackBuilder.Build(matches); // Build: Efficient fusion of correspondences
// trackBuilder.Filter(); // Filter: Remove tracks that have conflict
// trackBuilder.Export(tracks); // Export tree to usable data structure
#pragma once
#include <cassert>
#include <cstdint>
#include <functional>
#include <iterator>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <utility>
#include <vector>
#include <basalt/utils/common_types.h>
#include <basalt/utils/union_find.h>
namespace basalt {
/// TrackBuild class creates feature tracks from matches
struct TrackBuilder {
std::map<ImageFeaturePair, TrackId> map_node_to_index;
UnionFind uf_tree;
/// Build tracks for a given series of pairWise matches
void Build(const Matches& map_pair_wise_matches) {
// 1. We need to know how much single set we will have.
// i.e each set is made of a tuple : (imageIndex, featureIndex)
std::set<ImageFeaturePair> allFeatures;
// For each couple of images list the used features
for (const auto& iter : map_pair_wise_matches) {
const auto I = iter.first.first;
const auto J = iter.first.second;
const MatchData& matchData = iter.second;
// Retrieve all shared features and add them to a set
for (const auto& match : matchData.inliers) {
allFeatures.emplace(I, match.first);
allFeatures.emplace(J, match.second);
}
}
// 2. Build the 'flat' representation where a tuple (the node)
// is attached to a unique index.
TrackId cpt = 0;
for (const auto& feat : allFeatures) {
map_node_to_index.emplace(feat, cpt);
++cpt;
}
// Clean some memory
allFeatures.clear();
// 3. Add the node and the pairwise correpondences in the UF tree.
uf_tree.InitSets(map_node_to_index.size());
// 4. Union of the matched features corresponding UF tree sets
for (const auto& iter : map_pair_wise_matches) {
const auto I = iter.first.first;
const auto J = iter.first.second;
const MatchData& matchData = iter.second;
for (const auto& match : matchData.inliers) {
const ImageFeaturePair pairI(I, match.first);
const ImageFeaturePair pairJ(J, match.second);
// Link feature correspondences to the corresponding containing sets.
uf_tree.Union(map_node_to_index[pairI], map_node_to_index[pairJ]);
}
}
}
/// Remove bad tracks (too short or track with ids collision)
bool Filter(size_t minimumTrackLength = 2) {
// Remove bad tracks:
// - track that are too short,
// - track with id conflicts:
// i.e. tracks that have many times the same image index
// From the UF tree, create tracks of the image indexes.
// If an image index appears twice the track must disappear
// If a track is too short it has to be removed.
std::map<TrackId, std::set<TimeCamId>> tracks;
std::set<TrackId> problematic_track_id;
// Build tracks from the UF tree, track problematic ids.
for (const auto& iter : map_node_to_index) {
const TrackId track_id = uf_tree.Find(iter.second);
if (problematic_track_id.count(track_id) != 0) {
continue; // Track already marked
}
const ImageFeaturePair& feat = iter.first;
if (tracks[track_id].count(feat.first)) {
problematic_track_id.insert(track_id);
} else {
tracks[track_id].insert(feat.first);
}
}
// - track that are too short,
for (const auto& val : tracks) {
if (val.second.size() < minimumTrackLength) {
problematic_track_id.insert(val.first);
}
}
for (uint32_t& root_index : uf_tree.m_cc_parent) {
if (problematic_track_id.count(root_index) > 0) {
// reset selected root
uf_tree.m_cc_size[root_index] = 1;
root_index = UnionFind::InvalidIndex();
}
}
return false;
}
/// Return the number of connected set in the UnionFind structure (tree
/// forest)
size_t TrackCount() const {
std::set<TrackId> parent_id(uf_tree.m_cc_parent.begin(),
uf_tree.m_cc_parent.end());
// Erase the "special marker" that depicted rejected tracks
parent_id.erase(UnionFind::InvalidIndex());
return parent_id.size();
}
/// Export tracks as a map (each entry is a map of imageId and
/// featureIndex):
/// {TrackIndex => {imageIndex => featureIndex}}
void Export(FeatureTracks& tracks) {
tracks.clear();
for (const auto& iter : map_node_to_index) {
const TrackId track_id = uf_tree.Find(iter.second);
const ImageFeaturePair& feat = iter.first;
// ensure never add rejected elements (track marked as invalid)
if (track_id != UnionFind::InvalidIndex()) {
tracks[track_id].emplace(feat);
}
}
}
};
/// Find common tracks between images.
bool GetTracksInImages(const std::set<TimeCamId>& image_ids,
const FeatureTracks& all_tracks,
std::vector<TrackId>& shared_track_ids) {
shared_track_ids.clear();
// Go along the tracks
for (const auto& kv_track : all_tracks) {
// Look if the track contains the provided view index & save the point ids
size_t observed_image_count = 0;
for (const auto& imageId : image_ids) {
if (kv_track.second.count(imageId) > 0) {
++observed_image_count;
} else {
break;
}
}
if (observed_image_count == image_ids.size()) {
shared_track_ids.push_back(kv_track.first);
}
}
return !shared_track_ids.empty();
}
/// Find all tracks in an image.
bool GetTracksInImage(const TimeCamId& image_id,
const FeatureTracks& all_tracks,
std::vector<TrackId>& track_ids) {
std::set<TimeCamId> image_set;
image_set.insert(image_id);
return GetTracksInImages(image_set, all_tracks, track_ids);
}
/// Find shared tracks between map and image
bool GetSharedTracks(const TimeCamId& image_id, const FeatureTracks& all_tracks,
const Landmarks& landmarks,
std::vector<TrackId>& track_ids) {
track_ids.clear();
for (const auto& kv : landmarks) {
const TrackId trackId = kv.first;
if (all_tracks.at(trackId).count(image_id) > 0) {
track_ids.push_back(trackId);
}
}
return !track_ids.empty();
}
} // namespace basalt

View File

@@ -0,0 +1,94 @@
// Adapted from OpenMVG
// Copyright (c) 2016 Pierre MOULON
// 2018 Nikolaus DEMMEL
// This file was originally part of OpenMVG, an Open Multiple View Geometry C++
// library.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#pragma once
#include <cstdint>
#include <limits>
#include <numeric>
#include <vector>
// Union-Find/Disjoint-Set data structure
//--
// A disjoint-set data structure also called a unionfind data structure
// or mergefind set, is a data structure that keeps track of a set of elements
// partitioned into a number of disjoint (non-overlapping) subsets.
// It supports two operations:
// - Find: Determine which subset a particular element is in.
// - It returns an item from this set that serves as its "representative";
// - Union: Join two subsets into a single subset.
// Sometime a Connected method is implemented:
// - Connected:
// - By comparing the result of two Find operations, one can determine whether
// two elements are in the same subset.
//--
struct UnionFind {
using ValueType = uint32_t;
// Special Value for invalid parent index
static ValueType InvalidIndex() {
return std::numeric_limits<ValueType>::max();
}
// Represent the DS/UF forest thanks to two array:
// A parent 'pointer tree' where each node holds a reference to its parent
// node
std::vector<ValueType> m_cc_parent;
// A rank array used for union by rank
std::vector<ValueType> m_cc_rank;
// A 'size array' to know the size of each connected component
std::vector<ValueType> m_cc_size;
// Init the UF structure with num_cc nodes
void InitSets(const ValueType num_cc) {
// all set size are 1 (independent nodes)
m_cc_size.resize(num_cc, 1);
// Parents id have their own CC id {0,n}
m_cc_parent.resize(num_cc);
std::iota(m_cc_parent.begin(), m_cc_parent.end(), 0);
// Rank array (0)
m_cc_rank.resize(num_cc, 0);
}
// Return the number of nodes that have been initialized in the UF tree
std::size_t GetNumNodes() const { return m_cc_size.size(); }
// Return the representative set id of I nth component
ValueType Find(ValueType i) {
// Recursively set all branch as children of root (Path compression)
if (m_cc_parent[i] != i && m_cc_parent[i] != InvalidIndex())
m_cc_parent[i] = Find(m_cc_parent[i]);
return m_cc_parent[i];
}
// Replace sets containing I and J with their union
void Union(ValueType i, ValueType j) {
i = Find(i);
j = Find(j);
if (i == j) { // Already in the same set. Nothing to do
return;
}
// x and y are not already in same set. Merge them.
// Perform an union by rank:
// - always attach the smaller tree to the root of the larger tree
if (m_cc_rank[i] < m_cc_rank[j]) {
m_cc_parent[i] = j;
m_cc_size[j] += m_cc_size[i];
} else {
m_cc_parent[j] = i;
m_cc_size[i] += m_cc_size[j];
if (m_cc_rank[i] == m_cc_rank[j]) ++m_cc_rank[i];
}
}
};

View File

@@ -0,0 +1,111 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <string>
namespace basalt {
enum class LinearizationType { ABS_QR, ABS_SC, REL_SC };
struct VioConfig {
VioConfig();
void load(const std::string& filename);
void save(const std::string& filename);
std::string optical_flow_type;
int optical_flow_detection_grid_size;
float optical_flow_max_recovered_dist2;
int optical_flow_pattern;
int optical_flow_max_iterations;
int optical_flow_levels;
float optical_flow_epipolar_error;
int optical_flow_skip_frames;
LinearizationType vio_linearization_type;
bool vio_sqrt_marg;
int vio_max_states;
int vio_max_kfs;
int vio_min_frames_after_kf;
float vio_new_kf_keypoints_thresh;
bool vio_debug;
bool vio_extended_logging;
// double vio_outlier_threshold;
// int vio_filter_iteration;
int vio_max_iterations;
double vio_obs_std_dev;
double vio_obs_huber_thresh;
double vio_min_triangulation_dist;
bool vio_enforce_realtime;
bool vio_use_lm;
double vio_lm_lambda_initial;
double vio_lm_lambda_min;
double vio_lm_lambda_max;
bool vio_scale_jacobian;
double vio_init_pose_weight;
double vio_init_ba_weight;
double vio_init_bg_weight;
bool vio_marg_lost_landmarks;
double vio_kf_marg_feature_ratio;
double mapper_obs_std_dev;
double mapper_obs_huber_thresh;
int mapper_detection_num_points;
double mapper_num_frames_to_match;
double mapper_frames_to_match_threshold;
double mapper_min_matches;
double mapper_ransac_threshold;
double mapper_min_track_length;
double mapper_max_hamming_distance;
double mapper_second_best_test_ratio;
int mapper_bow_num_bits;
double mapper_min_triangulation_dist;
bool mapper_no_factor_weights;
bool mapper_use_factors;
bool mapper_use_lm;
double mapper_lm_lambda_min;
double mapper_lm_lambda_max;
};
} // namespace basalt

View File

@@ -0,0 +1,106 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <Eigen/Dense>
#include <pangolin/gl/gldraw.h>
#include <basalt/utils/sophus_utils.hpp>
const uint8_t cam_color[3]{250, 0, 26};
const uint8_t state_color[3]{250, 0, 26};
const uint8_t pose_color[3]{0, 50, 255};
const uint8_t gt_color[3]{0, 171, 47};
inline void render_camera(const Eigen::Matrix4d& T_w_c, float lineWidth,
const uint8_t* color, float sizeFactor) {
const float sz = sizeFactor;
const float width = 640, height = 480, fx = 500, fy = 500, cx = 320, cy = 240;
const Eigen::aligned_vector<Eigen::Vector3f> lines = {
{0, 0, 0},
{sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz},
{0, 0, 0},
{sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz},
{0, 0, 0},
{sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz},
{0, 0, 0},
{sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz},
{sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz},
{sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz},
{sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz},
{sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz},
{sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz},
{sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz},
{sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz},
{sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz}};
glPushMatrix();
glMultMatrixd(T_w_c.data());
glColor3ubv(color);
glLineWidth(lineWidth);
pangolin::glDrawLines(lines);
glPopMatrix();
}
inline void getcolor(float p, float np, float& r, float& g, float& b) {
float inc = 4.0 / np;
float x = p * inc;
r = 0.0f;
g = 0.0f;
b = 0.0f;
if ((0 <= x && x <= 1) || (5 <= x && x <= 6))
r = 1.0f;
else if (4 <= x && x <= 5)
r = x - 4;
else if (1 <= x && x <= 2)
r = 1.0f - (x - 1);
if (1 <= x && x <= 3)
g = 1.0f;
else if (0 <= x && x <= 1)
g = x - 0;
else if (3 <= x && x <= 4)
g = 1.0f - (x - 3);
if (3 <= x && x <= 5)
b = 1.0f;
else if (2 <= x && x <= 3)
b = x - 2;
else if (5 <= x && x <= 6)
b = 1.0f - (x - 5);
}

View File

@@ -0,0 +1,167 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/vi_estimator/landmark_database.h>
namespace basalt {
template <class Scalar_>
class BundleAdjustmentBase {
public:
using Scalar = Scalar_;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using Vec6 = Eigen::Matrix<Scalar, 6, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using Mat6 = Eigen::Matrix<Scalar, 6, 6>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using SE3 = Sophus::SE3<Scalar>;
void computeError(Scalar& error,
std::map<int, std::vector<std::pair<TimeCamId, Scalar>>>*
outliers = nullptr,
Scalar outlier_threshold = 0) const;
void filterOutliers(Scalar outlier_threshold, int min_num_obs);
void optimize_single_frame_pose(
PoseStateWithLin<Scalar>& state_t,
const std::vector<std::vector<int>>& connected_obs) const;
template <class Scalar2>
void get_current_points(
Eigen::aligned_vector<Eigen::Matrix<Scalar2, 3, 1>>& points,
std::vector<int>& ids) const;
void computeDelta(const AbsOrderMap& marg_order, VecX& delta) const;
void linearizeMargPrior(const MargLinData<Scalar>& mld,
const AbsOrderMap& aom, MatX& abs_H, VecX& abs_b,
Scalar& marg_prior_error) const;
void computeMargPriorError(const MargLinData<Scalar>& mld,
Scalar& marg_prior_error) const;
Scalar computeMargPriorModelCostChange(const MargLinData<Scalar>& mld,
const VecX& marg_scaling,
const VecX& marg_pose_inc) const;
// TODO: Old version for squared H and b. Remove when no longer needed.
Scalar computeModelCostChange(const MatX& H, const VecX& b,
const VecX& inc) const;
template <class Scalar2>
void computeProjections(
std::vector<Eigen::aligned_vector<Eigen::Matrix<Scalar2, 4, 1>>>& data,
FrameId last_state_t_ns) const;
/// Triangulates the point and returns homogenous representation. First 3
/// components - unit-length direction vector. Last component inverse
/// distance.
template <class Derived>
static Eigen::Matrix<typename Derived::Scalar, 4, 1> triangulate(
const Eigen::MatrixBase<Derived>& f0,
const Eigen::MatrixBase<Derived>& f1,
const Sophus::SE3<typename Derived::Scalar>& T_0_1) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3);
// suffix "2" to avoid name clash with class-wide typedefs
using Scalar_2 = typename Derived::Scalar;
using Vec4_2 = Eigen::Matrix<Scalar_2, 4, 1>;
Eigen::Matrix<Scalar_2, 3, 4> P1, P2;
P1.setIdentity();
P2 = T_0_1.inverse().matrix3x4();
Eigen::Matrix<Scalar_2, 4, 4> A(4, 4);
A.row(0) = f0[0] * P1.row(2) - f0[2] * P1.row(0);
A.row(1) = f0[1] * P1.row(2) - f0[2] * P1.row(1);
A.row(2) = f1[0] * P2.row(2) - f1[2] * P2.row(0);
A.row(3) = f1[1] * P2.row(2) - f1[2] * P2.row(1);
Eigen::JacobiSVD<Eigen::Matrix<Scalar_2, 4, 4>> mySVD(A,
Eigen::ComputeFullV);
Vec4_2 worldPoint = mySVD.matrixV().col(3);
worldPoint /= worldPoint.template head<3>().norm();
// Enforce same direction of bearing vector and initial point
if (f0.dot(worldPoint.template head<3>()) < 0) worldPoint *= -1;
return worldPoint;
}
inline void backup() {
for (auto& kv : frame_states) kv.second.backup();
for (auto& kv : frame_poses) kv.second.backup();
lmdb.backup();
}
inline void restore() {
for (auto& kv : frame_states) kv.second.restore();
for (auto& kv : frame_poses) kv.second.restore();
lmdb.restore();
}
// protected:
PoseStateWithLin<Scalar> getPoseStateWithLin(int64_t t_ns) const {
auto it = frame_poses.find(t_ns);
if (it != frame_poses.end()) return it->second;
auto it2 = frame_states.find(t_ns);
if (it2 == frame_states.end()) {
std::cerr << "Could not find pose " << t_ns << std::endl;
std::abort();
}
return PoseStateWithLin<Scalar>(it2->second);
}
Eigen::aligned_map<int64_t, PoseVelBiasStateWithLin<Scalar>> frame_states;
Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>> frame_poses;
// Point management
LandmarkDatabase<Scalar> lmdb;
Scalar obs_std_dev;
Scalar huber_thresh;
basalt::Calibration<Scalar> calib;
};
} // namespace basalt

View File

@@ -0,0 +1,156 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/utils/imu_types.h>
#include <basalt/utils/eigen_utils.hpp>
namespace basalt {
template <class Scalar_>
struct KeypointObservation {
using Scalar = Scalar_;
int kpt_id;
Eigen::Matrix<Scalar, 2, 1> pos;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
// keypoint position defined relative to some frame
template <class Scalar_>
struct Keypoint {
using Scalar = Scalar_;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using ObsMap = Eigen::aligned_map<TimeCamId, Vec2>;
using MapIter = typename ObsMap::iterator;
// 3D position parameters
Vec2 direction;
Scalar inv_dist;
// Observations
TimeCamId host_kf_id;
ObsMap obs;
inline void backup() {
backup_direction = direction;
backup_inv_dist = inv_dist;
}
inline void restore() {
direction = backup_direction;
inv_dist = backup_inv_dist;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
Vec2 backup_direction;
Scalar backup_inv_dist;
};
template <class Scalar_>
class LandmarkDatabase {
public:
using Scalar = Scalar_;
// Non-const
void addLandmark(KeypointId lm_id, const Keypoint<Scalar>& pos);
void removeFrame(const FrameId& frame);
void removeKeyframes(const std::set<FrameId>& kfs_to_marg,
const std::set<FrameId>& poses_to_marg,
const std::set<FrameId>& states_to_marg_all);
void addObservation(const TimeCamId& tcid_target,
const KeypointObservation<Scalar>& o);
Keypoint<Scalar>& getLandmark(KeypointId lm_id);
// Const
const Keypoint<Scalar>& getLandmark(KeypointId lm_id) const;
std::vector<TimeCamId> getHostKfs() const;
std::vector<const Keypoint<Scalar>*> getLandmarksForHost(
const TimeCamId& tcid) const;
const std::unordered_map<TimeCamId,
std::map<TimeCamId, std::set<KeypointId>>>&
getObservations() const;
const Eigen::aligned_unordered_map<KeypointId, Keypoint<Scalar>>&
getLandmarks() const;
bool landmarkExists(int lm_id) const;
size_t numLandmarks() const;
int numObservations() const;
int numObservations(KeypointId lm_id) const;
void removeLandmark(KeypointId lm_id);
void removeObservations(KeypointId lm_id, const std::set<TimeCamId>& obs);
inline void backup() {
for (auto& kv : kpts) kv.second.backup();
}
inline void restore() {
for (auto& kv : kpts) kv.second.restore();
}
private:
using MapIter =
typename Eigen::aligned_unordered_map<KeypointId,
Keypoint<Scalar>>::iterator;
MapIter removeLandmarkHelper(MapIter it);
typename Keypoint<Scalar>::MapIter removeLandmarkObservationHelper(
MapIter it, typename Keypoint<Scalar>::MapIter it2);
Eigen::aligned_unordered_map<KeypointId, Keypoint<Scalar>> kpts;
std::unordered_map<TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>
observations;
static constexpr int min_num_obs = 2;
};
} // namespace basalt

View File

@@ -0,0 +1,68 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <Eigen/Dense>
#include <set>
namespace basalt {
template <class Scalar_>
class MargHelper {
public:
using Scalar = Scalar_;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
// Modifies abs_H and abs_b as a side effect.
static void marginalizeHelperSqToSq(MatX& abs_H, VecX& abs_b,
const std::set<int>& idx_to_keep,
const std::set<int>& idx_to_marg,
MatX& marg_H, VecX& marg_b);
// Modifies abs_H and abs_b as a side effect.
static void marginalizeHelperSqToSqrt(MatX& abs_H, VecX& abs_b,
const std::set<int>& idx_to_keep,
const std::set<int>& idx_to_marg,
MatX& marg_sqrt_H, VecX& marg_sqrt_b);
// Modifies Q2Jp and Q2r as a side effect.
static void marginalizeHelperSqrtToSqrt(MatX& Q2Jp, VecX& Q2r,
const std::set<int>& idx_to_keep,
const std::set<int>& idx_to_marg,
MatX& marg_sqrt_H, VecX& marg_sqrt_b);
};
} // namespace basalt

View File

@@ -0,0 +1,209 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <memory>
#include <thread>
#include <Eigen/Dense>
#include <sophus/se3.hpp>
#include <basalt/utils/common_types.h>
#include <basalt/utils/nfr.h>
#include <basalt/vi_estimator/sc_ba_base.h>
#include <basalt/vi_estimator/vio_estimator.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
namespace basalt {
template <size_t N>
class HashBow;
class NfrMapper : public ScBundleAdjustmentBase<double> {
public:
using Scalar = double;
using Ptr = std::shared_ptr<NfrMapper>;
template <class AccumT>
struct MapperLinearizeAbsReduce
: public ScBundleAdjustmentBase<Scalar>::LinearizeAbsReduce<AccumT> {
using RollPitchFactorConstIter =
Eigen::aligned_vector<RollPitchFactor>::const_iterator;
using RelPoseFactorConstIter =
Eigen::aligned_vector<RelPoseFactor>::const_iterator;
using RelLinDataConstIter =
Eigen::aligned_vector<RelLinData>::const_iterator;
MapperLinearizeAbsReduce(
AbsOrderMap& aom,
const Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>>*
frame_poses)
: ScBundleAdjustmentBase<Scalar>::LinearizeAbsReduce<AccumT>(aom),
frame_poses(frame_poses) {
this->accum.reset(aom.total_size);
roll_pitch_error = 0;
rel_error = 0;
}
MapperLinearizeAbsReduce(const MapperLinearizeAbsReduce& other, tbb::split)
: ScBundleAdjustmentBase<Scalar>::LinearizeAbsReduce<AccumT>(other.aom),
frame_poses(other.frame_poses) {
this->accum.reset(this->aom.total_size);
roll_pitch_error = 0;
rel_error = 0;
}
void join(MapperLinearizeAbsReduce& rhs) {
this->accum.join(rhs.accum);
roll_pitch_error += rhs.roll_pitch_error;
rel_error += rhs.rel_error;
}
void operator()(const tbb::blocked_range<RelLinDataConstIter>& range) {
for (const RelLinData& rld : range) {
Eigen::MatrixXd rel_H;
Eigen::VectorXd rel_b;
linearizeRel(rld, rel_H, rel_b);
linearizeAbs(rel_H, rel_b, rld, this->aom, this->accum);
}
}
void operator()(const tbb::blocked_range<RollPitchFactorConstIter>& range) {
for (const RollPitchFactor& rpf : range) {
const Sophus::SE3d& pose = frame_poses->at(rpf.t_ns).getPose();
int idx = this->aom.abs_order_map.at(rpf.t_ns).first;
Eigen::Matrix<double, 2, POSE_SIZE> J;
Sophus::Vector2d res = basalt::rollPitchError(pose, rpf.R_w_i_meas, &J);
this->accum.template addH<POSE_SIZE, POSE_SIZE>(
idx, idx, J.transpose() * rpf.cov_inv * J);
this->accum.template addB<POSE_SIZE>(idx,
J.transpose() * rpf.cov_inv * res);
roll_pitch_error += res.transpose() * rpf.cov_inv * res;
}
}
void operator()(const tbb::blocked_range<RelPoseFactorConstIter>& range) {
for (const RelPoseFactor& rpf : range) {
const Sophus::SE3d& pose_i = frame_poses->at(rpf.t_i_ns).getPose();
const Sophus::SE3d& pose_j = frame_poses->at(rpf.t_j_ns).getPose();
int idx_i = this->aom.abs_order_map.at(rpf.t_i_ns).first;
int idx_j = this->aom.abs_order_map.at(rpf.t_j_ns).first;
Sophus::Matrix6d Ji, Jj;
Sophus::Vector6d res =
basalt::relPoseError(rpf.T_i_j, pose_i, pose_j, &Ji, &Jj);
this->accum.template addH<POSE_SIZE, POSE_SIZE>(
idx_i, idx_i, Ji.transpose() * rpf.cov_inv * Ji);
this->accum.template addH<POSE_SIZE, POSE_SIZE>(
idx_i, idx_j, Ji.transpose() * rpf.cov_inv * Jj);
this->accum.template addH<POSE_SIZE, POSE_SIZE>(
idx_j, idx_i, Jj.transpose() * rpf.cov_inv * Ji);
this->accum.template addH<POSE_SIZE, POSE_SIZE>(
idx_j, idx_j, Jj.transpose() * rpf.cov_inv * Jj);
this->accum.template addB<POSE_SIZE>(
idx_i, Ji.transpose() * rpf.cov_inv * res);
this->accum.template addB<POSE_SIZE>(
idx_j, Jj.transpose() * rpf.cov_inv * res);
rel_error += res.transpose() * rpf.cov_inv * res;
}
}
double roll_pitch_error;
double rel_error;
const Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>>* frame_poses;
};
NfrMapper(const basalt::Calibration<double>& calib, const VioConfig& config);
void addMargData(basalt::MargData::Ptr& data);
void processMargData(basalt::MargData& m);
bool extractNonlinearFactors(basalt::MargData& m);
void optimize(int num_iterations = 10);
Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>>& getFramePoses();
void computeRelPose(double& rel_error);
void computeRollPitch(double& roll_pitch_error);
void detect_keypoints();
// Feature matching and inlier filtering for stereo pairs with known pose
void match_stereo();
void match_all();
void build_tracks();
void setup_opt();
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
Eigen::aligned_vector<RollPitchFactor> roll_pitch_factors;
Eigen::aligned_vector<RelPoseFactor> rel_pose_factors;
std::unordered_map<int64_t, OpticalFlowInput::Ptr> img_data;
Corners feature_corners;
Matches feature_matches;
FeatureTracks feature_tracks;
std::shared_ptr<HashBow<256>> hash_bow_database;
VioConfig config;
double lambda, min_lambda, max_lambda, lambda_vee;
};
} // namespace basalt

View File

@@ -0,0 +1,452 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/vi_estimator/ba_base.h>
#include <tbb/blocked_range.h>
namespace basalt {
template <class Scalar_>
class ScBundleAdjustmentBase : public BundleAdjustmentBase<Scalar_> {
public:
using Scalar = Scalar_;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec6 = Eigen::Matrix<Scalar, 6, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat3 = Eigen::Matrix<Scalar, 3, 3>;
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using Mat6 = Eigen::Matrix<Scalar, 6, 6>;
using Mat63 = Eigen::Matrix<Scalar, 6, 3>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using SO3 = Sophus::SO3<Scalar>;
using SE3 = Sophus::SE3<Scalar>;
struct RelLinDataBase {
std::vector<std::pair<TimeCamId, TimeCamId>> order;
Eigen::aligned_vector<Mat6> d_rel_d_h;
Eigen::aligned_vector<Mat6> d_rel_d_t;
};
struct FrameRelLinData {
Mat6 Hpp;
Vec6 bp;
std::vector<int> lm_id;
Eigen::aligned_vector<Mat63> Hpl;
FrameRelLinData() {
Hpp.setZero();
bp.setZero();
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct RelLinData : public RelLinDataBase {
RelLinData(size_t num_keypoints, size_t num_rel_poses) {
Hll.reserve(num_keypoints);
Hllinv.reserve(num_keypoints);
bl.reserve(num_keypoints);
lm_to_obs.reserve(num_keypoints);
Hpppl.reserve(num_rel_poses);
order.reserve(num_rel_poses);
d_rel_d_h.reserve(num_rel_poses);
d_rel_d_t.reserve(num_rel_poses);
error = 0;
}
void invert_keypoint_hessians() {
for (const auto& [kpt_idx, hll] : Hll) {
Mat3 Hll_inv;
Hll_inv.setIdentity();
// Use ldlt b/c it has good speed (no numeric indefiniteness of this 3x3
// matrix expected), and compared ot a direct inverse (which may be even
// faster), it can handle degenerate cases where Hll is not invertible.
hll.ldlt().solveInPlace(Hll_inv);
Hllinv[kpt_idx] = Hll_inv;
}
}
using RelLinDataBase::d_rel_d_h;
using RelLinDataBase::d_rel_d_t;
using RelLinDataBase::order;
Eigen::aligned_unordered_map<int, Mat3> Hll;
Eigen::aligned_unordered_map<int, Mat3> Hllinv;
Eigen::aligned_unordered_map<int, Vec3> bl;
Eigen::aligned_unordered_map<int, std::vector<std::pair<size_t, size_t>>>
lm_to_obs;
Eigen::aligned_vector<FrameRelLinData> Hpppl;
Scalar error;
};
struct FrameAbsLinData {
Mat6 Hphph;
Vec6 bph;
Mat6 Hptpt;
Vec6 bpt;
Mat6 Hphpt;
std::vector<int> lm_id;
Eigen::aligned_vector<Mat63> Hphl;
Eigen::aligned_vector<Mat63> Hptl;
FrameAbsLinData() {
Hphph.setZero();
Hptpt.setZero();
Hphpt.setZero();
bph.setZero();
bpt.setZero();
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
struct AbsLinData {
AbsLinData(size_t num_keypoints, size_t num_rel_poses) {
Hll.reserve(num_keypoints);
Hllinv.reserve(num_keypoints);
bl.reserve(num_keypoints);
lm_to_obs.reserve(num_keypoints);
Hpppl.reserve(num_rel_poses);
order.reserve(num_rel_poses);
error = 0;
}
void invert_keypoint_hessians() {
for (const auto& [kpt_idx, hll] : Hll) {
Mat3 Hll_inv;
Hll_inv.setIdentity();
// Use ldlt b/c it has good speed (no numeric indefiniteness of this 3x3
// matrix expected), and compared ot a direct inverse (which may be even
// faster), it can handle degenerate cases where Hll is not invertible.
hll.ldlt().solveInPlace(Hll_inv);
Hllinv[kpt_idx] = Hll_inv;
}
}
std::vector<std::pair<TimeCamId, TimeCamId>> order;
Eigen::aligned_unordered_map<int, Mat3> Hll;
Eigen::aligned_unordered_map<int, Mat3> Hllinv;
Eigen::aligned_unordered_map<int, Vec3> bl;
Eigen::aligned_unordered_map<int, std::vector<std::pair<size_t, size_t>>>
lm_to_obs;
Eigen::aligned_vector<FrameAbsLinData> Hpppl;
Scalar error;
};
using BundleAdjustmentBase<Scalar>::computeDelta;
void linearizeHelper(
Eigen::aligned_vector<RelLinData>& rld_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
Scalar& error) const {
linearizeHelperStatic(rld_vec, obs_to_lin, this, error);
}
void linearizeAbsHelper(
Eigen::aligned_vector<AbsLinData>& ald_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
Scalar& error) const {
linearizeHelperAbsStatic(ald_vec, obs_to_lin, this, error);
}
static void linearizeHelperStatic(
Eigen::aligned_vector<RelLinData>& rld_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
const BundleAdjustmentBase<Scalar>* ba_base, Scalar& error);
void linearizeHelperAbs(
Eigen::aligned_vector<AbsLinData>& ald_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
Scalar& error) const {
linearizeHelperAbsStatic(ald_vec, obs_to_lin, this, error);
}
static void linearizeHelperAbsStatic(
Eigen::aligned_vector<AbsLinData>& ald_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
const BundleAdjustmentBase<Scalar>* ba_base, Scalar& error);
static void linearizeRel(const RelLinData& rld, MatX& H, VecX& b);
static void updatePoints(const AbsOrderMap& aom, const RelLinData& rld,
const VecX& inc, LandmarkDatabase<Scalar>& lmdb,
Scalar* l_diff = nullptr);
static void updatePointsAbs(const AbsOrderMap& aom, const AbsLinData& ald,
const VecX& inc, LandmarkDatabase<Scalar>& lmdb,
Scalar* l_diff = nullptr);
static Eigen::VectorXd checkNullspace(
const MatX& H, const VecX& b, const AbsOrderMap& marg_order,
const Eigen::aligned_map<int64_t, PoseVelBiasStateWithLin<Scalar>>&
frame_states,
const Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>>& frame_poses,
bool verbose = true);
static Eigen::VectorXd checkEigenvalues(const MatX& H, bool verbose = true);
static void computeImuError(
const AbsOrderMap& aom, Scalar& imu_error, Scalar& bg_error,
Scalar& ba_error,
const Eigen::aligned_map<int64_t, PoseVelBiasStateWithLin<Scalar>>&
states,
const Eigen::aligned_map<int64_t, IntegratedImuMeasurement<Scalar>>&
imu_meas,
const Vec3& gyro_bias_weight, const Vec3& accel_bias_weight,
const Vec3& g);
template <class AccumT>
static void linearizeAbs(const MatX& rel_H, const VecX& rel_b,
const RelLinDataBase& rld, const AbsOrderMap& aom,
AccumT& accum) {
// int asize = aom.total_size;
// BASALT_ASSERT(abs_H.cols() == asize);
// BASALT_ASSERT(abs_H.rows() == asize);
// BASALT_ASSERT(abs_b.rows() == asize);
for (size_t i = 0; i < rld.order.size(); i++) {
const TimeCamId& tcid_h = rld.order[i].first;
const TimeCamId& tcid_ti = rld.order[i].second;
int abs_h_idx = aom.abs_order_map.at(tcid_h.frame_id).first;
int abs_ti_idx = aom.abs_order_map.at(tcid_ti.frame_id).first;
accum.template addB<POSE_SIZE>(
abs_h_idx, rld.d_rel_d_h[i].transpose() *
rel_b.template segment<POSE_SIZE>(i * POSE_SIZE));
accum.template addB<POSE_SIZE>(
abs_ti_idx, rld.d_rel_d_t[i].transpose() *
rel_b.template segment<POSE_SIZE>(i * POSE_SIZE));
for (size_t j = 0; j < rld.order.size(); j++) {
BASALT_ASSERT(rld.order[i].first == rld.order[j].first);
const TimeCamId& tcid_tj = rld.order[j].second;
int abs_tj_idx = aom.abs_order_map.at(tcid_tj.frame_id).first;
if (tcid_h.frame_id == tcid_ti.frame_id ||
tcid_h.frame_id == tcid_tj.frame_id)
continue;
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_h_idx, abs_h_idx,
rld.d_rel_d_h[i].transpose() *
rel_H.template block<POSE_SIZE, POSE_SIZE>(POSE_SIZE * i,
POSE_SIZE * j) *
rld.d_rel_d_h[j]);
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_ti_idx, abs_h_idx,
rld.d_rel_d_t[i].transpose() *
rel_H.template block<POSE_SIZE, POSE_SIZE>(POSE_SIZE * i,
POSE_SIZE * j) *
rld.d_rel_d_h[j]);
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_h_idx, abs_tj_idx,
rld.d_rel_d_h[i].transpose() *
rel_H.template block<POSE_SIZE, POSE_SIZE>(POSE_SIZE * i,
POSE_SIZE * j) *
rld.d_rel_d_t[j]);
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_ti_idx, abs_tj_idx,
rld.d_rel_d_t[i].transpose() *
rel_H.template block<POSE_SIZE, POSE_SIZE>(POSE_SIZE * i,
POSE_SIZE * j) *
rld.d_rel_d_t[j]);
}
}
}
template <class AccumT>
static void linearizeAbs(const AbsLinData& ald, const AbsOrderMap& aom,
AccumT& accum) {
for (size_t i = 0; i < ald.order.size(); i++) {
const TimeCamId& tcid_h = ald.order[i].first;
const TimeCamId& tcid_ti = ald.order[i].second;
int abs_h_idx = aom.abs_order_map.at(tcid_h.frame_id).first;
int abs_ti_idx = aom.abs_order_map.at(tcid_ti.frame_id).first;
const FrameAbsLinData& fald = ald.Hpppl.at(i);
// Pose H and b part
accum.template addH<POSE_SIZE, POSE_SIZE>(abs_h_idx, abs_h_idx,
fald.Hphph);
accum.template addH<POSE_SIZE, POSE_SIZE>(abs_ti_idx, abs_ti_idx,
fald.Hptpt);
accum.template addH<POSE_SIZE, POSE_SIZE>(abs_h_idx, abs_ti_idx,
fald.Hphpt);
accum.template addH<POSE_SIZE, POSE_SIZE>(abs_ti_idx, abs_h_idx,
fald.Hphpt.transpose());
accum.template addB<POSE_SIZE>(abs_h_idx, fald.bph);
accum.template addB<POSE_SIZE>(abs_ti_idx, fald.bpt);
// Schur complement for landmark part
for (size_t j = 0; j < fald.lm_id.size(); j++) {
Eigen::Matrix<Scalar, POSE_SIZE, 3> H_phl_H_ll_inv, H_ptl_H_ll_inv;
int lm_id = fald.lm_id.at(j);
H_phl_H_ll_inv = fald.Hphl[j] * ald.Hllinv.at(lm_id);
H_ptl_H_ll_inv = fald.Hptl[j] * ald.Hllinv.at(lm_id);
accum.template addB<POSE_SIZE>(abs_h_idx,
-H_phl_H_ll_inv * ald.bl.at(lm_id));
accum.template addB<POSE_SIZE>(abs_ti_idx,
-H_ptl_H_ll_inv * ald.bl.at(lm_id));
const auto& other_obs = ald.lm_to_obs.at(lm_id);
for (size_t k = 0; k < other_obs.size(); k++) {
int other_frame_idx = other_obs[k].first;
int other_lm_idx = other_obs[k].second;
const FrameAbsLinData& fald_other = ald.Hpppl.at(other_frame_idx);
const TimeCamId& tcid_hk = ald.order.at(other_frame_idx).first;
const TimeCamId& tcid_tk = ald.order.at(other_frame_idx).second;
// Assume same host frame
BASALT_ASSERT(tcid_hk.frame_id == tcid_h.frame_id &&
tcid_hk.cam_id == tcid_h.cam_id);
int abs_tk_idx = aom.abs_order_map.at(tcid_tk.frame_id).first;
Eigen::Matrix<Scalar, 3, POSE_SIZE> H_l_ph_other =
fald_other.Hphl[other_lm_idx].transpose();
Eigen::Matrix<Scalar, 3, POSE_SIZE> H_l_pt_other =
fald_other.Hptl[other_lm_idx].transpose();
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_h_idx, abs_h_idx, -H_phl_H_ll_inv * H_l_ph_other);
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_ti_idx, abs_h_idx, -H_ptl_H_ll_inv * H_l_ph_other);
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_h_idx, abs_tk_idx, -H_phl_H_ll_inv * H_l_pt_other);
accum.template addH<POSE_SIZE, POSE_SIZE>(
abs_ti_idx, abs_tk_idx, -H_ptl_H_ll_inv * H_l_pt_other);
}
}
}
}
template <class AccumT>
struct LinearizeAbsReduce {
static_assert(std::is_same_v<typename AccumT::Scalar, Scalar>);
using RelLinConstDataIter =
typename Eigen::aligned_vector<RelLinData>::const_iterator;
LinearizeAbsReduce(const AbsOrderMap& aom) : aom(aom) {
accum.reset(aom.total_size);
}
LinearizeAbsReduce(const LinearizeAbsReduce& other, tbb::split)
: aom(other.aom) {
accum.reset(aom.total_size);
}
void operator()(const tbb::blocked_range<RelLinConstDataIter>& range) {
for (const RelLinData& rld : range) {
MatX rel_H;
VecX rel_b;
linearizeRel(rld, rel_H, rel_b);
linearizeAbs(rel_H, rel_b, rld, aom, accum);
}
}
void join(LinearizeAbsReduce& rhs) { accum.join(rhs.accum); }
const AbsOrderMap& aom;
AccumT accum;
};
template <class AccumT>
struct LinearizeAbsReduce2 {
static_assert(std::is_same_v<typename AccumT::Scalar, Scalar>);
using AbsLinDataConstIter =
typename Eigen::aligned_vector<AbsLinData>::const_iterator;
LinearizeAbsReduce2(const AbsOrderMap& aom) : aom(aom) {
accum.reset(aom.total_size);
}
LinearizeAbsReduce2(const LinearizeAbsReduce2& other, tbb::split)
: aom(other.aom) {
accum.reset(aom.total_size);
}
void operator()(const tbb::blocked_range<AbsLinDataConstIter>& range) {
for (const AbsLinData& ald : range) {
linearizeAbs(ald, aom, accum);
}
}
void join(LinearizeAbsReduce2& rhs) { accum.join(rhs.accum); }
const AbsOrderMap& aom;
AccumT accum;
};
};
} // namespace basalt

View File

@@ -0,0 +1,124 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <basalt/vi_estimator/ba_base.h>
#include <basalt/vi_estimator/sc_ba_base.h>
namespace basalt {
template <class Scalar_>
class SqrtBundleAdjustmentBase : public BundleAdjustmentBase<Scalar_> {
public:
using Scalar = Scalar_;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat3 = Eigen::Matrix<Scalar, 3, 3>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using SO3 = Sophus::SO3<Scalar>;
using RelLinDataBase =
typename ScBundleAdjustmentBase<Scalar>::RelLinDataBase;
using FrameRelLinData =
typename ScBundleAdjustmentBase<Scalar>::FrameRelLinData;
using RelLinData = typename ScBundleAdjustmentBase<Scalar>::RelLinData;
using FrameAbsLinData =
typename ScBundleAdjustmentBase<Scalar>::FrameAbsLinData;
using AbsLinData = typename ScBundleAdjustmentBase<Scalar>::AbsLinData;
using BundleAdjustmentBase<Scalar>::computeDelta;
void linearizeHelper(
Eigen::aligned_vector<RelLinData>& rld_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
Scalar& error) const {
ScBundleAdjustmentBase<Scalar>::linearizeHelperStatic(rld_vec, obs_to_lin,
this, error);
}
void linearizeAbsHelper(
Eigen::aligned_vector<AbsLinData>& ald_vec,
const std::unordered_map<
TimeCamId, std::map<TimeCamId, std::set<KeypointId>>>& obs_to_lin,
Scalar& error) const {
ScBundleAdjustmentBase<Scalar>::linearizeHelperAbsStatic(
ald_vec, obs_to_lin, this, error);
}
static void linearizeRel(const RelLinData& rld, MatX& H, VecX& b) {
ScBundleAdjustmentBase<Scalar>::linearizeRel(rld, H, b);
}
static void updatePoints(const AbsOrderMap& aom, const RelLinData& rld,
const VecX& inc, LandmarkDatabase<Scalar>& lmdb,
Scalar* l_diff = nullptr) {
ScBundleAdjustmentBase<Scalar>::updatePoints(aom, rld, inc, lmdb, l_diff);
}
static void updatePointsAbs(const AbsOrderMap& aom, const AbsLinData& ald,
const VecX& inc, LandmarkDatabase<Scalar>& lmdb,
Scalar* l_diff = nullptr) {
ScBundleAdjustmentBase<Scalar>::updatePointsAbs(aom, ald, inc, lmdb,
l_diff);
}
static Eigen::VectorXd checkNullspace(
const MargLinData<Scalar_>& mld,
const Eigen::aligned_map<int64_t, PoseVelBiasStateWithLin<Scalar>>&
frame_states,
const Eigen::aligned_map<int64_t, PoseStateWithLin<Scalar>>& frame_poses,
bool verbose = true);
static Eigen::VectorXd checkEigenvalues(const MargLinData<Scalar_>& mld,
bool verbose = true);
template <class AccumT>
static void linearizeAbs(const MatX& rel_H, const VecX& rel_b,
const RelLinDataBase& rld, const AbsOrderMap& aom,
AccumT& accum) {
return ScBundleAdjustmentBase<Scalar>::template linearizeAbs<AccumT>(
rel_H, rel_b, rld, aom, accum);
}
template <class AccumT>
static void linearizeAbs(const AbsLinData& ald, const AbsOrderMap& aom,
AccumT& accum) {
return ScBundleAdjustmentBase<Scalar>::template linearizeAbs<AccumT>(
ald, aom, accum);
}
};
} // namespace basalt

View File

@@ -0,0 +1,261 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <thread>
#include <basalt/imu/preintegration.h>
#include <basalt/utils/time_utils.hpp>
#include <basalt/vi_estimator/sqrt_ba_base.h>
#include <basalt/vi_estimator/vio_estimator.h>
#include <basalt/imu/preintegration.h>
namespace basalt {
template <class Scalar_>
class SqrtKeypointVioEstimator : public VioEstimatorBase,
public SqrtBundleAdjustmentBase<Scalar_> {
public:
using Scalar = Scalar_;
typedef std::shared_ptr<SqrtKeypointVioEstimator> Ptr;
static const int N = 9;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using MatN3 = Eigen::Matrix<Scalar, N, 3>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using SE3 = Sophus::SE3<Scalar>;
using typename SqrtBundleAdjustmentBase<Scalar>::RelLinData;
using typename SqrtBundleAdjustmentBase<Scalar>::AbsLinData;
using BundleAdjustmentBase<Scalar>::computeError;
using BundleAdjustmentBase<Scalar>::get_current_points;
using BundleAdjustmentBase<Scalar>::computeDelta;
using BundleAdjustmentBase<Scalar>::computeProjections;
using BundleAdjustmentBase<Scalar>::triangulate;
using BundleAdjustmentBase<Scalar>::backup;
using BundleAdjustmentBase<Scalar>::restore;
using BundleAdjustmentBase<Scalar>::getPoseStateWithLin;
using BundleAdjustmentBase<Scalar>::computeModelCostChange;
using SqrtBundleAdjustmentBase<Scalar>::linearizeHelper;
using SqrtBundleAdjustmentBase<Scalar>::linearizeAbsHelper;
using SqrtBundleAdjustmentBase<Scalar>::linearizeRel;
using SqrtBundleAdjustmentBase<Scalar>::linearizeAbs;
using SqrtBundleAdjustmentBase<Scalar>::updatePoints;
using SqrtBundleAdjustmentBase<Scalar>::updatePointsAbs;
using SqrtBundleAdjustmentBase<Scalar>::linearizeMargPrior;
using SqrtBundleAdjustmentBase<Scalar>::computeMargPriorError;
using SqrtBundleAdjustmentBase<Scalar>::computeMargPriorModelCostChange;
using SqrtBundleAdjustmentBase<Scalar>::checkNullspace;
using SqrtBundleAdjustmentBase<Scalar>::checkEigenvalues;
SqrtKeypointVioEstimator(const Eigen::Vector3d& g,
const basalt::Calibration<double>& calib,
const VioConfig& config);
void initialize(int64_t t_ns, const Sophus::SE3d& T_w_i,
const Eigen::Vector3d& vel_w_i, const Eigen::Vector3d& bg,
const Eigen::Vector3d& ba) override;
void initialize(const Eigen::Vector3d& bg,
const Eigen::Vector3d& ba) override;
virtual ~SqrtKeypointVioEstimator() { maybe_join(); }
inline void maybe_join() override {
if (processing_thread) {
processing_thread->join();
processing_thread.reset();
}
}
void addIMUToQueue(const ImuData<double>::Ptr& data) override;
void addVisionToQueue(const OpticalFlowResult::Ptr& data) override;
typename ImuData<Scalar>::Ptr popFromImuDataQueue();
bool measure(const OpticalFlowResult::Ptr& opt_flow_meas,
const typename IntegratedImuMeasurement<Scalar>::Ptr& meas);
// int64_t propagate();
// void addNewState(int64_t data_t_ns);
void optimize_and_marg(const std::map<int64_t, int>& num_points_connected,
const std::unordered_set<KeypointId>& lost_landmaks);
void marginalize(const std::map<int64_t, int>& num_points_connected,
const std::unordered_set<KeypointId>& lost_landmaks);
void optimize();
void debug_finalize() override;
void logMargNullspace();
Eigen::VectorXd checkMargNullspace() const;
Eigen::VectorXd checkMargEigenvalues() const;
int64_t get_t_ns() const {
return frame_states.at(last_state_t_ns).getState().t_ns;
}
const SE3& get_T_w_i() const {
return frame_states.at(last_state_t_ns).getState().T_w_i;
}
const Vec3& get_vel_w_i() const {
return frame_states.at(last_state_t_ns).getState().vel_w_i;
}
const PoseVelBiasState<Scalar>& get_state() const {
return frame_states.at(last_state_t_ns).getState();
}
PoseVelBiasState<Scalar> get_state(int64_t t_ns) const {
PoseVelBiasState<Scalar> state;
auto it = frame_states.find(t_ns);
if (it != frame_states.end()) {
return it->second.getState();
}
auto it2 = frame_poses.find(t_ns);
if (it2 != frame_poses.end()) {
state.T_w_i = it2->second.getPose();
}
return state;
}
void setMaxStates(size_t val) override { max_states = val; }
void setMaxKfs(size_t val) override { max_kfs = val; }
Eigen::aligned_vector<SE3> getFrameStates() const {
Eigen::aligned_vector<SE3> res;
for (const auto& kv : frame_states) {
res.push_back(kv.second.getState().T_w_i);
}
return res;
}
Eigen::aligned_vector<SE3> getFramePoses() const {
Eigen::aligned_vector<SE3> res;
for (const auto& kv : frame_poses) {
res.push_back(kv.second.getPose());
}
return res;
}
Eigen::aligned_map<int64_t, SE3> getAllPosesMap() const {
Eigen::aligned_map<int64_t, SE3> res;
for (const auto& kv : frame_poses) {
res[kv.first] = kv.second.getPose();
}
for (const auto& kv : frame_states) {
res[kv.first] = kv.second.getState().T_w_i;
}
return res;
}
Sophus::SE3d getT_w_i_init() override {
return T_w_i_init.template cast<double>();
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
using BundleAdjustmentBase<Scalar>::frame_poses;
using BundleAdjustmentBase<Scalar>::frame_states;
using BundleAdjustmentBase<Scalar>::lmdb;
using BundleAdjustmentBase<Scalar>::obs_std_dev;
using BundleAdjustmentBase<Scalar>::huber_thresh;
using BundleAdjustmentBase<Scalar>::calib;
private:
bool take_kf;
int frames_after_kf;
std::set<int64_t> kf_ids;
int64_t last_state_t_ns;
Eigen::aligned_map<int64_t, IntegratedImuMeasurement<Scalar>> imu_meas;
const Vec3 g;
// Input
Eigen::aligned_map<int64_t, OpticalFlowResult::Ptr> prev_opt_flow_res;
std::map<int64_t, int> num_points_kf;
// Marginalization
MargLinData<Scalar> marg_data;
// Used only for debug and log purporses.
MargLinData<Scalar> nullspace_marg_data;
Vec3 gyro_bias_sqrt_weight, accel_bias_sqrt_weight;
size_t max_states;
size_t max_kfs;
SE3 T_w_i_init;
bool initialized;
bool opt_started;
VioConfig config;
constexpr static Scalar vee_factor = Scalar(2.0);
constexpr static Scalar initial_vee = Scalar(2.0);
Scalar lambda, min_lambda, max_lambda, lambda_vee;
std::shared_ptr<std::thread> processing_thread;
// timing and stats
ExecutionStats stats_all_;
ExecutionStats stats_sums_;
};
} // namespace basalt

View File

@@ -0,0 +1,251 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <thread>
#include <basalt/vi_estimator/sqrt_ba_base.h>
#include <basalt/vi_estimator/vio_estimator.h>
#include <basalt/utils/time_utils.hpp>
namespace basalt {
template <class Scalar_>
class SqrtKeypointVoEstimator : public VioEstimatorBase,
public SqrtBundleAdjustmentBase<Scalar_> {
public:
using Scalar = Scalar_;
typedef std::shared_ptr<SqrtKeypointVoEstimator> Ptr;
static const int N = 9;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using MatN3 = Eigen::Matrix<Scalar, N, 3>;
using MatX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
using SE3 = Sophus::SE3<Scalar>;
using typename SqrtBundleAdjustmentBase<Scalar>::RelLinData;
using typename SqrtBundleAdjustmentBase<Scalar>::AbsLinData;
using BundleAdjustmentBase<Scalar>::computeError;
using BundleAdjustmentBase<Scalar>::get_current_points;
using BundleAdjustmentBase<Scalar>::computeDelta;
using BundleAdjustmentBase<Scalar>::computeProjections;
using BundleAdjustmentBase<Scalar>::triangulate;
using BundleAdjustmentBase<Scalar>::backup;
using BundleAdjustmentBase<Scalar>::restore;
using BundleAdjustmentBase<Scalar>::getPoseStateWithLin;
using BundleAdjustmentBase<Scalar>::computeModelCostChange;
using SqrtBundleAdjustmentBase<Scalar>::linearizeHelper;
using SqrtBundleAdjustmentBase<Scalar>::linearizeAbsHelper;
using SqrtBundleAdjustmentBase<Scalar>::linearizeRel;
using SqrtBundleAdjustmentBase<Scalar>::linearizeAbs;
using SqrtBundleAdjustmentBase<Scalar>::updatePoints;
using SqrtBundleAdjustmentBase<Scalar>::updatePointsAbs;
using SqrtBundleAdjustmentBase<Scalar>::linearizeMargPrior;
using SqrtBundleAdjustmentBase<Scalar>::computeMargPriorError;
using SqrtBundleAdjustmentBase<Scalar>::computeMargPriorModelCostChange;
using SqrtBundleAdjustmentBase<Scalar>::checkNullspace;
using SqrtBundleAdjustmentBase<Scalar>::checkEigenvalues;
SqrtKeypointVoEstimator(const basalt::Calibration<double>& calib,
const VioConfig& config);
void initialize(int64_t t_ns, const Sophus::SE3d& T_w_i,
const Eigen::Vector3d& vel_w_i, const Eigen::Vector3d& bg,
const Eigen::Vector3d& ba) override;
void initialize(const Eigen::Vector3d& bg,
const Eigen::Vector3d& ba) override;
virtual ~SqrtKeypointVoEstimator() { maybe_join(); }
inline void maybe_join() override {
if (processing_thread) {
processing_thread->join();
processing_thread.reset();
}
}
void addIMUToQueue(const ImuData<double>::Ptr& data) override;
void addVisionToQueue(const OpticalFlowResult::Ptr& data) override;
bool measure(const OpticalFlowResult::Ptr& opt_flow_meas, bool add_frame);
// int64_t propagate();
// void addNewState(int64_t data_t_ns);
void optimize_and_marg(const std::map<int64_t, int>& num_points_connected,
const std::unordered_set<KeypointId>& lost_landmaks);
void marginalize(const std::map<int64_t, int>& num_points_connected,
const std::unordered_set<KeypointId>& lost_landmaks);
void optimize();
void logMargNullspace();
Eigen::VectorXd checkMargNullspace() const;
Eigen::VectorXd checkMargEigenvalues() const;
int64_t get_t_ns() const {
return frame_states.at(last_state_t_ns).getState().t_ns;
}
const SE3& get_T_w_i() const {
return frame_states.at(last_state_t_ns).getState().T_w_i;
}
const Vec3& get_vel_w_i() const {
return frame_states.at(last_state_t_ns).getState().vel_w_i;
}
const PoseVelBiasState<Scalar>& get_state() const {
return frame_states.at(last_state_t_ns).getState();
}
PoseVelBiasState<Scalar> get_state(int64_t t_ns) const {
PoseVelBiasState<Scalar> state;
auto it = frame_states.find(t_ns);
if (it != frame_states.end()) {
return it->second.getState();
}
auto it2 = frame_poses.find(t_ns);
if (it2 != frame_poses.end()) {
state.T_w_i = it2->second.getPose();
}
return state;
}
void setMaxStates(size_t val) override { max_states = val; }
void setMaxKfs(size_t val) override { max_kfs = val; }
Eigen::aligned_vector<SE3> getFrameStates() const {
Eigen::aligned_vector<SE3> res;
for (const auto& kv : frame_states) {
res.push_back(kv.second.getState().T_w_i);
}
return res;
}
Eigen::aligned_vector<SE3> getFramePoses() const {
Eigen::aligned_vector<SE3> res;
for (const auto& kv : frame_poses) {
res.push_back(kv.second.getPose());
}
return res;
}
Eigen::aligned_map<int64_t, SE3> getAllPosesMap() const {
Eigen::aligned_map<int64_t, SE3> res;
for (const auto& kv : frame_poses) {
res[kv.first] = kv.second.getPose();
}
for (const auto& kv : frame_states) {
res[kv.first] = kv.second.getState().T_w_i;
}
return res;
}
Sophus::SE3d getT_w_i_init() override {
return T_w_i_init.template cast<double>();
}
void debug_finalize() override;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
using BundleAdjustmentBase<Scalar>::frame_poses;
using BundleAdjustmentBase<Scalar>::frame_states;
using BundleAdjustmentBase<Scalar>::lmdb;
using BundleAdjustmentBase<Scalar>::obs_std_dev;
using BundleAdjustmentBase<Scalar>::huber_thresh;
using BundleAdjustmentBase<Scalar>::calib;
private:
bool take_kf; // true if next frame should become kf
int frames_after_kf; // number of frames since last kf
std::set<int64_t> kf_ids; // sliding window frame ids
// timestamp of latest state in the sliding window
// TODO: check and document when this is equal to kf_ids.rbegin() and when
// not. It may be only relevant for VIO, not VO.
int64_t last_state_t_ns = -1;
// Input
Eigen::aligned_map<int64_t, OpticalFlowResult::Ptr> prev_opt_flow_res;
std::map<int64_t, int> num_points_kf;
// Marginalization
MargLinData<Scalar> marg_data;
// Used only for debug and log purporses.
MargLinData<Scalar> nullspace_marg_data;
size_t max_states;
size_t max_kfs;
SE3 T_w_i_init;
bool initialized;
VioConfig config;
constexpr static Scalar vee_factor = Scalar(2.0);
constexpr static Scalar initial_vee = Scalar(2.0);
Scalar lambda, min_lambda, max_lambda, lambda_vee;
std::shared_ptr<std::thread> processing_thread;
// timing and stats
ExecutionStats stats_all_;
ExecutionStats stats_sums_;
};
} // namespace basalt

View File

@@ -0,0 +1,136 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt.git
Copyright (c) 2019, 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.
*/
#pragma once
#include <atomic>
#include <basalt/optical_flow/optical_flow.h>
#include <basalt/utils/imu_types.h>
namespace basalt {
struct VioVisualizationData {
typedef std::shared_ptr<VioVisualizationData> Ptr;
int64_t t_ns;
Eigen::aligned_vector<Sophus::SE3d> states;
Eigen::aligned_vector<Sophus::SE3d> frames;
Eigen::aligned_vector<Eigen::Vector3d> points;
std::vector<int> point_ids;
OpticalFlowResult::Ptr opt_flow_res;
std::vector<Eigen::aligned_vector<Eigen::Vector4d>> projections;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
class VioEstimatorBase {
public:
typedef std::shared_ptr<VioEstimatorBase> Ptr;
VioEstimatorBase()
: out_state_queue(nullptr),
out_marg_queue(nullptr),
out_vis_queue(nullptr) {
vision_data_queue.set_capacity(10);
imu_data_queue.set_capacity(300);
last_processed_t_ns = 0;
finished = false;
}
std::atomic<int64_t> last_processed_t_ns;
std::atomic<bool> finished;
tbb::concurrent_bounded_queue<OpticalFlowResult::Ptr> vision_data_queue;
tbb::concurrent_bounded_queue<ImuData<double>::Ptr> imu_data_queue;
tbb::concurrent_bounded_queue<PoseVelBiasState<double>::Ptr>*
out_state_queue = nullptr;
tbb::concurrent_bounded_queue<MargData::Ptr>* out_marg_queue = nullptr;
tbb::concurrent_bounded_queue<VioVisualizationData::Ptr>* out_vis_queue =
nullptr;
virtual void initialize(int64_t t_ns, const Sophus::SE3d& T_w_i,
const Eigen::Vector3d& vel_w_i,
const Eigen::Vector3d& bg,
const Eigen::Vector3d& ba) = 0;
virtual void initialize(const Eigen::Vector3d& bg,
const Eigen::Vector3d& ba) = 0;
virtual void maybe_join() = 0;
virtual inline void drain_input_queues() {
// Input threads should abort when vio is finished, but might be stuck in
// full push to full queue. So this can help to drain queues after joining
// the processing thread.
while (!imu_data_queue.empty()) {
ImuData<double>::Ptr d;
imu_data_queue.pop(d);
}
while (!vision_data_queue.empty()) {
OpticalFlowResult::Ptr d;
vision_data_queue.pop(d);
}
}
virtual inline void debug_finalize() {}
virtual Sophus::SE3d getT_w_i_init() = 0;
// Legacy functions. Should not be used in the new code.
virtual void setMaxStates(size_t val) = 0;
virtual void setMaxKfs(size_t val) = 0;
virtual void addIMUToQueue(const ImuData<double>::Ptr& data) = 0;
virtual void addVisionToQueue(const OpticalFlowResult::Ptr& data) = 0;
};
class VioEstimatorFactory {
public:
static VioEstimatorBase::Ptr getVioEstimator(const VioConfig& config,
const Calibration<double>& cam,
const Eigen::Vector3d& g,
bool use_imu, bool use_double);
};
double alignSVD(const std::vector<int64_t>& filter_t_ns,
const Eigen::aligned_vector<Eigen::Vector3d>& filter_t_w_i,
const std::vector<int64_t>& gt_t_ns,
Eigen::aligned_vector<Eigen::Vector3d>& gt_t_w_i);
} // namespace basalt