2023-03-22 22:20:09 -07:00
|
|
|
/*
|
2016-07-17 21:57:10 -04:00
|
|
|
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
2015-11-27 11:09:39 -05:00
|
|
|
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 Universite de Sherbrooke nor the
|
|
|
|
|
names of its contributors may be used to endorse or promote products
|
|
|
|
|
derived from this software without specific prior written permission.
|
|
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
|
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <rtabmap/utilite/ULogger.h>
|
|
|
|
|
#include <rtabmap/utilite/UStl.h>
|
|
|
|
|
#include <rtabmap/utilite/UMath.h>
|
|
|
|
|
#include <rtabmap/utilite/UConversion.h>
|
|
|
|
|
#include <rtabmap/utilite/UTimer.h>
|
2018-10-15 16:58:57 +01:00
|
|
|
#include <locale.h>
|
2015-11-27 11:09:39 -05:00
|
|
|
#include <set>
|
|
|
|
|
|
2018-02-03 15:43:32 -05:00
|
|
|
#include <rtabmap/core/Version.h>
|
2018-10-01 19:33:56 -04:00
|
|
|
#include <rtabmap/core/optimizer/OptimizerG2O.h>
|
2016-03-28 18:19:17 -04:00
|
|
|
#include <rtabmap/core/util3d_transforms.h>
|
|
|
|
|
#include <rtabmap/core/util3d_motion_estimation.h>
|
2017-01-03 21:05:00 -05:00
|
|
|
#include <rtabmap/core/util3d.h>
|
2015-11-27 11:09:39 -05:00
|
|
|
|
2018-10-24 06:18:10 +12:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#if defined(RTABMAP_G2O) || defined(RTABMAP_ORB_SLAM)
|
2015-11-27 11:09:39 -05:00
|
|
|
#include "g2o/core/sparse_optimizer.h"
|
|
|
|
|
#include "g2o/core/block_solver.h"
|
|
|
|
|
#include "g2o/core/factory.h"
|
|
|
|
|
#include "g2o/core/optimization_algorithm_factory.h"
|
|
|
|
|
#include "g2o/core/optimization_algorithm_gauss_newton.h"
|
|
|
|
|
#include "g2o/core/optimization_algorithm_levenberg.h"
|
2017-09-17 13:28:25 -04:00
|
|
|
#include "g2o/core/robust_kernel_impl.h"
|
2018-05-30 17:20:54 -04:00
|
|
|
namespace g2o {
|
|
|
|
|
// bug #include "g2o/core/eigen_types.h" not found on Indigo
|
|
|
|
|
typedef Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic,Eigen::ColMajor> MatrixXD;
|
|
|
|
|
}
|
2017-09-17 13:28:25 -04:00
|
|
|
|
|
|
|
|
#ifdef RTABMAP_G2O
|
2016-03-28 18:19:17 -04:00
|
|
|
#include "g2o/types/sba/types_sba.h"
|
2017-09-17 13:28:25 -04:00
|
|
|
#include "g2o/solvers/eigen/linear_solver_eigen.h"
|
|
|
|
|
#include "g2o/config.h"
|
2017-05-17 16:59:35 -04:00
|
|
|
#include "g2o/types/slam2d/types_slam2d.h"
|
|
|
|
|
#include "g2o/types/slam3d/types_slam3d.h"
|
2022-04-28 09:18:30 -04:00
|
|
|
#include "g2o/edge_se3_xyzprior.h" // Include after types_slam3d.h to be ignored on newest g2o versions
|
2019-04-09 20:05:24 -04:00
|
|
|
#include "g2o/edge_se3_gravity.h"
|
2022-04-28 09:18:30 -04:00
|
|
|
#include "g2o/edge_xy_prior.h" // Include after types_slam2d.h to be ignored on newest g2o versions
|
|
|
|
|
#include "g2o/edge_xyz_prior.h" // Include after types_slam3d.h to be ignored on newest g2o versions
|
2016-02-15 19:35:24 -05:00
|
|
|
#ifdef G2O_HAVE_CSPARSE
|
2015-11-27 11:09:39 -05:00
|
|
|
#include "g2o/solvers/csparse/linear_solver_csparse.h"
|
2016-02-15 19:35:24 -05:00
|
|
|
#endif
|
2015-11-27 11:09:39 -05:00
|
|
|
#include "g2o/solvers/pcg/linear_solver_pcg.h"
|
2016-02-15 19:35:24 -05:00
|
|
|
#ifdef G2O_HAVE_CHOLMOD
|
2016-01-14 17:07:01 -05:00
|
|
|
#include "g2o/solvers/cholmod/linear_solver_cholmod.h"
|
|
|
|
|
#endif
|
2018-10-24 06:16:40 +12:00
|
|
|
#endif // RTABMAP_G2O
|
2017-09-17 13:28:25 -04:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-09-17 13:28:25 -04:00
|
|
|
#include "g2o/types/types_sba.h"
|
|
|
|
|
#include "g2o/types/types_six_dof_expmap.h"
|
|
|
|
|
#include "g2o/solvers/linear_solver_eigen.h"
|
2026-04-05 12:36:54 -07:00
|
|
|
#include "g2o/edge_se3_expmap.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(RTABMAP_G2O) || defined(RTABMAP_ORB_SLAM)
|
|
|
|
|
namespace rtabmap {
|
|
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
|
|
|
|
typedef g2o::VertexSE3Expmap VertexCam;
|
|
|
|
|
#else
|
|
|
|
|
typedef g2o::VertexCam VertexCam;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
#include "g2o/edge_sbacam_gravity.h"
|
|
|
|
|
#include "g2o/edge_sbacam_prior.h"
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2015-11-27 11:09:39 -05:00
|
|
|
|
|
|
|
|
typedef g2o::BlockSolver< g2o::BlockSolverTraits<-1, -1> > SlamBlockSolver;
|
2017-01-03 21:05:00 -05:00
|
|
|
typedef g2o::LinearSolverEigen<SlamBlockSolver::PoseMatrixType> SlamLinearEigenSolver;
|
2017-09-17 13:28:25 -04:00
|
|
|
#ifdef RTABMAP_G2O
|
2015-11-27 11:09:39 -05:00
|
|
|
typedef g2o::LinearSolverPCG<SlamBlockSolver::PoseMatrixType> SlamLinearPCGSolver;
|
2016-02-15 19:35:24 -05:00
|
|
|
#ifdef G2O_HAVE_CSPARSE
|
|
|
|
|
typedef g2o::LinearSolverCSparse<SlamBlockSolver::PoseMatrixType> SlamLinearCSparseSolver;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef G2O_HAVE_CHOLMOD
|
2016-01-14 17:07:01 -05:00
|
|
|
typedef g2o::LinearSolverCholmod<SlamBlockSolver::PoseMatrixType> SlamLinearCholmodSolver;
|
|
|
|
|
#endif
|
2015-11-27 11:09:39 -05:00
|
|
|
|
2025-10-13 22:54:35 -07:00
|
|
|
// We check if g2o/types/sba/sba_utils.h exists to know we use a version after December 24 2020
|
2025-03-13 19:58:13 -07:00
|
|
|
// where VertexSBAPointXYZ has been renamed to VertexPointXYZ
|
|
|
|
|
// (g2o: 0fcccb302787e70ff19f65e70fb103a1295b33a2)
|
2025-10-13 22:54:35 -07:00
|
|
|
#ifdef RTABMAP_G2O_WITH_SBA_UTILS
|
2021-01-11 11:38:31 -05:00
|
|
|
namespace g2o {
|
|
|
|
|
typedef VertexPointXYZ VertexSBAPointXYZ;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-02-03 15:43:32 -05:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
2015-11-27 11:09:39 -05:00
|
|
|
#include "vertigo/g2o/edge_switchPrior.h"
|
|
|
|
|
#include "vertigo/g2o/edge_se2Switchable.h"
|
|
|
|
|
#include "vertigo/g2o/edge_se3Switchable.h"
|
|
|
|
|
#include "vertigo/g2o/vertex_switchLinear.h"
|
2016-03-01 14:25:00 -05:00
|
|
|
#endif
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2015-11-27 11:09:39 -05:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#endif // end defined(RTABMAP_G2O) || defined(RTABMAP_ORB_SLAM)
|
2015-11-27 11:09:39 -05:00
|
|
|
|
2018-10-24 06:18:10 +12:00
|
|
|
enum {
|
|
|
|
|
PARAM_OFFSET=0,
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
#define MULTICAM_OFFSET 10 // 10 means max 10 cameras per pose
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
namespace rtabmap {
|
|
|
|
|
|
|
|
|
|
bool OptimizerG2O::available()
|
|
|
|
|
{
|
2021-03-09 16:00:41 -05:00
|
|
|
#if defined(RTABMAP_G2O) || defined(RTABMAP_ORB_SLAM)
|
2015-11-27 11:09:39 -05:00
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-15 19:35:24 -05:00
|
|
|
bool OptimizerG2O::isCSparseAvailable()
|
|
|
|
|
{
|
|
|
|
|
#ifdef G2O_HAVE_CSPARSE
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OptimizerG2O::isCholmodAvailable()
|
|
|
|
|
{
|
|
|
|
|
#ifdef G2O_HAVE_CHOLMOD
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 15:37:06 -05:00
|
|
|
OptimizerG2O::OptimizerG2O(const ParametersMap & parameters) :
|
|
|
|
|
Optimizer(parameters),
|
|
|
|
|
solver_(Parameters::defaultg2oSolver()),
|
|
|
|
|
optimizer_(Parameters::defaultg2oOptimizer()),
|
|
|
|
|
pixelVariance_(Parameters::defaultg2oPixelVariance()),
|
|
|
|
|
robustKernelDelta_(Parameters::defaultg2oRobustKernelDelta()),
|
|
|
|
|
baseline_(Parameters::defaultg2oBaseline())
|
|
|
|
|
{
|
2022-01-15 13:00:12 -05:00
|
|
|
#ifdef RTABMAP_G2O
|
2022-01-03 15:37:06 -05:00
|
|
|
// Issue on android, have to explicitly register this type when using fixed root prior below
|
|
|
|
|
if(!g2o::Factory::instance()->knowsTag("CACHE_SE3_OFFSET"))
|
|
|
|
|
{
|
2023-03-13 22:10:24 -07:00
|
|
|
#if defined(RTABMAP_G2O_CPP11) && RTABMAP_G2O_CPP11 == 1
|
2023-03-22 20:42:33 -07:00
|
|
|
g2o::Factory::instance()->registerType("CACHE_SE3_OFFSET", std::make_unique<g2o::HyperGraphElementCreator<g2o::CacheSE3Offset> >());
|
2022-01-03 15:37:06 -05:00
|
|
|
#else
|
|
|
|
|
g2o::Factory::instance()->registerType("CACHE_SE3_OFFSET", new g2o::HyperGraphElementCreator<g2o::CacheSE3Offset>);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2022-01-15 13:00:12 -05:00
|
|
|
#endif
|
2022-01-03 15:37:06 -05:00
|
|
|
parseParameters(parameters);
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-15 19:35:24 -05:00
|
|
|
void OptimizerG2O::parseParameters(const ParametersMap & parameters)
|
|
|
|
|
{
|
|
|
|
|
Optimizer::parseParameters(parameters);
|
|
|
|
|
|
|
|
|
|
Parameters::parse(parameters, Parameters::kg2oSolver(), solver_);
|
|
|
|
|
Parameters::parse(parameters, Parameters::kg2oOptimizer(), optimizer_);
|
2016-04-06 15:24:36 -04:00
|
|
|
Parameters::parse(parameters, Parameters::kg2oPixelVariance(), pixelVariance_);
|
2017-01-03 21:05:00 -05:00
|
|
|
Parameters::parse(parameters, Parameters::kg2oRobustKernelDelta(), robustKernelDelta_);
|
|
|
|
|
Parameters::parse(parameters, Parameters::kg2oBaseline(), baseline_);
|
2016-04-06 15:24:36 -04:00
|
|
|
UASSERT(pixelVariance_ > 0.0);
|
2017-01-03 21:05:00 -05:00
|
|
|
UASSERT(baseline_ >= 0.0);
|
2016-02-15 19:35:24 -05:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-09-17 13:28:25 -04:00
|
|
|
if(solver_ != 3)
|
|
|
|
|
{
|
2021-03-09 16:00:41 -05:00
|
|
|
UWARN("g2o built with ORB_SLAM has only Eigen solver available, using Eigen=3 instead of %d.", solver_);
|
2017-09-17 13:28:25 -04:00
|
|
|
solver_ = 3;
|
|
|
|
|
}
|
|
|
|
|
#else
|
2016-02-15 19:35:24 -05:00
|
|
|
#ifndef G2O_HAVE_CHOLMOD
|
|
|
|
|
if(solver_ == 2)
|
|
|
|
|
{
|
|
|
|
|
UWARN("g2o is not built with chmold, so it cannot be used as solver. Using CSparse instead.");
|
|
|
|
|
solver_ = 0;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifndef G2O_HAVE_CSPARSE
|
|
|
|
|
if(solver_ == 0)
|
|
|
|
|
{
|
|
|
|
|
UWARN("g2o is not built with csparse, so it cannot be used as solver. Using PCG instead.");
|
|
|
|
|
solver_ = 1;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
|
|
|
|
|
2016-02-15 19:35:24 -05:00
|
|
|
}
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
std::map<int, Transform> OptimizerG2O::optimize(
|
|
|
|
|
int rootId,
|
|
|
|
|
const std::map<int, Transform> & poses,
|
|
|
|
|
const std::multimap<int, Link> & edgeConstraints,
|
2018-05-30 15:35:08 -04:00
|
|
|
cv::Mat & outputCovariance,
|
2015-11-27 11:09:39 -05:00
|
|
|
std::list<std::map<int, Transform> > * intermediateGraphes,
|
|
|
|
|
double * finalError,
|
|
|
|
|
int * iterationsDone)
|
|
|
|
|
{
|
2018-06-15 16:31:45 -04:00
|
|
|
outputCovariance = cv::Mat::eye(6,6,CV_64FC1);
|
2015-11-27 11:09:39 -05:00
|
|
|
std::map<int, Transform> optimizedPoses;
|
2016-03-01 14:25:00 -05:00
|
|
|
#ifdef RTABMAP_G2O
|
2025-10-28 20:56:54 -07:00
|
|
|
UDEBUG("Optimizing graph... (rootId=%d)", rootId);
|
2016-03-01 14:25:00 -05:00
|
|
|
|
|
|
|
|
#ifndef RTABMAP_VERTIGO
|
|
|
|
|
if(this->isRobust())
|
|
|
|
|
{
|
|
|
|
|
UWARN("Vertigo robust optimization is not available! Robust optimization is now disabled.");
|
|
|
|
|
setRobust(false);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
optimizedPoses.clear();
|
2018-12-07 18:29:41 -05:00
|
|
|
if(edgeConstraints.size()>=1 && poses.size()>=2 && iterations() > 0 && poses.rbegin()->first > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
|
|
|
|
// Apply g2o optimization
|
|
|
|
|
|
|
|
|
|
g2o::SparseOptimizer optimizer;
|
2020-05-31 11:22:12 -04:00
|
|
|
//optimizer.setVerbose(ULogger::level()==ULogger::kDebug);
|
2018-10-15 13:41:44 -04:00
|
|
|
if (isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
g2o::ParameterSE2Offset* odomOffset = new g2o::ParameterSE2Offset();
|
|
|
|
|
odomOffset->setId(PARAM_OFFSET);
|
|
|
|
|
optimizer.addParameter(odomOffset);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g2o::ParameterSE3Offset* odomOffset = new g2o::ParameterSE3Offset();
|
|
|
|
|
odomOffset->setId(PARAM_OFFSET);
|
|
|
|
|
optimizer.addParameter(odomOffset);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<SlamBlockSolver> blockSolver;
|
|
|
|
|
|
|
|
|
|
if(solver_ == 3)
|
|
|
|
|
{
|
|
|
|
|
//eigen
|
2023-03-22 20:42:33 -07:00
|
|
|
auto linearSolver = std::make_unique<SlamLinearEigenSolver>();
|
2018-02-03 15:43:32 -05:00
|
|
|
linearSolver->setBlockOrdering(false);
|
2023-03-22 20:42:33 -07:00
|
|
|
blockSolver = std::make_unique<SlamBlockSolver>(std::move(linearSolver));
|
2018-02-03 15:43:32 -05:00
|
|
|
}
|
|
|
|
|
#ifdef G2O_HAVE_CHOLMOD
|
|
|
|
|
else if(solver_ == 2)
|
|
|
|
|
{
|
|
|
|
|
//chmold
|
2023-03-22 20:42:33 -07:00
|
|
|
auto linearSolver = std::make_unique<SlamLinearCholmodSolver>();
|
2018-02-03 15:43:32 -05:00
|
|
|
linearSolver->setBlockOrdering(false);
|
2023-03-22 20:42:33 -07:00
|
|
|
blockSolver = std::make_unique<SlamBlockSolver>(std::move(linearSolver));
|
2018-02-03 15:43:32 -05:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef G2O_HAVE_CSPARSE
|
|
|
|
|
else if(solver_ == 0)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
//csparse
|
2023-03-22 20:42:33 -07:00
|
|
|
auto linearSolver = std::make_unique<SlamLinearCSparseSolver>();
|
2018-02-03 15:43:32 -05:00
|
|
|
linearSolver->setBlockOrdering(false);
|
2023-03-22 20:42:33 -07:00
|
|
|
blockSolver = std::make_unique<SlamBlockSolver>(std::move(linearSolver));
|
2018-02-03 15:43:32 -05:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//pcg
|
2023-03-22 20:42:33 -07:00
|
|
|
auto linearSolver = std::make_unique<SlamLinearPCGSolver>();
|
|
|
|
|
blockSolver = std::make_unique<SlamBlockSolver>(std::move(linearSolver));
|
2018-02-03 15:43:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(optimizer_ == 1)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmGaussNewton(std::move(blockSolver)));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmLevenberg(std::move(blockSolver)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
2016-02-15 19:35:24 -05:00
|
|
|
SlamBlockSolver * blockSolver = 0;
|
|
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
if(solver_ == 3)
|
|
|
|
|
{
|
|
|
|
|
//eigen
|
|
|
|
|
SlamLinearEigenSolver * linearSolver = new SlamLinearEigenSolver();
|
|
|
|
|
linearSolver->setBlockOrdering(false);
|
|
|
|
|
blockSolver = new SlamBlockSolver(linearSolver);
|
|
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef G2O_HAVE_CHOLMOD
|
2017-01-03 21:05:00 -05:00
|
|
|
else if(solver_ == 2)
|
2016-01-14 17:07:01 -05:00
|
|
|
{
|
|
|
|
|
//chmold
|
|
|
|
|
SlamLinearCholmodSolver * linearSolver = new SlamLinearCholmodSolver();
|
|
|
|
|
linearSolver->setBlockOrdering(false);
|
|
|
|
|
blockSolver = new SlamBlockSolver(linearSolver);
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
|
|
|
|
#ifdef G2O_HAVE_CSPARSE
|
2016-03-28 18:19:17 -04:00
|
|
|
else if(solver_ == 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
|
|
|
|
//csparse
|
|
|
|
|
SlamLinearCSparseSolver* linearSolver = new SlamLinearCSparseSolver();
|
|
|
|
|
linearSolver->setBlockOrdering(false);
|
|
|
|
|
blockSolver = new SlamBlockSolver(linearSolver);
|
2016-02-15 19:35:24 -05:00
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
|
|
|
|
else
|
2016-02-15 19:35:24 -05:00
|
|
|
{
|
|
|
|
|
//pcg
|
|
|
|
|
SlamLinearPCGSolver * linearSolver = new SlamLinearPCGSolver();
|
|
|
|
|
blockSolver = new SlamBlockSolver(linearSolver);
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
|
2016-03-28 18:19:17 -04:00
|
|
|
if(optimizer_ == 1)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmGaussNewton(blockSolver));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmLevenberg(blockSolver));
|
|
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
2017-05-24 15:03:27 -04:00
|
|
|
// detect if there is a global pose prior set, if so remove rootId
|
2021-12-25 16:57:03 -05:00
|
|
|
bool hasGravityConstraints = false;
|
|
|
|
|
if(!priorsIgnored() || (!isSlam2d() && gravitySigma() > 0))
|
2017-05-24 15:03:27 -04:00
|
|
|
{
|
2017-09-26 14:13:06 -04:00
|
|
|
for(std::multimap<int, Link>::const_iterator iter=edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
|
2017-05-24 15:03:27 -04:00
|
|
|
{
|
2021-12-25 16:57:03 -05:00
|
|
|
if(iter->second.from() == iter->second.to())
|
2017-09-26 14:13:06 -04:00
|
|
|
{
|
2021-12-25 16:57:03 -05:00
|
|
|
if(!priorsIgnored() && iter->second.type() == Link::kPosePrior)
|
|
|
|
|
{
|
2025-10-28 20:56:54 -07:00
|
|
|
if(rootId!=0) {
|
|
|
|
|
UDEBUG("Removed rootId=%d because there are priors.");
|
|
|
|
|
}
|
2021-12-25 16:57:03 -05:00
|
|
|
rootId = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-01-02 23:30:55 -05:00
|
|
|
else if(!isSlam2d() &&
|
|
|
|
|
gravitySigma() > 0 &&
|
|
|
|
|
iter->second.type() == Link::kGravity)
|
2021-12-25 16:57:03 -05:00
|
|
|
{
|
|
|
|
|
hasGravityConstraints = true;
|
|
|
|
|
if(priorsIgnored())
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-26 14:13:06 -04:00
|
|
|
}
|
2017-05-24 15:03:27 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-07 18:29:41 -05:00
|
|
|
int landmarkVertexOffset = poses.rbegin()->first+1;
|
2019-01-06 17:00:39 -05:00
|
|
|
std::map<int, bool> isLandmarkWithRotation;
|
2018-12-07 18:29:41 -05:00
|
|
|
|
2022-01-12 16:14:42 -05:00
|
|
|
UDEBUG("fill poses to g2o... (rootId=%d hasGravityConstraints=%d isSlam2d=%d)", rootId, hasGravityConstraints?1:0, isSlam2d()?1:0);
|
2015-11-27 11:09:39 -05:00
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
UASSERT(!iter->second.isNull());
|
|
|
|
|
g2o::HyperGraph::Vertex * vertex = 0;
|
2018-12-07 18:29:41 -05:00
|
|
|
int id = iter->first;
|
2015-11-27 11:09:39 -05:00
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(id > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
g2o::VertexSE2 * v2 = new g2o::VertexSE2();
|
|
|
|
|
v2->setEstimate(g2o::SE2(iter->second.x(), iter->second.y(), iter->second.theta()));
|
|
|
|
|
if(id == rootId)
|
|
|
|
|
{
|
2021-12-10 21:58:11 -05:00
|
|
|
UDEBUG("Set %d fixed", id);
|
2018-12-07 18:29:41 -05:00
|
|
|
v2->setFixed(true);
|
|
|
|
|
}
|
|
|
|
|
vertex = v2;
|
|
|
|
|
}
|
|
|
|
|
else if(!landmarksIgnored())
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
// check if it is SE2 or only PointXY
|
|
|
|
|
std::multimap<int, Link>::const_iterator jter=edgeConstraints.find(id);
|
2019-04-26 14:03:25 -04:00
|
|
|
UASSERT(jter != edgeConstraints.end());
|
|
|
|
|
|
|
|
|
|
if (1 / static_cast<double>(jter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
2019-01-06 17:00:39 -05:00
|
|
|
{
|
2019-04-26 14:03:25 -04:00
|
|
|
g2o::VertexPointXY * v2 = new g2o::VertexPointXY();
|
|
|
|
|
v2->setEstimate(Eigen::Vector2d(iter->second.x(), iter->second.y()));
|
|
|
|
|
vertex = v2;
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(id, false));
|
|
|
|
|
id = landmarkVertexOffset - id;
|
2019-01-06 17:00:39 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-04-26 14:03:25 -04:00
|
|
|
g2o::VertexSE2 * v2 = new g2o::VertexSE2();
|
|
|
|
|
v2->setEstimate(g2o::SE2(iter->second.x(), iter->second.y(), iter->second.theta()));
|
2021-12-10 21:58:11 -05:00
|
|
|
if(id == rootId)
|
|
|
|
|
{
|
|
|
|
|
UDEBUG("Set %d fixed", id);
|
|
|
|
|
v2->setFixed(true);
|
|
|
|
|
}
|
2019-04-26 14:03:25 -04:00
|
|
|
vertex = v2;
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(id, true));
|
|
|
|
|
id = landmarkVertexOffset - id;
|
2019-01-06 17:00:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
continue;
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(id > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
g2o::VertexSE3 * v3 = new g2o::VertexSE3();
|
|
|
|
|
|
|
|
|
|
Eigen::Affine3d a = iter->second.toEigen3d();
|
|
|
|
|
Eigen::Isometry3d pose;
|
|
|
|
|
pose = a.linear();
|
|
|
|
|
pose.translation() = a.translation();
|
|
|
|
|
v3->setEstimate(pose);
|
2021-12-25 16:57:03 -05:00
|
|
|
if(id == rootId && !hasGravityConstraints)
|
2018-12-07 18:29:41 -05:00
|
|
|
{
|
2021-12-10 21:58:11 -05:00
|
|
|
UDEBUG("Set %d fixed", id);
|
2018-12-07 18:29:41 -05:00
|
|
|
v3->setFixed(true);
|
|
|
|
|
}
|
|
|
|
|
vertex = v3;
|
|
|
|
|
}
|
|
|
|
|
else if(!landmarksIgnored())
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
// check if it is SE3 or only PointXYZ
|
|
|
|
|
std::multimap<int, Link>::const_iterator jter=edgeConstraints.find(id);
|
2019-04-26 14:03:25 -04:00
|
|
|
UASSERT(jter != edgeConstraints.end());
|
|
|
|
|
|
|
|
|
|
if (1 / static_cast<double>(jter->second.infMatrix().at<double>(3,3)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(jter->second.infMatrix().at<double>(4,4)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(jter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
2019-01-06 17:00:39 -05:00
|
|
|
{
|
2019-04-26 14:03:25 -04:00
|
|
|
g2o::VertexPointXYZ * v3 = new g2o::VertexPointXYZ();
|
|
|
|
|
v3->setEstimate(Eigen::Vector3d(iter->second.x(), iter->second.y(), iter->second.z()));
|
|
|
|
|
vertex = v3;
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(id, false));
|
|
|
|
|
id = landmarkVertexOffset - id;
|
2019-01-06 17:00:39 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-04-26 14:03:25 -04:00
|
|
|
g2o::VertexSE3 * v3 = new g2o::VertexSE3();
|
|
|
|
|
Eigen::Affine3d a = iter->second.toEigen3d();
|
|
|
|
|
Eigen::Isometry3d pose;
|
|
|
|
|
pose = a.linear();
|
|
|
|
|
pose.translation() = a.translation();
|
|
|
|
|
v3->setEstimate(pose);
|
2021-12-25 16:57:03 -05:00
|
|
|
if(id == rootId && !hasGravityConstraints)
|
2021-12-10 21:58:11 -05:00
|
|
|
{
|
|
|
|
|
UDEBUG("Set %d fixed", id);
|
|
|
|
|
v3->setFixed(true);
|
|
|
|
|
}
|
2019-04-26 14:03:25 -04:00
|
|
|
vertex = v3;
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(id, true));
|
|
|
|
|
id = landmarkVertexOffset - id;
|
2019-01-06 17:00:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
continue;
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
2021-12-25 16:57:03 -05:00
|
|
|
if(vertex == 0)
|
|
|
|
|
{
|
|
|
|
|
UERROR("Could not create vertex for node %d", id);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
vertex->setId(id);
|
|
|
|
|
UASSERT_MSG(optimizer.addVertex(vertex), uFormat("cannot insert vertex %d!?", iter->first).c_str());
|
2022-01-02 23:30:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup root prior (fixed x,y,z,yaw)
|
|
|
|
|
if(!isSlam2d() && rootId !=0 && hasGravityConstraints)
|
|
|
|
|
{
|
|
|
|
|
g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(optimizer.vertex(rootId));
|
|
|
|
|
if(v1)
|
|
|
|
|
{
|
|
|
|
|
g2o::EdgeSE3Prior * e = new g2o::EdgeSE3Prior();
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
Eigen::Affine3d a = poses.at(rootId).toEigen3d();
|
|
|
|
|
Eigen::Isometry3d pose;
|
|
|
|
|
pose = a.linear();
|
|
|
|
|
pose.translation() = a.translation();
|
|
|
|
|
e->setMeasurement(pose);
|
|
|
|
|
e->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity()*10e6;
|
|
|
|
|
// pitch and roll not fixed
|
|
|
|
|
information(3,3) = information(4,4) = 1;
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
if (!optimizer.addEdge(e))
|
2021-12-25 16:57:03 -05:00
|
|
|
{
|
2022-01-02 23:30:55 -05:00
|
|
|
delete e;
|
|
|
|
|
UERROR("Map: Failed adding fixed constraint of rootid %d, set as fixed instead", rootId);
|
|
|
|
|
v1->setFixed(true);
|
2021-12-25 16:57:03 -05:00
|
|
|
}
|
2022-01-02 23:30:55 -05:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UDEBUG("Set %d fixed with prior (have gravity constraints)", rootId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Map: Failed adding fixed constraint of rootid %d (not found in added vertices)", rootId);
|
2021-12-25 16:57:03 -05:00
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UDEBUG("fill edges to g2o...");
|
2018-02-03 15:43:32 -05:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
2019-04-12 21:31:20 -04:00
|
|
|
int vertigoVertexId = landmarkVertexOffset - (poses.begin()->first<0?poses.begin()->first-1:0);
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
2015-11-27 11:09:39 -05:00
|
|
|
for(std::multimap<int, Link>::const_iterator iter=edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
|
|
|
|
|
{
|
2016-11-14 19:54:31 -05:00
|
|
|
int id1 = iter->second.from();
|
2015-11-27 11:09:39 -05:00
|
|
|
int id2 = iter->second.to();
|
|
|
|
|
|
|
|
|
|
UASSERT(!iter->second.transform().isNull());
|
|
|
|
|
|
|
|
|
|
g2o::HyperGraph::Edge * edge = 0;
|
|
|
|
|
|
2017-05-22 21:05:34 -04:00
|
|
|
if(id1 == id2)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2022-04-28 09:18:30 -04:00
|
|
|
if(iter->second.type() == Link::kPosePrior && !priorsIgnored() &&
|
|
|
|
|
(!landmarksIgnored() || id1>0))
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2022-04-28 09:18:30 -04:00
|
|
|
int idTag= id1;
|
|
|
|
|
if(id1<0)
|
|
|
|
|
{
|
|
|
|
|
// landmark prior, offset ids
|
|
|
|
|
id1 = landmarkVertexOffset - id1;
|
|
|
|
|
id2 = landmarkVertexOffset - id2;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-26 14:13:06 -04:00
|
|
|
if(isSlam2d())
|
2017-05-22 21:05:34 -04:00
|
|
|
{
|
2022-04-28 09:18:30 -04:00
|
|
|
if(idTag < 0 && !isLandmarkWithRotation.at(idTag))
|
|
|
|
|
{
|
|
|
|
|
g2o::EdgeXYPrior * priorEdge = new g2o::EdgeXYPrior();
|
|
|
|
|
g2o::VertexPointXY* v1 = (g2o::VertexPointXY*)optimizer.vertex(id1);
|
|
|
|
|
priorEdge->setVertex(0, v1);
|
|
|
|
|
priorEdge->setMeasurement(Eigen::Vector2d(iter->second.transform().x(), iter->second.transform().y()));
|
|
|
|
|
Eigen::Matrix<double, 2, 2> information = Eigen::Matrix<double, 2, 2>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
}
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
|
|
|
|
else if (1 / static_cast<double>(iter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
2019-01-03 15:49:25 -05:00
|
|
|
{
|
|
|
|
|
g2o::EdgeSE2XYPrior * priorEdge = new g2o::EdgeSE2XYPrior();
|
|
|
|
|
g2o::VertexSE2* v1 = (g2o::VertexSE2*)optimizer.vertex(id1);
|
|
|
|
|
priorEdge->setVertex(0, v1);
|
2019-01-03 16:16:16 -05:00
|
|
|
priorEdge->setMeasurement(Eigen::Vector2d(iter->second.transform().x(), iter->second.transform().y()));
|
2019-01-03 15:49:25 -05:00
|
|
|
Eigen::Matrix<double, 2, 2> information = Eigen::Matrix<double, 2, 2>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
}
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g2o::EdgeSE2Prior * priorEdge = new g2o::EdgeSE2Prior();
|
|
|
|
|
g2o::VertexSE2* v1 = (g2o::VertexSE2*)optimizer.vertex(id1);
|
|
|
|
|
priorEdge->setVertex(0, v1);
|
2025-11-23 14:07:01 -08:00
|
|
|
auto pose = g2o::SE2(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta());
|
|
|
|
|
v1->setEstimate(pose); // This will help g2o to converge faster (https://github.com/introlab/rtabmap_ros/issues/1371)
|
|
|
|
|
priorEdge->setMeasurement(pose);
|
2019-01-03 15:49:25 -05:00
|
|
|
priorEdge->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(0,2) = iter->second.infMatrix().at<double>(0,5); // x-theta
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
information(1,2) = iter->second.infMatrix().at<double>(1,5); // y-theta
|
|
|
|
|
information(2,0) = iter->second.infMatrix().at<double>(5,0); // theta-x
|
|
|
|
|
information(2,1) = iter->second.infMatrix().at<double>(5,1); // theta-y
|
|
|
|
|
information(2,2) = iter->second.infMatrix().at<double>(5,5); // theta-theta
|
|
|
|
|
}
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-05-22 21:05:34 -04:00
|
|
|
{
|
2022-04-28 09:18:30 -04:00
|
|
|
if(idTag < 0 && !isLandmarkWithRotation.at(idTag))
|
|
|
|
|
{
|
|
|
|
|
//XYZ case
|
|
|
|
|
g2o::EdgeXYZPrior * priorEdge = new g2o::EdgeXYZPrior();
|
|
|
|
|
g2o::VertexPointXYZ* v1 = (g2o::VertexPointXYZ*)optimizer.vertex(id1);
|
|
|
|
|
priorEdge->setVertex(0, v1);
|
|
|
|
|
priorEdge->setMeasurement(Eigen::Vector3d(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().z()));
|
|
|
|
|
priorEdge->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(0,2) = iter->second.infMatrix().at<double>(0,2); // x-z
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
information(1,2) = iter->second.infMatrix().at<double>(1,2); // y-z
|
|
|
|
|
information(2,0) = iter->second.infMatrix().at<double>(2,0); // z-x
|
|
|
|
|
information(2,1) = iter->second.infMatrix().at<double>(2,1); // z-y
|
|
|
|
|
information(2,2) = iter->second.infMatrix().at<double>(2,2); // z-z
|
|
|
|
|
}
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
|
|
|
|
else if (1 / static_cast<double>(iter->second.infMatrix().at<double>(3,3)) >= 9999.0 ||
|
2019-01-06 17:00:39 -05:00
|
|
|
1 / static_cast<double>(iter->second.infMatrix().at<double>(4,4)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(iter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
2019-01-03 15:49:25 -05:00
|
|
|
{
|
2019-04-09 20:05:24 -04:00
|
|
|
//GPS XYZ case
|
2022-04-28 09:18:30 -04:00
|
|
|
g2o::EdgeSE3XYZPrior * priorEdge = new g2o::EdgeSE3XYZPrior();
|
2019-01-03 15:49:25 -05:00
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
priorEdge->setVertex(0, v1);
|
2019-01-03 16:16:16 -05:00
|
|
|
priorEdge->setMeasurement(Eigen::Vector3d(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().z()));
|
2019-01-06 17:00:39 -05:00
|
|
|
priorEdge->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
|
2019-01-03 15:49:25 -05:00
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(0,2) = iter->second.infMatrix().at<double>(0,2); // x-z
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
information(1,2) = iter->second.infMatrix().at<double>(1,2); // y-z
|
|
|
|
|
information(2,0) = iter->second.infMatrix().at<double>(2,0); // z-x
|
|
|
|
|
information(2,1) = iter->second.infMatrix().at<double>(2,1); // z-y
|
|
|
|
|
information(2,2) = iter->second.infMatrix().at<double>(2,2); // z-z
|
|
|
|
|
}
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-04-09 20:05:24 -04:00
|
|
|
// XYZ+RPY case
|
2019-01-03 15:49:25 -05:00
|
|
|
g2o::EdgeSE3Prior * priorEdge = new g2o::EdgeSE3Prior();
|
|
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
priorEdge->setVertex(0, v1);
|
|
|
|
|
Eigen::Affine3d a = iter->second.transform().toEigen3d();
|
|
|
|
|
Eigen::Isometry3d pose;
|
|
|
|
|
pose = a.linear();
|
|
|
|
|
pose.translation() = a.translation();
|
2025-11-23 14:07:01 -08:00
|
|
|
v1->setEstimate(pose); // This will help g2o to converge faster (https://github.com/introlab/rtabmap_ros/issues/1371)
|
2019-01-03 15:49:25 -05:00
|
|
|
priorEdge->setMeasurement(pose);
|
|
|
|
|
priorEdge->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
memcpy(information.data(), iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
|
|
|
|
|
}
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
2017-05-22 21:05:34 -04:00
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2019-05-31 15:36:35 -04:00
|
|
|
else if(!isSlam2d() && gravitySigma() > 0 && iter->second.type() == Link::kGravity && poses.find(iter->first) != poses.end())
|
2019-04-09 20:05:24 -04:00
|
|
|
{
|
|
|
|
|
Eigen::Matrix<double, 6, 1> m;
|
|
|
|
|
// Up vector in robot frame
|
|
|
|
|
m.head<3>() = Eigen::Vector3d::UnitZ();
|
|
|
|
|
// Observed Gravity vector in world frame
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.transform().getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
m.tail<3>() = Transform(0,0,0,roll,pitch,0).toEigen3d() * -Eigen::Vector3d::UnitZ();
|
|
|
|
|
|
|
|
|
|
Eigen::MatrixXd information = Eigen::MatrixXd::Identity(3, 3) * 1.0/(gravitySigma()*gravitySigma());
|
|
|
|
|
|
|
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
EdgeSE3Gravity* priorEdge(new EdgeSE3Gravity());
|
|
|
|
|
priorEdge->setMeasurement(m);
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
priorEdge->vertices()[0] = v1;
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
else if(id1<0 || id2 < 0)
|
|
|
|
|
{
|
|
|
|
|
if(!landmarksIgnored())
|
|
|
|
|
{
|
|
|
|
|
//landmarks
|
|
|
|
|
UASSERT((id1 < 0 && id2 > 0) || (id1 > 0 && id2 < 0));
|
2019-01-06 17:00:39 -05:00
|
|
|
|
|
|
|
|
Transform t;
|
|
|
|
|
if(id2 < 0)
|
2018-12-07 18:29:41 -05:00
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
t = iter->second.transform();
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
t = iter->second.transform().inverse();
|
|
|
|
|
std::swap(id1, id2); // should be node -> landmark
|
|
|
|
|
}
|
|
|
|
|
int idTag= id2;
|
|
|
|
|
id2 = landmarkVertexOffset - id2;
|
2018-12-07 18:29:41 -05:00
|
|
|
|
2024-04-06 13:41:03 -07:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
|
|
|
|
VertexSwitchLinear * v = 0;
|
|
|
|
|
if(this->isRobust() && isLandmarkWithRotation.at(idTag))
|
|
|
|
|
{
|
|
|
|
|
// For landmark links, add switchable edges
|
|
|
|
|
// Currently supporting only constraints with rotation
|
|
|
|
|
|
|
|
|
|
// create new switch variable
|
|
|
|
|
// Sunderhauf IROS 2012:
|
|
|
|
|
// "Since it is reasonable to initially accept all loop closure constraints,
|
|
|
|
|
// a proper and convenient initial value for all switch variables would be
|
|
|
|
|
// sij = 1 when using the linear switch function"
|
|
|
|
|
v = new VertexSwitchLinear();
|
|
|
|
|
v->setEstimate(1.0);
|
|
|
|
|
v->setId(vertigoVertexId++);
|
|
|
|
|
UASSERT_MSG(optimizer.addVertex(v), uFormat("cannot insert switchable vertex %d!?", v->id()).c_str());
|
|
|
|
|
|
|
|
|
|
// create switch prior factor
|
|
|
|
|
// "If the front-end is not able to assign sound individual values
|
|
|
|
|
// for Ξij , it is save to set all Ξij = 1, since this value is close
|
|
|
|
|
// to the individual optimal choice of Ξij for a large range of
|
|
|
|
|
// outliers."
|
|
|
|
|
EdgeSwitchPrior * prior = new EdgeSwitchPrior();
|
|
|
|
|
prior->setMeasurement(1.0);
|
|
|
|
|
prior->setVertex(0, v);
|
|
|
|
|
UASSERT_MSG(optimizer.addEdge(prior), uFormat("cannot insert switchable prior edge %d!?", v->id()).c_str());
|
|
|
|
|
}
|
|
|
|
|
else if(this->isRobust() && !isLandmarkWithRotation.at(idTag))
|
|
|
|
|
{
|
|
|
|
|
UWARN("%s cannot be used for landmark constraints without orientation.", Parameters::kOptimizerRobust().c_str());
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-01-06 17:00:39 -05:00
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
if(isLandmarkWithRotation.at(idTag))
|
2018-12-07 18:29:41 -05:00
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(0,2) = iter->second.infMatrix().at<double>(0,5); // x-theta
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
information(1,2) = iter->second.infMatrix().at<double>(1,5); // y-theta
|
|
|
|
|
information(2,0) = iter->second.infMatrix().at<double>(5,0); // theta-x
|
|
|
|
|
information(2,1) = iter->second.infMatrix().at<double>(5,1); // theta-y
|
|
|
|
|
information(2,2) = iter->second.infMatrix().at<double>(5,5); // theta-theta
|
|
|
|
|
}
|
2024-04-06 13:41:03 -07:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
|
|
|
|
if(this->isRobust())
|
|
|
|
|
{
|
|
|
|
|
EdgeSE2Switchable * e = new EdgeSE2Switchable();
|
|
|
|
|
g2o::VertexSE2* v1 = (g2o::VertexSE2*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE2* v2 = (g2o::VertexSE2*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setVertex(2, v);
|
|
|
|
|
e->setMeasurement(g2o::SE2(t.x(), t.y(), t.theta()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
g2o::EdgeSE2 * e = new g2o::EdgeSE2();
|
|
|
|
|
g2o::VertexSE2* v1 = (g2o::VertexSE2*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE2* v2 = (g2o::VertexSE2*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setMeasurement(g2o::SE2(t.x(), t.y(), t.theta()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
Eigen::Matrix<double, 2, 2> information = Eigen::Matrix<double, 2, 2>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
cv::Mat linearCov = cv::Mat(iter->second.infMatrix(), cv::Range(0,2), cv::Range(0,2)).clone();
|
|
|
|
|
memcpy(information.data(), linearCov.data, linearCov.total()*sizeof(double));
|
|
|
|
|
}
|
|
|
|
|
g2o::EdgeSE2PointXY* e = new g2o::EdgeSE2PointXY;
|
|
|
|
|
e->vertices()[0] = optimizer.vertex(id1);
|
|
|
|
|
e->vertices()[1] = optimizer.vertex(id2);
|
|
|
|
|
e->setMeasurement(Eigen::Vector2d(t.x(), t.y()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
e->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
edge = e;
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
2019-01-06 17:00:39 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if(isLandmarkWithRotation.at(idTag))
|
|
|
|
|
{
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
memcpy(information.data(), iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
|
2019-01-06 17:00:39 -05:00
|
|
|
Eigen::Affine3d a = t.toEigen3d();
|
|
|
|
|
Eigen::Isometry3d constraint;
|
|
|
|
|
constraint = a.linear();
|
|
|
|
|
constraint.translation() = a.translation();
|
|
|
|
|
|
2024-04-06 13:41:03 -07:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
|
|
|
|
if(this->isRobust())
|
|
|
|
|
{
|
|
|
|
|
EdgeSE3Switchable * e = new EdgeSE3Switchable();
|
|
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE3* v2 = (g2o::VertexSE3*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setVertex(2, v);
|
|
|
|
|
e->setMeasurement(constraint);
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
g2o::EdgeSE3 * e = new g2o::EdgeSE3();
|
|
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE3* v2 = (g2o::VertexSE3*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setMeasurement(constraint);
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
2019-01-06 17:00:39 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
cv::Mat linearCov = cv::Mat(iter->second.infMatrix(), cv::Range(0,3), cv::Range(0,3)).clone();
|
|
|
|
|
memcpy(information.data(), linearCov.data, linearCov.total()*sizeof(double));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g2o::EdgeSE3PointXYZ* e = new g2o::EdgeSE3PointXYZ;
|
|
|
|
|
e->vertices()[0] = optimizer.vertex(id1);
|
|
|
|
|
e->vertices()[1] = optimizer.vertex(id2);
|
|
|
|
|
e->setMeasurement(Eigen::Vector3d(t.x(), t.y(), t.z()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
e->setParameterId(0, PARAM_OFFSET);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
else
|
|
|
|
|
{
|
2018-02-03 15:43:32 -05:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
2017-05-22 21:05:34 -04:00
|
|
|
VertexSwitchLinear * v = 0;
|
2015-11-27 11:09:39 -05:00
|
|
|
if(this->isRobust() &&
|
|
|
|
|
iter->second.type() != Link::kNeighbor &&
|
|
|
|
|
iter->second.type() != Link::kNeighborMerged)
|
|
|
|
|
{
|
2017-05-22 21:05:34 -04:00
|
|
|
// For loop closure links, add switchable edges
|
|
|
|
|
|
|
|
|
|
// create new switch variable
|
|
|
|
|
// Sunderhauf IROS 2012:
|
|
|
|
|
// "Since it is reasonable to initially accept all loop closure constraints,
|
|
|
|
|
// a proper and convenient initial value for all switch variables would be
|
|
|
|
|
// sij = 1 when using the linear switch function"
|
|
|
|
|
v = new VertexSwitchLinear();
|
|
|
|
|
v->setEstimate(1.0);
|
|
|
|
|
v->setId(vertigoVertexId++);
|
|
|
|
|
UASSERT_MSG(optimizer.addVertex(v), uFormat("cannot insert switchable vertex %d!?", v->id()).c_str());
|
|
|
|
|
|
|
|
|
|
// create switch prior factor
|
|
|
|
|
// "If the front-end is not able to assign sound individual values
|
|
|
|
|
// for Ξij , it is save to set all Ξij = 1, since this value is close
|
|
|
|
|
// to the individual optimal choice of Ξij for a large range of
|
|
|
|
|
// outliers."
|
|
|
|
|
EdgeSwitchPrior * prior = new EdgeSwitchPrior();
|
|
|
|
|
prior->setMeasurement(1.0);
|
|
|
|
|
prior->setVertex(0, v);
|
|
|
|
|
UASSERT_MSG(optimizer.addEdge(prior), uFormat("cannot insert switchable prior edge %d!?", v->id()).c_str());
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2016-03-01 14:25:00 -05:00
|
|
|
#endif
|
2017-05-22 21:05:34 -04:00
|
|
|
|
|
|
|
|
if(isSlam2d())
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2017-05-22 21:05:34 -04:00
|
|
|
Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
information(0,0) = iter->second.infMatrix().at<double>(0,0); // x-x
|
|
|
|
|
information(0,1) = iter->second.infMatrix().at<double>(0,1); // x-y
|
|
|
|
|
information(0,2) = iter->second.infMatrix().at<double>(0,5); // x-theta
|
|
|
|
|
information(1,0) = iter->second.infMatrix().at<double>(1,0); // y-x
|
|
|
|
|
information(1,1) = iter->second.infMatrix().at<double>(1,1); // y-y
|
|
|
|
|
information(1,2) = iter->second.infMatrix().at<double>(1,5); // y-theta
|
|
|
|
|
information(2,0) = iter->second.infMatrix().at<double>(5,0); // theta-x
|
|
|
|
|
information(2,1) = iter->second.infMatrix().at<double>(5,1); // theta-y
|
|
|
|
|
information(2,2) = iter->second.infMatrix().at<double>(5,5); // theta-theta
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-03 15:43:32 -05:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
2017-05-22 21:05:34 -04:00
|
|
|
if(this->isRobust() &&
|
|
|
|
|
iter->second.type() != Link::kNeighbor &&
|
|
|
|
|
iter->second.type() != Link::kNeighborMerged)
|
|
|
|
|
{
|
|
|
|
|
EdgeSE2Switchable * e = new EdgeSE2Switchable();
|
|
|
|
|
g2o::VertexSE2* v1 = (g2o::VertexSE2*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE2* v2 = (g2o::VertexSE2*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setVertex(2, v);
|
|
|
|
|
e->setMeasurement(g2o::SE2(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
g2o::EdgeSE2 * e = new g2o::EdgeSE2();
|
|
|
|
|
g2o::VertexSE2* v1 = (g2o::VertexSE2*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE2* v2 = (g2o::VertexSE2*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setMeasurement(g2o::SE2(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
2017-05-17 16:59:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
2017-05-22 21:05:34 -04:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity();
|
|
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
memcpy(information.data(), iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Eigen::Affine3d a = iter->second.transform().toEigen3d();
|
|
|
|
|
Eigen::Isometry3d constraint;
|
2017-11-09 12:03:01 -05:00
|
|
|
constraint = a.linear();
|
2017-05-22 21:05:34 -04:00
|
|
|
constraint.translation() = a.translation();
|
|
|
|
|
|
2018-02-03 15:43:32 -05:00
|
|
|
#if defined(RTABMAP_VERTIGO)
|
2017-05-22 21:05:34 -04:00
|
|
|
if(this->isRobust() &&
|
|
|
|
|
iter->second.type() != Link::kNeighbor &&
|
|
|
|
|
iter->second.type() != Link::kNeighborMerged)
|
|
|
|
|
{
|
|
|
|
|
EdgeSE3Switchable * e = new EdgeSE3Switchable();
|
|
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE3* v2 = (g2o::VertexSE3*)optimizer.vertex(id2);
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setVertex(2, v);
|
|
|
|
|
e->setMeasurement(constraint);
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-05-17 16:59:35 -04:00
|
|
|
#endif
|
2017-05-22 21:05:34 -04:00
|
|
|
{
|
|
|
|
|
g2o::EdgeSE3 * e = new g2o::EdgeSE3();
|
|
|
|
|
g2o::VertexSE3* v1 = (g2o::VertexSE3*)optimizer.vertex(id1);
|
|
|
|
|
g2o::VertexSE3* v2 = (g2o::VertexSE3*)optimizer.vertex(id2);
|
2025-11-08 10:58:34 -08:00
|
|
|
UASSERT_MSG(v1 != 0, uFormat("v1=%d v2=%d", id1, id2).c_str());
|
|
|
|
|
UASSERT_MSG(v2 != 0, uFormat("v1=%d v2=%d", id1, id2).c_str());
|
2017-05-22 21:05:34 -04:00
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
e->setMeasurement(constraint);
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
edge = e;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
|
2017-09-26 14:13:06 -04:00
|
|
|
if (edge && !optimizer.addEdge(edge))
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
|
|
|
|
delete edge;
|
|
|
|
|
UERROR("Map: Failed adding constraint between %d and %d, skipping", id1, id2);
|
|
|
|
|
}
|
2017-05-17 16:59:35 -04:00
|
|
|
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UDEBUG("Initial optimization...");
|
|
|
|
|
optimizer.initializeOptimization();
|
|
|
|
|
|
2022-11-22 12:26:02 -08:00
|
|
|
if(!optimizer.verifyInformationMatrices(true))
|
|
|
|
|
{
|
|
|
|
|
UERROR("This error can be caused by (1) bad covariance matrix "
|
2018-07-18 16:15:41 -04:00
|
|
|
"set in odometry messages "
|
|
|
|
|
"(see requirements in g2o::OptimizableGraph::verifyInformationMatrices() function) "
|
|
|
|
|
"or that (2) PCL and g2o hadn't "
|
|
|
|
|
"been built both with or without \"-march=native\" compilation "
|
|
|
|
|
"flag (if one library is built with this flag and not the other, "
|
|
|
|
|
"this is causing Eigen to not work properly, resulting in segmentation faults).");
|
2022-11-22 12:26:02 -08:00
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
2015-12-02 11:55:46 -05:00
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
UINFO("g2o optimizing begin (max iterations=%d, robust=%d)", iterations(), isRobust()?1:0);
|
|
|
|
|
int it = 0;
|
|
|
|
|
UTimer timer;
|
|
|
|
|
double lastError = 0.0;
|
2025-01-27 14:20:41 -08:00
|
|
|
|
|
|
|
|
if (!optimizer.solver()->init()) {
|
|
|
|
|
UERROR("g2o: Error while initializing solver");
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
if(intermediateGraphes || this->epsilon() > 0.0)
|
|
|
|
|
{
|
|
|
|
|
for(int i=0; i<iterations(); ++i)
|
|
|
|
|
{
|
|
|
|
|
if(intermediateGraphes)
|
|
|
|
|
{
|
|
|
|
|
if(i > 0)
|
|
|
|
|
{
|
|
|
|
|
std::map<int, Transform> tmpPoses;
|
|
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
int id = iter->first;
|
|
|
|
|
if(id > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
const g2o::VertexSE2* v = (const g2o::VertexSE2*)optimizer.vertex(id);
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(v->estimate().translation()[0], v->estimate().translation()[1], iter->second.z(), roll, pitch, v->estimate().rotation().angle());
|
|
|
|
|
tmpPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
else if(!landmarksIgnored())
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
const g2o::OptimizableGraph::Vertex* v = (const g2o::OptimizableGraph::Vertex*)optimizer.vertex(landmarkVertexOffset - id);
|
2018-12-07 18:29:41 -05:00
|
|
|
if(v)
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
if(isLandmarkWithRotation.at(id))
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexSE2* vSE2 = (const g2o::VertexSE2*)v;
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(vSE2->estimate().translation()[0], vSE2->estimate().translation()[1], iter->second.z(), roll, pitch, vSE2->estimate().rotation().angle());
|
|
|
|
|
tmpPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexPointXY* vP = (const g2o::VertexPointXY*)v;
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(vP->estimate()[0], vP->estimate()[1], iter->second.z(), roll, pitch, yaw);
|
|
|
|
|
tmpPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
int id = iter->first;
|
|
|
|
|
if(id > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
const g2o::VertexSE3* v = (const g2o::VertexSE3*)optimizer.vertex(id);
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
Transform t = Transform::fromEigen3d(v->estimate());
|
|
|
|
|
tmpPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
else if(!landmarksIgnored())
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
const g2o::OptimizableGraph::Vertex* v = (const g2o::OptimizableGraph::Vertex*)optimizer.vertex(landmarkVertexOffset - id);
|
2018-12-07 18:29:41 -05:00
|
|
|
if(v)
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
if(isLandmarkWithRotation.at(id))
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexSE3* vSE3 = (const g2o::VertexSE3*)v;
|
|
|
|
|
Transform t = Transform::fromEigen3d(vSE3->estimate());
|
|
|
|
|
tmpPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexPointXYZ* vP = (const g2o::VertexPointXYZ*)v;
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(vP->estimate()[0], vP->estimate()[1], vP->estimate()[2], roll, pitch, yaw);
|
|
|
|
|
tmpPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
intermediateGraphes->push_back(tmpPoses);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-27 14:20:41 -08:00
|
|
|
g2o::OptimizationAlgorithm::SolverResult result = optimizer.solver()->solve(i);
|
|
|
|
|
++it;
|
2015-11-27 11:09:39 -05:00
|
|
|
|
|
|
|
|
// early stop condition
|
|
|
|
|
optimizer.computeActiveErrors();
|
|
|
|
|
double chi2 = optimizer.activeRobustChi2();
|
|
|
|
|
UDEBUG("iteration %d: %d nodes, %d edges, chi2: %f", i, (int)optimizer.vertices().size(), (int)optimizer.edges().size(), chi2);
|
2015-12-02 11:55:46 -05:00
|
|
|
|
|
|
|
|
if(i>0 && optimizer.activeRobustChi2() > 1000000000000.0)
|
|
|
|
|
{
|
2025-11-23 12:03:17 -08:00
|
|
|
UERROR("g2o: Large optimization error detected (%f), aborting optimization!");
|
2015-12-02 11:55:46 -05:00
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-27 14:20:41 -08:00
|
|
|
if(result == g2o::OptimizationAlgorithm::Fail)
|
|
|
|
|
{
|
|
|
|
|
UERROR("g2o: Solver failed, aborting optimization!");
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
double errorDelta = lastError - chi2;
|
|
|
|
|
if(i>0 && errorDelta < this->epsilon())
|
|
|
|
|
{
|
|
|
|
|
if(errorDelta < 0)
|
|
|
|
|
{
|
|
|
|
|
UDEBUG("Negative improvement?! Ignore and continue optimizing... (%f < %f)", errorDelta, this->epsilon());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UINFO("Stop optimizing, not enough improvement (%f < %f)", errorDelta, this->epsilon());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(i==0 && chi2 < this->epsilon())
|
|
|
|
|
{
|
|
|
|
|
UINFO("Stop optimizing, error is already under epsilon (%f < %f)", chi2, this->epsilon());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
lastError = chi2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
it = optimizer.optimize(iterations());
|
|
|
|
|
optimizer.computeActiveErrors();
|
|
|
|
|
UDEBUG("%d nodes, %d edges, chi2: %f", (int)optimizer.vertices().size(), (int)optimizer.edges().size(), optimizer.activeRobustChi2());
|
|
|
|
|
}
|
|
|
|
|
if(finalError)
|
|
|
|
|
{
|
|
|
|
|
*finalError = lastError;
|
|
|
|
|
}
|
|
|
|
|
if(iterationsDone)
|
|
|
|
|
{
|
|
|
|
|
*iterationsDone = it;
|
|
|
|
|
}
|
|
|
|
|
UINFO("g2o optimizing end (%d iterations done, error=%f, time = %f s)", it, optimizer.activeRobustChi2(), timer.ticks());
|
|
|
|
|
|
2015-12-02 11:55:46 -05:00
|
|
|
if(optimizer.activeRobustChi2() > 1000000000000.0)
|
|
|
|
|
{
|
2025-11-23 12:03:17 -08:00
|
|
|
UERROR("g2o: Large optimization error detected (%f), aborting optimization!");
|
2015-12-02 11:55:46 -05:00
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
int id = iter->first;
|
|
|
|
|
if(id > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
const g2o::VertexSE2* v = (const g2o::VertexSE2*)optimizer.vertex(id);
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(v->estimate().translation()[0], v->estimate().translation()[1], iter->second.z(), roll, pitch, v->estimate().rotation().angle());
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
else if(!landmarksIgnored())
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
const g2o::OptimizableGraph::Vertex* v = (const g2o::OptimizableGraph::Vertex*)optimizer.vertex(landmarkVertexOffset - id);
|
|
|
|
|
|
2018-12-07 18:29:41 -05:00
|
|
|
if(v)
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
if(isLandmarkWithRotation.at(id))
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexSE2* vSE2 = (const g2o::VertexSE2*)v;
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(vSE2->estimate().translation()[0], vSE2->estimate().translation()[1], iter->second.z(), roll, pitch, vSE2->estimate().rotation().angle());
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexPointXY* vP = (const g2o::VertexPointXY*)v;
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(vP->estimate()[0], vP->estimate()[1], iter->second.z(), roll, pitch, yaw);
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
2018-05-30 15:35:08 -04:00
|
|
|
|
|
|
|
|
g2o::VertexSE2* v = (g2o::VertexSE2*)optimizer.vertex(poses.rbegin()->first);
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
UTimer t;
|
|
|
|
|
g2o::SparseBlockMatrix<g2o::MatrixXD> spinv;
|
|
|
|
|
optimizer.computeMarginals(spinv, v);
|
|
|
|
|
UINFO("Computed marginals = %fs (cols=%d rows=%d, v=%d id=%d)", t.ticks(), spinv.cols(), spinv.rows(), v->hessianIndex(), poses.rbegin()->first);
|
2018-06-15 16:31:45 -04:00
|
|
|
if(v->hessianIndex() >= 0 && v->hessianIndex() < (int)spinv.blockCols().size())
|
|
|
|
|
{
|
|
|
|
|
g2o::SparseBlockMatrix<g2o::MatrixXD>::SparseMatrixBlock * block = spinv.blockCols()[v->hessianIndex()].begin()->second;
|
|
|
|
|
UASSERT(block && block->cols() == 3 && block->cols() == 3);
|
|
|
|
|
outputCovariance.at<double>(0,0) = (*block)(0,0); // x-x
|
|
|
|
|
outputCovariance.at<double>(0,1) = (*block)(0,1); // x-y
|
|
|
|
|
outputCovariance.at<double>(0,5) = (*block)(0,2); // x-theta
|
|
|
|
|
outputCovariance.at<double>(1,0) = (*block)(1,0); // y-x
|
|
|
|
|
outputCovariance.at<double>(1,1) = (*block)(1,1); // y-y
|
|
|
|
|
outputCovariance.at<double>(1,5) = (*block)(1,2); // y-theta
|
|
|
|
|
outputCovariance.at<double>(5,0) = (*block)(2,0); // theta-x
|
|
|
|
|
outputCovariance.at<double>(5,1) = (*block)(2,1); // theta-y
|
|
|
|
|
outputCovariance.at<double>(5,5) = (*block)(2,2); // theta-theta
|
|
|
|
|
}
|
|
|
|
|
else if(v->hessianIndex() < 0)
|
|
|
|
|
{
|
|
|
|
|
UWARN("Computing marginals: vertex %d has negative hessian index (%d). Cannot compute last pose covariance.", poses.rbegin()->first, v->hessianIndex());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("Computing marginals: vertex %d has hessian not valid (%d > block size=%d). Cannot compute last pose covariance.", poses.rbegin()->first, v->hessianIndex(), (int)spinv.blockCols().size());
|
|
|
|
|
}
|
2018-05-30 15:35:08 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!? Cannot compute marginals...", poses.rbegin()->first);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
int id = iter->first;
|
|
|
|
|
if(id > 0)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
const g2o::VertexSE3* v = (const g2o::VertexSE3*)optimizer.vertex(id);
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
Transform t = Transform::fromEigen3d(v->estimate());
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
else if(!landmarksIgnored())
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
const g2o::OptimizableGraph::Vertex* v = (const g2o::OptimizableGraph::Vertex*)optimizer.vertex(landmarkVertexOffset - id);
|
|
|
|
|
|
2018-12-07 18:29:41 -05:00
|
|
|
if(v)
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
if(isLandmarkWithRotation.at(id))
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexSE3* vSE3 = (const g2o::VertexSE3*)v;
|
|
|
|
|
Transform t = Transform::fromEigen3d(vSE3->estimate());
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const g2o::VertexPointXYZ* vP = (const g2o::VertexPointXYZ*)v;
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
Transform t(vP->estimate()[0], vP->estimate()[1], vP->estimate()[2], roll, pitch, yaw);
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(id, t));
|
|
|
|
|
UASSERT_MSG(!t.isNull(), uFormat("Optimized pose %d is null!?!?", id).c_str());
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!?", id);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
2018-05-30 15:35:08 -04:00
|
|
|
|
|
|
|
|
g2o::VertexSE3* v = (g2o::VertexSE3*)optimizer.vertex(poses.rbegin()->first);
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
UTimer t;
|
|
|
|
|
g2o::SparseBlockMatrix<g2o::MatrixXD> spinv;
|
|
|
|
|
optimizer.computeMarginals(spinv, v);
|
|
|
|
|
UINFO("Computed marginals = %fs (cols=%d rows=%d, v=%d id=%d)", t.ticks(), spinv.cols(), spinv.rows(), v->hessianIndex(), poses.rbegin()->first);
|
2018-06-15 16:31:45 -04:00
|
|
|
if(v->hessianIndex() >= 0 && v->hessianIndex() < (int)spinv.blockCols().size())
|
|
|
|
|
{
|
|
|
|
|
g2o::SparseBlockMatrix<g2o::MatrixXD>::SparseMatrixBlock * block = spinv.blockCols()[v->hessianIndex()].begin()->second;
|
|
|
|
|
UASSERT(block && block->cols() == 6 && block->cols() == 6);
|
|
|
|
|
memcpy(outputCovariance.data, block->data(), outputCovariance.total()*sizeof(double));
|
|
|
|
|
}
|
|
|
|
|
else if(v->hessianIndex() < 0)
|
|
|
|
|
{
|
|
|
|
|
UWARN("Computing marginals: vertex %d has negative hessian index (%d). Cannot compute last pose covariance.", poses.rbegin()->first, v->hessianIndex());
|
|
|
|
|
}
|
2018-07-18 16:15:41 -04:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
2018-06-15 16:31:45 -04:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("Computing marginals: vertex %d has hessian not valid (%d > block size=%d). Cannot compute last pose covariance.", poses.rbegin()->first, v->hessianIndex(), (int)spinv.blockCols().size());
|
|
|
|
|
}
|
2018-07-18 16:15:41 -04:00
|
|
|
#endif
|
2018-05-30 15:35:08 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Vertex %d not found!? Cannot compute marginals...", poses.rbegin()->first);
|
|
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(poses.size() == 1 || iterations() <= 0)
|
|
|
|
|
{
|
|
|
|
|
optimizedPoses = poses;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("This method should be called at least with 1 pose!");
|
|
|
|
|
}
|
|
|
|
|
UDEBUG("Optimizing graph...end!");
|
2017-09-17 13:28:25 -04:00
|
|
|
#else
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
|
|
|
|
UERROR("G2O graph optimization cannot be used with g2o built from ORB_SLAM, only SBA is available.");
|
2015-11-27 11:09:39 -05:00
|
|
|
#else
|
|
|
|
|
UERROR("Not built with G2O support!");
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2015-11-27 11:09:39 -05:00
|
|
|
#endif
|
|
|
|
|
return optimizedPoses;
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::map<int, Transform> OptimizerG2O::optimizeBA(
|
|
|
|
|
int rootId,
|
|
|
|
|
const std::map<int, Transform> & poses,
|
|
|
|
|
const std::multimap<int, Link> & links,
|
2022-07-20 15:20:14 -04:00
|
|
|
const std::map<int, std::vector<CameraModel> > & models,
|
2016-11-14 19:54:31 -05:00
|
|
|
std::map<int, cv::Point3f> & points3DMap,
|
2018-10-24 20:01:57 -04:00
|
|
|
const std::map<int, std::map<int, FeatureBA> > & wordReferences,
|
2017-01-03 21:05:00 -05:00
|
|
|
std::set<int> * outliers)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
|
|
|
|
std::map<int, Transform> optimizedPoses;
|
2021-03-09 16:00:41 -05:00
|
|
|
#if defined(RTABMAP_G2O) || defined(RTABMAP_ORB_SLAM)
|
2016-03-28 18:19:17 -04:00
|
|
|
UDEBUG("Optimizing graph...");
|
|
|
|
|
|
|
|
|
|
optimizedPoses.clear();
|
2018-12-07 18:29:41 -05:00
|
|
|
if(poses.size()>=2 && iterations() > 0 && (models.size() == poses.size() || poses.begin()->first < 0))
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
|
|
|
|
g2o::SparseOptimizer optimizer;
|
2020-05-31 11:22:12 -04:00
|
|
|
//optimizer.setVerbose(ULogger::level()==ULogger::kDebug);
|
2023-03-13 22:10:24 -07:00
|
|
|
#if defined(RTABMAP_G2O_CPP11) && !defined(RTABMAP_ORB_SLAM)
|
2018-02-03 15:43:32 -05:00
|
|
|
std::unique_ptr<g2o::BlockSolver_6_3::LinearSolverType> linearSolver;
|
|
|
|
|
#else
|
2016-03-28 18:19:17 -04:00
|
|
|
g2o::BlockSolver_6_3::LinearSolverType * linearSolver = 0;
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-09-17 13:28:25 -04:00
|
|
|
linearSolver = new g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType>();
|
|
|
|
|
#else
|
2017-01-03 21:05:00 -05:00
|
|
|
if(solver_ == 3)
|
|
|
|
|
{
|
|
|
|
|
//eigen
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
2023-03-22 20:42:33 -07:00
|
|
|
linearSolver = std::make_unique<g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType> >();
|
2018-02-03 15:43:32 -05:00
|
|
|
#else
|
2017-01-03 21:05:00 -05:00
|
|
|
linearSolver = new g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType>();
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
2017-01-03 21:05:00 -05:00
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef G2O_HAVE_CHOLMOD
|
2017-01-03 21:05:00 -05:00
|
|
|
else if(solver_ == 2)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
|
|
|
|
//chmold
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
2023-03-22 20:42:33 -07:00
|
|
|
linearSolver = std::make_unique<g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> >();
|
2018-02-03 15:43:32 -05:00
|
|
|
#else
|
2016-03-28 18:19:17 -04:00
|
|
|
linearSolver = new g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType>();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
|
|
|
|
#ifdef G2O_HAVE_CSPARSE
|
2016-03-28 18:19:17 -04:00
|
|
|
else if(solver_ == 0)
|
|
|
|
|
{
|
|
|
|
|
//csparse
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
2023-03-22 20:42:33 -07:00
|
|
|
linearSolver = std::make_unique<g2o::LinearSolverCSparse<g2o::BlockSolver_6_3::PoseMatrixType> >();
|
2018-02-03 15:43:32 -05:00
|
|
|
#else
|
2016-03-28 18:19:17 -04:00
|
|
|
linearSolver = new g2o::LinearSolverCSparse<g2o::BlockSolver_6_3::PoseMatrixType>();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
#endif
|
|
|
|
|
else
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
|
|
|
|
//pcg
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
2023-03-22 20:42:33 -07:00
|
|
|
linearSolver = std::make_unique<g2o::LinearSolverPCG<g2o::BlockSolver_6_3::PoseMatrixType> >();
|
2018-02-03 15:43:32 -05:00
|
|
|
#else
|
2016-03-28 18:19:17 -04:00
|
|
|
linearSolver = new g2o::LinearSolverPCG<g2o::BlockSolver_6_3::PoseMatrixType>();
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2018-02-03 15:43:32 -05:00
|
|
|
}
|
2021-03-09 16:00:41 -05:00
|
|
|
#endif // RTABMAP_ORB_SLAM
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifndef RTABMAP_ORB_SLAM
|
2026-04-04 19:48:03 -07:00
|
|
|
// ISSUE: It seems the fatal error
|
|
|
|
|
// "[SetJac] infinite jac" happens relatively
|
|
|
|
|
// easily with GaussNewton on SBA problem,
|
|
|
|
|
// ignore optimizer_ and always use Levenberg for SBA.
|
|
|
|
|
// TODO: Note that g2o/RobustKernelDelta parameter could be
|
|
|
|
|
// potentially tuned to avoid that error with GaussNewton.
|
|
|
|
|
if(0)//optimizer_ == 1)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2018-02-03 15:43:32 -05:00
|
|
|
#ifdef RTABMAP_G2O_CPP11
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmGaussNewton(
|
2023-03-22 20:42:33 -07:00
|
|
|
std::make_unique<g2o::BlockSolver_6_3>(std::move(linearSolver))));
|
2018-02-03 15:43:32 -05:00
|
|
|
#else
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmGaussNewton(new g2o::BlockSolver_6_3(linearSolver)));
|
|
|
|
|
#endif
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
else
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2023-03-13 22:10:24 -07:00
|
|
|
#if defined(RTABMAP_G2O_CPP11) && !defined(RTABMAP_ORB_SLAM)
|
2018-02-03 15:43:32 -05:00
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmLevenberg(
|
2023-03-22 20:42:33 -07:00
|
|
|
std::make_unique<g2o::BlockSolver_6_3>(std::move(linearSolver))));
|
2018-02-03 15:43:32 -05:00
|
|
|
#else
|
|
|
|
|
optimizer.setAlgorithm(new g2o::OptimizationAlgorithmLevenberg(new g2o::BlockSolver_6_3(linearSolver)));
|
|
|
|
|
#endif
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-29 11:30:33 -07:00
|
|
|
// detect if there are gravity constraints
|
|
|
|
|
bool hasGravityConstraints = false;
|
|
|
|
|
if(!isSlam2d() && gravitySigma() > 0)
|
|
|
|
|
{
|
|
|
|
|
for(std::multimap<int, Link>::const_iterator iter=links.begin(); iter!=links.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
if( iter->second.from() == iter->second.to() &&
|
|
|
|
|
iter->second.type() == Link::kGravity)
|
|
|
|
|
{
|
|
|
|
|
hasGravityConstraints = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-03 15:43:32 -05:00
|
|
|
|
2026-03-29 11:30:33 -07:00
|
|
|
|
|
|
|
|
UDEBUG("fill %ld poses to g2o... (rootId=%d hasGravityConstraints=%d isSlam2d=%d)", poses.size(), rootId, hasGravityConstraints?1:0, isSlam2d()?1:0);
|
2018-12-07 18:29:41 -05:00
|
|
|
for(std::map<int, Transform>::const_iterator iter=poses.begin(); iter!=poses.end(); ++iter)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(iter->first > 0)
|
|
|
|
|
{
|
|
|
|
|
// Get camera model
|
2022-07-20 15:20:14 -04:00
|
|
|
std::map<int, std::vector<CameraModel> >::const_iterator iterModel = models.find(iter->first);
|
|
|
|
|
UASSERT(iterModel != models.end() && !iterModel->second.empty());
|
|
|
|
|
for(size_t i=0; i<iterModel->second.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
UASSERT(iterModel->second[i].isValidForProjection());
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
Transform camPose = iter->second * iterModel->second[i].localTransform();
|
2016-08-10 10:35:53 -04:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
// Add node's pose
|
|
|
|
|
UASSERT(!camPose.isNull());
|
2026-04-05 12:36:54 -07:00
|
|
|
|
|
|
|
|
rtabmap::VertexCam * vCam = new rtabmap::VertexCam();
|
2016-08-10 10:35:53 -04:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
Eigen::Affine3d a = camPose.toEigen3d();
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2022-07-20 15:20:14 -04:00
|
|
|
a = a.inverse();
|
|
|
|
|
vCam->setEstimate(g2o::SE3Quat(a.linear(), a.translation()));
|
2017-09-17 13:28:25 -04:00
|
|
|
#else
|
2022-07-20 15:20:14 -04:00
|
|
|
g2o::SBACam cam(Eigen::Quaterniond(a.linear()), a.translation());
|
|
|
|
|
cam.setKcam(
|
|
|
|
|
iterModel->second[i].fx(),
|
|
|
|
|
iterModel->second[i].fy(),
|
|
|
|
|
iterModel->second[i].cx(),
|
|
|
|
|
iterModel->second[i].cy(),
|
|
|
|
|
iterModel->second[i].Tx()<0.0?-iterModel->second[i].Tx()/iterModel->second[i].fx():baseline_); // baseline in meters
|
|
|
|
|
vCam->setEstimate(cam);
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2022-07-20 15:20:14 -04:00
|
|
|
vCam->setId(iter->first*MULTICAM_OFFSET + i);
|
2017-01-03 21:05:00 -05:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
// negative root means that all other poses should be fixed instead of the root
|
2026-03-29 11:30:33 -07:00
|
|
|
bool fixNode = (rootId >= 0 && iter->first == rootId) || (rootId < 0 && iter->first != -rootId);
|
|
|
|
|
|
|
|
|
|
UASSERT_MSG(optimizer.addVertex(vCam), uFormat("cannot insert cam vertex %d (pose=%d)!?", vCam->id(), iter->first).c_str());
|
|
|
|
|
|
|
|
|
|
if(this->isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
if(fixNode)
|
|
|
|
|
{
|
|
|
|
|
UDEBUG("Set node %d fixed", iter->first);
|
|
|
|
|
vCam->setFixed(true);
|
|
|
|
|
}
|
|
|
|
|
else if(i==0) // Only set prior on the first camera
|
|
|
|
|
{
|
|
|
|
|
// add a singleton constraint that locks the position of the robot on the plane
|
|
|
|
|
EdgeSBACamPrior* planeConstraint = new EdgeSBACamPrior();
|
|
|
|
|
Eigen::Matrix<double, 6, 6> pinfo = Eigen::Matrix<double, 6, 6>::Zero();
|
|
|
|
|
pinfo(2, 2) = 1e9;
|
|
|
|
|
planeConstraint->setInformation(pinfo);
|
|
|
|
|
g2o::SE3Quat fixedZ = g2o::SE3Quat();
|
2026-03-29 12:34:09 -07:00
|
|
|
fixedZ.setTranslation(Eigen::Vector3d(0,0,iter->second.z()));
|
2026-03-29 11:30:33 -07:00
|
|
|
planeConstraint->setMeasurement(fixedZ);
|
|
|
|
|
Eigen::Affine3d a = iterModel->second[i].localTransform().inverse().toEigen3d();
|
|
|
|
|
planeConstraint->setCameraInvLocalTransform(g2o::SE3Quat(a.linear(), a.translation()));
|
|
|
|
|
planeConstraint->vertices()[0] = vCam;
|
|
|
|
|
optimizer.addEdge(planeConstraint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(fixNode)
|
|
|
|
|
{
|
|
|
|
|
if(rootId < 0 || !hasGravityConstraints)
|
|
|
|
|
{
|
|
|
|
|
UDEBUG("Set node %d fixed", iter->first);
|
|
|
|
|
vCam->setFixed(true);
|
|
|
|
|
}
|
|
|
|
|
else if(hasGravityConstraints && i==0) // Only set prior on the first camera in case of multi-cam
|
|
|
|
|
{
|
|
|
|
|
// Setup root prior (fixed x,y,z,yaw)
|
|
|
|
|
EdgeSBACamPrior * e = new EdgeSBACamPrior();
|
|
|
|
|
e->vertices()[0] = vCam;
|
|
|
|
|
Eigen::Affine3d a = iter->second.toEigen3d();
|
|
|
|
|
e->setMeasurement(g2o::SE3Quat(a.linear(), a.translation()));
|
|
|
|
|
a = iterModel->second[i].localTransform().inverse().toEigen3d();
|
|
|
|
|
e->setCameraInvLocalTransform(g2o::SE3Quat(a.linear(), a.translation()));
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity()*10e6;
|
|
|
|
|
// pitch and roll not fixed
|
|
|
|
|
information(3,3) = information(4,4) = 1;
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
if (!optimizer.addEdge(e))
|
|
|
|
|
{
|
|
|
|
|
delete e;
|
|
|
|
|
UERROR("Map: Failed adding fixed constraint of node %d, set as fixed instead", iter->first);
|
|
|
|
|
vCam->setFixed(true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UDEBUG("Set node %d fixed with prior (have gravity constraints)", iter->first);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-03 21:05:00 -05:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
/*UDEBUG("camPose %d (camid=%d) (fixed=%d) fx=%f fy=%f cx=%f cy=%f Tx=%f baseline=%f t=%s",
|
|
|
|
|
iter->first,
|
|
|
|
|
vCam->id(),
|
|
|
|
|
vCam->fixed()?1:0,
|
|
|
|
|
iterModel->second[i].fx(),
|
|
|
|
|
iterModel->second[i].fy(),
|
|
|
|
|
iterModel->second[i].cx(),
|
|
|
|
|
iterModel->second[i].cy(),
|
|
|
|
|
iterModel->second[i].Tx(),
|
|
|
|
|
iterModel->second[i].Tx()<0.0?-iterModel->second[i].Tx()/iterModel->second[i].fx():baseline_,
|
|
|
|
|
camPose.prettyPrint().c_str());*/
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
UDEBUG("fill edges to g2o...");
|
2016-03-28 18:19:17 -04:00
|
|
|
for(std::multimap<int, Link>::const_iterator iter=links.begin(); iter!=links.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(iter->second.from() > 0 &&
|
|
|
|
|
iter->second.to() > 0 &&
|
|
|
|
|
uContains(poses, iter->second.from()) &&
|
2016-11-14 19:54:31 -05:00
|
|
|
uContains(poses, iter->second.to()))
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
|
|
|
|
// add edge
|
2016-11-14 19:54:31 -05:00
|
|
|
int id1 = iter->second.from();
|
2016-03-28 18:19:17 -04:00
|
|
|
int id2 = iter->second.to();
|
|
|
|
|
|
2019-04-09 20:05:24 -04:00
|
|
|
if(id1 == id2)
|
|
|
|
|
{
|
|
|
|
|
g2o::HyperGraph::Edge * edge = 0;
|
2019-05-31 15:36:35 -04:00
|
|
|
if(gravitySigma() > 0 && iter->second.type() == Link::kGravity && poses.find(iter->first) != poses.end())
|
2019-04-09 20:05:24 -04:00
|
|
|
{
|
|
|
|
|
Eigen::Matrix<double, 6, 1> m;
|
|
|
|
|
// Up vector in robot frame
|
|
|
|
|
m.head<3>() = Eigen::Vector3d::UnitZ();
|
|
|
|
|
// Observed Gravity vector in world frame
|
|
|
|
|
float roll, pitch, yaw;
|
|
|
|
|
iter->second.transform().getEulerAngles(roll, pitch, yaw);
|
|
|
|
|
m.tail<3>() = Transform(0,0,0,roll,pitch,0).toEigen3d() * -Eigen::Vector3d::UnitZ();
|
|
|
|
|
|
|
|
|
|
Eigen::MatrixXd information = Eigen::MatrixXd::Identity(3, 3) * 1.0/(gravitySigma()*gravitySigma());
|
|
|
|
|
|
2026-04-05 12:36:54 -07:00
|
|
|
rtabmap::VertexCam* v1 = (rtabmap::VertexCam*)optimizer.vertex(id1*MULTICAM_OFFSET);
|
2019-04-09 20:05:24 -04:00
|
|
|
EdgeSBACamGravity* priorEdge(new EdgeSBACamGravity());
|
2022-07-20 15:20:14 -04:00
|
|
|
std::map<int, std::vector<CameraModel> >::const_iterator iterModel = models.find(iter->first);
|
|
|
|
|
// Gravity constraint added only to first camera of a pose
|
|
|
|
|
UASSERT(iterModel != models.end() && !iterModel->second.empty() && !iterModel->second[0].localTransform().isNull());
|
|
|
|
|
priorEdge->setCameraInvLocalTransform(iterModel->second[0].localTransform().inverse().toEigen3d().linear());
|
2019-04-09 20:05:24 -04:00
|
|
|
priorEdge->setMeasurement(m);
|
|
|
|
|
priorEdge->setInformation(information);
|
|
|
|
|
priorEdge->vertices()[0] = v1;
|
|
|
|
|
edge = priorEdge;
|
|
|
|
|
}
|
|
|
|
|
if (edge && !optimizer.addEdge(edge))
|
|
|
|
|
{
|
|
|
|
|
delete edge;
|
|
|
|
|
UERROR("Map: Failed adding constraint between %d and %d, skipping", id1, id2);
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(id1>0 && id2>0) // not supporting landmarks
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2017-09-26 14:13:06 -04:00
|
|
|
UASSERT(!iter->second.transform().isNull());
|
|
|
|
|
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity();
|
2017-11-30 16:52:03 -05:00
|
|
|
if(!isCovarianceIgnored())
|
|
|
|
|
{
|
|
|
|
|
memcpy(information.data(), iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
|
|
|
|
|
}
|
2017-09-26 14:13:06 -04:00
|
|
|
|
|
|
|
|
// between cameras, not base_link
|
2022-07-20 15:20:14 -04:00
|
|
|
Transform camLink = models.at(id1)[0].localTransform().inverse()*iter->second.transform()*models.at(id2)[0].localTransform();
|
|
|
|
|
/*UDEBUG("added edge %d->%d (camIDs %d->%d) (in cam frame=%s)",
|
|
|
|
|
id1,
|
|
|
|
|
id2,
|
|
|
|
|
id1*MULTICAM_OFFSET,
|
|
|
|
|
id2*MULTICAM_OFFSET,
|
|
|
|
|
camLink.prettyPrint().c_str());*/
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-11-30 16:52:03 -05:00
|
|
|
EdgeSE3Expmap * e = new EdgeSE3Expmap();
|
2022-07-20 15:20:14 -04:00
|
|
|
g2o::VertexSE3Expmap* v1 = (g2o::VertexSE3Expmap*)optimizer.vertex(id1*MULTICAM_OFFSET);
|
|
|
|
|
g2o::VertexSE3Expmap* v2 = (g2o::VertexSE3Expmap*)optimizer.vertex(id2*MULTICAM_OFFSET);
|
2017-09-26 14:13:06 -04:00
|
|
|
|
2017-11-30 16:52:03 -05:00
|
|
|
Transform camPose1 = Transform::fromEigen3d(v1->estimate()).inverse();
|
|
|
|
|
Transform camPose2Inv = Transform::fromEigen3d(v2->estimate());
|
|
|
|
|
|
|
|
|
|
camLink = camPose1 * camPose1 * camLink * camPose2Inv * camPose2Inv;
|
|
|
|
|
#else
|
2017-09-26 14:13:06 -04:00
|
|
|
g2o::EdgeSBACam * e = new g2o::EdgeSBACam();
|
2022-07-20 15:20:14 -04:00
|
|
|
g2o::VertexCam* v1 = (g2o::VertexCam*)optimizer.vertex(id1*MULTICAM_OFFSET);
|
|
|
|
|
g2o::VertexCam* v2 = (g2o::VertexCam*)optimizer.vertex(id2*MULTICAM_OFFSET);
|
2017-11-30 16:52:03 -05:00
|
|
|
#endif
|
2017-09-26 14:13:06 -04:00
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
2017-11-30 16:52:03 -05:00
|
|
|
Eigen::Affine3d a = camLink.toEigen3d();
|
2017-11-09 12:03:01 -05:00
|
|
|
e->setMeasurement(g2o::SE3Quat(a.linear(), a.translation()));
|
2017-09-26 14:13:06 -04:00
|
|
|
e->setInformation(information);
|
|
|
|
|
|
|
|
|
|
if (!optimizer.addEdge(e))
|
|
|
|
|
{
|
|
|
|
|
delete e;
|
|
|
|
|
UERROR("Map: Failed adding constraint between %d and %d, skipping", id1, id2);
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
UDEBUG("fill hard edges between camera 0 and other cameras (multicam)...");
|
|
|
|
|
for(std::map<int, std::vector<CameraModel> >::const_iterator iter=models.begin(); iter!=models.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
int id = iter->first;
|
|
|
|
|
if(uContains(poses, id))
|
|
|
|
|
{
|
|
|
|
|
for(size_t i=1; i<iter->second.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
// add edge
|
|
|
|
|
// Set large information matrix to keep these links fixed
|
|
|
|
|
Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity()*9999999;
|
|
|
|
|
|
|
|
|
|
// between cameras, not base_link
|
|
|
|
|
Transform camLink = iter->second[0].localTransform().inverse()*iter->second[i].localTransform();
|
|
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
|
|
|
|
EdgeSE3Expmap * e = new EdgeSE3Expmap();
|
|
|
|
|
g2o::VertexSE3Expmap* v1 = (g2o::VertexSE3Expmap*)optimizer.vertex(id*MULTICAM_OFFSET);
|
|
|
|
|
g2o::VertexSE3Expmap* v2 = (g2o::VertexSE3Expmap*)optimizer.vertex(id*MULTICAM_OFFSET+i);
|
|
|
|
|
|
|
|
|
|
Transform camPose1 = Transform::fromEigen3d(v1->estimate()).inverse();
|
|
|
|
|
Transform camPose2Inv = Transform::fromEigen3d(v2->estimate());
|
|
|
|
|
|
|
|
|
|
camLink = camPose1 * camPose1 * camLink * camPose2Inv * camPose2Inv;
|
|
|
|
|
#else
|
|
|
|
|
g2o::EdgeSBACam * e = new g2o::EdgeSBACam();
|
|
|
|
|
g2o::VertexCam* v1 = (g2o::VertexCam*)optimizer.vertex(id*MULTICAM_OFFSET);
|
|
|
|
|
g2o::VertexCam* v2 = (g2o::VertexCam*)optimizer.vertex(id*MULTICAM_OFFSET+i);
|
|
|
|
|
#endif
|
|
|
|
|
/*UDEBUG("added edge between subcam 0->%d id:%d->%d (in cam frame=%s)",
|
|
|
|
|
i,
|
|
|
|
|
v1->id(),
|
|
|
|
|
v2->id(),
|
|
|
|
|
camLink.prettyPrint().c_str());*/
|
|
|
|
|
|
|
|
|
|
UASSERT(v1 != 0);
|
|
|
|
|
UASSERT(v2 != 0);
|
|
|
|
|
e->setVertex(0, v1);
|
|
|
|
|
e->setVertex(1, v2);
|
|
|
|
|
Eigen::Affine3d a = camLink.toEigen3d();
|
|
|
|
|
e->setMeasurement(g2o::SE3Quat(a.linear(), a.translation()));
|
|
|
|
|
e->setInformation(information);
|
|
|
|
|
|
|
|
|
|
if (!optimizer.addEdge(e))
|
|
|
|
|
{
|
|
|
|
|
delete e;
|
|
|
|
|
UERROR("Map: Failed adding constraint between %d and %d, skipping", v1->id(), v2->id());
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-28 18:19:17 -04:00
|
|
|
UDEBUG("fill 3D points to g2o...");
|
2022-07-20 15:20:14 -04:00
|
|
|
const int stepVertexId = poses.rbegin()->first*MULTICAM_OFFSET+MULTICAM_OFFSET;
|
2018-07-27 16:01:17 -04:00
|
|
|
int negVertexOffset = stepVertexId;
|
|
|
|
|
if(wordReferences.size() && wordReferences.rbegin()->first>0)
|
|
|
|
|
{
|
|
|
|
|
negVertexOffset += wordReferences.rbegin()->first;
|
|
|
|
|
}
|
|
|
|
|
UDEBUG("stepVertexId=%d, negVertexOffset=%d", stepVertexId, negVertexOffset);
|
2017-01-03 21:05:00 -05:00
|
|
|
std::list<g2o::OptimizableGraph::Edge*> edges;
|
2018-10-24 20:01:57 -04:00
|
|
|
for(std::map<int, std::map<int, FeatureBA> >::const_iterator iter = wordReferences.begin(); iter!=wordReferences.end(); ++iter)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2018-07-27 16:01:17 -04:00
|
|
|
int id = iter->first;
|
|
|
|
|
if(points3DMap.find(id) != points3DMap.end())
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2018-07-27 16:01:17 -04:00
|
|
|
cv::Point3f pt3d = points3DMap.at(id);
|
2020-09-15 15:18:49 -04:00
|
|
|
if(!util3d::isFinite(pt3d))
|
|
|
|
|
{
|
|
|
|
|
UWARN("Ignoring 3D point %d because it has nan value(s)!", id);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2016-11-14 19:54:31 -05:00
|
|
|
g2o::VertexSBAPointXYZ* vpt3d = new g2o::VertexSBAPointXYZ();
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2016-11-14 19:54:31 -05:00
|
|
|
vpt3d->setEstimate(Eigen::Vector3d(pt3d.x, pt3d.y, pt3d.z));
|
2018-07-27 16:01:17 -04:00
|
|
|
if(id<0)
|
|
|
|
|
{
|
|
|
|
|
vpt3d->setId(negVertexOffset + id*-1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
vpt3d->setId(stepVertexId + id);
|
|
|
|
|
}
|
|
|
|
|
UASSERT(vpt3d->id() > 0);
|
2016-11-14 19:54:31 -05:00
|
|
|
vpt3d->setMarginalized(true);
|
|
|
|
|
optimizer.addVertex(vpt3d);
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2017-11-30 16:52:03 -05:00
|
|
|
//UDEBUG("Added 3D point %d (%f,%f,%f)", vpt3d->id()-stepVertexId, pt3d.x, pt3d.y, pt3d.z);
|
2017-01-03 21:05:00 -05:00
|
|
|
|
2016-11-14 19:54:31 -05:00
|
|
|
// set observations
|
2018-10-24 20:01:57 -04:00
|
|
|
for(std::map<int, FeatureBA>::const_iterator jter=iter->second.begin(); jter!=iter->second.end(); ++jter)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2022-07-20 15:20:14 -04:00
|
|
|
int poseId = jter->first;
|
|
|
|
|
int camIndex = jter->second.cameraIndex;
|
|
|
|
|
int camId = poseId*MULTICAM_OFFSET+camIndex;
|
|
|
|
|
if(poses.find(poseId) != poses.end() && optimizer.vertex(camId) != 0)
|
2016-11-14 19:54:31 -05:00
|
|
|
{
|
2018-10-24 20:01:57 -04:00
|
|
|
const FeatureBA & pt = jter->second;
|
|
|
|
|
double depth = pt.depth;
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
//UDEBUG("Added observation pt=%d to cam=%d (%d,%d) depth=%f (camIndex=%d)", vpt3d->id()-stepVertexId, camId, (int)pt.kpt.pt.x, (int)pt.kpt.pt.y, depth, camIndex);
|
2016-11-14 19:54:31 -05:00
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
g2o::OptimizableGraph::Edge * e;
|
2017-09-17 13:28:25 -04:00
|
|
|
double baseline = 0.0;
|
2026-04-05 12:36:54 -07:00
|
|
|
rtabmap::VertexCam* vcam = dynamic_cast<rtabmap::VertexCam*>(optimizer.vertex(camId));
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2026-04-05 12:36:54 -07:00
|
|
|
|
2022-07-20 15:20:14 -04:00
|
|
|
std::map<int, std::vector<CameraModel> >::const_iterator iterModel = models.find(poseId);
|
2017-09-17 13:28:25 -04:00
|
|
|
|
2026-04-05 12:36:54 -07:00
|
|
|
UASSERT(iterModel != models.end() && camIndex<(int)iterModel->second.size() && iterModel->second[camIndex].isValidForProjection());
|
2022-07-20 15:20:14 -04:00
|
|
|
baseline = iterModel->second[camIndex].Tx()<0.0?-iterModel->second[camIndex].Tx()/iterModel->second[camIndex].fx():baseline_;
|
2017-09-17 13:28:25 -04:00
|
|
|
#else
|
|
|
|
|
baseline = vcam->estimate().baseline;
|
|
|
|
|
#endif
|
2017-01-03 21:05:00 -05:00
|
|
|
double variance = pixelVariance_;
|
2017-09-17 13:28:25 -04:00
|
|
|
if(uIsFinite(depth) && depth > 0.0 && baseline > 0.0)
|
2016-11-14 19:54:31 -05:00
|
|
|
{
|
2017-01-03 21:05:00 -05:00
|
|
|
// stereo edge
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-09-17 13:28:25 -04:00
|
|
|
g2o::EdgeStereoSE3ProjectXYZ* es = new g2o::EdgeStereoSE3ProjectXYZ();
|
2022-07-20 15:20:14 -04:00
|
|
|
float disparity = baseline * iterModel->second[camIndex].fx() / depth;
|
2018-10-24 20:01:57 -04:00
|
|
|
Eigen::Vector3d obs( pt.kpt.pt.x, pt.kpt.pt.y, pt.kpt.pt.x-disparity);
|
2017-09-17 13:28:25 -04:00
|
|
|
es->setMeasurement(obs);
|
|
|
|
|
//variance *= log(exp(1)+disparity);
|
|
|
|
|
es->setInformation(Eigen::Matrix3d::Identity() / variance);
|
2022-07-20 15:20:14 -04:00
|
|
|
es->fx = iterModel->second[camIndex].fx();
|
|
|
|
|
es->fy = iterModel->second[camIndex].fy();
|
|
|
|
|
es->cx = iterModel->second[camIndex].cx();
|
|
|
|
|
es->cy = iterModel->second[camIndex].cy();
|
2017-09-17 13:28:25 -04:00
|
|
|
es->bf = baseline*es->fx;
|
|
|
|
|
e = es;
|
|
|
|
|
#else
|
2017-01-03 21:05:00 -05:00
|
|
|
g2o::EdgeProjectP2SC* es = new g2o::EdgeProjectP2SC();
|
2017-09-17 13:28:25 -04:00
|
|
|
float disparity = baseline * vcam->estimate().Kcam(0,0) / depth;
|
2018-10-24 20:01:57 -04:00
|
|
|
Eigen::Vector3d obs( pt.kpt.pt.x, pt.kpt.pt.y, pt.kpt.pt.x-disparity);
|
2017-01-03 21:05:00 -05:00
|
|
|
es->setMeasurement(obs);
|
|
|
|
|
//variance *= log(exp(1)+disparity);
|
|
|
|
|
es->setInformation(Eigen::Matrix3d::Identity() / variance);
|
|
|
|
|
e = es;
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2017-01-03 21:05:00 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-09-17 13:28:25 -04:00
|
|
|
if(baseline > 0.0)
|
2017-01-03 21:05:00 -05:00
|
|
|
{
|
2018-02-01 22:17:46 -05:00
|
|
|
UDEBUG("Stereo camera model detected but current "
|
2020-09-15 15:18:49 -04:00
|
|
|
"observation (pt=%d to cam=%d, kpt=[%d,%d]) has null depth (%f m), adding "
|
2017-01-03 21:05:00 -05:00
|
|
|
"mono observation instead.",
|
2020-09-15 15:18:49 -04:00
|
|
|
vpt3d->id()-stepVertexId, camId, (int)pt.kpt.pt.x, (int)pt.kpt.pt.y, depth);
|
2017-01-03 21:05:00 -05:00
|
|
|
}
|
|
|
|
|
// mono edge
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-09-17 13:28:25 -04:00
|
|
|
g2o::EdgeSE3ProjectXYZ* em = new g2o::EdgeSE3ProjectXYZ();
|
2018-10-24 20:01:57 -04:00
|
|
|
Eigen::Vector2d obs( pt.kpt.pt.x, pt.kpt.pt.y);
|
2017-09-17 13:28:25 -04:00
|
|
|
em->setMeasurement(obs);
|
|
|
|
|
em->setInformation(Eigen::Matrix2d::Identity() / variance);
|
2022-07-20 15:20:14 -04:00
|
|
|
em->fx = iterModel->second[camIndex].fx();
|
|
|
|
|
em->fy = iterModel->second[camIndex].fy();
|
|
|
|
|
em->cx = iterModel->second[camIndex].cx();
|
|
|
|
|
em->cy = iterModel->second[camIndex].cy();
|
2017-09-17 13:28:25 -04:00
|
|
|
e = em;
|
|
|
|
|
|
|
|
|
|
#else
|
2017-01-03 21:05:00 -05:00
|
|
|
g2o::EdgeProjectP2MC* em = new g2o::EdgeProjectP2MC();
|
2018-10-24 20:01:57 -04:00
|
|
|
Eigen::Vector2d obs( pt.kpt.pt.x, pt.kpt.pt.y);
|
2017-01-03 21:05:00 -05:00
|
|
|
em->setMeasurement(obs);
|
|
|
|
|
em->setInformation(Eigen::Matrix2d::Identity() / variance);
|
|
|
|
|
e = em;
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2017-01-03 21:05:00 -05:00
|
|
|
}
|
|
|
|
|
e->setVertex(0, vpt3d);
|
|
|
|
|
e->setVertex(1, vcam);
|
2017-11-30 16:52:03 -05:00
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
if(robustKernelDelta_ > 0.0)
|
|
|
|
|
{
|
|
|
|
|
g2o::RobustKernelHuber* kernel = new g2o::RobustKernelHuber;
|
|
|
|
|
kernel->setDelta(robustKernelDelta_);
|
|
|
|
|
e->setRobustKernel(kernel);
|
2016-11-14 19:54:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
optimizer.addEdge(e);
|
2017-01-03 21:05:00 -05:00
|
|
|
edges.push_back(e);
|
2016-11-14 19:54:31 -05:00
|
|
|
}
|
|
|
|
|
}
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UDEBUG("Initial optimization...");
|
|
|
|
|
optimizer.initializeOptimization();
|
|
|
|
|
|
|
|
|
|
UASSERT(optimizer.verifyInformationMatrices());
|
|
|
|
|
|
2018-10-15 17:07:18 -04:00
|
|
|
UDEBUG("g2o optimizing begin (max iterations=%d, robustKernel=%f)", iterations(), robustKernelDelta_);
|
2016-03-28 18:19:17 -04:00
|
|
|
|
|
|
|
|
int it = 0;
|
|
|
|
|
UTimer timer;
|
2017-01-03 21:05:00 -05:00
|
|
|
int outliersCount = 0;
|
|
|
|
|
int outliersCountFar = 0;
|
|
|
|
|
|
|
|
|
|
for(int i=0; i<(robustKernelDelta_>0.0?2:1); ++i)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2017-11-30 16:52:03 -05:00
|
|
|
it += optimizer.optimize(i==0&&robustKernelDelta_>0.0?5:iterations());
|
2017-01-03 21:05:00 -05:00
|
|
|
|
|
|
|
|
// early stop condition
|
|
|
|
|
optimizer.computeActiveErrors();
|
|
|
|
|
double chi2 = optimizer.activeRobustChi2();
|
2017-11-30 16:52:03 -05:00
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
if(uIsNan(chi2))
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2026-04-04 19:48:03 -07:00
|
|
|
UERROR("Optimization generated NANs, aborting optimization! Try another g2o's optimizer (current %s=%d) or solver (current %s=%d).",
|
|
|
|
|
Parameters::kg2oOptimizer().c_str(), optimizer_, Parameters::kg2oSolver().c_str(), solver_);
|
2017-01-03 21:05:00 -05:00
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
UDEBUG("iteration %d: %d nodes, %d edges, chi2: %f", i, (int)optimizer.vertices().size(), (int)optimizer.edges().size(), chi2);
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
if(i>0 && (optimizer.activeRobustChi2() > 1000000000000.0 || !uIsFinite(optimizer.activeRobustChi2())))
|
|
|
|
|
{
|
|
|
|
|
UWARN("g2o: Large optimization error detected (%f), aborting optimization!");
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
2016-03-28 18:19:17 -04:00
|
|
|
|
2017-01-03 21:05:00 -05:00
|
|
|
if(robustKernelDelta_>0.0)
|
|
|
|
|
{
|
|
|
|
|
for(std::list<g2o::OptimizableGraph::Edge*>::iterator iter=edges.begin(); iter!=edges.end();++iter)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2017-01-03 21:05:00 -05:00
|
|
|
if((*iter)->level() == 0 && (*iter)->chi2() > (*iter)->robustKernel()->delta())
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2017-01-03 21:05:00 -05:00
|
|
|
(*iter)->setLevel(1);
|
|
|
|
|
++outliersCount;
|
2017-09-17 13:28:25 -04:00
|
|
|
double d = 0.0;
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2017-09-17 13:28:25 -04:00
|
|
|
if(dynamic_cast<g2o::EdgeStereoSE3ProjectXYZ*>(*iter) != 0)
|
|
|
|
|
{
|
|
|
|
|
d = ((g2o::EdgeStereoSE3ProjectXYZ*)(*iter))->measurement()[0]-((g2o::EdgeStereoSE3ProjectXYZ*)(*iter))->measurement()[2];
|
|
|
|
|
}
|
2022-07-20 15:20:14 -04:00
|
|
|
//UDEBUG("Ignoring edge (%d<->%d) d=%f var=%f kernel=%f chi2=%f", (*iter)->vertex(0)->id()-stepVertexId, (*iter)->vertex(1)->id(), d, 1.0/((g2o::EdgeStereoSE3ProjectXYZ*)(*iter))->information()(0,0), (*iter)->robustKernel()->delta(), (*iter)->chi2());
|
2017-09-17 13:28:25 -04:00
|
|
|
#else
|
|
|
|
|
if(dynamic_cast<g2o::EdgeProjectP2SC*>(*iter) != 0)
|
|
|
|
|
{
|
|
|
|
|
d = ((g2o::EdgeProjectP2SC*)(*iter))->measurement()[0]-((g2o::EdgeProjectP2SC*)(*iter))->measurement()[2];
|
|
|
|
|
}
|
2022-07-20 15:20:14 -04:00
|
|
|
//UDEBUG("Ignoring edge (%d<->%d) d=%f var=%f kernel=%f chi2=%f", (*iter)->vertex(0)->id()-stepVertexId, (*iter)->vertex(1)->id(), d, 1.0/((g2o::EdgeProjectP2SC*)(*iter))->information()(0,0), (*iter)->robustKernel()->delta(), (*iter)->chi2());
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
2017-01-03 21:05:00 -05:00
|
|
|
|
2026-04-04 19:48:03 -07:00
|
|
|
int id=-1;
|
2018-07-27 16:01:17 -04:00
|
|
|
if((*iter)->vertex(0)->id() > negVertexOffset)
|
|
|
|
|
{
|
2026-04-04 19:48:03 -07:00
|
|
|
id = negVertexOffset - (*iter)->vertex(0)->id();
|
2018-07-27 16:01:17 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-04-04 19:48:03 -07:00
|
|
|
id = (*iter)->vertex(0)->id() - stepVertexId;
|
2018-07-27 16:01:17 -04:00
|
|
|
}
|
2026-04-04 19:48:03 -07:00
|
|
|
UASSERT_MSG(points3DMap.find(id) != points3DMap.end(), uFormat("word id=%d points3DMap=%ld vertex id=%d (negVertexOffset=%d stepVertexId=%d)",
|
|
|
|
|
id, points3DMap.size(), (*iter)->vertex(0)->id(), negVertexOffset, stepVertexId).c_str());
|
|
|
|
|
cv::Point3f pt3d = points3DMap.at(id);
|
2017-01-03 21:05:00 -05:00
|
|
|
((g2o::VertexSBAPointXYZ*)(*iter)->vertex(0))->setEstimate(Eigen::Vector3d(pt3d.x, pt3d.y, pt3d.z));
|
|
|
|
|
|
|
|
|
|
if(outliers)
|
|
|
|
|
{
|
|
|
|
|
outliers->insert((*iter)->vertex(0)->id()-stepVertexId);
|
|
|
|
|
}
|
|
|
|
|
if(d < 5.0)
|
|
|
|
|
{
|
|
|
|
|
outliersCountFar++;
|
|
|
|
|
}
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
2017-01-03 21:05:00 -05:00
|
|
|
//(*iter)->setRobustKernel(0);
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
2017-01-03 21:05:00 -05:00
|
|
|
if(i==0)
|
|
|
|
|
optimizer.initializeOptimization(0);
|
|
|
|
|
UDEBUG("outliers=%d outliersCountFar=%d", outliersCount, outliersCountFar);
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
2018-10-15 17:07:18 -04:00
|
|
|
UDEBUG("g2o optimizing end (%d iterations done, error=%f, outliers=%d/%d (delta=%f) time = %f s)", it, optimizer.activeRobustChi2(), outliersCount, (int)edges.size(), robustKernelDelta_, timer.ticks());
|
2016-03-28 18:19:17 -04:00
|
|
|
|
|
|
|
|
if(optimizer.activeRobustChi2() > 1000000000000.0)
|
|
|
|
|
{
|
2017-11-30 16:52:03 -05:00
|
|
|
UWARN("g2o: Large optimization error detected (%f), aborting optimization!");
|
2016-03-28 18:19:17 -04:00
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-14 19:54:31 -05:00
|
|
|
// update poses
|
2016-03-28 18:19:17 -04:00
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(iter->first > 0)
|
2016-03-28 18:19:17 -04:00
|
|
|
{
|
2022-07-20 15:20:14 -04:00
|
|
|
int camId = iter->first*MULTICAM_OFFSET;
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2022-07-20 15:20:14 -04:00
|
|
|
const g2o::VertexSE3Expmap* v = (const g2o::VertexSE3Expmap*)optimizer.vertex(camId);
|
2018-12-07 18:29:41 -05:00
|
|
|
#else
|
2022-07-20 15:20:14 -04:00
|
|
|
const g2o::VertexCam* v = (const g2o::VertexCam*)optimizer.vertex(camId);
|
2018-12-07 18:29:41 -05:00
|
|
|
#endif
|
|
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
Transform t = Transform::fromEigen3d(v->estimate());
|
2017-01-03 21:05:00 -05:00
|
|
|
|
2021-03-09 16:00:41 -05:00
|
|
|
#ifdef RTABMAP_ORB_SLAM
|
2018-12-07 18:29:41 -05:00
|
|
|
t=t.inverse();
|
2017-09-17 13:28:25 -04:00
|
|
|
#endif
|
|
|
|
|
|
2018-12-07 18:29:41 -05:00
|
|
|
// remove model local transform
|
2022-07-20 15:20:14 -04:00
|
|
|
t *= models.at(iter->first)[0].localTransform().inverse();
|
2017-11-30 16:52:03 -05:00
|
|
|
|
2019-08-17 03:55:04 -04:00
|
|
|
//UDEBUG("%d from=%s to=%s", iter->first, iter->second.prettyPrint().c_str(), t.prettyPrint().c_str());
|
2018-12-07 18:29:41 -05:00
|
|
|
if(t.isNull())
|
|
|
|
|
{
|
|
|
|
|
UERROR("Optimized pose %d is null!?!?", iter->first);
|
|
|
|
|
optimizedPoses.clear();
|
|
|
|
|
return optimizedPoses;
|
|
|
|
|
}
|
2016-11-14 19:54:31 -05:00
|
|
|
|
2018-12-07 18:29:41 -05:00
|
|
|
if(this->isSlam2d())
|
|
|
|
|
{
|
2026-03-29 11:30:33 -07:00
|
|
|
// The optimized poses should be already fixed to original height,
|
|
|
|
|
// but it may have varied a little (not exaclty the same number).
|
|
|
|
|
// Here we just put back the original z value.
|
|
|
|
|
if(fabs(t.z() - iter->second.z()) < 0.001)
|
|
|
|
|
{
|
|
|
|
|
t.z() = iter->second.z();
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(iter->first, t));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("Planar constraints didn't work!? original pose (%d), pose %s -> %s. Falling back to old approach.",
|
|
|
|
|
iter->first,
|
|
|
|
|
iter->second.prettyPrint().c_str(),
|
|
|
|
|
t.prettyPrint().c_str());
|
|
|
|
|
// get transform between old and new pose
|
|
|
|
|
t = iter->second.inverse() * t;
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(iter->first, iter->second * t.to3DoF()));
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
optimizedPoses.insert(std::pair<int, Transform>(iter->first, t));
|
|
|
|
|
}
|
2016-11-14 19:54:31 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-07-20 15:20:14 -04:00
|
|
|
UERROR("Vertex (pose) %d (cam=%d) not found!?", iter->first, camId);
|
2016-11-14 19:54:31 -05:00
|
|
|
}
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
2016-11-14 19:54:31 -05:00
|
|
|
|
|
|
|
|
//update points3D
|
2017-01-03 21:05:00 -05:00
|
|
|
|
2016-11-14 19:54:31 -05:00
|
|
|
for(std::map<int, cv::Point3f>::iterator iter = points3DMap.begin(); iter!=points3DMap.end(); ++iter)
|
|
|
|
|
{
|
2018-07-27 16:01:17 -04:00
|
|
|
const g2o::VertexSBAPointXYZ* v;
|
|
|
|
|
int id = iter->first;
|
|
|
|
|
if(id<0)
|
|
|
|
|
{
|
|
|
|
|
v = (const g2o::VertexSBAPointXYZ*)optimizer.vertex(negVertexOffset + id*-1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
v = (const g2o::VertexSBAPointXYZ*)optimizer.vertex(stepVertexId + id);
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-14 19:54:31 -05:00
|
|
|
if(v)
|
|
|
|
|
{
|
|
|
|
|
cv::Point3f p(v->estimate()[0], v->estimate()[1], v->estimate()[2]);
|
|
|
|
|
//UDEBUG("%d from=%f,%f,%f to=%f,%f,%f", iter->first, iter->second.x, iter->second.y, iter->second.z, p.x, p.y, p.z);
|
|
|
|
|
iter->second = p;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-01-03 21:05:00 -05:00
|
|
|
iter->second.x = iter->second.y = iter->second.z = std::numeric_limits<float>::quiet_NaN();
|
2016-11-14 19:54:31 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
else if(poses.size() > 1 && (poses.size() != models.size() && poses.begin()->first > 0))
|
2016-11-14 19:54:31 -05:00
|
|
|
{
|
|
|
|
|
UERROR("This method should be called with size of poses = size camera models!");
|
2016-03-28 18:19:17 -04:00
|
|
|
}
|
|
|
|
|
else if(poses.size() == 1 || iterations() <= 0)
|
|
|
|
|
{
|
|
|
|
|
optimizedPoses = poses;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("This method should be called at least with 1 pose!");
|
|
|
|
|
}
|
|
|
|
|
UDEBUG("Optimizing graph...end!");
|
|
|
|
|
#else
|
|
|
|
|
UERROR("Not built with G2O support!");
|
|
|
|
|
#endif
|
|
|
|
|
return optimizedPoses;
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
|
2026-05-26 17:43:34 -07:00
|
|
|
bool OptimizerG2O::loadGraph(
|
|
|
|
|
const std::string & fileName,
|
|
|
|
|
std::map<int, Transform> & poses,
|
|
|
|
|
std::multimap<int, Link> & edgeConstraints)
|
|
|
|
|
{
|
|
|
|
|
FILE * file = 0;
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
|
fopen_s(&file, fileName.c_str(), "r");
|
|
|
|
|
#else
|
|
|
|
|
file = fopen(fileName.c_str(), "r");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if(!file)
|
|
|
|
|
{
|
|
|
|
|
UERROR("Cannot open file %s", fileName.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// saveGraph() writes landmarks (originally negative ids, remapped to
|
|
|
|
|
// landmarkOffset - id) first in DESCENDING file-id order, then regular
|
|
|
|
|
// poses in ASCENDING file-id order. We recover landmarkOffset from this
|
|
|
|
|
// order to restore the original negative landmark ids.
|
|
|
|
|
struct VertexEntry {
|
|
|
|
|
int fileId;
|
|
|
|
|
Transform transform;
|
|
|
|
|
bool definitelyLandmark; // VERTEX_XY / VERTEX_TRACKXYZ
|
|
|
|
|
};
|
|
|
|
|
struct EdgeEntry {
|
|
|
|
|
int from;
|
|
|
|
|
int to;
|
|
|
|
|
Link::Type type;
|
|
|
|
|
Transform transform;
|
|
|
|
|
cv::Mat info;
|
|
|
|
|
bool isPrior; // from==to, prior on a single vertex
|
|
|
|
|
bool hasLandmarkEndpoint; // tag implies a landmark on one side
|
|
|
|
|
};
|
|
|
|
|
std::vector<VertexEntry> verticesList;
|
|
|
|
|
std::vector<EdgeEntry> edgesList;
|
|
|
|
|
|
|
|
|
|
char line[2048];
|
|
|
|
|
while(fgets(line, 2048, file) != NULL)
|
|
|
|
|
{
|
|
|
|
|
std::list<std::string> tokenList = uSplit(uReplaceChar(uReplaceChar(line, '\n', ' '), '\r', ' '), ' ');
|
|
|
|
|
std::vector<std::string> v;
|
|
|
|
|
v.reserve(tokenList.size());
|
|
|
|
|
for(std::list<std::string>::const_iterator iter = tokenList.begin(); iter != tokenList.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
if(!iter->empty())
|
|
|
|
|
{
|
|
|
|
|
v.push_back(*iter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(v.empty())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const std::string & tag = v[0];
|
|
|
|
|
|
|
|
|
|
// Skip parameters, switch helpers and unrelated entries
|
|
|
|
|
if(tag == "PARAMS_SE2OFFSET" || tag == "PARAMS_SE3OFFSET" ||
|
|
|
|
|
tag == "VERTEX_SWITCH" || tag == "EDGE_SWITCH_PRIOR")
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(tag == "VERTEX_SE2" && v.size() == 5)
|
|
|
|
|
{
|
|
|
|
|
VertexEntry e;
|
|
|
|
|
e.fileId = atoi(v[1].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), uStr2Float(v[4]));
|
|
|
|
|
e.definitelyLandmark = false;
|
|
|
|
|
verticesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "VERTEX_XY" && v.size() == 4)
|
|
|
|
|
{
|
|
|
|
|
VertexEntry e;
|
|
|
|
|
e.fileId = atoi(v[1].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), 0);
|
|
|
|
|
e.definitelyLandmark = true;
|
|
|
|
|
verticesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "VERTEX_SE3:QUAT" && v.size() == 9)
|
|
|
|
|
{
|
|
|
|
|
VertexEntry e;
|
|
|
|
|
e.fileId = atoi(v[1].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), uStr2Float(v[4]),
|
|
|
|
|
uStr2Float(v[5]), uStr2Float(v[6]), uStr2Float(v[7]), uStr2Float(v[8]));
|
|
|
|
|
e.definitelyLandmark = false;
|
|
|
|
|
verticesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "VERTEX_TRACKXYZ" && v.size() == 5)
|
|
|
|
|
{
|
|
|
|
|
VertexEntry e;
|
|
|
|
|
e.fileId = atoi(v[1].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), uStr2Float(v[4]), 0, 0, 0);
|
|
|
|
|
e.definitelyLandmark = true;
|
|
|
|
|
verticesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_SE2" && v.size() == 12)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = atoi(v[2].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[3]), uStr2Float(v[4]), uStr2Float(v[5]));
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[6]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[7]);
|
|
|
|
|
e.info.at<double>(0, 5) = e.info.at<double>(5, 0) = uStr2Double(v[8]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[9]);
|
|
|
|
|
e.info.at<double>(1, 5) = e.info.at<double>(5, 1) = uStr2Double(v[10]);
|
|
|
|
|
e.info.at<double>(5, 5) = uStr2Double(v[11]);
|
|
|
|
|
e.type = Link::kUndef; // disambiguated after we know landmarkOffset
|
|
|
|
|
e.isPrior = false;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_SE2_XY" && v.size() == 8)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = atoi(v[2].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[3]), uStr2Float(v[4]), 0);
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[5]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[6]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[7]);
|
|
|
|
|
e.type = Link::kLandmark;
|
|
|
|
|
e.isPrior = false;
|
|
|
|
|
e.hasLandmarkEndpoint = true;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if((tag == "EDGE_SE3:QUAT" || tag == "EDGE_SE3") && v.size() == 31)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = atoi(v[2].c_str());
|
|
|
|
|
e.transform = Transform(uStr2Float(v[3]), uStr2Float(v[4]), uStr2Float(v[5]),
|
|
|
|
|
uStr2Float(v[6]), uStr2Float(v[7]), uStr2Float(v[8]), uStr2Float(v[9]));
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
int idx = 10;
|
|
|
|
|
for(int r = 0; r < 6; ++r)
|
|
|
|
|
{
|
|
|
|
|
for(int c = r; c < 6; ++c)
|
|
|
|
|
{
|
|
|
|
|
e.info.at<double>(r, c) = uStr2Double(v[idx++]);
|
|
|
|
|
if(r != c) e.info.at<double>(c, r) = e.info.at<double>(r, c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// EDGE_SE3 (no :QUAT) is the landmark variant emitted by saveGraph
|
|
|
|
|
bool landmarkTag = (tag == "EDGE_SE3");
|
|
|
|
|
e.type = landmarkTag ? Link::kLandmark : Link::kUndef;
|
|
|
|
|
e.isPrior = false;
|
|
|
|
|
e.hasLandmarkEndpoint = landmarkTag;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_SE3_TRACKXYZ" && v.size() == 13)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = atoi(v[2].c_str());
|
|
|
|
|
// v[3] = param_offset id, ignored
|
|
|
|
|
e.transform = Transform(uStr2Float(v[4]), uStr2Float(v[5]), uStr2Float(v[6]), 0, 0, 0);
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[7]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[8]);
|
|
|
|
|
e.info.at<double>(0, 2) = e.info.at<double>(2, 0) = uStr2Double(v[9]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[10]);
|
|
|
|
|
e.info.at<double>(1, 2) = e.info.at<double>(2, 1) = uStr2Double(v[11]);
|
|
|
|
|
e.info.at<double>(2, 2) = uStr2Double(v[12]);
|
|
|
|
|
e.type = Link::kLandmark;
|
|
|
|
|
e.isPrior = false;
|
|
|
|
|
e.hasLandmarkEndpoint = true;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_PRIOR_SE2" && v.size() == 11)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = e.from;
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), uStr2Float(v[4]));
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[5]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[6]);
|
|
|
|
|
e.info.at<double>(0, 5) = e.info.at<double>(5, 0) = uStr2Double(v[7]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[8]);
|
|
|
|
|
e.info.at<double>(1, 5) = e.info.at<double>(5, 1) = uStr2Double(v[9]);
|
|
|
|
|
e.info.at<double>(5, 5) = uStr2Double(v[10]);
|
|
|
|
|
e.type = Link::kPosePrior;
|
|
|
|
|
e.isPrior = true;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_PRIOR_SE2_XY" && v.size() == 7)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = e.from;
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), 0);
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[4]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[5]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[6]);
|
|
|
|
|
// no orientation info on this prior
|
|
|
|
|
e.info.at<double>(3, 3) = e.info.at<double>(4, 4) = e.info.at<double>(5, 5) = 1.0 / 9999.0;
|
|
|
|
|
e.type = Link::kPosePrior;
|
|
|
|
|
e.isPrior = true;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_SE3_PRIOR" && v.size() == 31)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = e.from;
|
|
|
|
|
// v[2] = param_offset id, ignored
|
|
|
|
|
e.transform = Transform(uStr2Float(v[3]), uStr2Float(v[4]), uStr2Float(v[5]),
|
|
|
|
|
uStr2Float(v[6]), uStr2Float(v[7]), uStr2Float(v[8]), uStr2Float(v[9]));
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
int idx = 10;
|
|
|
|
|
for(int r = 0; r < 6; ++r)
|
|
|
|
|
{
|
|
|
|
|
for(int c = r; c < 6; ++c)
|
|
|
|
|
{
|
|
|
|
|
e.info.at<double>(r, c) = uStr2Double(v[idx++]);
|
|
|
|
|
if(r != c) e.info.at<double>(c, r) = e.info.at<double>(r, c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
e.type = Link::kPosePrior;
|
|
|
|
|
e.isPrior = true;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_POINTXYZ_PRIOR" && v.size() == 11)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = e.from;
|
|
|
|
|
e.transform = Transform(uStr2Float(v[2]), uStr2Float(v[3]), uStr2Float(v[4]), 0, 0, 0);
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[5]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[6]);
|
|
|
|
|
e.info.at<double>(0, 2) = e.info.at<double>(2, 0) = uStr2Double(v[7]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[8]);
|
|
|
|
|
e.info.at<double>(1, 2) = e.info.at<double>(2, 1) = uStr2Double(v[9]);
|
|
|
|
|
e.info.at<double>(2, 2) = uStr2Double(v[10]);
|
|
|
|
|
// no orientation info on this prior
|
|
|
|
|
e.info.at<double>(3, 3) = e.info.at<double>(4, 4) = e.info.at<double>(5, 5) = 1.0 / 9999.0;
|
|
|
|
|
e.type = Link::kPosePrior;
|
|
|
|
|
e.isPrior = true;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_SE2_SWITCHABLE" && v.size() == 13)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = atoi(v[2].c_str());
|
|
|
|
|
// v[3] = switch vertex id, ignored
|
|
|
|
|
e.transform = Transform(uStr2Float(v[4]), uStr2Float(v[5]), uStr2Float(v[6]));
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
e.info.at<double>(0, 0) = uStr2Double(v[7]);
|
|
|
|
|
e.info.at<double>(0, 1) = e.info.at<double>(1, 0) = uStr2Double(v[8]);
|
|
|
|
|
e.info.at<double>(0, 5) = e.info.at<double>(5, 0) = uStr2Double(v[9]);
|
|
|
|
|
e.info.at<double>(1, 1) = uStr2Double(v[10]);
|
|
|
|
|
e.info.at<double>(1, 5) = e.info.at<double>(5, 1) = uStr2Double(v[11]);
|
|
|
|
|
e.info.at<double>(5, 5) = uStr2Double(v[12]);
|
|
|
|
|
e.type = Link::kUndef;
|
|
|
|
|
e.isPrior = false;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else if(tag == "EDGE_SE3_SWITCHABLE" && v.size() == 32)
|
|
|
|
|
{
|
|
|
|
|
EdgeEntry e;
|
|
|
|
|
e.from = atoi(v[1].c_str());
|
|
|
|
|
e.to = atoi(v[2].c_str());
|
|
|
|
|
// v[3] = switch vertex id, ignored
|
|
|
|
|
e.transform = Transform(uStr2Float(v[4]), uStr2Float(v[5]), uStr2Float(v[6]),
|
|
|
|
|
uStr2Float(v[7]), uStr2Float(v[8]), uStr2Float(v[9]), uStr2Float(v[10]));
|
|
|
|
|
e.info = cv::Mat::eye(6, 6, CV_64FC1);
|
|
|
|
|
int idx = 11;
|
|
|
|
|
for(int r = 0; r < 6; ++r)
|
|
|
|
|
{
|
|
|
|
|
for(int c = r; c < 6; ++c)
|
|
|
|
|
{
|
|
|
|
|
e.info.at<double>(r, c) = uStr2Double(v[idx++]);
|
|
|
|
|
if(r != c) e.info.at<double>(c, r) = e.info.at<double>(r, c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
e.type = Link::kUndef;
|
|
|
|
|
e.isPrior = false;
|
|
|
|
|
e.hasLandmarkEndpoint = false;
|
|
|
|
|
edgesList.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("Unsupported or malformed g2o line: \"%s\" (tag=%s, tokens=%d)", line, tag.c_str(), (int)v.size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fclose(file);
|
|
|
|
|
|
|
|
|
|
// Recover landmarkOffset from vertex order:
|
|
|
|
|
// file order = [landmarks with DESCENDING file_ids] + [regular poses with ASCENDING file_ids]
|
|
|
|
|
// Walk backwards from the end and take the longest ascending suffix as the regular poses.
|
|
|
|
|
// landmarkOffset = max regular pose id (= last fileId of that suffix).
|
|
|
|
|
int landmarkOffset = 0;
|
|
|
|
|
int firstRegularIdx = (int)verticesList.size();
|
|
|
|
|
if(!verticesList.empty())
|
|
|
|
|
{
|
|
|
|
|
firstRegularIdx = (int)verticesList.size() - 1;
|
|
|
|
|
while(firstRegularIdx > 0 &&
|
|
|
|
|
verticesList[firstRegularIdx - 1].fileId < verticesList[firstRegularIdx].fileId)
|
|
|
|
|
{
|
|
|
|
|
--firstRegularIdx;
|
|
|
|
|
}
|
|
|
|
|
landmarkOffset = verticesList.back().fileId;
|
|
|
|
|
|
|
|
|
|
// If the alleged regular suffix actually starts on a definite landmark
|
|
|
|
|
// (VERTEX_XY / VERTEX_TRACKXYZ), then there are no regular poses and
|
|
|
|
|
// saveGraph used landmarkOffset = 0; restore that case.
|
|
|
|
|
if(verticesList[firstRegularIdx].definitelyLandmark)
|
|
|
|
|
{
|
|
|
|
|
landmarkOffset = 0;
|
|
|
|
|
firstRegularIdx = (int)verticesList.size();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Insert vertices into poses, remapping landmark file ids back to negative.
|
|
|
|
|
for(int i = 0; i < (int)verticesList.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
int originalId;
|
|
|
|
|
if(i < firstRegularIdx)
|
|
|
|
|
{
|
|
|
|
|
originalId = landmarkOffset - verticesList[i].fileId; // negative
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
originalId = verticesList[i].fileId;
|
|
|
|
|
}
|
|
|
|
|
if(poses.find(originalId) == poses.end())
|
|
|
|
|
{
|
|
|
|
|
poses.insert(std::make_pair(originalId, verticesList[i].transform));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UWARN("Vertex %d (file id %d) already exists, ignoring duplicate", originalId, verticesList[i].fileId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remap edge endpoints. Any file id > landmarkOffset (or, if landmarkOffset == 0
|
|
|
|
|
// and there are any landmarks at all, any id present in the landmark prefix)
|
|
|
|
|
// is a landmark and gets the negative id back.
|
|
|
|
|
bool allLandmarks = (landmarkOffset == 0 && firstRegularIdx == (int)verticesList.size() && !verticesList.empty());
|
|
|
|
|
auto remap = [&](int fileId) -> int {
|
|
|
|
|
if(landmarkOffset > 0 && fileId > landmarkOffset)
|
|
|
|
|
{
|
|
|
|
|
return landmarkOffset - fileId; // negative
|
|
|
|
|
}
|
|
|
|
|
if(allLandmarks)
|
|
|
|
|
{
|
|
|
|
|
return -fileId;
|
|
|
|
|
}
|
|
|
|
|
return fileId;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for(const EdgeEntry & e : edgesList)
|
|
|
|
|
{
|
|
|
|
|
int from = remap(e.from);
|
|
|
|
|
int to = e.isPrior ? from : remap(e.to);
|
|
|
|
|
Link::Type type = e.type;
|
|
|
|
|
// Promote ambiguous edges (EDGE_SE2) to kLandmark when an endpoint
|
|
|
|
|
// turns out to be a landmark after remapping.
|
|
|
|
|
if(type == Link::kUndef && (from < 0 || to < 0))
|
|
|
|
|
{
|
|
|
|
|
type = Link::kLandmark;
|
|
|
|
|
}
|
|
|
|
|
edgeConstraints.insert(std::make_pair(from, Link(from, to, type, e.transform, e.info)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UINFO("Graph loaded from %s (%d poses, %d edges, landmarkOffset=%d)",
|
|
|
|
|
fileName.c_str(), (int)poses.size(), (int)edgeConstraints.size(), landmarkOffset);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
bool OptimizerG2O::saveGraph(
|
|
|
|
|
const std::string & fileName,
|
|
|
|
|
const std::map<int, Transform> & poses,
|
2018-10-15 13:41:44 -04:00
|
|
|
const std::multimap<int, Link> & edgeConstraints)
|
2015-11-27 11:09:39 -05:00
|
|
|
{
|
|
|
|
|
FILE * file = 0;
|
|
|
|
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
|
fopen_s(&file, fileName.c_str(), "w");
|
|
|
|
|
#else
|
|
|
|
|
file = fopen(fileName.c_str(), "w");
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if(file)
|
|
|
|
|
{
|
2018-10-15 16:58:57 +01:00
|
|
|
// force periods to be used instead of commas
|
|
|
|
|
setlocale(LC_ALL, "en_US.UTF-8");
|
|
|
|
|
|
2018-10-15 13:41:44 -04:00
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
// PARAMS_SE2OFFSET id x y theta (set for priors)
|
|
|
|
|
fprintf(file, "PARAMS_SE2OFFSET %d 0 0 0\n", PARAM_OFFSET);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// PARAMS_SE3OFFSET id x y z qw qx qy qz (set for priors)
|
|
|
|
|
Eigen::Vector3f v = Eigen::Vector3f::Zero();
|
|
|
|
|
Eigen::Quaternionf q = Eigen::Quaternionf::Identity();
|
|
|
|
|
fprintf(file, "PARAMS_SE3OFFSET %d %f %f %f %f %f %f %f\n",
|
|
|
|
|
PARAM_OFFSET,
|
|
|
|
|
v.x(),
|
|
|
|
|
v.y(),
|
|
|
|
|
v.z(),
|
|
|
|
|
q.x(),
|
|
|
|
|
q.y(),
|
|
|
|
|
q.z(),
|
|
|
|
|
q.w());
|
|
|
|
|
}
|
2018-10-15 16:58:57 +01:00
|
|
|
|
2024-01-20 12:04:26 -08:00
|
|
|
// For landmarks, determinate which one has observation with orientation
|
|
|
|
|
std::map<int, bool> isLandmarkWithRotation;
|
|
|
|
|
for(std::multimap<int, Link>::const_iterator iter = edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
|
|
|
|
|
{
|
|
|
|
|
int landmarkId = iter->second.from() < 0?iter->second.from():iter->second.to() < 0?iter->second.to():0;
|
|
|
|
|
if(landmarkId != 0 && isLandmarkWithRotation.find(landmarkId) == isLandmarkWithRotation.end())
|
|
|
|
|
{
|
|
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
|
|
|
|
if (1 / static_cast<double>(iter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
|
|
|
|
{
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(landmarkId, false));
|
|
|
|
|
UDEBUG("Tag %d has no orientation", landmarkId);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(landmarkId, true));
|
|
|
|
|
UDEBUG("Tag %d has orientation", landmarkId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (1 / static_cast<double>(iter->second.infMatrix().at<double>(3,3)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(iter->second.infMatrix().at<double>(4,4)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(iter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
|
|
|
|
{
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(landmarkId, false));
|
|
|
|
|
UDEBUG("Tag %d has no orientation", landmarkId);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
isLandmarkWithRotation.insert(std::make_pair(landmarkId, true));
|
|
|
|
|
UDEBUG("Tag %d has orientation", landmarkId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int landmarkOffset = poses.size()&&poses.rbegin()->first>0?poses.rbegin()->first:0;
|
2015-11-27 11:09:39 -05:00
|
|
|
for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
|
|
|
|
|
{
|
2018-10-15 13:41:44 -04:00
|
|
|
if (isSlam2d())
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(iter->first > 0)
|
|
|
|
|
{
|
|
|
|
|
// VERTEX_SE2 id x y theta
|
|
|
|
|
fprintf(file, "VERTEX_SE2 %d %f %f %f\n",
|
2024-01-20 12:04:26 -08:00
|
|
|
iter->first,
|
2018-12-07 18:29:41 -05:00
|
|
|
iter->second.x(),
|
|
|
|
|
iter->second.y(),
|
|
|
|
|
iter->second.theta());
|
|
|
|
|
}
|
|
|
|
|
else if(!landmarksIgnored())
|
|
|
|
|
{
|
2024-01-20 12:04:26 -08:00
|
|
|
if(uValue(isLandmarkWithRotation, iter->first, false))
|
|
|
|
|
{
|
|
|
|
|
// VERTEX_SE2 id x y theta
|
|
|
|
|
fprintf(file, "VERTEX_SE2 %d %f %f %f\n",
|
|
|
|
|
landmarkOffset-iter->first,
|
|
|
|
|
iter->second.x(),
|
|
|
|
|
iter->second.y(),
|
|
|
|
|
iter->second.theta());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// VERTEX_XY id x y
|
|
|
|
|
fprintf(file, "VERTEX_XY %d %f %f\n",
|
|
|
|
|
landmarkOffset-iter->first,
|
|
|
|
|
iter->second.x(),
|
|
|
|
|
iter->second.y());
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
2018-10-15 13:41:44 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if(iter->first > 0)
|
|
|
|
|
{
|
|
|
|
|
// VERTEX_SE3 id x y z qw qx qy qz
|
|
|
|
|
Eigen::Quaternionf q = iter->second.getQuaternionf();
|
|
|
|
|
fprintf(file, "VERTEX_SE3:QUAT %d %f %f %f %f %f %f %f\n",
|
|
|
|
|
iter->first,
|
|
|
|
|
iter->second.x(),
|
|
|
|
|
iter->second.y(),
|
|
|
|
|
iter->second.z(),
|
|
|
|
|
q.x(),
|
|
|
|
|
q.y(),
|
|
|
|
|
q.z(),
|
|
|
|
|
q.w());
|
|
|
|
|
}
|
|
|
|
|
else if(!landmarksIgnored())
|
|
|
|
|
{
|
2024-01-20 12:04:26 -08:00
|
|
|
if(uValue(isLandmarkWithRotation, iter->first, false))
|
|
|
|
|
{
|
|
|
|
|
// VERTEX_SE3 id x y z qw qx qy qz
|
|
|
|
|
Eigen::Quaternionf q = iter->second.getQuaternionf();
|
|
|
|
|
fprintf(file, "VERTEX_SE3:QUAT %d %f %f %f %f %f %f %f\n",
|
|
|
|
|
landmarkOffset-iter->first,
|
|
|
|
|
iter->second.x(),
|
|
|
|
|
iter->second.y(),
|
|
|
|
|
iter->second.z(),
|
|
|
|
|
q.x(),
|
|
|
|
|
q.y(),
|
|
|
|
|
q.z(),
|
|
|
|
|
q.w());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// VERTEX_TRACKXYZ id x y z
|
|
|
|
|
fprintf(file, "VERTEX_TRACKXYZ %d %f %f %f\n",
|
|
|
|
|
landmarkOffset-iter->first,
|
|
|
|
|
iter->second.x(),
|
|
|
|
|
iter->second.y(),
|
|
|
|
|
iter->second.z());
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
2018-10-15 13:41:44 -04:00
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
|
2018-12-07 18:29:41 -05:00
|
|
|
int virtualVertexId = landmarkOffset - (poses.size()&&poses.rbegin()->first<0?poses.rbegin()->first:0);
|
2015-11-27 11:09:39 -05:00
|
|
|
for(std::multimap<int, Link>::const_iterator iter = edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
|
|
|
|
|
{
|
2018-12-07 18:29:41 -05:00
|
|
|
if (iter->second.type() == Link::kLandmark)
|
|
|
|
|
{
|
|
|
|
|
if (this->landmarksIgnored())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
2024-01-20 12:04:26 -08:00
|
|
|
if(uValue(isLandmarkWithRotation, iter->first, false))
|
|
|
|
|
{
|
|
|
|
|
// EDGE_SE2 observed_vertex_id observing_vertex_id x y qx qy qz qw inf_11 inf_12 inf_13 inf_22 inf_23 inf_33
|
|
|
|
|
fprintf(file, "EDGE_SE2 %d %d %f %f %f %f %f %f %f %f %f\n",
|
|
|
|
|
iter->second.from()<0?landmarkOffset-iter->second.from():iter->second.from(),
|
|
|
|
|
iter->second.to()<0?landmarkOffset-iter->second.to():iter->second.to(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.transform().theta(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(5, 5));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// EDGE_SE2_XY observed_vertex_id observing_vertex_id x y inf_11 inf_12 inf_22
|
|
|
|
|
fprintf(file, "EDGE_SE2_XY %d %d %f %f %f %f %f\n",
|
|
|
|
|
iter->second.from()<0?landmarkOffset-iter->second.from():iter->second.from(),
|
|
|
|
|
iter->second.to()<0?landmarkOffset-iter->second.to():iter->second.to(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1));
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-20 12:04:26 -08:00
|
|
|
if(uValue(isLandmarkWithRotation, iter->first, false))
|
|
|
|
|
{
|
|
|
|
|
// EDGE_SE3 observed_vertex_id observing_vertex_id x y z qx qy qz qw inf_11 inf_12 .. inf_16 inf_22 .. inf_66
|
|
|
|
|
Eigen::Quaternionf q = iter->second.transform().getQuaternionf();
|
|
|
|
|
fprintf(file, "EDGE_SE3 %d %d %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
|
|
|
|
|
iter->second.from()<0?landmarkOffset-iter->second.from():iter->second.from(),
|
|
|
|
|
iter->second.to()<0?landmarkOffset-iter->second.to():iter->second.to(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.transform().z(),
|
|
|
|
|
q.x(),
|
|
|
|
|
q.y(),
|
|
|
|
|
q.z(),
|
|
|
|
|
q.w(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(3, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(3, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(3, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(4, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(4, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(5, 5));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// EDGE_SE3_TRACKXYZ observed_vertex_id observing_vertex_id param_offset x y z inf_11 inf_12 inf_13 inf_22 inf_23 inf_33
|
|
|
|
|
fprintf(file, "EDGE_SE3_TRACKXYZ %d %d %d %f %f %f %f %f %f %f %f %f\n",
|
|
|
|
|
iter->second.from()<0?landmarkOffset-iter->second.from():iter->second.from(),
|
|
|
|
|
iter->second.to()<0?landmarkOffset-iter->second.to():iter->second.to(),
|
|
|
|
|
PARAM_OFFSET,
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.transform().z(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 2));
|
|
|
|
|
}
|
2018-12-07 18:29:41 -05:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 13:41:44 -04:00
|
|
|
std::string prefix = isSlam2d()? "EDGE_SE2" :"EDGE_SE3:QUAT";
|
2015-11-27 11:09:39 -05:00
|
|
|
std::string suffix = "";
|
2018-10-15 16:58:57 +01:00
|
|
|
std::string to = uFormat(" %d", iter->second.to());
|
2015-11-27 11:09:39 -05:00
|
|
|
|
2019-01-03 15:49:25 -05:00
|
|
|
bool isSE2 = true;
|
|
|
|
|
bool isSE3 = true;
|
2018-12-27 13:22:15 +00:00
|
|
|
|
2019-05-31 15:36:35 -04:00
|
|
|
if (iter->second.type() == Link::kGravity)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if (iter->second.type() == Link::kPosePrior)
|
2018-10-15 13:41:44 -04:00
|
|
|
{
|
|
|
|
|
if (this->priorsIgnored())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-01-03 15:49:25 -05:00
|
|
|
if (isSlam2d())
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
if (1 / static_cast<double>(iter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
2019-01-03 15:49:25 -05:00
|
|
|
{
|
|
|
|
|
prefix = "EDGE_PRIOR_SE2_XY";
|
|
|
|
|
isSE2 = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
prefix = "EDGE_PRIOR_SE2";
|
|
|
|
|
}
|
2018-12-27 13:22:15 +00:00
|
|
|
|
2019-01-03 15:49:25 -05:00
|
|
|
// based on https://github.com/RainerKuemmerle/g2o/blob/38347944c6ad7a3b31976b97406ff0de20be1530/g2o/types/slam2d/edge_se2_prior.cpp#L42
|
2018-10-15 13:41:44 -04:00
|
|
|
// there is no pid for the 2d prior case
|
|
|
|
|
to = "";
|
2019-01-03 15:49:25 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-01-06 17:00:39 -05:00
|
|
|
if (1 / static_cast<double>(iter->second.infMatrix().at<double>(3,3)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(iter->second.infMatrix().at<double>(4,4)) >= 9999.0 ||
|
|
|
|
|
1 / static_cast<double>(iter->second.infMatrix().at<double>(5,5)) >= 9999.0)
|
2019-01-03 15:49:25 -05:00
|
|
|
{
|
|
|
|
|
to = "";
|
|
|
|
|
prefix = "EDGE_POINTXYZ_PRIOR";
|
|
|
|
|
isSE3 = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
to = uFormat(" %d", PARAM_OFFSET);
|
|
|
|
|
prefix = "EDGE_SE3_PRIOR";
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-15 13:41:44 -04:00
|
|
|
}
|
|
|
|
|
else if(this->isRobust() &&
|
2015-11-27 11:09:39 -05:00
|
|
|
iter->second.type() != Link::kNeighbor &&
|
|
|
|
|
iter->second.type() != Link::kNeighborMerged)
|
|
|
|
|
{
|
|
|
|
|
fprintf(file, "VERTEX_SWITCH %d 1\n", virtualVertexId);
|
|
|
|
|
fprintf(file, "EDGE_SWITCH_PRIOR %d 1 1.0\n", virtualVertexId);
|
2018-10-15 13:41:44 -04:00
|
|
|
prefix = isSlam2d() ? "EDGE_SE2_SWITCHABLE" : "EDGE_SE3_SWITCHABLE";
|
2015-11-27 11:09:39 -05:00
|
|
|
suffix = uFormat(" %d", virtualVertexId++);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 13:41:44 -04:00
|
|
|
if(isSlam2d())
|
|
|
|
|
{
|
2019-01-03 15:49:25 -05:00
|
|
|
if (isSE2)
|
|
|
|
|
{
|
|
|
|
|
// EDGE_SE2 observed_vertex_id observing_vertex_id x y qx qy qz qw inf_11 inf_12 inf_13 inf_22 inf_23 inf_33
|
|
|
|
|
// EDGE_SE2_PRIOR observed_vertex_id x y qx qy qz qw inf_11 inf_12 inf_13 inf_22 inf_23 inf_33
|
|
|
|
|
fprintf(file, "%s %d%s%s %f %f %f %f %f %f %f %f %f\n",
|
2018-12-27 13:22:15 +00:00
|
|
|
prefix.c_str(),
|
|
|
|
|
iter->second.from(),
|
|
|
|
|
to.c_str(),
|
|
|
|
|
suffix.c_str(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.transform().theta(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(5, 5));
|
2019-01-03 15:49:25 -05:00
|
|
|
}
|
2018-12-27 13:22:15 +00:00
|
|
|
else
|
2019-01-03 15:49:25 -05:00
|
|
|
{
|
|
|
|
|
// EDGE_XY observed_vertex_id observing_vertex_id x y inf_11 inf_12 inf_22
|
|
|
|
|
// EDGE_POINTXY_PRIOR x y inf_11 inf_12 inf_22
|
|
|
|
|
fprintf(file, "%s %d%s%s %f %f %f %f %f\n",
|
2018-12-27 13:22:15 +00:00
|
|
|
prefix.c_str(),
|
|
|
|
|
iter->second.from(),
|
|
|
|
|
to.c_str(),
|
|
|
|
|
suffix.c_str(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1));
|
2019-01-03 15:49:25 -05:00
|
|
|
}
|
2018-10-15 13:41:44 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2019-01-03 15:49:25 -05:00
|
|
|
if (isSE3)
|
|
|
|
|
{
|
|
|
|
|
// EDGE_SE3 observed_vertex_id observing_vertex_id x y z qx qy qz qw inf_11 inf_12 .. inf_16 inf_22 .. inf_66
|
|
|
|
|
// EDGE_SE3_PRIOR observed_vertex_id offset_parameter_id x y z qx qy qz qw inf_11 inf_12 .. inf_16 inf_22 .. inf_66
|
|
|
|
|
Eigen::Quaternionf q = iter->second.transform().getQuaternionf();
|
|
|
|
|
fprintf(file, "%s %d%s%s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
|
2018-12-27 13:22:15 +00:00
|
|
|
prefix.c_str(),
|
|
|
|
|
iter->second.from(),
|
|
|
|
|
to.c_str(),
|
|
|
|
|
suffix.c_str(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.transform().z(),
|
|
|
|
|
q.x(),
|
|
|
|
|
q.y(),
|
|
|
|
|
q.z(),
|
|
|
|
|
q.w(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(3, 3),
|
|
|
|
|
iter->second.infMatrix().at<double>(3, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(3, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(4, 4),
|
|
|
|
|
iter->second.infMatrix().at<double>(4, 5),
|
|
|
|
|
iter->second.infMatrix().at<double>(5, 5));
|
2019-01-03 15:49:25 -05:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// EDGE_XYZ observed_vertex_id observing_vertex_id x y z qx qy qz qw inf_11 inf_12 .. inf_13 inf_22 .. inf_33
|
|
|
|
|
// EDGE_POINTXYZ_PRIOR observed_vertex_id x y z inf_11 inf_12 .. inf_13 inf_22 .. inf_33
|
|
|
|
|
fprintf(file, "%s %d%s%s %f %f %f %f %f %f %f %f %f\n",
|
2018-12-27 13:22:15 +00:00
|
|
|
prefix.c_str(),
|
|
|
|
|
iter->second.from(),
|
|
|
|
|
to.c_str(),
|
|
|
|
|
suffix.c_str(),
|
|
|
|
|
iter->second.transform().x(),
|
|
|
|
|
iter->second.transform().y(),
|
|
|
|
|
iter->second.transform().z(),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 0),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(0, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 1),
|
|
|
|
|
iter->second.infMatrix().at<double>(1, 2),
|
|
|
|
|
iter->second.infMatrix().at<double>(2, 2));
|
2019-01-03 15:49:25 -05:00
|
|
|
}
|
2018-10-15 13:41:44 -04:00
|
|
|
}
|
2015-11-27 11:09:39 -05:00
|
|
|
}
|
|
|
|
|
UINFO("Graph saved to %s", fileName.c_str());
|
2018-10-15 16:58:57 +01:00
|
|
|
|
2015-11-27 11:09:39 -05:00
|
|
|
fclose(file);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UERROR("Cannot save to file %s", fileName.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} /* namespace rtabmap */
|