Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
695 lines
35 KiB
695 lines
35 KiB
// 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. |
|
|
|
#include "../precomp.hpp" |
|
#include "../usac.hpp" |
|
|
|
namespace cv { namespace usac { |
|
class EpipolarGeometryDegeneracyImpl : public EpipolarGeometryDegeneracy { |
|
private: |
|
Mat points_mat; |
|
const int min_sample_size; |
|
public: |
|
explicit EpipolarGeometryDegeneracyImpl (const Mat &points_, int sample_size_) : |
|
points_mat(points_), min_sample_size (sample_size_) |
|
{ |
|
CV_DbgAssert(!points_mat.empty() && points_mat.isContinuous()); |
|
} |
|
/* |
|
* Do oriented constraint to verify if epipolar geometry is in front or behind the camera. |
|
* Return: true if all points are in front of the camers w.r.t. tested epipolar geometry - satisfies constraint. |
|
* false - otherwise. |
|
* x'^T F x = 0 |
|
* e' × x' ~+ Fx <=> λe' × x' = Fx, λ > 0 |
|
* e × x ~+ x'^T F |
|
*/ |
|
inline bool isModelValid(const Mat &F_, const std::vector<int> &sample) const override { |
|
const Vec3d ep = Utils::getRightEpipole(F_); |
|
const auto * const e = ep.val; // of size 3x1 |
|
const auto * const F = (double *) F_.data; |
|
const float * points = points_mat.ptr<float>(); |
|
|
|
// without loss of generality, let the first point in sample be in front of the camera. |
|
int pt = 4*sample[0]; |
|
// check only two first elements of vectors (e × x) and (x'^T F) |
|
// s1 = (x'^T F)[0] = x2 * F11 + y2 * F21 + 1 * F31 |
|
// s2 = (e × x)[0] = e'_2 * 1 - e'_3 * y1 |
|
// sign1 = s1 * s2 |
|
const double sign1 = (F[0]*points[pt+2]+F[3]*points[pt+3]+F[6])*(e[1]-e[2]*points[pt+1]); |
|
|
|
for (int i = 1; i < min_sample_size; i++) { |
|
pt = 4 * sample[i]; |
|
// if signum of the first point and tested point differs |
|
// then two points are on different sides of the camera. |
|
if (sign1*(F[0]*points[pt+2]+F[3]*points[pt+3]+F[6])*(e[1]-e[2]*points[pt+1])<0) |
|
return false; |
|
} |
|
return true; |
|
} |
|
}; |
|
void EpipolarGeometryDegeneracy::recoverRank (Mat &model, bool is_fundamental_mat) { |
|
/* |
|
* Do singular value decomposition. |
|
* Make last eigen value zero of diagonal matrix of singular values. |
|
*/ |
|
Matx33d U, Vt; |
|
Vec3d w; |
|
SVD::compute(model, w, U, Vt, SVD::MODIFY_A); |
|
if (is_fundamental_mat) |
|
model = Mat(U * Matx33d(w(0), 0, 0, 0, w(1), 0, 0, 0, 0) * Vt); |
|
else { |
|
const double mean_singular_val = (w[0] + w[1]) * 0.5; |
|
model = Mat(U * Matx33d(mean_singular_val, 0, 0, 0, mean_singular_val, 0, 0, 0, 0) * Vt); |
|
} |
|
} |
|
Ptr<EpipolarGeometryDegeneracy> EpipolarGeometryDegeneracy::create (const Mat &points_, |
|
int sample_size_) { |
|
return makePtr<EpipolarGeometryDegeneracyImpl>(points_, sample_size_); |
|
} |
|
|
|
class HomographyDegeneracyImpl : public HomographyDegeneracy { |
|
private: |
|
Mat points_mat; |
|
const float TOLERANCE = 2 * FLT_EPSILON; // 2 from area of triangle |
|
public: |
|
explicit HomographyDegeneracyImpl (const Mat &points_) : |
|
points_mat(points_) |
|
{ |
|
CV_DbgAssert(!points_mat.empty() && points_mat.isContinuous()); |
|
} |
|
|
|
inline bool isSampleGood (const std::vector<int> &sample) const override { |
|
const int smpl1 = 4*sample[0], smpl2 = 4*sample[1], smpl3 = 4*sample[2], smpl4 = 4*sample[3]; |
|
// planar correspondences must lie on the same side of any line from two points in sample |
|
const float * points = points_mat.ptr<float>(); |
|
const float x1 = points[smpl1], y1 = points[smpl1+1], X1 = points[smpl1+2], Y1 = points[smpl1+3]; |
|
const float x2 = points[smpl2], y2 = points[smpl2+1], X2 = points[smpl2+2], Y2 = points[smpl2+3]; |
|
const float x3 = points[smpl3], y3 = points[smpl3+1], X3 = points[smpl3+2], Y3 = points[smpl3+3]; |
|
const float x4 = points[smpl4], y4 = points[smpl4+1], X4 = points[smpl4+2], Y4 = points[smpl4+3]; |
|
// line from points 1 and 2 |
|
const float ab_cross_x = y1 - y2, ab_cross_y = x2 - x1, ab_cross_z = x1 * y2 - y1 * x2; |
|
const float AB_cross_x = Y1 - Y2, AB_cross_y = X2 - X1, AB_cross_z = X1 * Y2 - Y1 * X2; |
|
|
|
// check if points 3 and 4 are on the same side of line ab on both images |
|
if ((ab_cross_x * x3 + ab_cross_y * y3 + ab_cross_z) * |
|
(AB_cross_x * X3 + AB_cross_y * Y3 + AB_cross_z) < 0) |
|
return false; |
|
if ((ab_cross_x * x4 + ab_cross_y * y4 + ab_cross_z) * |
|
(AB_cross_x * X4 + AB_cross_y * Y4 + AB_cross_z) < 0) |
|
return false; |
|
|
|
// line from points 3 and 4 |
|
const float cd_cross_x = y3 - y4, cd_cross_y = x4 - x3, cd_cross_z = x3 * y4 - y3 * x4; |
|
const float CD_cross_x = Y3 - Y4, CD_cross_y = X4 - X3, CD_cross_z = X3 * Y4 - Y3 * X4; |
|
|
|
// check if points 1 and 2 are on the same side of line cd on both images |
|
if ((cd_cross_x * x1 + cd_cross_y * y1 + cd_cross_z) * |
|
(CD_cross_x * X1 + CD_cross_y * Y1 + CD_cross_z) < 0) |
|
return false; |
|
if ((cd_cross_x * x2 + cd_cross_y * y2 + cd_cross_z) * |
|
(CD_cross_x * X2 + CD_cross_y * Y2 + CD_cross_z) < 0) |
|
return false; |
|
|
|
// Checks if points are not collinear |
|
// If area of triangle constructed with 3 points is less than threshold then points are collinear: |
|
// |x1 y1 1| |x1 y1 1| |
|
// (1/2) det |x2 y2 1| = (1/2) det |x2-x1 y2-y1 0| = det |x2-x1 y2-y1| < 2 * threshold |
|
// |x3 y3 1| |x3-x1 y3-y1 0| |x3-x1 y3-y1| |
|
// for points on the first image |
|
if (fabsf((x2-x1) * (y3-y1) - (y2-y1) * (x3-x1)) < TOLERANCE) return false; //1,2,3 |
|
if (fabsf((x2-x1) * (y4-y1) - (y2-y1) * (x4-x1)) < TOLERANCE) return false; //1,2,4 |
|
if (fabsf((x3-x1) * (y4-y1) - (y3-y1) * (x4-x1)) < TOLERANCE) return false; //1,3,4 |
|
if (fabsf((x3-x2) * (y4-y2) - (y3-y2) * (x4-x2)) < TOLERANCE) return false; //2,3,4 |
|
// for points on the second image |
|
if (fabsf((X2-X1) * (Y3-Y1) - (Y2-Y1) * (X3-X1)) < TOLERANCE) return false; //1,2,3 |
|
if (fabsf((X2-X1) * (Y4-Y1) - (Y2-Y1) * (X4-X1)) < TOLERANCE) return false; //1,2,4 |
|
if (fabsf((X3-X1) * (Y4-Y1) - (Y3-Y1) * (X4-X1)) < TOLERANCE) return false; //1,3,4 |
|
if (fabsf((X3-X2) * (Y4-Y2) - (Y3-Y2) * (X4-X2)) < TOLERANCE) return false; //2,3,4 |
|
|
|
return true; |
|
} |
|
}; |
|
Ptr<HomographyDegeneracy> HomographyDegeneracy::create (const Mat &points_) { |
|
return makePtr<HomographyDegeneracyImpl>(points_); |
|
} |
|
|
|
class FundamentalDegeneracyViaEImpl : public FundamentalDegeneracyViaE { |
|
private: |
|
bool is_F_objective; |
|
std::vector<std::vector<int>> instances = {{0,1,2,3,4}, {2,3,4,5,6}, {0,1,4,5,6}}; |
|
std::vector<int> e_sample; |
|
const Ptr<Quality> quality; |
|
Ptr<EpipolarGeometryDegeneracy> e_degen, f_degen; |
|
Ptr<EssentialMinimalSolver5pts> e_solver; |
|
std::vector<Mat> e_models; |
|
const int E_SAMPLE_SIZE = 5; |
|
Matx33d K2_inv_t, K1_inv; |
|
public: |
|
FundamentalDegeneracyViaEImpl (const Ptr<Quality> &quality_, const Mat &pts, const Mat &calib_pts, const Matx33d &K1, const Matx33d &K2, bool is_f_objective) |
|
: quality(quality_) { |
|
is_F_objective = is_f_objective; |
|
e_solver = EssentialMinimalSolver5pts::create(calib_pts, false/*svd*/, true/*nister*/); |
|
f_degen = is_F_objective ? EpipolarGeometryDegeneracy::create(pts, 7) : EpipolarGeometryDegeneracy::create(calib_pts, 7); |
|
e_degen = EpipolarGeometryDegeneracy::create(calib_pts, E_SAMPLE_SIZE); |
|
e_sample = std::vector<int>(E_SAMPLE_SIZE); |
|
if (is_f_objective) { |
|
K2_inv_t = K2.inv().t(); |
|
K1_inv = K1.inv(); |
|
} |
|
} |
|
bool isModelValid (const Mat &F, const std::vector<int> &sample) const override { |
|
return f_degen->isModelValid(F, sample); |
|
} |
|
bool recoverIfDegenerate (const std::vector<int> &sample_7pt, const Mat &/*best*/, const Score &best_score, |
|
Mat &out_model, Score &out_score) override { |
|
out_score = Score(); |
|
for (const auto &instance : instances) { |
|
for (int i = 0; i < E_SAMPLE_SIZE; i++) |
|
e_sample[i] = sample_7pt[instance[i]]; |
|
const int num_models = e_solver->estimate(e_sample, e_models); |
|
for (int i = 0; i < num_models; i++) { |
|
if (e_degen->isModelValid(e_models[i], e_sample)) { |
|
const Mat model = is_F_objective ? Mat(K2_inv_t * Matx33d(e_models[i]) * K1_inv) : e_models[i]; |
|
const auto sc = quality->getScore(model); |
|
if (sc.isBetter(out_score)) { |
|
out_score = sc; |
|
model.copyTo(out_model); |
|
} |
|
} |
|
} |
|
if (out_score.isBetter(best_score)) break; |
|
} |
|
return true; |
|
} |
|
}; |
|
Ptr<FundamentalDegeneracyViaE> FundamentalDegeneracyViaE::create (const Ptr<Quality> &quality, const Mat &pts, |
|
const Mat &calib_pts, const Matx33d &K1, const Matx33d &K2, bool is_f_objective) { |
|
return makePtr<FundamentalDegeneracyViaEImpl>(quality, pts, calib_pts, K1, K2, is_f_objective); |
|
} |
|
///////////////////////////////// Fundamental Matrix Degeneracy /////////////////////////////////// |
|
class FundamentalDegeneracyImpl : public FundamentalDegeneracy { |
|
private: |
|
RNG rng; |
|
const Ptr<Quality> quality; |
|
const Ptr<Error> f_error; |
|
Ptr<Quality> h_repr_quality; |
|
Mat points_mat; |
|
const Ptr<ReprojectionErrorForward> h_reproj_error; |
|
Ptr<EpipolarNonMinimalSolver> f_non_min_solver; |
|
Ptr<HomographyNonMinimalSolver> h_non_min_solver; |
|
Ptr<UniformRandomGenerator> random_gen_H; |
|
const EpipolarGeometryDegeneracyImpl ep_deg; |
|
// threshold to find inliers for homography model |
|
const double homography_threshold, log_conf = log(0.05), H_SAMPLE_THR_SQR = 49/*7^2*/, MAX_H_THR = 225/*15^2*/; |
|
double f_threshold_sqr, best_focal = -1; |
|
// points (1-7) to verify in sample |
|
std::vector<std::vector<int>> h_sample {{0,1,2},{3,4,5},{0,1,6},{3,4,6},{2,5,6}}; |
|
std::vector<std::vector<int>> h_sample_ver {{3,4,5,6},{0,1,2,6},{2,3,4,5},{0,1,2,5},{0,1,3,4}}; |
|
std::vector<int> non_planar_supports, h_inliers, h_outliers, h_outliers_eval, f_inliers; |
|
std::vector<double> weights; |
|
std::vector<Mat> h_models; |
|
const int points_size, max_iters_plane_and_parallax, MAX_H_SUBSET = 50, MAX_ITERS_H = 6; |
|
int num_h_outliers, num_models_used_so_far = 0, estimated_min_non_planar_support, |
|
num_h_outliers_eval, TENT_MIN_NON_PLANAR_SUPP; |
|
const int MAX_MODELS_TO_TEST = 21, H_INLS_DEGEN_SAMPLE = 5; // 5 by DEGENSAC, Chum et al. |
|
Matx33d K, K2, K_inv, K2_inv, K2_inv_t, true_K2_inv, true_K2_inv_t, true_K1_inv, true_K1, true_K2_t; |
|
Score best_focal_score; |
|
bool true_K_given, is_principal_pt_set = false; |
|
public: |
|
FundamentalDegeneracyImpl (int state, const Ptr<Quality> &quality_, const Mat &points_, |
|
int sample_size_, int plane_and_parallax_iters, double homography_threshold_, |
|
double f_inlier_thr_sqr, const Mat true_K1_, const Mat true_K2_) : |
|
rng (state), quality(quality_), f_error(quality_->getErrorFnc()), points_mat(points_), |
|
h_reproj_error(ReprojectionErrorForward::create(points_)), |
|
ep_deg (points_, sample_size_), homography_threshold (homography_threshold_), |
|
points_size (quality_->getPointsSize()), |
|
max_iters_plane_and_parallax(plane_and_parallax_iters) { |
|
if (sample_size_ == 8) { |
|
// add more homography samples to test for 8-points F |
|
h_sample.emplace_back(std::vector<int>{0, 1, 7}); h_sample_ver.emplace_back(std::vector<int>{2,3,4,5,6}); |
|
h_sample.emplace_back(std::vector<int>{0, 2, 7}); h_sample_ver.emplace_back(std::vector<int>{1,3,4,5,6}); |
|
h_sample.emplace_back(std::vector<int>{3, 5, 7}); h_sample_ver.emplace_back(std::vector<int>{0,1,2,4,6}); |
|
h_sample.emplace_back(std::vector<int>{3, 6, 7}); h_sample_ver.emplace_back(std::vector<int>{0,1,2,4,5}); |
|
h_sample.emplace_back(std::vector<int>{2, 4, 7}); h_sample_ver.emplace_back(std::vector<int>{0,1,3,5,6}); |
|
} |
|
non_planar_supports = std::vector<int>(MAX_MODELS_TO_TEST); |
|
h_inliers = std::vector<int>(points_size); |
|
h_outliers = std::vector<int>(points_size); |
|
h_outliers_eval = std::vector<int>(points_size); |
|
f_inliers = std::vector<int>(points_size); |
|
h_non_min_solver = HomographyNonMinimalSolver::create(points_); |
|
f_non_min_solver = EpipolarNonMinimalSolver::create(points_, true /*F*/); |
|
num_h_outliers_eval = num_h_outliers = points_size; |
|
f_threshold_sqr = f_inlier_thr_sqr; |
|
h_repr_quality = MsacQuality::create(points_.rows, homography_threshold_, h_reproj_error); |
|
true_K_given = ! true_K1_.empty() && ! true_K2_.empty(); |
|
if (true_K_given) { |
|
true_K1 = Matx33d((double *)true_K1_.data); |
|
true_K2_inv = Matx33d(Mat(true_K2_.inv())); |
|
true_K2_t = Matx33d(true_K2_).t(); |
|
true_K1_inv = true_K1.inv(); |
|
true_K2_inv_t = true_K2_inv.t(); |
|
} |
|
random_gen_H = UniformRandomGenerator::create(rng.uniform(0, INT_MAX), points_size, MAX_H_SUBSET); |
|
estimated_min_non_planar_support = TENT_MIN_NON_PLANAR_SUPP = std::min(5, (int)(0.05*points_size)); |
|
} |
|
bool estimateHfrom3Points (const Mat &F_best, const std::vector<int> &sample, Mat &H_best) { |
|
Score H_best_score; |
|
// find e', null vector of F^T |
|
const Vec3d e_prime = Utils::getLeftEpipole(F_best); |
|
const Matx33d A = Math::getSkewSymmetric(e_prime) * Matx33d(F_best); |
|
bool is_degenerate = false; |
|
int idx = -1; |
|
for (const auto &h_i : h_sample) { // only 5 samples |
|
idx++; |
|
Matx33d H; |
|
if (!getH(A, e_prime, 4 * sample[h_i[0]], 4 * sample[h_i[1]], 4 * sample[h_i[2]], H)) |
|
continue; |
|
h_reproj_error->setModelParameters(Mat(H)); |
|
const auto &ver_pts = h_sample_ver[idx]; |
|
int inliers_in_plane = 3; // 3 points are always inliers |
|
for (int s : ver_pts) |
|
if (h_reproj_error->getError(sample[s]) < homography_threshold) { |
|
if (++inliers_in_plane >= H_INLS_DEGEN_SAMPLE) |
|
break; |
|
} |
|
if (inliers_in_plane >= H_INLS_DEGEN_SAMPLE) { |
|
is_degenerate = true; |
|
const auto h_score = h_repr_quality->getScore(Mat(H)); |
|
if (h_score.isBetter(H_best_score)) { |
|
H_best_score = h_score; |
|
H_best = Mat(H); |
|
} |
|
} |
|
} |
|
if (!is_degenerate) |
|
return false; |
|
int h_inls_cnt = optimizeH(H_best, H_best_score); |
|
for (int iter = 0; iter < 2; iter++) { |
|
if (h_non_min_solver->estimate(h_inliers, h_inls_cnt, h_models, weights) == 0) |
|
break; |
|
const auto h_score = h_repr_quality->getScore(h_models[0]); |
|
if (h_score.isBetter(H_best_score)) { |
|
H_best_score = h_score; |
|
h_models[0].copyTo(H_best); |
|
h_inls_cnt = h_repr_quality->getInliers(H_best, h_inliers); |
|
} else break; |
|
} |
|
getOutliersH(H_best); |
|
return true; |
|
} |
|
bool optimizeF (const Mat &F, const Score &score, Mat &F_new, Score &new_score) { |
|
std::vector<Mat> Fs; |
|
if (f_non_min_solver->estimate(f_inliers, quality->getInliers(F, f_inliers), Fs, weights)) { |
|
const auto F_polished_score = quality->getScore(f_error->getErrors(Fs[0])); |
|
if (F_polished_score.isBetter(score)) { |
|
Fs[0].copyTo(F_new); new_score = F_polished_score; |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
int optimizeH (Mat &H_best, Score &H_best_score) { |
|
int h_inls_cnt = h_repr_quality->getInliers(H_best, h_inliers); |
|
random_gen_H->setSubsetSize(h_inls_cnt <= MAX_H_SUBSET ? (int)(0.8*h_inls_cnt) : MAX_H_SUBSET); |
|
if (random_gen_H->getSubsetSize() >= 4/*min H sample size*/) { |
|
for (int iter = 0; iter < MAX_ITERS_H; iter++) { |
|
if (h_non_min_solver->estimate(random_gen_H->generateUniqueRandomSubset(h_inliers, h_inls_cnt), random_gen_H->getSubsetSize(), h_models, weights) == 0) |
|
continue; |
|
const auto h_score = h_repr_quality->getScore(h_models[0]); |
|
if (h_score.isBetter(H_best_score)) { |
|
h_models[0].copyTo(H_best); |
|
// if more inliers than previous best |
|
if (h_score.inlier_number > H_best_score.inlier_number || h_score.inlier_number >= MAX_H_SUBSET) { |
|
h_inls_cnt = h_repr_quality->getInliers(H_best, h_inliers); |
|
random_gen_H->setSubsetSize(h_inls_cnt <= MAX_H_SUBSET ? (int)(0.8*h_inls_cnt) : MAX_H_SUBSET); |
|
} |
|
H_best_score = h_score; |
|
} |
|
} |
|
} |
|
return h_inls_cnt; |
|
} |
|
|
|
bool recoverIfDegenerate (const std::vector<int> &sample, const Mat &F_best, const Score &F_best_score, |
|
Mat &non_degenerate_model, Score &non_degenerate_model_score) override { |
|
const auto swapF = [&] (const Mat &_F, const Score &_score) { |
|
const auto non_min_solver = EpipolarNonMinimalSolver::create(points_mat, true); |
|
if (! optimizeF(_F, _score, non_degenerate_model, non_degenerate_model_score)) { |
|
_F.copyTo(non_degenerate_model); |
|
non_degenerate_model_score = _score; |
|
} |
|
}; |
|
Mat F_from_H, F_from_E, H_best; Score F_from_H_score, F_from_E_score; |
|
if (! estimateHfrom3Points(F_best, sample, H_best)) { |
|
return false; // non degenerate |
|
} |
|
if (true_K_given) { |
|
if (getFfromTrueK(H_best, F_from_H, F_from_H_score)) { |
|
if (F_from_H_score.isBetter(F_from_E_score)) |
|
swapF(F_from_H, F_from_H_score); |
|
else swapF(F_from_E, F_from_E_score); |
|
return true; |
|
} else { |
|
non_degenerate_model_score = Score(); |
|
return true; // no translation |
|
} |
|
} |
|
const int non_planar_support_degen_F = getNonPlanarSupport(F_best); |
|
Score F_pl_par_score, F_calib_score; Mat F_pl_par, F_calib; |
|
if (calibDegensac(H_best, F_calib, F_calib_score, non_planar_support_degen_F, F_best_score)) { |
|
if (planeAndParallaxRANSAC(H_best, h_outliers, num_h_outliers, max_iters_plane_and_parallax, true, |
|
F_best_score, non_planar_support_degen_F, F_pl_par, F_pl_par_score) && F_pl_par_score.isBetter(F_calib_score) |
|
&& getNonPlanarSupport(F_pl_par) > getNonPlanarSupport(F_calib)) { |
|
swapF(F_pl_par, F_pl_par_score); |
|
return true; |
|
} |
|
swapF(F_calib, F_calib_score); |
|
return true; |
|
} else { |
|
if (planeAndParallaxRANSAC(H_best, h_outliers, num_h_outliers, max_iters_plane_and_parallax, true, |
|
F_best_score, non_planar_support_degen_F, F_pl_par, F_pl_par_score)) { |
|
swapF(F_pl_par, F_pl_par_score); |
|
return true; |
|
} |
|
} |
|
if (! isFDegenerate(non_planar_support_degen_F)) { |
|
return false; |
|
} |
|
non_degenerate_model_score = Score(); |
|
return true; |
|
} |
|
|
|
// RANSAC with plane-and-parallax to find new Fundamental matrix |
|
bool getFfromTrueK (const Matx33d &H, Mat &F_from_K, Score &F_from_K_score) { |
|
std::vector<Matx33d> R; std::vector<Vec3d> t; |
|
if (decomposeHomographyMat(true_K2_inv * H * true_K1, Matx33d::eye(), R, t, noArray()) == 1) { |
|
// std::cout << "Warning: translation is zero!\n"; |
|
return false; // is degenerate |
|
} |
|
// sign of translation does not make difference |
|
const Mat F1 = Mat(true_K2_inv_t * Math::getSkewSymmetric(t[0]) * R[0] * true_K1_inv); |
|
const Mat F2 = Mat(true_K2_inv_t * Math::getSkewSymmetric(t[1]) * R[1] * true_K1_inv); |
|
const auto score_f1 = quality->getScore(f_error->getErrors(F1)), score_f2 = quality->getScore(f_error->getErrors(F2)); |
|
if (score_f1.isBetter(score_f2)) { |
|
F_from_K = F1; F_from_K_score = score_f1; |
|
} else { |
|
F_from_K = F2; F_from_K_score = score_f2; |
|
} |
|
return true; |
|
} |
|
bool planeAndParallaxRANSAC (const Matx33d &H, std::vector<int> &non_planar_pts, int num_non_planar_pts, |
|
int max_iters_pl_par, bool use_preemptive, const Score &score_degen_F, int non_planar_support_degen_F, |
|
Mat &F_new, Score &F_new_score) { |
|
if (num_non_planar_pts < 2) |
|
return false; |
|
num_models_used_so_far = 0; // reset estimation of lambda for plane-and-parallax |
|
int max_iters = max_iters_pl_par, non_planar_support = 0, iters = 0; |
|
const float * points = points_mat.ptr<float>(); |
|
std::vector<Matx33d> F_good; |
|
const double CLOSE_THR = 1.0; |
|
for (; iters < max_iters; iters++) { |
|
// draw two random points |
|
int h_outlier1 = 4 * non_planar_pts[rng.uniform(0, num_non_planar_pts)]; |
|
int h_outlier2 = 4 * non_planar_pts[rng.uniform(0, num_non_planar_pts)]; |
|
while (h_outlier1 == h_outlier2) |
|
h_outlier2 = 4 * non_planar_pts[rng.uniform(0, num_non_planar_pts)]; |
|
|
|
const auto x1 = points[h_outlier1], y1 = points[h_outlier1+1], X1 = points[h_outlier1+2], Y1 = points[h_outlier1+3]; |
|
const auto x2 = points[h_outlier2], y2 = points[h_outlier2+1], X2 = points[h_outlier2+2], Y2 = points[h_outlier2+3]; |
|
if ((fabsf(X1 - X2) < CLOSE_THR && fabsf(Y1 - Y2) < CLOSE_THR) || |
|
(fabsf(x1 - x2) < CLOSE_THR && fabsf(y1 - y2) < CLOSE_THR)) |
|
continue; |
|
|
|
// do plane and parallax with outliers of H |
|
// F = [(p1' x Hp1) x (p2' x Hp2)]_x H = [e']_x H |
|
Vec3d ep = (Vec3d(X1, Y1, 1).cross(H * Vec3d(x1, y1, 1))) // l1 = p1' x Hp1 |
|
.cross((Vec3d(X2, Y2, 1).cross(H * Vec3d(x2, y2, 1))));// l2 = p2' x Hp2 |
|
const Matx33d F = Math::getSkewSymmetric(ep) * H; |
|
const auto * const f = F.val; |
|
// check orientation constraint of epipolar lines |
|
const bool valid = (f[0]*x1+f[1]*y1+f[2])*(ep[1]-ep[2]*Y1) * (f[0]*x2+f[1]*y2+f[2])*(ep[1]-ep[2]*Y2) > 0; |
|
if (!valid) continue; |
|
|
|
const int num_f_inliers_of_h_outliers = getNonPlanarSupport(Mat(F), num_models_used_so_far >= MAX_MODELS_TO_TEST, non_planar_support); |
|
if (non_planar_support < num_f_inliers_of_h_outliers) { |
|
non_planar_support = num_f_inliers_of_h_outliers; |
|
const double predicted_iters = log_conf / log(1 - pow(static_cast<double> |
|
(getNonPlanarSupport(Mat(F), non_planar_pts, num_non_planar_pts)) / num_non_planar_pts, 2)); |
|
if (use_preemptive && ! std::isinf(predicted_iters) && predicted_iters < max_iters) |
|
max_iters = static_cast<int>(predicted_iters); |
|
F_good = { F }; |
|
} else if (non_planar_support == num_f_inliers_of_h_outliers) { |
|
F_good.emplace_back(F); |
|
} |
|
} |
|
|
|
F_new_score = Score(); |
|
for (const auto &F_ : F_good) { |
|
const auto sc = quality->getScore(f_error->getErrors(Mat(F_))); |
|
if (sc.isBetter(F_new_score)) { |
|
F_new_score = sc; |
|
F_new = Mat(F_); |
|
} |
|
} |
|
if (F_new_score.isBetter(score_degen_F) && non_planar_support > non_planar_support_degen_F) |
|
return true; |
|
if (isFDegenerate(non_planar_support)) |
|
return false; |
|
return true; |
|
} |
|
bool calibDegensac (const Matx33d &H, Mat &F_new, Score &F_new_score, int non_planar_support_degen_F, const Score &F_degen_score) { |
|
const float * points = points_mat.ptr<float>(); |
|
if (! is_principal_pt_set) { |
|
// estimate principal points from coordinates |
|
float px1 = 0, py1 = 0, px2 = 0, py2 = 0; |
|
for (int i = 0; i < points_size; i++) { |
|
const int idx = 4*i; |
|
if (px1 < points[idx ]) px1 = points[idx ]; |
|
if (py1 < points[idx+1]) py1 = points[idx+1]; |
|
if (px2 < points[idx+2]) px2 = points[idx+2]; |
|
if (py2 < points[idx+3]) py2 = points[idx+3]; |
|
} |
|
setPrincipalPoint((int)(px1/2)+1, (int)(py1/2)+1, (int)(px2/2)+1, (int)(py2/2)+1); |
|
} |
|
std::vector<Mat> R; std::vector<Mat> t; std::vector<Mat> F_good; |
|
std::vector<double> best_f; |
|
int non_planar_support_out = 0; |
|
for (double f = 300; f <= 3000; f += 150.) { |
|
K(0,0) = K(1,1) = K2(0,0) = K2(1,1) = f; |
|
const double one_over_f = 1/f; |
|
K_inv(0,0) = K_inv(1,1) = K2_inv(0,0) = K2_inv(1,1) = K2_inv_t(0,0) = K2_inv_t(1,1) = one_over_f; |
|
K_inv(0,2) = -K(0,2)*one_over_f; K_inv(1,2) = -K(1,2)*one_over_f; |
|
K2_inv_t(2,0) = K2_inv(0,2) = -K2(0,2)*one_over_f; K2_inv_t(2,1) = K2_inv(1,2) = -K2(1,2)*one_over_f; |
|
if (decomposeHomographyMat(K2_inv * H * K, Matx33d::eye(), R, t, noArray()) == 1) continue; |
|
Mat F1 = Mat(K2_inv_t * Math::getSkewSymmetric(Vec3d(t[0])) * Matx33d(R[0]) * K_inv); |
|
Mat F2 = Mat(K2_inv_t * Math::getSkewSymmetric(Vec3d(t[2])) * Matx33d(R[2]) * K_inv); |
|
int non_planar_f1 = getNonPlanarSupport(F1, true, non_planar_support_out), |
|
non_planar_f2 = getNonPlanarSupport(F2, true, non_planar_support_out); |
|
if (non_planar_f1 < non_planar_f2) { |
|
non_planar_f1 = non_planar_f2; F1 = F2; |
|
} |
|
if (non_planar_support_out < non_planar_f1) { |
|
non_planar_support_out = non_planar_f1; |
|
F_good = {F1}; |
|
best_f = { f }; |
|
} else if (non_planar_support_out == non_planar_f1) { |
|
F_good.emplace_back(F1); |
|
best_f.emplace_back(f); |
|
} |
|
} |
|
F_new_score = Score(); |
|
for (int i = 0; i < (int) F_good.size(); i++) { |
|
const auto sc = quality->getScore(f_error->getErrors(F_good[i])); |
|
if (sc.isBetter(F_new_score)) { |
|
F_new_score = sc; |
|
F_good[i].copyTo(F_new); |
|
if (sc.isBetter(best_focal_score)) { |
|
best_focal = best_f[i]; // save best focal length |
|
best_focal_score = sc; |
|
} |
|
} |
|
} |
|
if (F_degen_score.isBetter(F_new_score) && non_planar_support_out <= non_planar_support_degen_F) |
|
return false; |
|
|
|
/* |
|
// logarithmic search -> faster but less accurate |
|
double f_min = 300, f_max = 3500; |
|
while (f_max - f_min > 100) { |
|
const double f_half = (f_max + f_min) * 0.5f, left_half = (f_min + f_half) * 0.5f, right_half = (f_half + f_max) * 0.5f; |
|
const double inl_in_left = eval_f(left_half), inl_in_right = eval_f(right_half); |
|
if (inl_in_left > inl_in_right) |
|
f_max = f_half; |
|
else f_min = f_half; |
|
} |
|
*/ |
|
return true; |
|
} |
|
void getOutliersH (const Mat &H_best) { |
|
// get H outliers |
|
num_h_outliers_eval = num_h_outliers = 0; |
|
const auto &h_errors = h_reproj_error->getErrors(H_best); |
|
for (int pt = 0; pt < points_size; pt++) |
|
if (h_errors[pt] > H_SAMPLE_THR_SQR) { |
|
h_outliers[num_h_outliers++] = pt; |
|
if (h_errors[pt] > MAX_H_THR) |
|
h_outliers_eval[num_h_outliers_eval++] = pt; |
|
} |
|
} |
|
|
|
bool verifyFundamental (const Mat &F_best, const Score &F_score, const std::vector<bool> &inliers_mask, Mat &F_new, Score &new_score) override { |
|
const int f_sample_size = 3, max_H_iters = 5; // 3.52 = log(0.01) / log(1 - std::pow(0.9, 3)); |
|
int num_f_inliers = 0; |
|
std::vector<int> inliers(points_size), f_sample(f_sample_size); |
|
for (int i = 0; i < points_size; i++) if (inliers_mask[i]) inliers[num_f_inliers++] = i; |
|
const auto sampler = UniformSampler::create(0, f_sample_size, num_f_inliers); |
|
// find e', null space of F^T |
|
const Vec3d e_prime = Utils::getLeftEpipole(F_best); |
|
const Matx33d A = Math::getSkewSymmetric(e_prime) * Matx33d(F_best); |
|
Score H_best_score; Mat H_best; |
|
for (int iter = 0; iter < max_H_iters; iter++) { |
|
sampler->generateSample(f_sample); |
|
Matx33d H; |
|
if (!getH(A, e_prime, 4*inliers[f_sample[0]], 4*inliers[f_sample[1]], 4*inliers[f_sample[2]], H)) |
|
continue; |
|
const auto h_score = h_repr_quality->getScore(Mat(H)); |
|
if (h_score.isBetter(H_best_score)) { |
|
H_best_score = h_score; H_best = Mat(H); |
|
} |
|
} |
|
if (H_best.empty()) return false; // non-degenerate |
|
optimizeH(H_best, H_best_score); |
|
getOutliersH(H_best); |
|
const int non_planar_support_best_F = getNonPlanarSupport(F_best); |
|
const bool is_F_degen = isFDegenerate(non_planar_support_best_F); |
|
Mat F_from_K; Score F_from_K_score; |
|
bool success = false; |
|
// generate non-degenerate F even though so-far-the-best one may not be degenerate |
|
if (true_K_given) { |
|
// use GT calibration |
|
if (getFfromTrueK(H_best, F_from_K, F_from_K_score)) { |
|
new_score = F_from_K_score; |
|
F_from_K.copyTo(F_new); |
|
success = true; |
|
} |
|
} else { |
|
// use calibrated DEGENSAC |
|
if (calibDegensac(H_best, F_from_K, F_from_K_score, non_planar_support_best_F, F_score)) { |
|
new_score = F_from_K_score; |
|
F_from_K.copyTo(F_new); |
|
success = true; |
|
} |
|
} |
|
if (!is_F_degen) { |
|
return false; |
|
} else if (success) // F is degenerate |
|
return true; // but successfully recovered |
|
|
|
// recover degenerate F using plane-and-parallax |
|
Score plane_parallax_score; Mat F_plane_parallax; |
|
if (planeAndParallaxRANSAC(H_best, h_outliers, num_h_outliers, 20, true, |
|
F_score, non_planar_support_best_F, F_plane_parallax, plane_parallax_score)) { |
|
new_score = plane_parallax_score; |
|
F_plane_parallax.copyTo(F_new); |
|
return true; |
|
} |
|
// plane-and-parallax failed. A previous non-degenerate so-far-the-best model will be used instead |
|
new_score = Score(); |
|
return true; |
|
} |
|
void setPrincipalPoint (double px_, double py_) override { |
|
setPrincipalPoint(px_, py_, 0, 0); |
|
} |
|
void setPrincipalPoint (double px_, double py_, double px2_, double py2_) override { |
|
if (px_ > DBL_EPSILON && py_ > DBL_EPSILON) { |
|
is_principal_pt_set = true; |
|
K = {1, 0, px_, 0, 1, py_, 0, 0, 1}; |
|
if (px2_ > DBL_EPSILON && py2_ > DBL_EPSILON) K2 = {1, 0, px2_, 0, 1, py2_, 0, 0, 1}; |
|
else K2 = K; |
|
K_inv = K2_inv = K2_inv_t = Matx33d::eye(); |
|
} |
|
} |
|
private: |
|
bool getH (const Matx33d &A, const Vec3d &e_prime, int smpl1, int smpl2, int smpl3, Matx33d &H) { |
|
const float * points = points_mat.ptr<float>(); |
|
Vec3d p1(points[smpl1 ], points[smpl1+1], 1), p2(points[smpl2 ], points[smpl2+1], 1), p3(points[smpl3 ], points[smpl3+1], 1); |
|
Vec3d P1(points[smpl1+2], points[smpl1+3], 1), P2(points[smpl2+2], points[smpl2+3], 1), P3(points[smpl3+2], points[smpl3+3], 1); |
|
const Matx33d M (p1[0], p1[1], 1, p2[0], p2[1], 1, p3[0], p3[1], 1); |
|
if (p1.cross(p2).dot(p3) * P1.cross(P2).dot(P3) < 0) return false; |
|
// (x′i × e') |
|
const Vec3d P1e = P1.cross(e_prime), P2e = P2.cross(e_prime), P3e = P3.cross(e_prime); |
|
// x′i × (A xi))^T (x′i × e′) / ‖x′i×e′‖^2, |
|
const Vec3d b (P1.cross(A * p1).dot(P1e) / (P1e[0]*P1e[0]+P1e[1]*P1e[1]+P1e[2]*P1e[2]), |
|
P2.cross(A * p2).dot(P2e) / (P2e[0]*P2e[0]+P2e[1]*P2e[1]+P2e[2]*P2e[2]), |
|
P3.cross(A * p3).dot(P3e) / (P3e[0]*P3e[0]+P3e[1]*P3e[1]+P3e[2]*P3e[2])); |
|
|
|
H = A - e_prime * (M.inv() * b).t(); |
|
return true; |
|
} |
|
int getNonPlanarSupport (const Mat &F, const std::vector<int> &pts, int num_pts) { |
|
int non_rand_support = 0; |
|
f_error->setModelParameters(F); |
|
for (int pt = 0; pt < num_pts; pt++) |
|
if (f_error->getError(pts[pt]) < f_threshold_sqr) |
|
non_rand_support++; |
|
return non_rand_support; |
|
} |
|
|
|
int getNonPlanarSupport (const Mat &F, bool preemptive=false, int max_so_far=0) { |
|
int non_rand_support = 0; |
|
f_error->setModelParameters(F); |
|
if (preemptive) { |
|
const auto preemptive_thr = -num_h_outliers_eval + max_so_far; |
|
for (int pt = 0; pt < num_h_outliers_eval; pt++) |
|
if (f_error->getError(h_outliers_eval[pt]) < f_threshold_sqr) |
|
non_rand_support++; |
|
else if (non_rand_support - pt < preemptive_thr) |
|
break; |
|
} else { |
|
for (int pt = 0; pt < num_h_outliers_eval; pt++) |
|
if (f_error->getError(h_outliers_eval[pt]) < f_threshold_sqr) |
|
non_rand_support++; |
|
if (num_models_used_so_far < MAX_MODELS_TO_TEST && !true_K_given/*for K we know that recovered F cannot be degenerate*/) { |
|
non_planar_supports[num_models_used_so_far++] = non_rand_support; |
|
if (num_models_used_so_far == MAX_MODELS_TO_TEST) { |
|
getLambda(non_planar_supports, 2.32, num_h_outliers_eval, 0, false, estimated_min_non_planar_support); |
|
if (estimated_min_non_planar_support < 3) estimated_min_non_planar_support = 3; |
|
} |
|
} |
|
} |
|
return non_rand_support; |
|
} |
|
inline bool isModelValid(const Mat &F, const std::vector<int> &sample) const override { |
|
return ep_deg.isModelValid(F, sample); |
|
} |
|
bool isFDegenerate (int num_f_inliers_h_outliers) const { |
|
if (num_models_used_so_far < MAX_MODELS_TO_TEST) |
|
// the minimum number of non-planar support has not estimated yet -> use tentative |
|
return num_f_inliers_h_outliers < std::min(TENT_MIN_NON_PLANAR_SUPP, (int)(0.1 * num_h_outliers_eval)); |
|
return num_f_inliers_h_outliers < estimated_min_non_planar_support; |
|
} |
|
}; |
|
Ptr<FundamentalDegeneracy> FundamentalDegeneracy::create (int state, const Ptr<Quality> &quality_, |
|
const Mat &points_, int sample_size_, int max_iters_plane_and_parallax, double homography_threshold_, |
|
double f_inlier_thr_sqr, const Mat true_K1, const Mat true_K2) { |
|
return makePtr<FundamentalDegeneracyImpl>(state, quality_, points_, sample_size_, |
|
max_iters_plane_and_parallax, homography_threshold_, f_inlier_thr_sqr, true_K1, true_K2); |
|
} |
|
|
|
|
|
class EssentialDegeneracyImpl : public EssentialDegeneracy { |
|
private: |
|
const EpipolarGeometryDegeneracyImpl ep_deg; |
|
public: |
|
explicit EssentialDegeneracyImpl (const Mat &points, int sample_size_) : |
|
ep_deg (points, sample_size_) {} |
|
inline bool isModelValid(const Mat &E, const std::vector<int> &sample) const override { |
|
return ep_deg.isModelValid(E, sample); |
|
} |
|
}; |
|
Ptr<EssentialDegeneracy> EssentialDegeneracy::create (const Mat &points_, int sample_size_) { |
|
return makePtr<EssentialDegeneracyImpl>(points_, sample_size_); |
|
} |
|
}}
|
|
|