opensfm/src/geometry/essential.h (133 lines of code) (raw):
// Copyright (c) 2011 libmv authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#pragma once
#include <foundation/numeric.h>
#include <Eigen/Eigen>
#include <Eigen/SVD>
// In the following code, polynomials are expressed as vectors containing
// their coeficients in the basis of monomials:
//
// [xxx xxy xyy yyy xxz xyz yyz xzz yzz zzz xx xy yy xz yz zz x y z 1]
//
// Note that there is an error in Stewenius' paper. In equation (9) they
// propose to use the basis:
//
// [xxx xxy xxz xyy xyz xzz yyy yyz yzz zzz xx xy xz yy yz zz x y z 1]
//
// But this is not the basis used in the rest of the paper, neither in
// the code they provide. I (pau) spent 4 hours debugging and reverse
// engineering their code to find the problem. :(
enum {
coef_xxx,
coef_xxy,
coef_xyy,
coef_yyy,
coef_xxz,
coef_xyz,
coef_yyz,
coef_xzz,
coef_yzz,
coef_zzz,
coef_xx,
coef_xy,
coef_yy,
coef_xz,
coef_yz,
coef_zz,
coef_x,
coef_y,
coef_z,
coef_1
};
template <class IT, class MAT>
inline void EncodeEpipolarEquation(IT begin, IT end, MAT *A) {
for (IT it = begin; it != end; ++it) {
int i = (it - begin);
const auto x1 = it->first;
const auto x2 = it->second;
A->row(i) << x2(0) * x1.transpose(), x2(1) * x1.transpose(),
x2(2) * x1.transpose();
}
}
// Compute the nullspace of the linear constraints given by the matches.
template <class IT>
Eigen::MatrixXd FivePointsNullspaceBasis(IT begin, IT end) {
Eigen::Matrix<double, 9, 9> A;
A.setZero(); // Make A square until Eigen supports rectangular SVD.
EncodeEpipolarEquation(begin, end, &A);
Eigen::JacobiSVD<Eigen::Matrix<double, 9, 9>> svd;
return svd.compute(A, Eigen::ComputeFullV).matrixV().topRightCorner<9, 4>();
}
// Multiply two polynomials of degree 1.
Eigen::Matrix<double, -1, 1> o1(const Eigen::Matrix<double, -1, 1> &a,
const Eigen::Matrix<double, -1, 1> &b);
// Multiply a polynomial of degree 2, a, by a polynomial of degree 1, b.
Eigen::Matrix<double, -1, 1> o2(const Eigen::Matrix<double, -1, 1> &a,
const Eigen::Matrix<double, -1, 1> &b);
// Builds the polynomial constraint matrix M.
Eigen::MatrixXd FivePointsPolynomialConstraints(const Eigen::MatrixXd &E_basis);
// Gauss--Jordan elimination for the constraint matrix.
bool FivePointsGaussJordan(Eigen::MatrixXd *Mp);
template <class IT>
std::vector<Eigen::Matrix<double, 3, 3>> EssentialFivePoints(IT begin, IT end) {
// Step 1: Nullspace exrtraction.
Eigen::MatrixXd E_basis = FivePointsNullspaceBasis(begin, end);
// Step 2: Constraint expansion.
Eigen::MatrixXd M = FivePointsPolynomialConstraints(E_basis);
// Step 3: Gauss-Jordan elimination.
if (!FivePointsGaussJordan(&M)) {
return std::vector<Eigen::Matrix<double, 3, 3>>();
}
// For the next steps, follow the matlab code given in Stewenius et al [1].
// Build the action matrix.
Eigen::MatrixXd B = M.topRightCorner<10, 10>();
Eigen::MatrixXd At = Eigen::MatrixXd::Zero(10, 10);
At.row(0) = -B.row(0);
At.row(1) = -B.row(1);
At.row(2) = -B.row(2);
At.row(3) = -B.row(4);
At.row(4) = -B.row(5);
At.row(5) = -B.row(7);
At(6, 0) = 1;
At(7, 1) = 1;
At(8, 3) = 1;
At(9, 6) = 1;
// Compute the solutions from action matrix's eigenvectors.
Eigen::EigenSolver<Eigen::MatrixXd> es(At);
typedef Eigen::EigenSolver<Eigen::MatrixXd>::EigenvectorsType Matc;
Matc V = es.eigenvectors();
Matc solutions(4, 10);
solutions.row(0) = V.row(6).array() / V.row(9).array();
solutions.row(1) = V.row(7).array() / V.row(9).array();
solutions.row(2) = V.row(8).array() / V.row(9).array();
solutions.row(3).setOnes();
// Get the ten candidate E matrices in vector form.
Matc Evec = E_basis * solutions;
// Build the essential matrices for the real solutions.
std::vector<Eigen::Matrix<double, 3, 3>> Es;
Es.reserve(10);
for (int s = 0; s < 10; ++s) {
Evec.col(s) /= Evec.col(s).norm();
bool is_real = true;
for (int i = 0; i < 9; ++i) {
if (Evec(i, s).imag() != 0) {
is_real = false;
break;
}
}
if (is_real) {
Eigen::Matrix<double, 3, 3> E;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
E(i, j) = Evec(3 * i + j, s).real();
}
}
Es.push_back(E);
}
}
return Es;
}
template <class IT>
std::vector<Eigen::Matrix<double, 3, 3>> EssentialNPoints(IT begin, IT end) {
const int count = end - begin;
Eigen::MatrixXd A(count, 9);
A.setZero();
EncodeEpipolarEquation(begin, end, &A);
Eigen::VectorXd solution;
std::vector<Eigen::Matrix<double, 3, 3>> Es;
if (foundation::SolveAX0(A, &solution)) {
Eigen::Matrix3d E =
Eigen::Map<Eigen::Matrix3d>(solution.data()).transpose();
if (count > 8) {
Eigen::JacobiSVD<Eigen::Matrix3d> USV(
E, Eigen::ComputeFullU | Eigen::ComputeFullV);
// Enforce essential matrix constraints
auto d = USV.singularValues();
const auto a = d[0];
const auto b = d[1];
d << (a + b) / 2., (a + b) / 2., 0.0;
E = USV.matrixU() * d.asDiagonal() * USV.matrixV().transpose();
}
Es.push_back(E);
}
return Es;
}
namespace geometry {
std::vector<Eigen::Matrix<double, 3, 3>> EssentialFivePoints(
const Eigen::Matrix<double, -1, 3> &x1,
const Eigen::Matrix<double, -1, 3> &x2);
std::vector<Eigen::Matrix<double, 3, 3>> EssentialNPoints(
const Eigen::Matrix<double, -1, 3> &x1,
const Eigen::Matrix<double, -1, 3> &x2);
} // namespace geometry