opencv/modules/calib3d/src/sqpnp.cpp
Manolis Lourakis 8d0fbc6a1e
Merge pull request #21702 from mlourakis:4.x
Fixes and optimizations for the SQPnP solver

* Fixes and optimizations

- optimized the calculation of qa_sum by moving equal elements outside the loop
- unrolled copying of the lower triangle of omega
- substituted SVD with eigendecomposition in the factorization of omega (2-3 times faster)
- fixed the initialization of lambda in FOAM
- added a cheirality test that checks a solution on all 3D points rather than on their mean. The old test rejected valid poses in some cases
- fixed some typos & errors in comments

* reverted to SVD

Eigen decomposition seems to yield larger errors in certain tests, reverted to SVD

* nearestRotationMatrixSVD

Added nearestRotationMatrixSVD()
Previous nearestRotationMatrix() renamed to nearestRotationMatrixFOAM() and reverts to nearestRotationMatrixSVD() for singular matrices

* fixed checks order

Fixed the order of checks in PoseSolver::solveInternal()
2022-05-13 18:11:14 +00:00

826 lines
30 KiB
C++

// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// This file is based on file issued with the following license:
/*
BSD 3-Clause License
Copyright (c) 2020, George Terzakis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "precomp.hpp"
#include "sqpnp.hpp"
#include <opencv2/calib3d.hpp>
namespace cv {
namespace sqpnp {
const double PoseSolver::RANK_TOLERANCE = 1e-7;
const double PoseSolver::SQP_SQUARED_TOLERANCE = 1e-10;
const double PoseSolver::SQP_DET_THRESHOLD = 1.001;
const double PoseSolver::ORTHOGONALITY_SQUARED_ERROR_THRESHOLD = 1e-8;
const double PoseSolver::EQUAL_VECTORS_SQUARED_DIFF = 1e-10;
const double PoseSolver::EQUAL_SQUARED_ERRORS_DIFF = 1e-6;
const double PoseSolver::POINT_VARIANCE_THRESHOLD = 1e-5;
const double PoseSolver::SQRT3 = std::sqrt(3);
const int PoseSolver::SQP_MAX_ITERATION = 15;
//No checking done here for overflow, since this is not public all call instances
//are assumed to be valid
template <typename tp, int snrows, int sncols,
int dnrows, int dncols>
void set(int row, int col, cv::Matx<tp, dnrows, dncols>& dest,
const cv::Matx<tp, snrows, sncols>& source)
{
for (int y = 0; y < snrows; y++)
{
for (int x = 0; x < sncols; x++)
{
dest(row + y, col + x) = source(y, x);
}
}
}
PoseSolver::PoseSolver()
: num_null_vectors_(-1),
num_solutions_(0)
{
}
void PoseSolver::solve(InputArray objectPoints, InputArray imagePoints, OutputArrayOfArrays rvecs,
OutputArrayOfArrays tvecs)
{
//Input checking
int objType = objectPoints.getMat().type();
CV_CheckType(objType, objType == CV_32FC3 || objType == CV_64FC3,
"Type of objectPoints must be CV_32FC3 or CV_64FC3");
int imgType = imagePoints.getMat().type();
CV_CheckType(imgType, imgType == CV_32FC2 || imgType == CV_64FC2,
"Type of imagePoints must be CV_32FC2 or CV_64FC2");
CV_Assert(objectPoints.rows() == 1 || objectPoints.cols() == 1);
CV_Assert(objectPoints.rows() >= 3 || objectPoints.cols() >= 3);
CV_Assert(imagePoints.rows() == 1 || imagePoints.cols() == 1);
CV_Assert(imagePoints.rows() * imagePoints.cols() == objectPoints.rows() * objectPoints.cols());
Mat _imagePoints;
if (imgType == CV_32FC2)
{
imagePoints.getMat().convertTo(_imagePoints, CV_64F);
}
else
{
_imagePoints = imagePoints.getMat();
}
Mat _objectPoints;
if (objType == CV_32FC3)
{
objectPoints.getMat().convertTo(_objectPoints, CV_64F);
}
else
{
_objectPoints = objectPoints.getMat();
}
num_null_vectors_ = -1;
num_solutions_ = 0;
computeOmega(_objectPoints, _imagePoints);
solveInternal(_objectPoints);
int depthRot = rvecs.fixedType() ? rvecs.depth() : CV_64F;
int depthTrans = tvecs.fixedType() ? tvecs.depth() : CV_64F;
rvecs.create(num_solutions_, 1, CV_MAKETYPE(depthRot, rvecs.fixedType() && rvecs.kind() == _InputArray::STD_VECTOR ? 3 : 1));
tvecs.create(num_solutions_, 1, CV_MAKETYPE(depthTrans, tvecs.fixedType() && tvecs.kind() == _InputArray::STD_VECTOR ? 3 : 1));
for (int i = 0; i < num_solutions_; i++)
{
Mat rvec;
Mat rotation = Mat(solutions_[i].r_hat).reshape(1, 3);
Rodrigues(rotation, rvec);
rvecs.getMatRef(i) = rvec;
tvecs.getMatRef(i) = Mat(solutions_[i].t);
}
}
void PoseSolver::computeOmega(InputArray objectPoints, InputArray imagePoints)
{
omega_ = cv::Matx<double, 9, 9>::zeros();
cv::Matx<double, 3, 9> qa_sum = cv::Matx<double, 3, 9>::zeros();
cv::Point2d sum_img(0, 0);
cv::Point3d sum_obj(0, 0, 0);
double sq_norm_sum = 0;
Mat _imagePoints = imagePoints.getMat();
Mat _objectPoints = objectPoints.getMat();
int n = _objectPoints.cols * _objectPoints.rows;
for (int i = 0; i < n; i++)
{
const cv::Point2d& img_pt = _imagePoints.at<cv::Point2d>(i);
const cv::Point3d& obj_pt = _objectPoints.at<cv::Point3d>(i);
sum_img += img_pt;
sum_obj += obj_pt;
const double& x = img_pt.x, & y = img_pt.y;
const double& X = obj_pt.x, & Y = obj_pt.y, & Z = obj_pt.z;
double sq_norm = x * x + y * y;
sq_norm_sum += sq_norm;
double X2 = X * X,
XY = X * Y,
XZ = X * Z,
Y2 = Y * Y,
YZ = Y * Z,
Z2 = Z * Z;
omega_(0, 0) += X2;
omega_(0, 1) += XY;
omega_(0, 2) += XZ;
omega_(1, 1) += Y2;
omega_(1, 2) += YZ;
omega_(2, 2) += Z2;
//Populating this manually saves operations by only calculating upper triangle
omega_(0, 6) += -x * X2; omega_(0, 7) += -x * XY; omega_(0, 8) += -x * XZ;
omega_(1, 7) += -x * Y2; omega_(1, 8) += -x * YZ;
omega_(2, 8) += -x * Z2;
omega_(3, 6) += -y * X2; omega_(3, 7) += -y * XY; omega_(3, 8) += -y * XZ;
omega_(4, 7) += -y * Y2; omega_(4, 8) += -y * YZ;
omega_(5, 8) += -y * Z2;
omega_(6, 6) += sq_norm * X2; omega_(6, 7) += sq_norm * XY; omega_(6, 8) += sq_norm * XZ;
omega_(7, 7) += sq_norm * Y2; omega_(7, 8) += sq_norm * YZ;
omega_(8, 8) += sq_norm * Z2;
//Compute qa_sum. Certain pairs of elements are equal, so filling them outside the loop saves some operations
qa_sum(0, 0) += X; qa_sum(0, 1) += Y; qa_sum(0, 2) += Z;
qa_sum(0, 6) += -x * X; qa_sum(0, 7) += -x * Y; qa_sum(0, 8) += -x * Z;
qa_sum(1, 6) += -y * X; qa_sum(1, 7) += -y * Y; qa_sum(1, 8) += -y * Z;
qa_sum(2, 6) += sq_norm * X; qa_sum(2, 7) += sq_norm * Y; qa_sum(2, 8) += sq_norm * Z;
}
//Complete qa_sum
qa_sum(1, 3) = qa_sum(0, 0); qa_sum(1, 4) = qa_sum(0, 1); qa_sum(1, 5) = qa_sum(0, 2);
qa_sum(2, 0) = qa_sum(0, 6); qa_sum(2, 1) = qa_sum(0, 7); qa_sum(2, 2) = qa_sum(0, 8);
qa_sum(2, 3) = qa_sum(1, 6); qa_sum(2, 4) = qa_sum(1, 7); qa_sum(2, 5) = qa_sum(1, 8);
//lower triangles of omega_'s off-diagonal blocks (0:2, 6:8), (3:5, 6:8) and (6:8, 6:8)
omega_(1, 6) = omega_(0, 7); omega_(2, 6) = omega_(0, 8); omega_(2, 7) = omega_(1, 8);
omega_(4, 6) = omega_(3, 7); omega_(5, 6) = omega_(3, 8); omega_(5, 7) = omega_(4, 8);
omega_(7, 6) = omega_(6, 7); omega_(8, 6) = omega_(6, 8); omega_(8, 7) = omega_(7, 8);
//upper triangle of omega_'s block (3:5, 3:5)
omega_(3, 3) = omega_(0, 0); omega_(3, 4) = omega_(0, 1); omega_(3, 5) = omega_(0, 2);
omega_(4, 4) = omega_(1, 1); omega_(4, 5) = omega_(1, 2);
omega_(5, 5) = omega_(2, 2);
//Mirror omega_'s upper triangle to lower triangle
//Note that elements (7, 6), (8, 6) & (8, 7) have already been assigned above
omega_(1, 0) = omega_(0, 1);
omega_(2, 0) = omega_(0, 2); omega_(2, 1) = omega_(1, 2);
omega_(3, 0) = omega_(0, 3); omega_(3, 1) = omega_(1, 3); omega_(3, 2) = omega_(2, 3);
omega_(4, 0) = omega_(0, 4); omega_(4, 1) = omega_(1, 4); omega_(4, 2) = omega_(2, 4); omega_(4, 3) = omega_(3, 4);
omega_(5, 0) = omega_(0, 5); omega_(5, 1) = omega_(1, 5); omega_(5, 2) = omega_(2, 5); omega_(5, 3) = omega_(3, 5); omega_(5, 4) = omega_(4, 5);
omega_(6, 0) = omega_(0, 6); omega_(6, 1) = omega_(1, 6); omega_(6, 2) = omega_(2, 6); omega_(6, 3) = omega_(3, 6); omega_(6, 4) = omega_(4, 6); omega_(6, 5) = omega_(5, 6);
omega_(7, 0) = omega_(0, 7); omega_(7, 1) = omega_(1, 7); omega_(7, 2) = omega_(2, 7); omega_(7, 3) = omega_(3, 7); omega_(7, 4) = omega_(4, 7); omega_(7, 5) = omega_(5, 7);
omega_(8, 0) = omega_(0, 8); omega_(8, 1) = omega_(1, 8); omega_(8, 2) = omega_(2, 8); omega_(8, 3) = omega_(3, 8); omega_(8, 4) = omega_(4, 8); omega_(8, 5) = omega_(5, 8);
cv::Matx<double, 3, 3> q;
q(0, 0) = n; q(0, 1) = 0; q(0, 2) = -sum_img.x;
q(1, 0) = 0; q(1, 1) = n; q(1, 2) = -sum_img.y;
q(2, 0) = -sum_img.x; q(2, 1) = -sum_img.y; q(2, 2) = sq_norm_sum;
double inv_n = 1.0 / n;
double detQ = n * (n * sq_norm_sum - sum_img.y * sum_img.y - sum_img.x * sum_img.x);
double point_coordinate_variance = detQ * inv_n * inv_n * inv_n;
CV_Assert(point_coordinate_variance >= POINT_VARIANCE_THRESHOLD);
Matx<double, 3, 3> q_inv;
analyticalInverse3x3Symm(q, q_inv);
p_ = -q_inv * qa_sum;
omega_ += qa_sum.t() * p_;
cv::SVD omega_svd(omega_, cv::SVD::FULL_UV);
s_ = omega_svd.w;
u_ = cv::Mat(omega_svd.vt.t());
#if 0
// EVD equivalent of the SVD; less accurate
cv::eigen(omega_, s_, u_);
u_ = u_.t(); // eigenvectors were returned as rows
#endif
CV_Assert(s_(0) >= 1e-7);
while (s_(7 - num_null_vectors_) < RANK_TOLERANCE) num_null_vectors_++;
CV_Assert(++num_null_vectors_ <= 6);
point_mean_ = cv::Vec3d(sum_obj.x / n, sum_obj.y / n, sum_obj.z / n);
}
void PoseSolver::solveInternal(InputArray objectPoints)
{
double min_sq_err = std::numeric_limits<double>::max();
int num_eigen_points = num_null_vectors_ > 0 ? num_null_vectors_ : 1;
for (int i = 9 - num_eigen_points; i < 9; i++)
{
const cv::Matx<double, 9, 1> e = SQRT3 * u_.col(i);
double orthogonality_sq_err = orthogonalityError(e);
SQPSolution solutions[2];
//If e is orthogonal, we can skip SQP
if (orthogonality_sq_err < ORTHOGONALITY_SQUARED_ERROR_THRESHOLD)
{
solutions[0].r_hat = det3x3(e) * e;
solutions[0].t = p_ * solutions[0].r_hat;
checkSolution(solutions[0], objectPoints, min_sq_err);
}
else
{
Matx<double, 9, 1> r;
nearestRotationMatrixFOAM(e, r);
solutions[0] = runSQP(r);
solutions[0].t = p_ * solutions[0].r_hat;
checkSolution(solutions[0], objectPoints, min_sq_err);
nearestRotationMatrixFOAM(-e, r);
solutions[1] = runSQP(r);
solutions[1].t = p_ * solutions[1].r_hat;
checkSolution(solutions[1], objectPoints, min_sq_err);
}
}
int index, c = 1;
while ((index = 9 - num_eigen_points - c) > 0 && min_sq_err > 3 * s_[index])
{
const cv::Matx<double, 9, 1> e = u_.col(index);
SQPSolution solutions[2];
Matx<double, 9, 1> r;
nearestRotationMatrixFOAM(e, r);
solutions[0] = runSQP(r);
solutions[0].t = p_ * solutions[0].r_hat;
checkSolution(solutions[0], objectPoints, min_sq_err);
nearestRotationMatrixFOAM(-e, r);
solutions[1] = runSQP(r);
solutions[1].t = p_ * solutions[1].r_hat;
checkSolution(solutions[1], objectPoints, min_sq_err);
c++;
}
}
PoseSolver::SQPSolution PoseSolver::runSQP(const cv::Matx<double, 9, 1>& r0)
{
cv::Matx<double, 9, 1> r = r0;
double delta_squared_norm = std::numeric_limits<double>::max();
cv::Matx<double, 9, 1> delta;
int step = 0;
while (delta_squared_norm > SQP_SQUARED_TOLERANCE && step++ < SQP_MAX_ITERATION)
{
solveSQPSystem(r, delta);
r += delta;
delta_squared_norm = cv::norm(delta, cv::NORM_L2SQR);
}
SQPSolution solution;
double det_r = det3x3(r);
if (det_r < 0)
{
r = -r;
det_r = -det_r;
}
if (det_r > SQP_DET_THRESHOLD)
{
nearestRotationMatrixFOAM(r, solution.r_hat);
}
else
{
solution.r_hat = r;
}
return solution;
}
void PoseSolver::solveSQPSystem(const cv::Matx<double, 9, 1>& r, cv::Matx<double, 9, 1>& delta)
{
double sqnorm_r1 = r(0) * r(0) + r(1) * r(1) + r(2) * r(2),
sqnorm_r2 = r(3) * r(3) + r(4) * r(4) + r(5) * r(5),
sqnorm_r3 = r(6) * r(6) + r(7) * r(7) + r(8) * r(8);
double dot_r1r2 = r(0) * r(3) + r(1) * r(4) + r(2) * r(5),
dot_r1r3 = r(0) * r(6) + r(1) * r(7) + r(2) * r(8),
dot_r2r3 = r(3) * r(6) + r(4) * r(7) + r(5) * r(8);
cv::Matx<double, 9, 3> N;
cv::Matx<double, 9, 6> H;
cv::Matx<double, 6, 6> JH;
computeRowAndNullspace(r, H, N, JH);
cv::Matx<double, 6, 1> g;
g(0) = 1 - sqnorm_r1; g(1) = 1 - sqnorm_r2; g(2) = 1 - sqnorm_r3; g(3) = -dot_r1r2; g(4) = -dot_r2r3; g(5) = -dot_r1r3;
cv::Matx<double, 6, 1> x;
x(0) = g(0) / JH(0, 0);
x(1) = g(1) / JH(1, 1);
x(2) = g(2) / JH(2, 2);
x(3) = (g(3) - JH(3, 0) * x(0) - JH(3, 1) * x(1)) / JH(3, 3);
x(4) = (g(4) - JH(4, 1) * x(1) - JH(4, 2) * x(2) - JH(4, 3) * x(3)) / JH(4, 4);
x(5) = (g(5) - JH(5, 0) * x(0) - JH(5, 2) * x(2) - JH(5, 3) * x(3) - JH(5, 4) * x(4)) / JH(5, 5);
delta = H * x;
cv::Matx<double, 3, 9> nt_omega = N.t() * omega_;
cv::Matx<double, 3, 3> W = nt_omega * N, W_inv;
analyticalInverse3x3Symm(W, W_inv);
cv::Matx<double, 3, 1> y = -W_inv * nt_omega * (delta + r);
delta += N * y;
}
bool PoseSolver::analyticalInverse3x3Symm(const cv::Matx<double, 3, 3>& Q,
cv::Matx<double, 3, 3>& Qinv,
const double& threshold)
{
// 1. Get the elements of the matrix
double a = Q(0, 0),
b = Q(1, 0), d = Q(1, 1),
c = Q(2, 0), e = Q(2, 1), f = Q(2, 2);
// 2. Determinant
double t2, t4, t7, t9, t12;
t2 = e * e;
t4 = a * d;
t7 = b * b;
t9 = b * c;
t12 = c * c;
double det = -t4 * f + a * t2 + t7 * f - 2.0 * t9 * e + t12 * d;
if (fabs(det) < threshold) return false;
// 3. Inverse
double t15, t20, t24, t30;
t15 = 1.0 / det;
t20 = (-b * f + c * e) * t15;
t24 = (b * e - c * d) * t15;
t30 = (a * e - t9) * t15;
Qinv(0, 0) = (-d * f + t2) * t15;
Qinv(0, 1) = Qinv(1, 0) = -t20;
Qinv(0, 2) = Qinv(2, 0) = -t24;
Qinv(1, 1) = -(a * f - t12) * t15;
Qinv(1, 2) = Qinv(2, 1) = t30;
Qinv(2, 2) = -(t4 - t7) * t15;
return true;
}
void PoseSolver::computeRowAndNullspace(const cv::Matx<double, 9, 1>& r,
cv::Matx<double, 9, 6>& H,
cv::Matx<double, 9, 3>& N,
cv::Matx<double, 6, 6>& K,
const double& norm_threshold)
{
H = cv::Matx<double, 9, 6>::zeros();
// 1. q1
double norm_r1 = sqrt(r(0) * r(0) + r(1) * r(1) + r(2) * r(2));
double inv_norm_r1 = norm_r1 > 1e-5 ? 1.0 / norm_r1 : 0.0;
H(0, 0) = r(0) * inv_norm_r1;
H(1, 0) = r(1) * inv_norm_r1;
H(2, 0) = r(2) * inv_norm_r1;
K(0, 0) = 2 * norm_r1;
// 2. q2
double norm_r2 = sqrt(r(3) * r(3) + r(4) * r(4) + r(5) * r(5));
double inv_norm_r2 = 1.0 / norm_r2;
H(3, 1) = r(3) * inv_norm_r2;
H(4, 1) = r(4) * inv_norm_r2;
H(5, 1) = r(5) * inv_norm_r2;
K(1, 0) = 0;
K(1, 1) = 2 * norm_r2;
// 3. q3 = (r3'*q2)*q2 - (r3'*q1)*q1 ; q3 = q3/norm(q3)
double norm_r3 = sqrt(r(6) * r(6) + r(7) * r(7) + r(8) * r(8));
double inv_norm_r3 = 1.0 / norm_r3;
H(6, 2) = r(6) * inv_norm_r3;
H(7, 2) = r(7) * inv_norm_r3;
H(8, 2) = r(8) * inv_norm_r3;
K(2, 0) = K(2, 1) = 0;
K(2, 2) = 2 * norm_r3;
// 4. q4
double dot_j4q1 = r(3) * H(0, 0) + r(4) * H(1, 0) + r(5) * H(2, 0),
dot_j4q2 = r(0) * H(3, 1) + r(1) * H(4, 1) + r(2) * H(5, 1);
H(0, 3) = r(3) - dot_j4q1 * H(0, 0);
H(1, 3) = r(4) - dot_j4q1 * H(1, 0);
H(2, 3) = r(5) - dot_j4q1 * H(2, 0);
H(3, 3) = r(0) - dot_j4q2 * H(3, 1);
H(4, 3) = r(1) - dot_j4q2 * H(4, 1);
H(5, 3) = r(2) - dot_j4q2 * H(5, 1);
double inv_norm_j4 = 1.0 / sqrt(H(0, 3) * H(0, 3) + H(1, 3) * H(1, 3) + H(2, 3) * H(2, 3) +
H(3, 3) * H(3, 3) + H(4, 3) * H(4, 3) + H(5, 3) * H(5, 3));
H(0, 3) *= inv_norm_j4;
H(1, 3) *= inv_norm_j4;
H(2, 3) *= inv_norm_j4;
H(3, 3) *= inv_norm_j4;
H(4, 3) *= inv_norm_j4;
H(5, 3) *= inv_norm_j4;
K(3, 0) = r(3) * H(0, 0) + r(4) * H(1, 0) + r(5) * H(2, 0);
K(3, 1) = r(0) * H(3, 1) + r(1) * H(4, 1) + r(2) * H(5, 1);
K(3, 2) = 0;
K(3, 3) = r(3) * H(0, 3) + r(4) * H(1, 3) + r(5) * H(2, 3) + r(0) * H(3, 3) + r(1) * H(4, 3) + r(2) * H(5, 3);
// 5. q5
double dot_j5q2 = r(6) * H(3, 1) + r(7) * H(4, 1) + r(8) * H(5, 1);
double dot_j5q3 = r(3) * H(6, 2) + r(4) * H(7, 2) + r(5) * H(8, 2);
double dot_j5q4 = r(6) * H(3, 3) + r(7) * H(4, 3) + r(8) * H(5, 3);
H(0, 4) = -dot_j5q4 * H(0, 3);
H(1, 4) = -dot_j5q4 * H(1, 3);
H(2, 4) = -dot_j5q4 * H(2, 3);
H(3, 4) = r(6) - dot_j5q2 * H(3, 1) - dot_j5q4 * H(3, 3);
H(4, 4) = r(7) - dot_j5q2 * H(4, 1) - dot_j5q4 * H(4, 3);
H(5, 4) = r(8) - dot_j5q2 * H(5, 1) - dot_j5q4 * H(5, 3);
H(6, 4) = r(3) - dot_j5q3 * H(6, 2); H(7, 4) = r(4) - dot_j5q3 * H(7, 2); H(8, 4) = r(5) - dot_j5q3 * H(8, 2);
Matx<double, 9, 1> q4 = H.col(4);
q4 /= cv::norm(q4);
set<double, 9, 1, 9, 6>(0, 4, H, q4);
K(4, 0) = 0;
K(4, 1) = r(6) * H(3, 1) + r(7) * H(4, 1) + r(8) * H(5, 1);
K(4, 2) = r(3) * H(6, 2) + r(4) * H(7, 2) + r(5) * H(8, 2);
K(4, 3) = r(6) * H(3, 3) + r(7) * H(4, 3) + r(8) * H(5, 3);
K(4, 4) = r(6) * H(3, 4) + r(7) * H(4, 4) + r(8) * H(5, 4) + r(3) * H(6, 4) + r(4) * H(7, 4) + r(5) * H(8, 4);
// 4. q6
double dot_j6q1 = r(6) * H(0, 0) + r(7) * H(1, 0) + r(8) * H(2, 0);
double dot_j6q3 = r(0) * H(6, 2) + r(1) * H(7, 2) + r(2) * H(8, 2);
double dot_j6q4 = r(6) * H(0, 3) + r(7) * H(1, 3) + r(8) * H(2, 3);
double dot_j6q5 = r(0) * H(6, 4) + r(1) * H(7, 4) + r(2) * H(8, 4) + r(6) * H(0, 4) + r(7) * H(1, 4) + r(8) * H(2, 4);
H(0, 5) = r(6) - dot_j6q1 * H(0, 0) - dot_j6q4 * H(0, 3) - dot_j6q5 * H(0, 4);
H(1, 5) = r(7) - dot_j6q1 * H(1, 0) - dot_j6q4 * H(1, 3) - dot_j6q5 * H(1, 4);
H(2, 5) = r(8) - dot_j6q1 * H(2, 0) - dot_j6q4 * H(2, 3) - dot_j6q5 * H(2, 4);
H(3, 5) = -dot_j6q5 * H(3, 4) - dot_j6q4 * H(3, 3);
H(4, 5) = -dot_j6q5 * H(4, 4) - dot_j6q4 * H(4, 3);
H(5, 5) = -dot_j6q5 * H(5, 4) - dot_j6q4 * H(5, 3);
H(6, 5) = r(0) - dot_j6q3 * H(6, 2) - dot_j6q5 * H(6, 4);
H(7, 5) = r(1) - dot_j6q3 * H(7, 2) - dot_j6q5 * H(7, 4);
H(8, 5) = r(2) - dot_j6q3 * H(8, 2) - dot_j6q5 * H(8, 4);
Matx<double, 9, 1> q5 = H.col(5);
q5 /= cv::norm(q5);
set<double, 9, 1, 9, 6>(0, 5, H, q5);
K(5, 0) = r(6) * H(0, 0) + r(7) * H(1, 0) + r(8) * H(2, 0);
K(5, 1) = 0; K(5, 2) = r(0) * H(6, 2) + r(1) * H(7, 2) + r(2) * H(8, 2);
K(5, 3) = r(6) * H(0, 3) + r(7) * H(1, 3) + r(8) * H(2, 3);
K(5, 4) = r(6) * H(0, 4) + r(7) * H(1, 4) + r(8) * H(2, 4) + r(0) * H(6, 4) + r(1) * H(7, 4) + r(2) * H(8, 4);
K(5, 5) = r(6) * H(0, 5) + r(7) * H(1, 5) + r(8) * H(2, 5) + r(0) * H(6, 5) + r(1) * H(7, 5) + r(2) * H(8, 5);
// Great! Now H is an orthogonalized, sparse basis of the Jacobian row space and K is filled.
//
// Now get a projector onto the null space H:
const cv::Matx<double, 9, 9> Pn = cv::Matx<double, 9, 9>::eye() - (H * H.t());
// Now we need to pick 3 columns of P with non-zero norm (> 0.3) and some angle between them (> 0.3).
//
// Find the 3 columns of Pn with largest norms
int index1 = 0,
index2 = 0,
index3 = 0;
double max_norm1 = std::numeric_limits<double>::min();
double min_dot12 = std::numeric_limits<double>::max();
double min_dot1323 = std::numeric_limits<double>::max();
double col_norms[9];
for (int i = 0; i < 9; i++)
{
col_norms[i] = cv::norm(Pn.col(i));
if (col_norms[i] >= norm_threshold)
{
if (max_norm1 < col_norms[i])
{
max_norm1 = col_norms[i];
index1 = i;
}
}
}
Matx<double, 9, 1> v1 = Pn.col(index1);
v1 /= max_norm1;
set<double, 9, 1, 9, 3>(0, 0, N, v1);
for (int i = 0; i < 9; i++)
{
if (i == index1) continue;
if (col_norms[i] >= norm_threshold)
{
double cos_v1_x_col = fabs(Pn.col(i).dot(v1) / col_norms[i]);
if (cos_v1_x_col <= min_dot12)
{
index2 = i;
min_dot12 = cos_v1_x_col;
}
}
}
Matx<double, 9, 1> v2 = Pn.col(index2);
Matx<double, 9, 1> n0 = N.col(0);
v2 -= v2.dot(n0) * n0;
v2 /= cv::norm(v2);
set<double, 9, 1, 9, 3>(0, 1, N, v2);
for (int i = 0; i < 9; i++)
{
if (i == index2 || i == index1) continue;
if (col_norms[i] >= norm_threshold)
{
double cos_v1_x_col = fabs(Pn.col(i).dot(v1) / col_norms[i]);
double cos_v2_x_col = fabs(Pn.col(i).dot(v2) / col_norms[i]);
if (cos_v1_x_col + cos_v2_x_col <= min_dot1323)
{
index3 = i;
min_dot1323 = cos_v2_x_col + cos_v2_x_col;
}
}
}
Matx<double, 9, 1> v3 = Pn.col(index3);
Matx<double, 9, 1> n1 = N.col(1);
v3 -= (v3.dot(n1)) * n1 - (v3.dot(n0)) * n0;
v3 /= cv::norm(v3);
set<double, 9, 1, 9, 3>(0, 2, N, v3);
}
// if e = u*w*vt then r=u*diag([1, 1, det(u)*det(v)])*vt
void PoseSolver::nearestRotationMatrixSVD(const cv::Matx<double, 9, 1>& e,
cv::Matx<double, 9, 1>& r)
{
cv::Matx<double, 3, 3> e33 = e.reshape<3, 3>();
cv::SVD e33_svd(e33, cv::SVD::FULL_UV);
double detuv = cv::determinant(e33_svd.u)*cv::determinant(e33_svd.vt);
cv::Matx<double, 3, 3> diag = cv::Matx33d::eye();
diag(2, 2) = detuv;
cv::Matx<double, 3, 3> r33 = cv::Mat(e33_svd.u*diag*e33_svd.vt);
r = r33.reshape<9, 1>();
}
// Faster nearest rotation computation based on FOAM. See M. Lourakis: "An Efficient Solution to Absolute Orientation", ICPR 2016
// and M. Lourakis, G. Terzakis: "Efficient Absolute Orientation Revisited", IROS 2018.
/* Solve the nearest orthogonal approximation problem
* i.e., given e, find R minimizing ||R-e||_F
*
* The computation borrows from Markley's FOAM algorithm
* "Attitude Determination Using Vector Observations: A Fast Optimal Matrix Algorithm", J. Astronaut. Sci. 1993.
*
* See also M. Lourakis: "An Efficient Solution to Absolute Orientation", ICPR 2016
*
* Copyright (C) 2019 Manolis Lourakis (lourakis **at** ics forth gr)
* Institute of Computer Science, Foundation for Research & Technology - Hellas
* Heraklion, Crete, Greece.
*/
void PoseSolver::nearestRotationMatrixFOAM(const cv::Matx<double, 9, 1>& e,
cv::Matx<double, 9, 1>& r)
{
int i;
double l, lprev, det_e, e_sq, adj_e_sq, adj_e[9];
// det(e)
det_e = e(0) * e(4) * e(8) - e(0) * e(5) * e(7) - e(1) * e(3) * e(8) + e(2) * e(3) * e(7) + e(1) * e(6) * e(5) - e(2) * e(6) * e(4);
if (fabs(det_e) < 1E-04) { // singular, handle it with SVD
PoseSolver::nearestRotationMatrixSVD(e, r);
return;
}
// e's adjoint
adj_e[0] = e(4) * e(8) - e(5) * e(7); adj_e[1] = e(2) * e(7) - e(1) * e(8); adj_e[2] = e(1) * e(5) - e(2) * e(4);
adj_e[3] = e(5) * e(6) - e(3) * e(8); adj_e[4] = e(0) * e(8) - e(2) * e(6); adj_e[5] = e(2) * e(3) - e(0) * e(5);
adj_e[6] = e(3) * e(7) - e(4) * e(6); adj_e[7] = e(1) * e(6) - e(0) * e(7); adj_e[8] = e(0) * e(4) - e(1) * e(3);
// ||e||^2, ||adj(e)||^2
e_sq = e(0) * e(0) + e(1) * e(1) + e(2) * e(2) + e(3) * e(3) + e(4) * e(4) + e(5) * e(5) + e(6) * e(6) + e(7) * e(7) + e(8) * e(8);
adj_e_sq = adj_e[0] * adj_e[0] + adj_e[1] * adj_e[1] + adj_e[2] * adj_e[2] + adj_e[3] * adj_e[3] + adj_e[4] * adj_e[4] + adj_e[5] * adj_e[5] + adj_e[6] * adj_e[6] + adj_e[7] * adj_e[7] + adj_e[8] * adj_e[8];
// compute l_max with Newton-Raphson from FOAM's characteristic polynomial, i.e. eq.(23) - (26)
l = 0.5*(e_sq + 3.0); // 1/2*(trace(mat(e)*mat(e)') + trace(eye(3)))
if (det_e < 0.0) l = -l;
for (i = 15, lprev = 0.0; fabs(l - lprev) > 1E-12 * fabs(lprev) && i > 0; --i) {
double tmp, p, pp;
tmp = (l * l - e_sq);
p = (tmp * tmp - 8.0 * l * det_e - 4.0 * adj_e_sq);
pp = 8.0 * (0.5 * tmp * l - det_e);
lprev = l;
l -= p / pp;
}
// the rotation matrix equals ((l^2 + e_sq)*e + 2*l*adj(e') - 2*e*e'*e) / (l*(l*l-e_sq) - 2*det(e)), i.e. eq.(14) using (18), (19)
{
// compute (l^2 + e_sq)*e
double tmp[9], e_et[9], denom;
const double a = l * l + e_sq;
// e_et=e*e'
e_et[0] = e(0) * e(0) + e(1) * e(1) + e(2) * e(2);
e_et[1] = e(0) * e(3) + e(1) * e(4) + e(2) * e(5);
e_et[2] = e(0) * e(6) + e(1) * e(7) + e(2) * e(8);
e_et[3] = e_et[1];
e_et[4] = e(3) * e(3) + e(4) * e(4) + e(5) * e(5);
e_et[5] = e(3) * e(6) + e(4) * e(7) + e(5) * e(8);
e_et[6] = e_et[2];
e_et[7] = e_et[5];
e_et[8] = e(6) * e(6) + e(7) * e(7) + e(8) * e(8);
// tmp=e_et*e
tmp[0] = e_et[0] * e(0) + e_et[1] * e(3) + e_et[2] * e(6);
tmp[1] = e_et[0] * e(1) + e_et[1] * e(4) + e_et[2] * e(7);
tmp[2] = e_et[0] * e(2) + e_et[1] * e(5) + e_et[2] * e(8);
tmp[3] = e_et[3] * e(0) + e_et[4] * e(3) + e_et[5] * e(6);
tmp[4] = e_et[3] * e(1) + e_et[4] * e(4) + e_et[5] * e(7);
tmp[5] = e_et[3] * e(2) + e_et[4] * e(5) + e_et[5] * e(8);
tmp[6] = e_et[6] * e(0) + e_et[7] * e(3) + e_et[8] * e(6);
tmp[7] = e_et[6] * e(1) + e_et[7] * e(4) + e_et[8] * e(7);
tmp[8] = e_et[6] * e(2) + e_et[7] * e(5) + e_et[8] * e(8);
// compute R as (a*e + 2*(l*adj(e)' - tmp))*denom; note that adj(e')=adj(e)'
denom = l * (l * l - e_sq) - 2.0 * det_e;
denom = 1.0 / denom;
r(0) = (a * e(0) + 2.0 * (l * adj_e[0] - tmp[0])) * denom;
r(1) = (a * e(1) + 2.0 * (l * adj_e[3] - tmp[1])) * denom;
r(2) = (a * e(2) + 2.0 * (l * adj_e[6] - tmp[2])) * denom;
r(3) = (a * e(3) + 2.0 * (l * adj_e[1] - tmp[3])) * denom;
r(4) = (a * e(4) + 2.0 * (l * adj_e[4] - tmp[4])) * denom;
r(5) = (a * e(5) + 2.0 * (l * adj_e[7] - tmp[5])) * denom;
r(6) = (a * e(6) + 2.0 * (l * adj_e[2] - tmp[6])) * denom;
r(7) = (a * e(7) + 2.0 * (l * adj_e[5] - tmp[7])) * denom;
r(8) = (a * e(8) + 2.0 * (l * adj_e[8] - tmp[8])) * denom;
}
}
double PoseSolver::det3x3(const cv::Matx<double, 9, 1>& e)
{
return e(0) * e(4) * e(8) + e(1) * e(5) * e(6) + e(2) * e(3) * e(7)
- e(6) * e(4) * e(2) - e(7) * e(5) * e(0) - e(8) * e(3) * e(1);
}
inline bool PoseSolver::positiveDepth(const SQPSolution& solution) const
{
const cv::Matx<double, 9, 1>& r = solution.r_hat;
const cv::Matx<double, 3, 1>& t = solution.t;
const cv::Vec3d& mean = point_mean_;
return (r(6) * mean(0) + r(7) * mean(1) + r(8) * mean(2) + t(2) > 0);
}
inline bool PoseSolver::positiveMajorityDepths(const SQPSolution& solution, InputArray objectPoints) const
{
const cv::Matx<double, 9, 1>& r = solution.r_hat;
const cv::Matx<double, 3, 1>& t = solution.t;
int npos = 0, nneg = 0;
Mat _objectPoints = objectPoints.getMat();
int n = _objectPoints.cols * _objectPoints.rows;
for (int i = 0; i < n; i++)
{
const cv::Point3d& obj_pt = _objectPoints.at<cv::Point3d>(i);
if (r(6) * obj_pt.x + r(7) * obj_pt.y + r(8) * obj_pt.z + t(2) > 0) ++npos;
else ++nneg;
}
return npos >= nneg;
}
void PoseSolver::checkSolution(SQPSolution& solution, InputArray objectPoints, double& min_error)
{
bool cheirok = positiveDepth(solution) || positiveMajorityDepths(solution, objectPoints); // check the majority if the check with centroid fails
if (cheirok)
{
solution.sq_error = (omega_ * solution.r_hat).ddot(solution.r_hat);
if (fabs(min_error - solution.sq_error) > EQUAL_SQUARED_ERRORS_DIFF)
{
if (min_error > solution.sq_error)
{
min_error = solution.sq_error;
solutions_[0] = solution;
num_solutions_ = 1;
}
}
else
{
bool found = false;
for (int i = 0; i < num_solutions_; i++)
{
if (cv::norm(solutions_[i].r_hat - solution.r_hat, cv::NORM_L2SQR) < EQUAL_VECTORS_SQUARED_DIFF)
{
if (solutions_[i].sq_error > solution.sq_error)
{
solutions_[i] = solution;
}
found = true;
break;
}
}
if (!found)
{
solutions_[num_solutions_++] = solution;
}
if (min_error > solution.sq_error) min_error = solution.sq_error;
}
}
}
double PoseSolver::orthogonalityError(const cv::Matx<double, 9, 1>& e)
{
double sq_norm_e1 = e(0) * e(0) + e(1) * e(1) + e(2) * e(2);
double sq_norm_e2 = e(3) * e(3) + e(4) * e(4) + e(5) * e(5);
double sq_norm_e3 = e(6) * e(6) + e(7) * e(7) + e(8) * e(8);
double dot_e1e2 = e(0) * e(3) + e(1) * e(4) + e(2) * e(5);
double dot_e1e3 = e(0) * e(6) + e(1) * e(7) + e(2) * e(8);
double dot_e2e3 = e(3) * e(6) + e(4) * e(7) + e(5) * e(8);
return (sq_norm_e1 - 1) * (sq_norm_e1 - 1) + (sq_norm_e2 - 1) * (sq_norm_e2 - 1) + (sq_norm_e3 - 1) * (sq_norm_e3 - 1) +
2 * (dot_e1e2 * dot_e1e2 + dot_e1e3 * dot_e1e3 + dot_e2e3 * dot_e2e3);
}
}
}