Added factor for Karcher mean constraint: fixes the geodesic mean to impose a global gauge constraint.
parent
137afaecbb
commit
2fb4668bba
|
@ -0,0 +1,62 @@
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
* 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 KarcherMeanFactor.cpp
|
||||||
|
* @author Frank Dellaert
|
||||||
|
* @date March 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtsam/nonlinear/GaussNewtonOptimizer.h>
|
||||||
|
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
|
||||||
|
#include <gtsam/slam/KarcherMeanFactor.h>
|
||||||
|
#include <gtsam/slam/PriorFactor.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace gtsam {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T FindKarcherMean(const vector<T>& rotations) {
|
||||||
|
// Cost function C(R) = \sum PriorFactor(R_i)::error(R)
|
||||||
|
// No closed form solution.
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
static const Key kKey(0);
|
||||||
|
for (const auto& R : rotations) {
|
||||||
|
graph.emplace_shared<PriorFactor<T> >(kKey, R);
|
||||||
|
}
|
||||||
|
Values initial;
|
||||||
|
initial.insert<T>(kKey, T());
|
||||||
|
auto result = GaussNewtonOptimizer(graph, initial).optimize();
|
||||||
|
return result.at<T>(kKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
template <typename CONTAINER>
|
||||||
|
KarcherMeanFactor<T>::KarcherMeanFactor(const CONTAINER& keys, int d)
|
||||||
|
: NonlinearFactor(keys) {
|
||||||
|
if (d <= 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"KarcherMeanFactor needs dimension for dynamic types.");
|
||||||
|
}
|
||||||
|
// Create the constant Jacobian made of D*D identity matrices,
|
||||||
|
// where D is the dimensionality of the manifold.
|
||||||
|
const auto I = Eigen::MatrixXd::Identity(d, d);
|
||||||
|
std::map<Key, Matrix> terms;
|
||||||
|
for (Key j : keys) {
|
||||||
|
terms[j] = I;
|
||||||
|
}
|
||||||
|
jacobian_ =
|
||||||
|
boost::make_shared<JacobianFactor>(terms, Eigen::VectorXd::Zero(d));
|
||||||
|
}
|
||||||
|
} // namespace gtsam
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
* 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 KarcherMeanFactor.h
|
||||||
|
* @author Frank Dellaert
|
||||||
|
* @date March 2019ƒ
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtsam/base/Matrix.h>
|
||||||
|
#include <gtsam/linear/JacobianFactor.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gtsam {
|
||||||
|
/**
|
||||||
|
* Optimize for the Karcher mean, minimizing the geodesic distance to each of
|
||||||
|
* the given rotations, ,by constructing a factor graph out of simple
|
||||||
|
* PriorFactors.
|
||||||
|
*/
|
||||||
|
template <class T>
|
||||||
|
T FindKarcherMean(const std::vector<T>& rotations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The KarcherMeanFactor creates a constraint on all SO(3) variables with
|
||||||
|
* given keys that the Karcher mean (see above) will stay the same. Note the
|
||||||
|
* mean itself is irrelevant to the constraint and is not a parameter: the
|
||||||
|
* constraint is implemented as enforcing that the sum of local updates is
|
||||||
|
* equal to zero, hence creating a rank-3 constraint. Note it is implemented as
|
||||||
|
* a soft constraint, as typically it is used to fix a gauge freedom.
|
||||||
|
* */
|
||||||
|
template <class T>
|
||||||
|
class KarcherMeanFactor : public NonlinearFactor {
|
||||||
|
/// Constant Jacobian made of d*d identity matrices
|
||||||
|
boost::shared_ptr<JacobianFactor> jacobian_;
|
||||||
|
|
||||||
|
enum {D = traits<T>::dimension};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Construct from given keys.
|
||||||
|
template <typename CONTAINER>
|
||||||
|
KarcherMeanFactor(const CONTAINER& keys, int d=D);
|
||||||
|
|
||||||
|
/// Destructor
|
||||||
|
virtual ~KarcherMeanFactor() {}
|
||||||
|
|
||||||
|
/// Calculate the error of the factor: always zero
|
||||||
|
double error(const Values& c) const override { return 0; }
|
||||||
|
|
||||||
|
/// get the dimension of the factor (number of rows on linearization)
|
||||||
|
size_t dim() const override { return D; }
|
||||||
|
|
||||||
|
/// linearize to a GaussianFactor
|
||||||
|
boost::shared_ptr<GaussianFactor> linearize(const Values& c) const override {
|
||||||
|
return jacobian_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// \KarcherMeanFactor
|
||||||
|
} // namespace gtsam
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
* 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 testKarcherMeanFactor.cpp
|
||||||
|
* @author Frank Dellaert
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtsam/geometry/Rot3.h>
|
||||||
|
#include <gtsam/geometry/SO4.h>
|
||||||
|
#include <gtsam/nonlinear/GaussNewtonOptimizer.h>
|
||||||
|
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
|
||||||
|
#include <gtsam/slam/BetweenFactor.h>
|
||||||
|
#include <gtsam/slam/KarcherMeanFactor-inl.h>
|
||||||
|
#include <gtsam/slam/KarcherMeanFactor.h>
|
||||||
|
|
||||||
|
#include <CppUnitLite/TestHarness.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gtsam;
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
// Rot3 version
|
||||||
|
/* ************************************************************************* */
|
||||||
|
static const Rot3 R = Rot3::Expmap(Vector3(0.1, 0, 0));
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
// Check that optimizing for Karcher mean (which minimizes Between distance)
|
||||||
|
// gets correct result.
|
||||||
|
TEST(KarcherMean, FindRot3) {
|
||||||
|
std::vector<Rot3> rotations = {R, R.inverse()};
|
||||||
|
Rot3 expected;
|
||||||
|
auto actual = FindKarcherMean(rotations);
|
||||||
|
EXPECT(assert_equal(expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
// Check that the InnerConstraint factor leaves the mean unchanged.
|
||||||
|
TEST(KarcherMean, FactorRot3) {
|
||||||
|
// Make a graph with two variables, one between, and one InnerConstraint
|
||||||
|
// The optimal result should satisfy the between, while moving the other
|
||||||
|
// variable to make the mean the same as before.
|
||||||
|
// Mean of R and R' is identity. Let's make a BetweenFactor making R21 =
|
||||||
|
// R*R*R, i.e. geodesic length is 3 rather than 2.
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
graph.emplace_shared<BetweenFactor<Rot3>>(1, 2, R * R * R);
|
||||||
|
std::vector<Key> keys{1, 2};
|
||||||
|
graph.emplace_shared<KarcherMeanFactor<Rot3>>(keys);
|
||||||
|
|
||||||
|
Values initial;
|
||||||
|
initial.insert<Rot3>(1, R.inverse());
|
||||||
|
initial.insert<Rot3>(2, R);
|
||||||
|
const auto expected = FindKarcherMean<Rot3>({R, R.inverse()});
|
||||||
|
|
||||||
|
auto result = GaussNewtonOptimizer(graph, initial).optimize();
|
||||||
|
const auto actual =
|
||||||
|
FindKarcherMean<Rot3>({result.at<Rot3>(1), result.at<Rot3>(2)});
|
||||||
|
EXPECT(assert_equal(expected, actual));
|
||||||
|
EXPECT(
|
||||||
|
assert_equal(R * R * R, result.at<Rot3>(1).between(result.at<Rot3>(2))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
// SO(4) version
|
||||||
|
/* ************************************************************************* */
|
||||||
|
static const SO4 Q = SO4::Expmap((Vector6() << 1, 2, 3, 4, 5, 6).finished());
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
TEST(KarcherMean, FindSO4) {
|
||||||
|
std::vector<SO4> rotations = {Q, Q.inverse()};
|
||||||
|
auto expected = SO4(); //::ChordalMean(rotations);
|
||||||
|
auto actual = FindKarcherMean(rotations);
|
||||||
|
EXPECT(assert_equal(expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
TEST(KarcherMean, FactorSO4) {
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
graph.emplace_shared<BetweenFactor<SO4>>(1, 2, Q * Q * Q);
|
||||||
|
std::vector<Key> keys{1, 2};
|
||||||
|
graph.emplace_shared<KarcherMeanFactor<SO4>>(keys);
|
||||||
|
|
||||||
|
Values initial;
|
||||||
|
initial.insert<SO4>(1, Q.inverse());
|
||||||
|
initial.insert<SO4>(2, Q);
|
||||||
|
const auto expected = FindKarcherMean<SO4>({Q, Q.inverse()});
|
||||||
|
|
||||||
|
auto result = GaussNewtonOptimizer(graph, initial).optimize();
|
||||||
|
const auto actual =
|
||||||
|
FindKarcherMean<SO4>({result.at<SO4>(1), result.at<SO4>(2)});
|
||||||
|
EXPECT(assert_equal(expected, actual));
|
||||||
|
EXPECT(assert_equal((Matrix)(Q * Q * Q).matrix(),
|
||||||
|
result.at<SO4>(1).between(result.at<SO4>(2))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
int main() {
|
||||||
|
TestResult tr;
|
||||||
|
return TestRegistry::runAllTests(tr);
|
||||||
|
}
|
||||||
|
/* ************************************************************************* */
|
Loading…
Reference in New Issue