491 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			491 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
| /* ----------------------------------------------------------------------------
 | |
|  
 | |
|  * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 | |
|  * Atlanta, Georgia 30332-0415
 | |
|  * All Rights Reserved
 | |
|  * Authors: Frank Dellaert, et al. (see THANKS for the full author list)
 | |
|  
 | |
|  * See LICENSE for the license information
 | |
|  
 | |
|  * -------------------------------------------------------------------------- */
 | |
| 
 | |
| /**
 | |
|  * @file   SmartProjectionFactor.h
 | |
|  * @brief  Base class to create smart factors on poses or cameras
 | |
|  * @author Luca Carlone
 | |
|  * @author Zsolt Kira
 | |
|  * @author Frank Dellaert
 | |
|  */
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <gtsam/slam/SmartFactorBase.h>
 | |
| 
 | |
| #include <gtsam/geometry/triangulation.h>
 | |
| #include <gtsam/inference/Symbol.h>
 | |
| #include <gtsam/slam/dataset.h>
 | |
| 
 | |
| #include <boost/optional.hpp>
 | |
| #include <boost/make_shared.hpp>
 | |
| #include <vector>
 | |
| 
 | |
| namespace gtsam {
 | |
| 
 | |
| /// Linearization mode: what factor to linearize to
 | |
| enum LinearizationMode {
 | |
|   HESSIAN, IMPLICIT_SCHUR, JACOBIAN_Q, JACOBIAN_SVD
 | |
| };
 | |
| 
 | |
| /// How to manage degeneracy
 | |
| enum DegeneracyMode {
 | |
|   IGNORE_DEGENERACY, ZERO_ON_DEGENERACY, HANDLE_INFINITY
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * SmartProjectionFactor: triangulates point and keeps an estimate of it around.
 | |
|  */
 | |
| template<class CAMERA>
 | |
| class SmartProjectionFactor: public SmartFactorBase<CAMERA> {
 | |
| 
 | |
| public:
 | |
| 
 | |
| private:
 | |
|   typedef SmartFactorBase<CAMERA> Base;
 | |
|   typedef SmartProjectionFactor<CAMERA> This;
 | |
|   typedef SmartProjectionFactor<CAMERA> SmartProjectionCameraFactor;
 | |
| 
 | |
| protected:
 | |
| 
 | |
|   LinearizationMode linearizationMode_; ///< How to linearize the factor
 | |
|   DegeneracyMode degeneracyMode_; ///< How to linearize the factor
 | |
| 
 | |
|   /// @name Caching triangulation
 | |
|   /// @{
 | |
|   const TriangulationParameters parameters_;
 | |
|   mutable TriangulationResult result_; ///< result from triangulateSafe
 | |
| 
 | |
|   const double retriangulationThreshold_; ///< threshold to decide whether to re-triangulate
 | |
|   mutable std::vector<Pose3> cameraPosesTriangulation_; ///< current triangulation poses
 | |
|   /// @}
 | |
| 
 | |
|   /// @name Parameters governing how triangulation result is treated
 | |
|   /// @{
 | |
|   const bool throwCheirality_; ///< If true, rethrows Cheirality exceptions (default: false)
 | |
|   const bool verboseCheirality_; ///< If true, prints text for Cheirality exceptions (default: false)
 | |
|   /// @}
 | |
| 
 | |
| public:
 | |
| 
 | |
|   /// shorthand for a smart pointer to a factor
 | |
|   typedef boost::shared_ptr<This> shared_ptr;
 | |
| 
 | |
|   /// shorthand for a set of cameras
 | |
|   typedef CameraSet<CAMERA> Cameras;
 | |
| 
 | |
|   /**
 | |
|    * Constructor
 | |
|    * @param rankTol tolerance used to check if point triangulation is degenerate
 | |
|    * @param linThreshold threshold on relative pose changes used to decide whether to relinearize (selective relinearization)
 | |
|    * @param manageDegeneracy is true, in presence of degenerate triangulation, the factor is converted to a rotation-only constraint,
 | |
|    * otherwise the factor is simply neglected
 | |
|    * @param enableEPI if set to true linear triangulation is refined with embedded LM iterations
 | |
|    */
 | |
|   SmartProjectionFactor(LinearizationMode linearizationMode = HESSIAN,
 | |
|       double rankTolerance = 1, DegeneracyMode degeneracyMode =
 | |
|           IGNORE_DEGENERACY, bool enableEPI = false,
 | |
|       double landmarkDistanceThreshold = 1e10,
 | |
|       double dynamicOutlierRejectionThreshold = -1) :
 | |
|       linearizationMode_(linearizationMode), //
 | |
|       degeneracyMode_(degeneracyMode), //
 | |
|       parameters_(rankTolerance, enableEPI, landmarkDistanceThreshold,
 | |
|           dynamicOutlierRejectionThreshold), //
 | |
|       result_(TriangulationResult::Degenerate()), //
 | |
|       retriangulationThreshold_(1e-5), //
 | |
|       throwCheirality_(false), verboseCheirality_(false) {
 | |
|   }
 | |
| 
 | |
|   /** Virtual destructor */
 | |
|   virtual ~SmartProjectionFactor() {
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * print
 | |
|    * @param s optional string naming the factor
 | |
|    * @param keyFormatter optional formatter useful for printing Symbols
 | |
|    */
 | |
|   void print(const std::string& s = "", const KeyFormatter& keyFormatter =
 | |
|       DefaultKeyFormatter) const {
 | |
|     std::cout << s << "SmartProjectionFactor\n";
 | |
|     std::cout << "linearizationMode:\n" << linearizationMode_ << std::endl;
 | |
|     std::cout << "triangulationParameters:\n" << parameters_ << std::endl;
 | |
|     std::cout << "result:\n" << result_ << std::endl;
 | |
|     Base::print("", keyFormatter);
 | |
|   }
 | |
| 
 | |
|   /// equals
 | |
|   virtual bool equals(const NonlinearFactor& p, double tol = 1e-9) const {
 | |
|     const This *e = dynamic_cast<const This*>(&p);
 | |
|     return e && linearizationMode_ == e->linearizationMode_
 | |
|         && Base::equals(p, tol);
 | |
|   }
 | |
| 
 | |
|   /// Check if the new linearization point is the same as the one used for previous triangulation
 | |
|   bool decideIfTriangulate(const Cameras& cameras) const {
 | |
|     // several calls to linearize will be done from the same linearization point, hence it is not needed to re-triangulate
 | |
|     // Note that this is not yet "selecting linearization", that will come later, and we only check if the
 | |
|     // current linearization is the "same" (up to tolerance) w.r.t. the last time we triangulated the point
 | |
| 
 | |
|     size_t m = cameras.size();
 | |
| 
 | |
|     bool retriangulate = false;
 | |
| 
 | |
|     // if we do not have a previous linearization point or the new linearization point includes more poses
 | |
|     if (cameraPosesTriangulation_.empty()
 | |
|         || cameras.size() != cameraPosesTriangulation_.size())
 | |
|       retriangulate = true;
 | |
| 
 | |
|     if (!retriangulate) {
 | |
|       for (size_t i = 0; i < cameras.size(); i++) {
 | |
|         if (!cameras[i].pose().equals(cameraPosesTriangulation_[i],
 | |
|             retriangulationThreshold_)) {
 | |
|           retriangulate = true; // at least two poses are different, hence we retriangulate
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (retriangulate) { // we store the current poses used for triangulation
 | |
|       cameraPosesTriangulation_.clear();
 | |
|       cameraPosesTriangulation_.reserve(m);
 | |
|       for (size_t i = 0; i < m; i++)
 | |
|         // cameraPosesTriangulation_[i] = cameras[i].pose();
 | |
|         cameraPosesTriangulation_.push_back(cameras[i].pose());
 | |
|     }
 | |
| 
 | |
|     return retriangulate; // if we arrive to this point all poses are the same and we don't need re-triangulation
 | |
|   }
 | |
| 
 | |
|   /// triangulateSafe
 | |
|   TriangulationResult triangulateSafe(const Cameras& cameras) const {
 | |
| 
 | |
|     size_t m = cameras.size();
 | |
|     if (m < 2) // if we have a single pose the corresponding factor is uninformative
 | |
|       return TriangulationResult::Degenerate();
 | |
| 
 | |
|     bool retriangulate = decideIfTriangulate(cameras);
 | |
|     if (retriangulate)
 | |
|       result_ = gtsam::triangulateSafe(cameras, this->measured_, parameters_);
 | |
|     return result_;
 | |
|   }
 | |
| 
 | |
|   /// triangulate
 | |
|   bool triangulateForLinearize(const Cameras& cameras) const {
 | |
|     triangulateSafe(cameras); // imperative, might reset result_
 | |
|     return (result_);
 | |
|   }
 | |
| 
 | |
|   /// linearize returns a Hessianfactor that is an approximation of error(p)
 | |
|   boost::shared_ptr<RegularHessianFactor<Base::Dim> > createHessianFactor(
 | |
|       const Cameras& cameras, const double lambda = 0.0, bool diagonalDamping =
 | |
|           false) const {
 | |
| 
 | |
|     size_t numKeys = this->keys_.size();
 | |
|     // Create structures for Hessian Factors
 | |
|     std::vector<Key> js;
 | |
|     std::vector<Matrix> Gs(numKeys * (numKeys + 1) / 2);
 | |
|     std::vector<Vector> gs(numKeys);
 | |
| 
 | |
|     if (this->measured_.size() != cameras.size()) {
 | |
|       std::cout
 | |
|           << "SmartProjectionHessianFactor: this->measured_.size() inconsistent with input"
 | |
|           << std::endl;
 | |
|       exit(1);
 | |
|     }
 | |
| 
 | |
|     triangulateSafe(cameras);
 | |
| 
 | |
|     if (degeneracyMode_ == ZERO_ON_DEGENERACY && !result_) {
 | |
|       // failed: return"empty" Hessian
 | |
|       BOOST_FOREACH(Matrix& m, Gs)
 | |
|         m = zeros(Base::Dim, Base::Dim);
 | |
|       BOOST_FOREACH(Vector& v, gs)
 | |
|         v = zero(Base::Dim);
 | |
|       return boost::make_shared<RegularHessianFactor<Base::Dim> >(this->keys_,
 | |
|           Gs, gs, 0.0);
 | |
|     }
 | |
| 
 | |
|     // Jacobian could be 3D Point3 OR 2D Unit3, difference is E.cols().
 | |
|     std::vector<typename Base::MatrixZD> Fblocks;
 | |
|     Matrix E;
 | |
|     Vector b;
 | |
|     computeJacobiansWithTriangulatedPoint(Fblocks, E, b, cameras);
 | |
| 
 | |
|     // Whiten using noise model
 | |
|     Base::whitenJacobians(Fblocks, E, b);
 | |
| 
 | |
|     // build augmented hessian
 | |
|     SymmetricBlockMatrix augmentedHessian = //
 | |
|         Cameras::SchurComplement(Fblocks, E, b, lambda, diagonalDamping);
 | |
| 
 | |
|     return boost::make_shared<RegularHessianFactor<Base::Dim> >(this->keys_,
 | |
|         augmentedHessian);
 | |
|   }
 | |
| 
 | |
|   // create factor
 | |
|   boost::shared_ptr<RegularImplicitSchurFactor<CAMERA> > createRegularImplicitSchurFactor(
 | |
|       const Cameras& cameras, double lambda) const {
 | |
|     if (triangulateForLinearize(cameras))
 | |
|       return Base::createRegularImplicitSchurFactor(cameras, *result_, lambda);
 | |
|     else
 | |
|       // failed: return empty
 | |
|       return boost::shared_ptr<RegularImplicitSchurFactor<CAMERA> >();
 | |
|   }
 | |
| 
 | |
|   /// create factor
 | |
|   boost::shared_ptr<JacobianFactorQ<Base::Dim, 2> > createJacobianQFactor(
 | |
|       const Cameras& cameras, double lambda) const {
 | |
|     if (triangulateForLinearize(cameras))
 | |
|       return Base::createJacobianQFactor(cameras, *result_, lambda);
 | |
|     else
 | |
|       // failed: return empty
 | |
|       return boost::make_shared<JacobianFactorQ<Base::Dim, 2> >(this->keys_);
 | |
|   }
 | |
| 
 | |
|   /// Create a factor, takes values
 | |
|   boost::shared_ptr<JacobianFactorQ<Base::Dim, 2> > createJacobianQFactor(
 | |
|       const Values& values, double lambda) const {
 | |
|     return createJacobianQFactor(this->cameras(values), lambda);
 | |
|   }
 | |
| 
 | |
|   /// different (faster) way to compute Jacobian factor
 | |
|   boost::shared_ptr<JacobianFactor> createJacobianSVDFactor(
 | |
|       const Cameras& cameras, double lambda) const {
 | |
|     if (triangulateForLinearize(cameras))
 | |
|       return Base::createJacobianSVDFactor(cameras, *result_, lambda);
 | |
|     else
 | |
|       // failed: return empty
 | |
|       return boost::make_shared<JacobianFactorSVD<Base::Dim, 2> >(this->keys_);
 | |
|   }
 | |
| 
 | |
|   /// linearize to a Hessianfactor
 | |
|   virtual boost::shared_ptr<RegularHessianFactor<Base::Dim> > linearizeToHessian(
 | |
|       const Values& values, double lambda = 0.0) const {
 | |
|     return createHessianFactor(this->cameras(values), lambda);
 | |
|   }
 | |
| 
 | |
|   /// linearize to an Implicit Schur factor
 | |
|   virtual boost::shared_ptr<RegularImplicitSchurFactor<CAMERA> > linearizeToImplicit(
 | |
|       const Values& values, double lambda = 0.0) const {
 | |
|     return createRegularImplicitSchurFactor(this->cameras(values), lambda);
 | |
|   }
 | |
| 
 | |
|   /// linearize to a JacobianfactorQ
 | |
|   virtual boost::shared_ptr<JacobianFactorQ<Base::Dim, 2> > linearizeToJacobian(
 | |
|       const Values& values, double lambda = 0.0) const {
 | |
|     return createJacobianQFactor(this->cameras(values), lambda);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Linearize to Gaussian Factor
 | |
|    * @param values Values structure which must contain camera poses for this factor
 | |
|    * @return a Gaussian factor
 | |
|    */
 | |
|   boost::shared_ptr<GaussianFactor> linearizeDamped(const Cameras& cameras,
 | |
|       const double lambda = 0.0) const {
 | |
|     // depending on flag set on construction we may linearize to different linear factors
 | |
|     switch (linearizationMode_) {
 | |
|     case HESSIAN:
 | |
|       return createHessianFactor(cameras, lambda);
 | |
|     case IMPLICIT_SCHUR:
 | |
|       return createRegularImplicitSchurFactor(cameras, lambda);
 | |
|     case JACOBIAN_SVD:
 | |
|       return createJacobianSVDFactor(cameras, lambda);
 | |
|     case JACOBIAN_Q:
 | |
|       return createJacobianQFactor(cameras, lambda);
 | |
|     default:
 | |
|       throw std::runtime_error("SmartFactorlinearize: unknown mode");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Linearize to Gaussian Factor
 | |
|    * @param values Values structure which must contain camera poses for this factor
 | |
|    * @return a Gaussian factor
 | |
|    */
 | |
|   boost::shared_ptr<GaussianFactor> linearizeDamped(const Values& values,
 | |
|       const double lambda = 0.0) const {
 | |
|     // depending on flag set on construction we may linearize to different linear factors
 | |
|     Cameras cameras = this->cameras(values);
 | |
|     return linearizeDamped(cameras, lambda);
 | |
|   }
 | |
| 
 | |
|   /// linearize
 | |
|   virtual boost::shared_ptr<GaussianFactor> linearize(
 | |
|       const Values& values) const {
 | |
|     return linearizeDamped(values);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Triangulate and compute derivative of error with respect to point
 | |
|    * @return whether triangulation worked
 | |
|    */
 | |
|   bool triangulateAndComputeE(Matrix& E, const Cameras& cameras) const {
 | |
|     bool nonDegenerate = triangulateForLinearize(cameras);
 | |
|     if (nonDegenerate)
 | |
|       cameras.project2(*result_, boost::none, E);
 | |
|     return nonDegenerate;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Triangulate and compute derivative of error with respect to point
 | |
|    * @return whether triangulation worked
 | |
|    */
 | |
|   bool triangulateAndComputeE(Matrix& E, const Values& values) const {
 | |
|     Cameras cameras = this->cameras(values);
 | |
|     return triangulateAndComputeE(E, cameras);
 | |
|   }
 | |
| 
 | |
|   /// Compute F, E only (called below in both vanilla and SVD versions)
 | |
|   /// Assumes the point has been computed
 | |
|   /// Note E can be 2m*3 or 2m*2, in case point is degenerate
 | |
|   void computeJacobiansWithTriangulatedPoint(
 | |
|       std::vector<typename Base::MatrixZD>& Fblocks, Matrix& E, Vector& b,
 | |
|       const Cameras& cameras) const {
 | |
| 
 | |
|     if (!result_) {
 | |
|       // Handle degeneracy
 | |
|       // TODO check flag whether we should do this
 | |
|       Unit3 backProjected = cameras[0].backprojectPointAtInfinity(
 | |
|           this->measured_.at(0));
 | |
|       Base::computeJacobians(Fblocks, E, b, cameras, backProjected);
 | |
|     } else {
 | |
|       // valid result: just return Base version
 | |
|       Base::computeJacobians(Fblocks, E, b, cameras, *result_);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Version that takes values, and creates the point
 | |
|   bool triangulateAndComputeJacobians(
 | |
|       std::vector<typename Base::MatrixZD>& Fblocks, Matrix& E, Vector& b,
 | |
|       const Values& values) const {
 | |
|     Cameras cameras = this->cameras(values);
 | |
|     bool nonDegenerate = triangulateForLinearize(cameras);
 | |
|     if (nonDegenerate)
 | |
|       computeJacobiansWithTriangulatedPoint(Fblocks, E, b, cameras);
 | |
|     return nonDegenerate;
 | |
|   }
 | |
| 
 | |
|   /// takes values
 | |
|   bool triangulateAndComputeJacobiansSVD(
 | |
|       std::vector<typename Base::MatrixZD>& Fblocks, Matrix& Enull, Vector& b,
 | |
|       const Values& values) const {
 | |
|     Cameras cameras = this->cameras(values);
 | |
|     bool nonDegenerate = triangulateForLinearize(cameras);
 | |
|     if (nonDegenerate)
 | |
|       Base::computeJacobiansSVD(Fblocks, Enull, b, cameras, *result_);
 | |
|     return nonDegenerate;
 | |
|   }
 | |
| 
 | |
|   /// Calculate vector of re-projection errors, before applying noise model
 | |
|   Vector reprojectionErrorAfterTriangulation(const Values& values) const {
 | |
|     Cameras cameras = this->cameras(values);
 | |
|     bool nonDegenerate = triangulateForLinearize(cameras);
 | |
|     if (nonDegenerate)
 | |
|       return Base::unwhitenedError(cameras, *result_);
 | |
|     else
 | |
|       return zero(cameras.size() * 2);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Calculate the error of the factor.
 | |
|    * This is the log-likelihood, e.g. \f$ 0.5(h(x)-z)^2/\sigma^2 \f$ in case of Gaussian.
 | |
|    * In this class, we take the raw prediction error \f$ h(x)-z \f$, ask the noise model
 | |
|    * to transform it to \f$ (h(x)-z)^2/\sigma^2 \f$, and then multiply by 0.5.
 | |
|    */
 | |
|   double totalReprojectionError(const Cameras& cameras,
 | |
|       boost::optional<Point3> externalPoint = boost::none) const {
 | |
| 
 | |
|     if (externalPoint)
 | |
|       result_ = TriangulationResult(*externalPoint);
 | |
|     else
 | |
|       result_ = triangulateSafe(cameras);
 | |
| 
 | |
|     if (result_)
 | |
|       // All good, just use version in base class
 | |
|       return Base::totalReprojectionError(cameras, *result_);
 | |
|     else if (degeneracyMode_ == HANDLE_INFINITY) {
 | |
|       // Otherwise, manage the exceptions with rotation-only factors
 | |
|       const Point2& z0 = this->measured_.at(0);
 | |
|       Unit3 backprojected = cameras.front().backprojectPointAtInfinity(z0);
 | |
|       return Base::totalReprojectionError(cameras, backprojected);
 | |
|     } else
 | |
|       // if we don't want to manage the exceptions we discard the factor
 | |
|       return 0.0;
 | |
|   }
 | |
| 
 | |
|   /// Calculate total reprojection error
 | |
|   virtual double error(const Values& values) const {
 | |
|     if (this->active(values)) {
 | |
|       return totalReprojectionError(Base::cameras(values));
 | |
|     } else { // else of active flag
 | |
|       return 0.0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /** return the landmark */
 | |
|   TriangulationResult point() const {
 | |
|     return result_;
 | |
|   }
 | |
| 
 | |
|   /** COMPUTE the landmark */
 | |
|   TriangulationResult point(const Values& values) const {
 | |
|     Cameras cameras = this->cameras(values);
 | |
|     return triangulateSafe(cameras);
 | |
|   }
 | |
| 
 | |
|   /// Is result valid?
 | |
|   inline bool isValid() const {
 | |
|     return result_;
 | |
|   }
 | |
| 
 | |
|   /** return the degenerate state */
 | |
|   inline bool isDegenerate() const {
 | |
|     return result_.degenerate();
 | |
|   }
 | |
| 
 | |
|   /** return the cheirality status flag */
 | |
|   inline bool isPointBehindCamera() const {
 | |
|     return result_.behindCamera();
 | |
|   }
 | |
| 
 | |
|   /** return cheirality verbosity */
 | |
|   inline bool verboseCheirality() const {
 | |
|     return verboseCheirality_;
 | |
|   }
 | |
| 
 | |
|   /** return flag for throwing cheirality exceptions */
 | |
|   inline bool throwCheirality() const {
 | |
|     return throwCheirality_;
 | |
|   }
 | |
| 
 | |
| private:
 | |
| 
 | |
|   /// Serialization function
 | |
|   friend class boost::serialization::access;
 | |
|   template<class ARCHIVE>
 | |
|   void serialize(ARCHIVE & ar, const unsigned int version) {
 | |
|     ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base);
 | |
|     ar & BOOST_SERIALIZATION_NVP(throwCheirality_);
 | |
|     ar & BOOST_SERIALIZATION_NVP(verboseCheirality_);
 | |
|   }
 | |
| }
 | |
| ;
 | |
| 
 | |
| /// traits
 | |
| template<class CAMERA>
 | |
| struct traits<SmartProjectionFactor<CAMERA> > : public Testable<
 | |
|     SmartProjectionFactor<CAMERA> > {
 | |
| };
 | |
| 
 | |
| } // \ namespace gtsam
 |