From 8b9d6b78dcf07059e5ad8048d39f2464a4ac0ee7 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Tue, 17 Dec 2013 05:54:29 +0000 Subject: [PATCH] Optimization on the Essential manifold ! --- .cproject | 8 + gtsam/geometry/EssentialMatrix.h | 1 + gtsam/geometry/tests/testEssentialMatrix.cpp | 170 +----------------- gtsam/slam/EssentialMatrixFactor.h | 54 ++++++ .../slam/tests/testEssentialMatrixFactor.cpp | 155 ++++++++++++++++ 5 files changed, 221 insertions(+), 167 deletions(-) create mode 100644 gtsam/slam/EssentialMatrixFactor.h create mode 100644 gtsam/slam/tests/testEssentialMatrixFactor.cpp diff --git a/.cproject b/.cproject index 2e23fe9ec..2dbaf2d0f 100644 --- a/.cproject +++ b/.cproject @@ -904,6 +904,14 @@ true true + + make + -j5 + testEssentialMatrixFactor.run + true + true + true + make -j2 diff --git a/gtsam/geometry/EssentialMatrix.h b/gtsam/geometry/EssentialMatrix.h index aca2fd96d..ed1f9b78a 100644 --- a/gtsam/geometry/EssentialMatrix.h +++ b/gtsam/geometry/EssentialMatrix.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace gtsam { diff --git a/gtsam/geometry/tests/testEssentialMatrix.cpp b/gtsam/geometry/tests/testEssentialMatrix.cpp index 70027cc28..3f0cb07f7 100644 --- a/gtsam/geometry/tests/testEssentialMatrix.cpp +++ b/gtsam/geometry/tests/testEssentialMatrix.cpp @@ -8,115 +8,21 @@ #include #include #include -#include -#include -#include + #include -#include -#include -#include - using namespace std; -using namespace boost::assign; using namespace gtsam; -/** - * Factor that evaluates epipolar error p'Ep for given essential matrix - */ -class EssentialMatrixFactor: public NoiseModelFactor1 { - - Point2 pA_, pB_; ///< Measurements in image A and B - Vector vA_, vB_; ///< Homogeneous versions - - typedef NoiseModelFactor1 Base; - -public: - - /// Constructor - EssentialMatrixFactor(Key key, const Point2& pA, const Point2& pB, - const SharedNoiseModel& model) : - Base(model, key), pA_(pA), pB_(pB), // - vA_(EssentialMatrix::Homogeneous(pA)), // - vB_(EssentialMatrix::Homogeneous(pB)) { - } - - /// print - virtual void print(const std::string& s, const KeyFormatter& keyFormatter = - DefaultKeyFormatter) const { - Base::print(s); - std::cout << " EssentialMatrixFactor with measurements\n (" - << pA_.vector().transpose() << ")' and (" << pB_.vector().transpose() - << ")'" << endl; - } - - /// vector of errors returns 1D vector - Vector evaluateError(const EssentialMatrix& E, boost::optional H = - boost::none) const { - return (Vector(1) << E.error(vA_, vB_, H)); - } - -}; - //************************************************************************* // Create two cameras and corresponding essential matrix E Rot3 aRb = Rot3::yaw(M_PI_2); Point3 aTb(0.1, 0, 0); -Pose3 identity, aPb(aRb, aTb); -typedef CalibratedCamera Cam; -Cam cameraA(identity), cameraB(aPb); -Matrix aEb_matrix = skewSymmetric(aTb.x(), aTb.y(), aTb.z()) * aRb.matrix(); - -// Create test data, we need at least 5 points -Point3 P[5] = { Point3(0, 0, 1), Point3(-0.1, 0, 1), Point3(0.1, 0, 1), // -Point3(0, 0.5, 0.5), Point3(0, -0.5, 0.5) }; - -// Project points in both cameras -vector pA(5), pB(5); -vector::iterator // -it1 = std::transform(P, P + 5, pA.begin(), - boost::bind(&Cam::project, &cameraA, _1, boost::none, boost::none)), // -it2 = std::transform(P, P + 5, pB.begin(), - boost::bind(&Cam::project, &cameraB, _1, boost::none, boost::none)); - -// Converto to homogenous coordinates -vector vA(5), vB(5); -vector::iterator // -it3 = std::transform(pA.begin(), pA.end(), vA.begin(), - &EssentialMatrix::Homogeneous), // -it4 = std::transform(pB.begin(), pB.end(), vB.begin(), - &EssentialMatrix::Homogeneous); - -//************************************************************************* -TEST (EssentialMatrix, testData) { - // Check E matrix - Matrix expected(3, 3); - expected << 0, 0, 0, 0, 0, -0.1, 0.1, 0, 0; - EXPECT(assert_equal(expected, aEb_matrix)); - - // Check some projections - EXPECT(assert_equal(Point2(0,0),pA[0])); - EXPECT(assert_equal(Point2(0,0.1),pB[0])); - EXPECT(assert_equal(Point2(0,-1),pA[4])); - EXPECT(assert_equal(Point2(-1,0.2),pB[4])); - - // Check homogeneous version - EXPECT(assert_equal((Vector(3) << -1,0.2,1),vB[4])); - - // Check epipolar constraint - for (size_t i = 0; i < 5; i++) - EXPECT_DOUBLES_EQUAL(0, vA[i].transpose() * aEb_matrix * vB[i], 1e-8); - - // Check epipolar constraint - EssentialMatrix trueE(aRb, aTb); - for (size_t i = 0; i < 5; i++) - EXPECT_DOUBLES_EQUAL(0, trueE.error(vA[i],vB[i]), 1e-8); -} //************************************************************************* TEST (EssentialMatrix, equality) { -// EssentialMatrix actual, expected; -// EXPECT(assert_equal(expected, actual)); + EssentialMatrix actual(aRb, aTb), expected(aRb, aTb); + EXPECT(assert_equal(expected, actual)); } //************************************************************************* @@ -135,76 +41,6 @@ TEST (EssentialMatrix, retract2) { EXPECT(assert_equal(expected, actual)); } -//************************************************************************* -TEST (EssentialMatrix, factor) { - EssentialMatrix trueE(aRb, aTb); - noiseModel::Unit::shared_ptr model = noiseModel::Unit::Create(1); - - for (size_t i = 0; i < 5; i++) { - EssentialMatrixFactor factor(1, pA[i], pB[i], model); - - // Check evaluation - Vector expected(1); - expected << 0; - Matrix HActual; - Vector actual = factor.evaluateError(trueE, HActual); - EXPECT(assert_equal(expected, actual, 1e-8)); - - // Use numerical derivatives to calculate the expected Jacobian - Matrix HExpected; - HExpected = numericalDerivative11( - boost::bind(&EssentialMatrixFactor::evaluateError, &factor, _1, - boost::none), trueE); - - // Verify the Jacobian is correct - CHECK(assert_equal(HExpected, HActual, 1e-9)); - } -} - -//************************************************************************* -TEST (EssentialMatrix, fromConstraints) { - // Here we want to optimize directly on essential matrix constraints - // Yi Ma's algorithm (Ma01ijcv) is a bit cumbersome to implement, - // but GTSAM does the equivalent anyway, provided we give the right - // factors. In this case, the factors are the constraints. - - // We start with a factor graph and add constraints to it - // Noise sigma is 1cm, assuming metric measurements - NonlinearFactorGraph graph; - noiseModel::Isotropic::shared_ptr model = noiseModel::Isotropic::Sigma(1,0.01); - for (size_t i = 0; i < 5; i++) - graph.add(EssentialMatrixFactor(1, pA[i], pB[i], model)); - - // Check error at ground truth - Values truth; - EssentialMatrix trueE(aRb, aTb); - truth.insert(1, trueE); - EXPECT_DOUBLES_EQUAL(0, graph.error(truth), 1e-8); - - // Check error at initial estimate - Values initial; - EssentialMatrix initialE = trueE.retract((Vector(5) << 0.1, -0.1, 0.1, 0.1, -0.1)); - initial.insert(1, initialE); - EXPECT_DOUBLES_EQUAL(640, graph.error(initial), 1e-2); - - // Optimize - LevenbergMarquardtParams parameters; - LevenbergMarquardtOptimizer optimizer(graph, initial, parameters); - Values result = optimizer.optimize(); - - // Check result - EssentialMatrix actual = result.at(1); - EXPECT(assert_equal(trueE, actual,1e-1)); - - // Check error at result - EXPECT_DOUBLES_EQUAL(0, graph.error(result), 1e-4); - - // Check errors individually - for (size_t i = 0; i < 5; i++) - EXPECT_DOUBLES_EQUAL(0, actual.error(vA[i],vB[i]), 1e-6); - -} - /* ************************************************************************* */ int main() { TestResult tr; diff --git a/gtsam/slam/EssentialMatrixFactor.h b/gtsam/slam/EssentialMatrixFactor.h new file mode 100644 index 000000000..5e07cabc0 --- /dev/null +++ b/gtsam/slam/EssentialMatrixFactor.h @@ -0,0 +1,54 @@ +/* + * @file EssentialMatrixFactor.cpp + * @brief EssentialMatrixFactor class + * @author Frank Dellaert + * @date December 17, 2013 + */ + +#pragma once + +#include +#include +#include + +namespace gtsam { + +/** + * Factor that evaluates epipolar error p'Ep for given essential matrix + */ +class EssentialMatrixFactor: public NoiseModelFactor1 { + + Point2 pA_, pB_; ///< Measurements in image A and B + Vector vA_, vB_; ///< Homogeneous versions + + typedef NoiseModelFactor1 Base; + +public: + + /// Constructor + EssentialMatrixFactor(Key key, const Point2& pA, const Point2& pB, + const SharedNoiseModel& model) : + Base(model, key), pA_(pA), pB_(pB), // + vA_(EssentialMatrix::Homogeneous(pA)), // + vB_(EssentialMatrix::Homogeneous(pB)) { + } + + /// print + virtual void print(const std::string& s, const KeyFormatter& keyFormatter = + DefaultKeyFormatter) const { + Base::print(s); + std::cout << " EssentialMatrixFactor with measurements\n (" + << pA_.vector().transpose() << ")' and (" << pB_.vector().transpose() + << ")'" << std::endl; + } + + /// vector of errors returns 1D vector + Vector evaluateError(const EssentialMatrix& E, boost::optional H = + boost::none) const { + return (Vector(1) << E.error(vA_, vB_, H)); + } + +}; + +} // gtsam + diff --git a/gtsam/slam/tests/testEssentialMatrixFactor.cpp b/gtsam/slam/tests/testEssentialMatrixFactor.cpp new file mode 100644 index 000000000..5a5753589 --- /dev/null +++ b/gtsam/slam/tests/testEssentialMatrixFactor.cpp @@ -0,0 +1,155 @@ +/* + * @file testEssentialMatrixFactor.cpp + * @brief Test EssentialMatrixFactor class + * @author Frank Dellaert + * @date December 17, 2013 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace boost::assign; +using namespace gtsam; + +//************************************************************************* +// Create two cameras and corresponding essential matrix E +Rot3 aRb = Rot3::yaw(M_PI_2); +Point3 aTb(0.1, 0, 0); +Pose3 identity, aPb(aRb, aTb); +typedef CalibratedCamera Cam; +Cam cameraA(identity), cameraB(aPb); +Matrix aEb_matrix = skewSymmetric(aTb.x(), aTb.y(), aTb.z()) * aRb.matrix(); + +// Create test data, we need at least 5 points +Point3 P[5] = { Point3(0, 0, 1), Point3(-0.1, 0, 1), Point3(0.1, 0, 1), // +Point3(0, 0.5, 0.5), Point3(0, -0.5, 0.5) }; + +// Project points in both cameras +vector pA(5), pB(5); +vector::iterator // +it1 = std::transform(P, P + 5, pA.begin(), + boost::bind(&Cam::project, &cameraA, _1, boost::none, boost::none)), // +it2 = std::transform(P, P + 5, pB.begin(), + boost::bind(&Cam::project, &cameraB, _1, boost::none, boost::none)); + +// Converto to homogenous coordinates +vector vA(5), vB(5); +vector::iterator // +it3 = std::transform(pA.begin(), pA.end(), vA.begin(), + &EssentialMatrix::Homogeneous), // +it4 = std::transform(pB.begin(), pB.end(), vB.begin(), + &EssentialMatrix::Homogeneous); + +//************************************************************************* +TEST (EssentialMatrix, testData) { + // Check E matrix + Matrix expected(3, 3); + expected << 0, 0, 0, 0, 0, -0.1, 0.1, 0, 0; + EXPECT(assert_equal(expected, aEb_matrix)); + + // Check some projections + EXPECT(assert_equal(Point2(0,0),pA[0])); + EXPECT(assert_equal(Point2(0,0.1),pB[0])); + EXPECT(assert_equal(Point2(0,-1),pA[4])); + EXPECT(assert_equal(Point2(-1,0.2),pB[4])); + + // Check homogeneous version + EXPECT(assert_equal((Vector(3) << -1,0.2,1),vB[4])); + + // Check epipolar constraint + for (size_t i = 0; i < 5; i++) + EXPECT_DOUBLES_EQUAL(0, vA[i].transpose() * aEb_matrix * vB[i], 1e-8); + + // Check epipolar constraint + EssentialMatrix trueE(aRb, aTb); + for (size_t i = 0; i < 5; i++) + EXPECT_DOUBLES_EQUAL(0, trueE.error(vA[i],vB[i]), 1e-8); +} + +//************************************************************************* +TEST (EssentialMatrix, factor) { + EssentialMatrix trueE(aRb, aTb); + noiseModel::Unit::shared_ptr model = noiseModel::Unit::Create(1); + + for (size_t i = 0; i < 5; i++) { + EssentialMatrixFactor factor(1, pA[i], pB[i], model); + + // Check evaluation + Vector expected(1); + expected << 0; + Matrix HActual; + Vector actual = factor.evaluateError(trueE, HActual); + EXPECT(assert_equal(expected, actual, 1e-8)); + + // Use numerical derivatives to calculate the expected Jacobian + Matrix HExpected; + HExpected = numericalDerivative11( + boost::bind(&EssentialMatrixFactor::evaluateError, &factor, _1, + boost::none), trueE); + + // Verify the Jacobian is correct + CHECK(assert_equal(HExpected, HActual, 1e-9)); + } +} + +//************************************************************************* +TEST (EssentialMatrix, fromConstraints) { + // Here we want to optimize directly on essential matrix constraints + // Yi Ma's algorithm (Ma01ijcv) is a bit cumbersome to implement, + // but GTSAM does the equivalent anyway, provided we give the right + // factors. In this case, the factors are the constraints. + + // We start with a factor graph and add constraints to it + // Noise sigma is 1cm, assuming metric measurements + NonlinearFactorGraph graph; + noiseModel::Isotropic::shared_ptr model = noiseModel::Isotropic::Sigma(1,0.01); + for (size_t i = 0; i < 5; i++) + graph.add(EssentialMatrixFactor(1, pA[i], pB[i], model)); + + // Check error at ground truth + Values truth; + EssentialMatrix trueE(aRb, aTb); + truth.insert(1, trueE); + EXPECT_DOUBLES_EQUAL(0, graph.error(truth), 1e-8); + + // Check error at initial estimate + Values initial; + EssentialMatrix initialE = trueE.retract((Vector(5) << 0.1, -0.1, 0.1, 0.1, -0.1)); + initial.insert(1, initialE); + EXPECT_DOUBLES_EQUAL(640, graph.error(initial), 1e-2); + + // Optimize + LevenbergMarquardtParams parameters; + LevenbergMarquardtOptimizer optimizer(graph, initial, parameters); + Values result = optimizer.optimize(); + + // Check result + EssentialMatrix actual = result.at(1); + EXPECT(assert_equal(trueE, actual,1e-1)); + + // Check error at result + EXPECT_DOUBLES_EQUAL(0, graph.error(result), 1e-4); + + // Check errors individually + for (size_t i = 0; i < 5; i++) + EXPECT_DOUBLES_EQUAL(0, actual.error(vA[i],vB[i]), 1e-6); + +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} +/* ************************************************************************* */ +