00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 #include "rtabmap/core/Graph.h"
00028
00029 #include <rtabmap/utilite/ULogger.h>
00030 #include <rtabmap/utilite/UStl.h>
00031 #include <rtabmap/utilite/UMath.h>
00032 #include <rtabmap/utilite/UConversion.h>
00033 #include <rtabmap/utilite/UTimer.h>
00034 #include <set>
00035
00036 #include <rtabmap/core/OptimizerGTSAM.h>
00037
00038 #ifdef RTABMAP_GTSAM
00039 #include <gtsam/geometry/Pose2.h>
00040 #include <gtsam/geometry/Pose3.h>
00041 #include <gtsam/inference/Key.h>
00042 #include <gtsam/inference/Symbol.h>
00043 #include <gtsam/slam/PriorFactor.h>
00044 #include <gtsam/slam/BetweenFactor.h>
00045 #include <gtsam/nonlinear/NonlinearFactorGraph.h>
00046 #include <gtsam/nonlinear/GaussNewtonOptimizer.h>
00047 #include <gtsam/nonlinear/DoglegOptimizer.h>
00048 #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
00049 #include <gtsam/nonlinear/NonlinearOptimizer.h>
00050 #include <gtsam/nonlinear/Marginals.h>
00051 #include <gtsam/nonlinear/Values.h>
00052
00053 #ifdef RTABMAP_VERTIGO
00054 #include "vertigo/gtsam/betweenFactorMaxMix.h"
00055 #include "vertigo/gtsam/betweenFactorSwitchable.h"
00056 #include "vertigo/gtsam/switchVariableLinear.h"
00057 #include "vertigo/gtsam/switchVariableSigmoid.h"
00058 #endif
00059 #endif // end RTABMAP_GTSAM
00060
00061 namespace rtabmap {
00062
00063 bool OptimizerGTSAM::available()
00064 {
00065 #ifdef RTABMAP_GTSAM
00066 return true;
00067 #else
00068 return false;
00069 #endif
00070 }
00071
00072 void OptimizerGTSAM::parseParameters(const ParametersMap & parameters)
00073 {
00074 Optimizer::parseParameters(parameters);
00075 Parameters::parse(parameters, Parameters::kGTSAMOptimizer(), optimizer_);
00076 }
00077
00078 std::map<int, Transform> OptimizerGTSAM::optimize(
00079 int rootId,
00080 const std::map<int, Transform> & poses,
00081 const std::multimap<int, Link> & edgeConstraints,
00082 cv::Mat & outputCovariance,
00083 std::list<std::map<int, Transform> > * intermediateGraphes,
00084 double * finalError,
00085 int * iterationsDone)
00086 {
00087 outputCovariance = cv::Mat::eye(6,6,CV_64FC1);
00088 std::map<int, Transform> optimizedPoses;
00089 #ifdef RTABMAP_GTSAM
00090
00091 #ifndef RTABMAP_VERTIGO
00092 if(this->isRobust())
00093 {
00094 UWARN("Vertigo robust optimization is not available! Robust optimization is now disabled.");
00095 setRobust(false);
00096 }
00097 #endif
00098
00099 UDEBUG("Optimizing graph...");
00100 if(edgeConstraints.size()>=1 && poses.size()>=2 && iterations() > 0)
00101 {
00102 gtsam::NonlinearFactorGraph graph;
00103
00104
00105 if(!priorsIgnored())
00106 {
00107 for(std::multimap<int, Link>::const_iterator iter=edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
00108 {
00109 if(iter->second.from() == iter->second.to())
00110 {
00111 rootId = 0;
00112 break;
00113 }
00114 }
00115 }
00116
00117
00118 if(rootId != 0)
00119 {
00120 UASSERT(uContains(poses, rootId));
00121 const Transform & initialPose = poses.at(rootId);
00122 if(isSlam2d())
00123 {
00124 gtsam::noiseModel::Diagonal::shared_ptr priorNoise = gtsam::noiseModel::Diagonal::Variances(gtsam::Vector3(0.01, 0.01, 0.01));
00125 graph.add(gtsam::PriorFactor<gtsam::Pose2>(rootId, gtsam::Pose2(initialPose.x(), initialPose.y(), initialPose.theta()), priorNoise));
00126 }
00127 else
00128 {
00129 gtsam::noiseModel::Diagonal::shared_ptr priorNoise = gtsam::noiseModel::Diagonal::Variances((gtsam::Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-4).finished());
00130 graph.add(gtsam::PriorFactor<gtsam::Pose3>(rootId, gtsam::Pose3(initialPose.toEigen4d()), priorNoise));
00131 }
00132 }
00133
00134 UDEBUG("fill poses to gtsam...");
00135 gtsam::Values initialEstimate;
00136 for(std::map<int, Transform>::const_iterator iter = poses.begin(); iter!=poses.end(); ++iter)
00137 {
00138 UASSERT(!iter->second.isNull());
00139 if(isSlam2d())
00140 {
00141 initialEstimate.insert(iter->first, gtsam::Pose2(iter->second.x(), iter->second.y(), iter->second.theta()));
00142 }
00143 else
00144 {
00145 initialEstimate.insert(iter->first, gtsam::Pose3(iter->second.toEigen4d()));
00146 }
00147 }
00148
00149 UDEBUG("fill edges to gtsam...");
00150 int switchCounter = poses.rbegin()->first+1;
00151 for(std::multimap<int, Link>::const_iterator iter=edgeConstraints.begin(); iter!=edgeConstraints.end(); ++iter)
00152 {
00153 int id1 = iter->second.from();
00154 int id2 = iter->second.to();
00155 UASSERT(!iter->second.transform().isNull());
00156 if(id1 == id2)
00157 {
00158 if(!priorsIgnored())
00159 {
00160 if(isSlam2d())
00161 {
00162 Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
00163 if(!isCovarianceIgnored())
00164 {
00165 information(0,0) = iter->second.infMatrix().at<double>(0,0);
00166 information(0,1) = iter->second.infMatrix().at<double>(0,1);
00167 information(0,2) = iter->second.infMatrix().at<double>(0,5);
00168 information(1,0) = iter->second.infMatrix().at<double>(1,0);
00169 information(1,1) = iter->second.infMatrix().at<double>(1,1);
00170 information(1,2) = iter->second.infMatrix().at<double>(1,5);
00171 information(2,0) = iter->second.infMatrix().at<double>(5,0);
00172 information(2,1) = iter->second.infMatrix().at<double>(5,1);
00173 information(2,2) = iter->second.infMatrix().at<double>(5,5);
00174 }
00175
00176 gtsam::noiseModel::Gaussian::shared_ptr model = gtsam::noiseModel::Gaussian::Information(information);
00177 graph.add(gtsam::PriorFactor<gtsam::Pose2>(id1, gtsam::Pose2(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta()), model));
00178 }
00179 else
00180 {
00181 Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity();
00182 if(!isCovarianceIgnored())
00183 {
00184 memcpy(information.data(), iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
00185 }
00186
00187 Eigen::Matrix<double, 6, 6> mgtsam = Eigen::Matrix<double, 6, 6>::Identity();
00188 mgtsam.block(0,0,3,3) = information.block(3,3,3,3);
00189 mgtsam.block(3,3,3,3) = information.block(0,0,3,3);
00190 mgtsam.block(0,3,3,3) = information.block(0,3,3,3);
00191 mgtsam.block(3,0,3,3) = information.block(3,0,3,3);
00192 gtsam::SharedNoiseModel model = gtsam::noiseModel::Gaussian::Information(mgtsam);
00193
00194 graph.add(gtsam::PriorFactor<gtsam::Pose3>(id1, gtsam::Pose3(iter->second.transform().toEigen4d()), model));
00195 }
00196 }
00197 }
00198 else
00199 {
00200 #ifdef RTABMAP_VERTIGO
00201 if(this->isRobust() &&
00202 iter->second.type() != Link::kNeighbor &&
00203 iter->second.type() != Link::kNeighborMerged &&
00204 iter->second.type() != Link::kPosePrior)
00205 {
00206
00207
00208
00209
00210
00211 double prior = 1.0;
00212 initialEstimate.insert(gtsam::Symbol('s',switchCounter), vertigo::SwitchVariableLinear(prior));
00213
00214
00215
00216
00217
00218
00219 gtsam::noiseModel::Diagonal::shared_ptr switchPriorModel = gtsam::noiseModel::Diagonal::Sigmas(gtsam::Vector1(1.0));
00220 graph.add(gtsam::PriorFactor<vertigo::SwitchVariableLinear> (gtsam::Symbol('s',switchCounter), vertigo::SwitchVariableLinear(prior), switchPriorModel));
00221 }
00222 #endif
00223
00224 if(isSlam2d())
00225 {
00226 Eigen::Matrix<double, 3, 3> information = Eigen::Matrix<double, 3, 3>::Identity();
00227 if(!isCovarianceIgnored())
00228 {
00229 information(0,0) = iter->second.infMatrix().at<double>(0,0);
00230 information(0,1) = iter->second.infMatrix().at<double>(0,1);
00231 information(0,2) = iter->second.infMatrix().at<double>(0,5);
00232 information(1,0) = iter->second.infMatrix().at<double>(1,0);
00233 information(1,1) = iter->second.infMatrix().at<double>(1,1);
00234 information(1,2) = iter->second.infMatrix().at<double>(1,5);
00235 information(2,0) = iter->second.infMatrix().at<double>(5,0);
00236 information(2,1) = iter->second.infMatrix().at<double>(5,1);
00237 information(2,2) = iter->second.infMatrix().at<double>(5,5);
00238 }
00239 gtsam::noiseModel::Gaussian::shared_ptr model = gtsam::noiseModel::Gaussian::Information(information);
00240
00241 #ifdef RTABMAP_VERTIGO
00242 if(this->isRobust() &&
00243 iter->second.type()!=Link::kNeighbor &&
00244 iter->second.type() != Link::kNeighborMerged)
00245 {
00246
00247 graph.add(vertigo::BetweenFactorSwitchableLinear<gtsam::Pose2>(id1, id2, gtsam::Symbol('s', switchCounter++), gtsam::Pose2(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta()), model));
00248 }
00249 else
00250 #endif
00251 {
00252 graph.add(gtsam::BetweenFactor<gtsam::Pose2>(id1, id2, gtsam::Pose2(iter->second.transform().x(), iter->second.transform().y(), iter->second.transform().theta()), model));
00253 }
00254 }
00255 else
00256 {
00257 Eigen::Matrix<double, 6, 6> information = Eigen::Matrix<double, 6, 6>::Identity();
00258 if(!isCovarianceIgnored())
00259 {
00260 memcpy(information.data(), iter->second.infMatrix().data, iter->second.infMatrix().total()*sizeof(double));
00261 }
00262
00263 Eigen::Matrix<double, 6, 6> mgtsam = Eigen::Matrix<double, 6, 6>::Identity();
00264 mgtsam.block(0,0,3,3) = information.block(3,3,3,3);
00265 mgtsam.block(3,3,3,3) = information.block(0,0,3,3);
00266 mgtsam.block(0,3,3,3) = information.block(0,3,3,3);
00267 mgtsam.block(3,0,3,3) = information.block(3,0,3,3);
00268 gtsam::SharedNoiseModel model = gtsam::noiseModel::Gaussian::Information(mgtsam);
00269
00270 #ifdef RTABMAP_VERTIGO
00271 if(this->isRobust() &&
00272 iter->second.type() != Link::kNeighbor &&
00273 iter->second.type() != Link::kNeighborMerged &&
00274 iter->second.type() != Link::kPosePrior)
00275 {
00276
00277 graph.add(vertigo::BetweenFactorSwitchableLinear<gtsam::Pose3>(id1, id2, gtsam::Symbol('s', switchCounter++), gtsam::Pose3(iter->second.transform().toEigen4d()), model));
00278 }
00279 else
00280 #endif
00281 {
00282 graph.add(gtsam::BetweenFactor<gtsam::Pose3>(id1, id2, gtsam::Pose3(iter->second.transform().toEigen4d()), model));
00283 }
00284 }
00285 }
00286 }
00287
00288 UDEBUG("create optimizer");
00289 gtsam::NonlinearOptimizer * optimizer;
00290
00291 if(optimizer_ == 2)
00292 {
00293 gtsam::DoglegParams parameters;
00294 parameters.relativeErrorTol = epsilon();
00295 parameters.maxIterations = iterations();
00296 optimizer = new gtsam::DoglegOptimizer(graph, initialEstimate, parameters);
00297 }
00298 else if(optimizer_ == 1)
00299 {
00300 gtsam::GaussNewtonParams parameters;
00301 parameters.relativeErrorTol = epsilon();
00302 parameters.maxIterations = iterations();
00303 optimizer = new gtsam::GaussNewtonOptimizer(graph, initialEstimate, parameters);
00304 }
00305 else
00306 {
00307 gtsam::LevenbergMarquardtParams parameters;
00308 parameters.relativeErrorTol = epsilon();
00309 parameters.maxIterations = iterations();
00310 optimizer = new gtsam::LevenbergMarquardtOptimizer(graph, initialEstimate, parameters);
00311 }
00312
00313 UINFO("GTSAM optimizing begin (max iterations=%d, robust=%d)", iterations(), isRobust()?1:0);
00314 UTimer timer;
00315 int it = 0;
00316 double lastError = optimizer->error();
00317 for(int i=0; i<iterations(); ++i)
00318 {
00319 if(intermediateGraphes && i > 0)
00320 {
00321 std::map<int, Transform> tmpPoses;
00322 for(gtsam::Values::const_iterator iter=optimizer->values().begin(); iter!=optimizer->values().end(); ++iter)
00323 {
00324 if(iter->value.dim() > 1)
00325 {
00326 if(isSlam2d())
00327 {
00328 gtsam::Pose2 p = iter->value.cast<gtsam::Pose2>();
00329 tmpPoses.insert(std::make_pair((int)iter->key, Transform(p.x(), p.y(), p.theta())));
00330 }
00331 else
00332 {
00333 gtsam::Pose3 p = iter->value.cast<gtsam::Pose3>();
00334 tmpPoses.insert(std::make_pair((int)iter->key, Transform::fromEigen4d(p.matrix())));
00335 }
00336 }
00337 }
00338 intermediateGraphes->push_back(tmpPoses);
00339 }
00340 try
00341 {
00342 optimizer->iterate();
00343 ++it;
00344 }
00345 catch(gtsam::IndeterminantLinearSystemException & e)
00346 {
00347 UERROR("GTSAM exception caught: %s", e.what());
00348 delete optimizer;
00349 return optimizedPoses;
00350 }
00351
00352
00353 double error = optimizer->error();
00354 UDEBUG("iteration %d error =%f", i+1, error);
00355 double errorDelta = lastError - error;
00356 if(i>0 && errorDelta < this->epsilon())
00357 {
00358 if(errorDelta < 0)
00359 {
00360 UDEBUG("Negative improvement?! Ignore and continue optimizing... (%f < %f)", errorDelta, this->epsilon());
00361 }
00362 else
00363 {
00364 UINFO("Stop optimizing, not enough improvement (%f < %f)", errorDelta, this->epsilon());
00365 break;
00366 }
00367 }
00368 else if(i==0 && error < this->epsilon())
00369 {
00370 UINFO("Stop optimizing, error is already under epsilon (%f < %f)", error, this->epsilon());
00371 break;
00372 }
00373 lastError = error;
00374 }
00375 if(finalError)
00376 {
00377 *finalError = lastError;
00378 }
00379 if(iterationsDone)
00380 {
00381 *iterationsDone = it;
00382 }
00383 UINFO("GTSAM optimizing end (%d iterations done, error=%f (initial=%f final=%f), time=%f s)",
00384 optimizer->iterations(), optimizer->error(), graph.error(initialEstimate), graph.error(optimizer->values()), timer.ticks());
00385
00386 gtsam::Marginals marginals(graph, optimizer->values());
00387 for(gtsam::Values::const_iterator iter=optimizer->values().begin(); iter!=optimizer->values().end(); ++iter)
00388 {
00389 if(iter->value.dim() > 1)
00390 {
00391 if(isSlam2d())
00392 {
00393 gtsam::Pose2 p = iter->value.cast<gtsam::Pose2>();
00394 optimizedPoses.insert(std::make_pair((int)iter->key, Transform(p.x(), p.y(), p.theta())));
00395 }
00396 else
00397 {
00398 gtsam::Pose3 p = iter->value.cast<gtsam::Pose3>();
00399 optimizedPoses.insert(std::make_pair((int)iter->key, Transform::fromEigen4d(p.matrix())));
00400 }
00401 }
00402 }
00403
00404
00405 try {
00406 UTimer t;
00407 gtsam::Marginals marginals(graph, optimizer->values());
00408 gtsam::Matrix info = marginals.marginalCovariance(optimizer->values().rbegin()->key);
00409 UINFO("Computed marginals = %fs (key=%d)", t.ticks(), optimizer->values().rbegin()->key);
00410 if(isSlam2d())
00411 {
00412 UASSERT(info.cols() == 3 && info.cols() == 3);
00413 outputCovariance.at<double>(0,0) = info(0,0);
00414 outputCovariance.at<double>(0,1) = info(0,1);
00415 outputCovariance.at<double>(0,5) = info(0,2);
00416 outputCovariance.at<double>(1,0) = info(1,0);
00417 outputCovariance.at<double>(1,1) = info(1,1);
00418 outputCovariance.at<double>(1,5) = info(1,2);
00419 outputCovariance.at<double>(5,0) = info(2,0);
00420 outputCovariance.at<double>(5,1) = info(2,1);
00421 outputCovariance.at<double>(5,5) = info(2,2);
00422 }
00423 else
00424 {
00425 UASSERT(info.cols() == 6 && info.cols() == 6);
00426 Eigen::Matrix<double, 6, 6> mgtsam = Eigen::Matrix<double, 6, 6>::Identity();
00427 mgtsam.block(3,3,3,3) = info.block(0,0,3,3);
00428 mgtsam.block(0,0,3,3) = info.block(3,3,3,3);
00429 mgtsam.block(0,3,3,3) = info.block(0,3,3,3);
00430 mgtsam.block(3,0,3,3) = info.block(3,0,3,3);
00431 memcpy(outputCovariance.data, mgtsam.data(), outputCovariance.total()*sizeof(double));
00432 }
00433 } catch(std::exception& e) {
00434 cout << e.what() << endl;
00435 }
00436
00437 delete optimizer;
00438 }
00439 else if(poses.size() == 1 || iterations() <= 0)
00440 {
00441 optimizedPoses = poses;
00442 }
00443 else
00444 {
00445 UWARN("This method should be called at least with 1 pose!");
00446 }
00447 UDEBUG("Optimizing graph...end!");
00448 #else
00449 UERROR("Not built with GTSAM support!");
00450 #endif
00451 return optimizedPoses;
00452 }
00453
00454 }