Merge pull request #23078 from ivashmak:update_vsac

Update USAC #23078

### Pull Request Readiness Checklist

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Maksym Ivashechkin 2023-06-16 08:59:13 +01:00 committed by GitHub
parent 3c0b71bcec
commit 44881592c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 4493 additions and 2314 deletions

View File

@ -548,12 +548,13 @@ enum RobotWorldHandEyeCalibrationMethod
CALIB_ROBOT_WORLD_HAND_EYE_LI = 1 //!< Simultaneous robot-world and hand-eye calibration using dual-quaternions and kronecker product @cite Li2010SimultaneousRA
};
enum SamplingMethod { SAMPLING_UNIFORM, SAMPLING_PROGRESSIVE_NAPSAC, SAMPLING_NAPSAC,
SAMPLING_PROSAC };
enum LocalOptimMethod {LOCAL_OPTIM_NULL, LOCAL_OPTIM_INNER_LO, LOCAL_OPTIM_INNER_AND_ITER_LO,
LOCAL_OPTIM_GC, LOCAL_OPTIM_SIGMA};
enum ScoreMethod {SCORE_METHOD_RANSAC, SCORE_METHOD_MSAC, SCORE_METHOD_MAGSAC, SCORE_METHOD_LMEDS};
enum NeighborSearchMethod { NEIGH_FLANN_KNN, NEIGH_GRID, NEIGH_FLANN_RADIUS };
enum SamplingMethod { SAMPLING_UNIFORM=0, SAMPLING_PROGRESSIVE_NAPSAC=1, SAMPLING_NAPSAC=2,
SAMPLING_PROSAC=3 };
enum LocalOptimMethod {LOCAL_OPTIM_NULL=0, LOCAL_OPTIM_INNER_LO=1, LOCAL_OPTIM_INNER_AND_ITER_LO=2,
LOCAL_OPTIM_GC=3, LOCAL_OPTIM_SIGMA=4};
enum ScoreMethod {SCORE_METHOD_RANSAC=0, SCORE_METHOD_MSAC=1, SCORE_METHOD_MAGSAC=2, SCORE_METHOD_LMEDS=3};
enum NeighborSearchMethod { NEIGH_FLANN_KNN=0, NEIGH_GRID=1, NEIGH_FLANN_RADIUS=2 };
enum PolishingMethod { NONE_POLISHER=0, LSQ_POLISHER=1, MAGSAC=2, COV_POLISHER=3 };
struct CV_EXPORTS_W_SIMPLE UsacParams
{ // in alphabetical order
@ -569,6 +570,8 @@ struct CV_EXPORTS_W_SIMPLE UsacParams
CV_PROP_RW SamplingMethod sampler;
CV_PROP_RW ScoreMethod score;
CV_PROP_RW double threshold;
CV_PROP_RW PolishingMethod final_polisher;
CV_PROP_RW int final_polisher_iterations;
};
/** @brief Converts a rotation matrix to a rotation vector or vice versa.

View File

@ -522,9 +522,9 @@ cv::Mat cv::findEssentialMat( InputArray points1, InputArray points2,
InputArray cameraMatrix1, InputArray cameraMatrix2,
InputArray dist_coeff1, InputArray dist_coeff2, OutputArray mask, const UsacParams &params) {
Ptr<usac::Model> model;
usac::setParameters(model, usac::EstimationMethod::Essential, params, mask.needed());
usac::setParameters(model, usac::EstimationMethod::ESSENTIAL, params, mask.needed());
Ptr<usac::RansacOutput> ransac_output;
if (usac::run(model, points1, points2, model->getRandomGeneratorState(),
if (usac::run(model, points1, points2,
ransac_output, cameraMatrix1, cameraMatrix2, dist_coeff1, dist_coeff2)) {
usac::saveMask(mask, ransac_output->getInliersMask());
return ransac_output->getModel();

View File

@ -451,9 +451,9 @@ cv::Mat cv::findHomography( InputArray _points1, InputArray _points2,
cv::Mat cv::findHomography(InputArray srcPoints, InputArray dstPoints, OutputArray mask,
const UsacParams &params) {
Ptr<usac::Model> model;
usac::setParameters(model, usac::EstimationMethod::Homography, params, mask.needed());
usac::setParameters(model, usac::EstimationMethod::HOMOGRAPHY, params, mask.needed());
Ptr<usac::RansacOutput> ransac_output;
if (usac::run(model, srcPoints, dstPoints, model->getRandomGeneratorState(),
if (usac::run(model, srcPoints, dstPoints,
ransac_output, noArray(), noArray(), noArray(), noArray())) {
usac::saveMask(mask, ransac_output->getInliersMask());
return ransac_output->getModel() / ransac_output->getModel().at<double>(2,2);
@ -913,10 +913,10 @@ cv::Mat cv::findFundamentalMat( cv::InputArray points1, cv::InputArray points2,
cv::Mat cv::findFundamentalMat( InputArray points1, InputArray points2,
OutputArray mask, const UsacParams &params) {
Ptr<usac::Model> model;
setParameters(model, usac::EstimationMethod::Fundamental, params, mask.needed());
setParameters(model, usac::EstimationMethod::FUNDAMENTAL, params, mask.needed());
CV_Assert(model);
Ptr<usac::RansacOutput> ransac_output;
if (usac::run(model, points1, points2, model->getRandomGeneratorState(),
if (usac::run(model, points1, points2,
ransac_output, noArray(), noArray(), noArray(), noArray())) {
usac::saveMask(mask, ransac_output->getInliersMask());
return ransac_output->getModel();

View File

@ -1086,9 +1086,9 @@ Mat estimateAffine2D(InputArray _from, InputArray _to, OutputArray _inliers,
Mat estimateAffine2D(InputArray _from, InputArray _to, OutputArray inliers,
const UsacParams &params) {
Ptr<usac::Model> model;
usac::setParameters(model, usac::EstimationMethod::Affine, params, inliers.needed());
usac::setParameters(model, usac::EstimationMethod::AFFINE, params, inliers.needed());
Ptr<usac::RansacOutput> ransac_output;
if (usac::run(model, _from, _to, model->getRandomGeneratorState(),
if (usac::run(model, _from, _to,
ransac_output, noArray(), noArray(), noArray(), noArray())) {
usac::saveMask(inliers, ransac_output->getInliersMask());
return ransac_output->getModel().rowRange(0,2);

View File

@ -199,21 +199,6 @@ public:
Mat tvec;
};
UsacParams::UsacParams()
{
confidence = 0.99;
isParallel = false;
loIterations = 5;
loMethod = LocalOptimMethod::LOCAL_OPTIM_INNER_LO;
loSampleSize = 14;
maxIterations = 5000;
neighborsSearch = NeighborSearchMethod::NEIGH_GRID;
randomGeneratorState = 0;
sampler = SamplingMethod::SAMPLING_UNIFORM;
score = ScoreMethod::SCORE_METHOD_MSAC;
threshold = 1.5;
}
bool solvePnPRansac(InputArray _opoints, InputArray _ipoints,
InputArray _cameraMatrix, InputArray _distCoeffs,
OutputArray _rvec, OutputArray _tvec, bool useExtrinsicGuess,
@ -407,7 +392,7 @@ bool solvePnPRansac( InputArray objectPoints, InputArray imagePoints,
usac::setParameters(model_params, cameraMatrix.empty() ? usac::EstimationMethod::P6P :
usac::EstimationMethod::P3P, params, inliers.needed());
Ptr<usac::RansacOutput> ransac_output;
if (usac::run(model_params, imagePoints, objectPoints, model_params->getRandomGeneratorState(),
if (usac::run(model_params, imagePoints, objectPoints,
ransac_output, cameraMatrix, noArray(), distCoeffs, noArray())) {
if (inliers.needed()) {
const auto &inliers_mask = ransac_output->getInliersMask();

View File

@ -6,10 +6,11 @@
#define OPENCV_USAC_USAC_HPP
namespace cv { namespace usac {
enum EstimationMethod { Homography, Fundamental, Fundamental8, Essential, Affine, P3P, P6P};
enum VerificationMethod { NullVerifier, SprtVerifier };
enum PolishingMethod { NonePolisher, LSQPolisher };
enum ErrorMetric {DIST_TO_LINE, SAMPSON_ERR, SGD_ERR, SYMM_REPR_ERR, FORW_REPR_ERR, RERPOJ};
enum EstimationMethod { HOMOGRAPHY=0, FUNDAMENTAL=1, FUNDAMENTAL8=2, ESSENTIAL=3, AFFINE=4, P3P=5, P6P=6};
enum VerificationMethod { NULL_VERIFIER=0, SPRT_VERIFIER=1, ASPRT=2 };
enum ErrorMetric {DIST_TO_LINE=0, SAMPSON_ERR=1, SGD_ERR=2, SYMM_REPR_ERR=3, FORW_REPR_ERR=4, RERPOJ=5};
enum MethodSolver { GEM_SOLVER=0, SVD_SOLVER=1 };
enum ModelConfidence {RANDOM=0, NON_RANDOM=1, UNKNOWN=2};
// Abstract Error class
class Error : public Algorithm {
@ -19,7 +20,6 @@ public:
// returns error of point wih @point_idx w.r.t. model
virtual float getError (int point_idx) const = 0;
virtual const std::vector<float> &getErrors (const Mat &model) = 0;
virtual Ptr<Error> clone () const = 0;
};
// Symmetric Reprojection Error for Homography
@ -58,6 +58,11 @@ public:
static Ptr<ReprojectionErrorAffine> create(const Mat &points);
};
class TrifocalTensorReprError : public Error {
public:
static Ptr<TrifocalTensorReprError> create(const Mat &points);
};
// Normalizing transformation of data points
class NormTransform : public Algorithm {
public:
@ -82,19 +87,22 @@ public:
virtual int getSampleSize() const = 0;
// return maximum number of possible solutions.
virtual int getMaxNumberOfSolutions () const = 0;
virtual Ptr<MinimalSolver> clone () const = 0;
};
//-------------------------- HOMOGRAPHY MATRIX -----------------------
class HomographyMinimalSolver4ptsGEM : public MinimalSolver {
class HomographyMinimalSolver4pts : public MinimalSolver {
public:
static Ptr<HomographyMinimalSolver4ptsGEM> create(const Mat &points_);
static Ptr<HomographyMinimalSolver4pts> create(const Mat &points, bool use_ge);
};
class PnPSVDSolver : public MinimalSolver {
public:
static Ptr<PnPSVDSolver> create (const Mat &points);
};
//-------------------------- FUNDAMENTAL MATRIX -----------------------
class FundamentalMinimalSolver7pts : public MinimalSolver {
public:
static Ptr<FundamentalMinimalSolver7pts> create(const Mat &points_);
static Ptr<FundamentalMinimalSolver7pts> create(const Mat &points, bool use_ge);
};
class FundamentalMinimalSolver8pts : public MinimalSolver {
@ -103,9 +111,9 @@ public:
};
//-------------------------- ESSENTIAL MATRIX -----------------------
class EssentialMinimalSolverStewenius5pts : public MinimalSolver {
class EssentialMinimalSolver5pts : public MinimalSolver {
public:
static Ptr<EssentialMinimalSolverStewenius5pts> create(const Mat &points_);
static Ptr<EssentialMinimalSolver5pts> create(const Mat &points, bool use_svd, bool is_nister);
};
//-------------------------- PNP -----------------------
@ -116,7 +124,7 @@ public:
class P3PSolver : public MinimalSolver {
public:
static Ptr<P3PSolver> create(const Mat &points_, const Mat &calib_norm_pts, const Matx33d &K);
static Ptr<P3PSolver> create(const Mat &points_, const Mat &calib_norm_pts, const Mat &K);
};
//-------------------------- AFFINE -----------------------
@ -125,9 +133,20 @@ public:
static Ptr<AffineMinimalSolver> create(const Mat &points_);
};
class TrifocalTensorMinimalSolver : public MinimalSolver {
public:
static Ptr<TrifocalTensorMinimalSolver> create(const Mat &points_);
virtual void getFundamentalMatricesFromTensor (const cv::Mat &tensor, cv::Mat &F21, cv::Mat &F31) = 0;
};
//////////////////////////////////////// NON MINIMAL SOLVER ///////////////////////////////////////
class NonMinimalSolver : public Algorithm {
public:
virtual int estimate (const Mat &model, const std::vector<int> &sample, int sample_size, std::vector<Mat>
&models, const std::vector<double> &weights) const {
CV_UNUSED(model);
return estimate(sample, sample_size, models, weights);
}
// Estimate models from non minimal sample. models.size() == number of found solutions
virtual int estimate (const std::vector<int> &sample, int sample_size,
std::vector<Mat> &models, const std::vector<double> &weights) const = 0;
@ -135,25 +154,34 @@ public:
virtual int getMinimumRequiredSampleSize() const = 0;
// return maximum number of possible solutions.
virtual int getMaxNumberOfSolutions () const = 0;
virtual Ptr<NonMinimalSolver> clone () const = 0;
virtual int estimate (const std::vector<bool>& mask, std::vector<Mat>& models,
const std::vector<double>& weights) = 0;
virtual void enforceRankConstraint (bool enforce) = 0;
};
//-------------------------- HOMOGRAPHY MATRIX -----------------------
class HomographyNonMinimalSolver : public NonMinimalSolver {
public:
static Ptr<HomographyNonMinimalSolver> create(const Mat &points_);
static Ptr<HomographyNonMinimalSolver> create(const Mat &points_, bool use_ge_=false);
static Ptr<HomographyNonMinimalSolver> create(const Mat &points_, const Matx33d &T1, const Matx33d &T2, bool use_ge);
};
//-------------------------- FUNDAMENTAL MATRIX -----------------------
class FundamentalNonMinimalSolver : public NonMinimalSolver {
class EpipolarNonMinimalSolver : public NonMinimalSolver {
public:
static Ptr<FundamentalNonMinimalSolver> create(const Mat &points_);
static Ptr<EpipolarNonMinimalSolver> create(const Mat &points_, bool is_fundamental);
static Ptr<EpipolarNonMinimalSolver> create(const Mat &points_, const Matx33d &T1, const Matx33d &T2, bool use_ge);
};
//-------------------------- ESSENTIAL MATRIX -----------------------
class EssentialNonMinimalSolver : public NonMinimalSolver {
class EssentialNonMinimalSolverViaF : public NonMinimalSolver {
public:
static Ptr<EssentialNonMinimalSolver> create(const Mat &points_);
static Ptr<EssentialNonMinimalSolverViaF> create(const Mat &points_, const cv::Mat &K1, const Mat &K2);
};
class EssentialNonMinimalSolverViaT : public NonMinimalSolver {
public:
static Ptr<EssentialNonMinimalSolverViaT> create(const Mat &points_);
};
//-------------------------- PNP -----------------------
@ -164,16 +192,20 @@ public:
class DLSPnP : public NonMinimalSolver {
public:
static Ptr<DLSPnP> create(const Mat &points_, const Mat &calib_norm_pts, const Matx33d &K);
static Ptr<DLSPnP> create(const Mat &points_, const Mat &calib_norm_pts, const Mat &K);
};
//-------------------------- AFFINE -----------------------
class AffineNonMinimalSolver : public NonMinimalSolver {
public:
static Ptr<AffineNonMinimalSolver> create(const Mat &points_);
static Ptr<AffineNonMinimalSolver> create(const Mat &points, InputArray T1, InputArray T2);
};
class LarssonOptimizer : public NonMinimalSolver {
public:
static Ptr<LarssonOptimizer> create(const Mat &calib_points_, const Matx33d &K1_, const Matx33d &K2_, int max_iters_, bool is_fundamental_);
};
//////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////// SCORE ///////////////////////////////////////////
class Score {
public:
@ -193,24 +225,16 @@ public:
}
};
class GammaValues
{
const double max_range_complete /*= 4.62*/, max_range_gamma /*= 1.52*/;
const int max_size_table /* = 3000 */;
std::vector<double> gamma_complete, gamma_incomplete, gamma;
GammaValues(); // use getSingleton()
class GammaValues : public Algorithm {
public:
static const GammaValues& getSingleton();
const std::vector<double>& getCompleteGammaValues() const;
const std::vector<double>& getIncompleteGammaValues() const;
const std::vector<double>& getGammaValues() const;
double getScaleOfGammaCompleteValues () const;
double getScaleOfGammaValues () const;
int getTableSize () const;
virtual ~GammaValues() override = default;
static Ptr<GammaValues> create(int DoF, int max_size_table=500);
virtual const std::vector<double> &getCompleteGammaValues() const = 0;
virtual const std::vector<double> &getIncompleteGammaValues() const = 0;
virtual const std::vector<double> &getGammaValues() const = 0;
virtual double getScaleOfGammaCompleteValues () const = 0;
virtual double getScaleOfGammaValues () const = 0;
virtual int getTableSize () const = 0;
};
////////////////////////////////////////// QUALITY ///////////////////////////////////////////
@ -223,9 +247,7 @@ public:
* @model: Mat current model, e.g., H matrix.
*/
virtual Score getScore (const Mat &model) const = 0;
virtual Score getScore (const std::vector<float> &/*errors*/) const {
CV_Error(cv::Error::StsNotImplemented, "getScore(errors)");
}
virtual Score getScore (const std::vector<float>& errors) const = 0;
// get @inliers of the @model. Assume threshold is given
// @inliers must be preallocated to maximum points size.
virtual int getInliers (const Mat &model, std::vector<int> &inliers) const = 0;
@ -236,11 +258,30 @@ public:
// set @inliers_mask: true if point i is inlier, false - otherwise.
virtual int getInliers (const Mat &model, std::vector<bool> &inliers_mask) const = 0;
virtual int getPointsSize() const = 0;
virtual Ptr<Quality> clone () const = 0;
virtual double getThreshold () const = 0;
virtual Ptr<Error> getErrorFnc () const = 0;
static int getInliers (const Ptr<Error> &error, const Mat &model,
std::vector<bool> &inliers_mask, double threshold);
static int getInliers (const Ptr<Error> &error, const Mat &model,
std::vector<int> &inliers, double threshold);
static int getInliers (const std::vector<float> &errors, std::vector<bool> &inliers,
double threshold);
static int getInliers (const std::vector<float> &errors, std::vector<int> &inliers,
double threshold);
Score selectBest (const std::vector<Mat> &models, int num_models, Mat &best) {
if (num_models == 0) return {};
int best_idx = 0;
Score best_score = getScore(models[0]);
for (int i = 1; i < num_models; i++) {
const auto sc = getScore(models[i]);
if (sc.isBetter(best_score)) {
best_score = sc;
best_idx = i;
}
}
models[best_idx].copyTo(best);
return best_score;
}
};
// RANSAC (binary) quality
@ -252,16 +293,16 @@ public:
// M-estimator quality - truncated Squared error
class MsacQuality : public Quality {
public:
static Ptr<MsacQuality> create(int points_size_, double threshold_, const Ptr<Error> &error_);
static Ptr<MsacQuality> create(int points_size_, double threshold_, const Ptr<Error> &error_, double k_msac=2.25);
};
// Marginlizing Sample Consensus quality, D. Barath et al.
class MagsacQuality : public Quality {
public:
static Ptr<MagsacQuality> create(double maximum_thr, int points_size_,const Ptr<Error> &error_,
const Ptr<GammaValues> &gamma_generator,
double tentative_inlier_threshold_, int DoF, double sigma_quantile,
double upper_incomplete_of_sigma_quantile,
double lower_incomplete_of_sigma_quantile, double C_);
double upper_incomplete_of_sigma_quantile);
};
// Least Median of Squares Quality
@ -273,31 +314,40 @@ public:
//////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////// DEGENERACY //////////////////////////////////
class Degeneracy : public Algorithm {
private:
Mat homogr;
public:
virtual ~Degeneracy() override = default;
/*
* Check if sample causes degenerate configurations.
* For example, test if points are collinear.
*/
virtual bool isSampleGood (const std::vector<int> &/*sample*/) const {
virtual bool isSampleGood (const std::vector<int>& sample) const {
CV_UNUSED(sample);
return true;
}
/*
* Check if model satisfies constraints.
* For example, test if epipolar geometry satisfies oriented constraint.
*/
virtual bool isModelValid (const Mat &/*model*/, const std::vector<int> &/*sample*/) const {
virtual bool isModelValid (const Mat& model, const std::vector<int>& sample) const {
CV_UNUSED(model);
CV_UNUSED(sample);
return true;
}
/*
* Fix degenerate model.
* Return true if model is degenerate, false - otherwise
*/
virtual bool recoverIfDegenerate (const std::vector<int> &/*sample*/,const Mat &/*best_model*/,
Mat &/*non_degenerate_model*/, Score &/*non_degenerate_model_score*/) {
virtual bool recoverIfDegenerate (const std::vector<int> &sample,const Mat &best_model, const Score &score,
Mat &non_degenerate_model, Score &non_degenerate_model_score) {
CV_UNUSED(sample);
CV_UNUSED(best_model);
CV_UNUSED(score);
CV_UNUSED(non_degenerate_model);
CV_UNUSED(non_degenerate_model_score);
return false;
}
virtual Ptr<Degeneracy> clone(int /*state*/) const { return makePtr<Degeneracy>(); }
};
class EpipolarGeometryDegeneracy : public Degeneracy {
@ -316,17 +366,31 @@ public:
static Ptr<HomographyDegeneracy> create(const Mat &points_);
};
class FundamentalDegeneracyViaE : public EpipolarGeometryDegeneracy {
public:
static Ptr<FundamentalDegeneracyViaE> create (const Ptr<Quality> &quality, const Mat &pts,
const Mat &calib_pts, const Matx33d &K1, const Matx33d &K2, bool is_f_objective);
};
class FundamentalDegeneracy : public EpipolarGeometryDegeneracy {
public:
// threshold for homography is squared so is around 2.236 pixels
virtual void setPrincipalPoint (double px_, double py_) = 0;
virtual void setPrincipalPoint (double px_, double py_, double px2_, double py2_) = 0;
virtual bool verifyFundamental (const Mat &F_best, const Score &F_score, const std::vector<bool> &inliers_mask, cv::Mat &F_new, Score &new_score) = 0;
static Ptr<FundamentalDegeneracy> create (int state, const Ptr<Quality> &quality_,
const Mat &points_, int sample_size_, double homography_threshold);
const Mat &points_, int sample_size_, int max_iters_plane_and_parallax,
double homography_threshold, double f_inlier_thr_sqr, const Mat true_K1=Mat(), const Mat true_K2=Mat());
};
/////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////// ESTIMATOR //////////////////////////////////
class Estimator : public Algorithm{
public:
virtual int estimateModelNonMinimalSample (const Mat& model, const std::vector<int> &sample, int sample_size, std::vector<Mat>
&models, const std::vector<double> &weights) const {
CV_UNUSED(model);
return estimateModelNonMinimalSample(sample, sample_size, models, weights);
}
/*
* Estimate models with minimal solver.
* Return number of valid solutions after estimation.
@ -352,7 +416,7 @@ public:
virtual int getMaxNumSolutions () const = 0;
// return maximum number of possible solutions of non-minimal estimation.
virtual int getMaxNumSolutionsNonMinimal () const = 0;
virtual Ptr<Estimator> clone() const = 0;
virtual void enforceRankConstraint (bool enforce) = 0;
};
class HomographyEstimator : public Estimator {
@ -368,7 +432,7 @@ public:
};
class EssentialEstimator : public Estimator {
public:
public :
static Ptr<EssentialEstimator> create (const Ptr<MinimalSolver> &min_solver_,
const Ptr<NonMinimalSolver> &non_min_solver_, const Ptr<Degeneracy> &degeneracy_);
};
@ -391,15 +455,12 @@ class ModelVerifier : public Algorithm {
public:
virtual ~ModelVerifier() override = default;
// Return true if model is good, false - otherwise.
virtual bool isModelGood(const Mat &model) = 0;
// Return true if score was computed during evaluation.
virtual bool getScore(Score &score) const = 0;
virtual bool isModelGood(const Mat &model, Score &score) = 0;
// update verifier by given inlier number
virtual void update (int highest_inlier_number) = 0;
virtual const std::vector<float> &getErrors() const = 0;
virtual bool hasErrors () const = 0;
virtual Ptr<ModelVerifier> clone (int state) const = 0;
static Ptr<ModelVerifier> create();
virtual void update (const Score &score, int iteration) = 0;
virtual void reset() = 0;
virtual void updateSPRT (double time_model_est, double time_corr_ver, double new_avg_models, double new_delta, double new_epsilon, const Score &best_score) = 0;
static Ptr<ModelVerifier> create(const Ptr<Quality> &qualtiy);
};
struct SPRT_history {
@ -421,7 +482,7 @@ struct SPRT_history {
double epsilon, delta, A;
// number of samples processed by test
int tested_samples; // k
SPRT_history () : epsilon(0), delta(0), A(0) {
SPRT_history () {
tested_samples = 0;
}
};
@ -431,14 +492,14 @@ struct SPRT_history {
* Matas, Jiri, and Ondrej Chum. "Randomized RANSAC with sequential probability ratio test."
* Tenth IEEE International Conference on Computer Vision (ICCV'05) Volume 1. Vol. 2. IEEE, 2005.
*/
class SPRT : public ModelVerifier {
class AdaptiveSPRT : public ModelVerifier {
public:
// return constant reference of vector of SPRT histories for SPRT termination.
virtual const std::vector<SPRT_history> &getSPRTvector () const = 0;
static Ptr<SPRT> create (int state, const Ptr<Error> &err_, int points_size_,
double inlier_threshold_, double prob_pt_of_good_model,
double prob_pt_of_bad_model, double time_sample, double avg_num_models,
ScoreMethod score_type_);
virtual int avgNumCheckedPts () const = 0;
static Ptr<AdaptiveSPRT> create (int state, const Ptr<Quality> &quality, int points_size_,
double inlier_threshold_, double prob_pt_of_good_model, double prob_pt_of_bad_model,
double time_sample, double avg_num_models, ScoreMethod score_type_,
double k_mlesac, bool is_adaptive = true);
};
//////////////////////////////////////////////////////////////////////////////////////////
@ -450,7 +511,6 @@ public:
virtual void setNewPointsSize (int points_size) = 0;
// generate sample. Fill @sample with indices of points.
virtual void generateSample (std::vector<int> &sample) = 0;
virtual Ptr<Sampler> clone (int state) const = 0;
};
////////////////////////////////////////////////////////////////////////////////////////////////
@ -460,6 +520,7 @@ public:
virtual ~NeighborhoodGraph() override = default;
// Return neighbors of the point with index @point_idx_ in the graph.
virtual const std::vector<int> &getNeighbors(int point_idx_) const = 0;
virtual const std::vector<std::vector<int>> &getGraph () const = 0;
};
class RadiusSearchNeighborhoodGraph : public NeighborhoodGraph {
@ -481,6 +542,11 @@ public:
int cell_size_x_img1_, int cell_size_y_img1_,
int cell_size_x_img2_, int cell_size_y_img2_, int max_neighbors);
};
class GridNeighborhoodGraph2Images : public NeighborhoodGraph {
public:
static Ptr<GridNeighborhoodGraph2Images> create(const Mat &points, int points_size,
float cell_size_x_img1_, float cell_size_y_img1_, float cell_size_x_img2_, float cell_size_y_img2_);
};
////////////////////////////////////// UNIFORM SAMPLER ////////////////////////////////////////////
class UniformSampler : public Sampler {
@ -488,6 +554,11 @@ public:
static Ptr<UniformSampler> create(int state, int sample_size_, int points_size_);
};
class QuasiUniformSampler : public Sampler {
public:
static Ptr<QuasiUniformSampler> create(int state, int sample_size_, int points_size_);
};
/////////////////////////////////// PROSAC (SIMPLE) SAMPLER ///////////////////////////////////////
class ProsacSimpleSampler : public Sampler {
public:
@ -523,60 +594,74 @@ public:
/////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// TERMINATION ///////////////////////////////////////////
class TerminationCriteria : public Algorithm {
class Termination : public Algorithm {
public:
// update termination object by given @model and @inlier number.
// and return maximum number of predicted iteration
virtual int update(const Mat &model, int inlier_number) = 0;
// clone termination
virtual Ptr<TerminationCriteria> clone () const = 0;
virtual int update(const Mat &model, int inlier_number) const = 0;
};
//////////////////////////////// STANDARD TERMINATION ///////////////////////////////////////////
class StandardTerminationCriteria : public TerminationCriteria {
class StandardTerminationCriteria : public Termination {
public:
static Ptr<StandardTerminationCriteria> create(double confidence, int points_size_,
int sample_size_, int max_iterations_);
};
///////////////////////////////////// SPRT TERMINATION //////////////////////////////////////////
class SPRTTermination : public TerminationCriteria {
class SPRTTermination : public Termination {
public:
static Ptr<SPRTTermination> create(const std::vector<SPRT_history> &sprt_histories_,
static Ptr<SPRTTermination> create(const Ptr<AdaptiveSPRT> &sprt,
double confidence, int points_size_, int sample_size_, int max_iterations_);
};
///////////////////////////// PROGRESSIVE-NAPSAC-SPRT TERMINATION /////////////////////////////////
class SPRTPNapsacTermination : public TerminationCriteria {
class SPRTPNapsacTermination : public Termination {
public:
static Ptr<SPRTPNapsacTermination> create(const std::vector<SPRT_history>&
sprt_histories_, double confidence, int points_size_, int sample_size_,
static Ptr<SPRTPNapsacTermination> create(const Ptr<AdaptiveSPRT> &
sprt, double confidence, int points_size_, int sample_size_,
int max_iterations_, double relax_coef_);
};
////////////////////////////////////// PROSAC TERMINATION /////////////////////////////////////////
class ProsacTerminationCriteria : public TerminationCriteria {
class ProsacTerminationCriteria : public Termination {
public:
virtual const std::vector<int> &getNonRandomInliers () const = 0;
virtual int updateTerminationLength (const Mat &model, int inliers_size, int &found_termination_length) const = 0;
static Ptr<ProsacTerminationCriteria> create(const Ptr<ProsacSampler> &sampler_,
const Ptr<Error> &error_, int points_size_, int sample_size, double confidence,
int max_iters, int min_termination_length, double beta, double non_randomness_phi,
double inlier_thresh);
double inlier_thresh, const std::vector<int> &non_rand_inliers);
};
//////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////// UTILS ////////////////////////////////////////////////
namespace Utils {
void densitySort (const Mat &points, int knn, Mat &sorted_points, std::vector<int> &sorted_mask);
/*
* calibrate points: [x'; 1] = K^-1 [x; 1]
* @points is matrix N x 4.
* @norm_points is output matrix N x 4 with calibrated points.
*/
void calibratePoints (const Matx33d &K1, const Matx33d &K2, const Mat &points, Mat &norm_points);
void calibrateAndNormalizePointsPnP (const Matx33d &K, const Mat &pts, Mat &calib_norm_pts);
void normalizeAndDecalibPointsPnP (const Matx33d &K, Mat &pts, Mat &calib_norm_pts);
void decomposeProjection (const Mat &P, Matx33d &K_, Mat &R, Mat &t, bool same_focal=false);
double getCalibratedThreshold (double threshold, const Matx33d &K1, const Matx33d &K2);
void calibratePoints (const Mat &K1, const Mat &K2, const Mat &points, Mat &norm_points);
void calibrateAndNormalizePointsPnP (const Mat &K, const Mat &pts, Mat &calib_norm_pts);
void normalizeAndDecalibPointsPnP (const Mat &K, Mat &pts, Mat &calib_norm_pts);
void decomposeProjection (const Mat &P, Matx33d &K_, Matx33d &R, Vec3d &t, bool same_focal=false);
double getCalibratedThreshold (double threshold, const Mat &K1, const Mat &K2);
float findMedian (std::vector<float> &array);
double intersectionOverUnion (const std::vector<bool> &a, const std::vector<bool> &b);
void triangulatePoints (const Mat &points, const Mat &E_, const Mat &K1, const Mat &K2, Mat &points3D, Mat &R, Mat &t,
std::vector<bool> &good_mask, std::vector<double> &depths1, std::vector<double> &depths2);
void triangulatePoints (const Mat &E, const Mat &points1, const Mat &points2, Mat &corr_points1, Mat &corr_points2,
const Mat &K1, const Mat &K2, Mat &points3D, Mat &R, Mat &t, const std::vector<bool> &good_point_mask);
int triangulatePointsRt (const Mat &points, Mat &points3D, const Mat &K1_, const Mat &K2_,
const cv::Mat &R, const cv::Mat &t_vec, std::vector<bool> &good_mask, std::vector<double> &depths1, std::vector<double> &depths2);
int decomposeHomography (const Matx33d &Hnorm, std::vector<Matx33d> &R, std::vector<Vec3d> &t);
double getPoissonCDF (double lambda, int tentative_inliers);
void getClosePoints (const cv::Mat &points, std::vector<std::vector<int>> &close_points, float close_thr_sqr);
Vec3d getLeftEpipole (const Mat &F);
Vec3d getRightEpipole (const Mat &F);
int removeClosePoints (const Mat &points, Mat &new_points, float thr);
}
namespace Math {
// return skew symmetric matrix
@ -587,6 +672,12 @@ namespace Math {
Vec3d rotMat2RotVec (const Matx33d &R);
}
class SolverPoly: public Algorithm {
public:
virtual int getRealRoots (const std::vector<double> &coeffs, std::vector<double> &real_roots) = 0;
static Ptr<SolverPoly> create();
};
///////////////////////////////////////// RANDOM GENERATOR /////////////////////////////////////
class RandomGenerator : public Algorithm {
public:
@ -609,7 +700,6 @@ public:
virtual int getRandomNumber (int max_rng) = 0;
virtual const std::vector<int> &generateUniqueRandomSubset (std::vector<int> &array1,
int size1) = 0;
virtual Ptr<RandomGenerator> clone (int state) const = 0;
};
class UniformRandomGenerator : public RandomGenerator {
@ -618,6 +708,24 @@ public:
static Ptr<UniformRandomGenerator> create (int state, int max_range, int subset_size_);
};
class WeightFunction : public Algorithm {
public:
virtual int getInliersWeights (const std::vector<float> &errors, std::vector<int> &inliers, std::vector<double> &weights) const = 0;
virtual int getInliersWeights (const std::vector<float> &errors, std::vector<int> &inliers, std::vector<double> &weights, double thr_sqr) const = 0;
virtual double getThreshold() const = 0;
};
class GaussWeightFunction : public WeightFunction {
public:
static Ptr<GaussWeightFunction> create (double thr, double sigma, double outlier_prob);
};
class MagsacWeightFunction : public WeightFunction {
public:
static Ptr<MagsacWeightFunction> create (const Ptr<GammaValues> &gamma_generator_,
int DoF_, double upper_incomplete_of_sigma_quantile, double C_, double max_sigma_);
};
///////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////// LOCAL OPTIMIZATION /////////////////////////////////////////
class LocalOptimization : public Algorithm {
@ -632,17 +740,18 @@ public:
*/
virtual bool refineModel (const Mat &best_model, const Score &best_model_score,
Mat &new_model, Score &new_model_score) = 0;
virtual Ptr<LocalOptimization> clone(int state) const = 0;
virtual void setCurrentRANSACiter (int /*ransac_iter*/) {}
virtual int getNumLOoptimizations () const { return 0; }
};
//////////////////////////////////// GRAPH CUT LO ////////////////////////////////////////
class GraphCut : public LocalOptimization {
public:
static Ptr<GraphCut>
create(const Ptr<Estimator> &estimator_, const Ptr<Error> &error_,
create(const Ptr<Estimator> &estimator_,
const Ptr<Quality> &quality_, const Ptr<NeighborhoodGraph> &neighborhood_graph_,
const Ptr<RandomGenerator> &lo_sampler_, double threshold_,
double spatial_coherence_term, int gc_iters);
double spatial_coherence_term, int gc_iters, Ptr<Termination> termination_= nullptr);
};
//////////////////////////////////// INNER + ITERATIVE LO ///////////////////////////////////////
@ -655,14 +764,13 @@ public:
int lo_iter_max_iterations, double threshold_multiplier);
};
class SigmaConsensus : public LocalOptimization {
class SimpleLocalOptimization : public LocalOptimization {
public:
static Ptr<SigmaConsensus>
create(const Ptr<Estimator> &estimator_, const Ptr<Error> &error_,
const Ptr<Quality> &quality, const Ptr<ModelVerifier> &verifier_,
int max_lo_sample_size, int number_of_irwls_iters_,
int DoF, double sigma_quantile, double upper_incomplete_of_sigma_quantile,
double C_, double maximum_thr);
static Ptr<SimpleLocalOptimization> create
(const Ptr<Quality> &quality_, const Ptr<NonMinimalSolver> &estimator_,
const Ptr<Termination> termination_, const Ptr<RandomGenerator> &random_gen,
const Ptr<WeightFunction> weight_fnc_,
int max_lo_iters_, double inlier_thr_sqr, bool updated_lo=false);
};
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -681,35 +789,86 @@ public:
Mat &new_model, Score &new_model_score) = 0;
};
///////////////////////////////////// LEAST SQUARES POLISHER //////////////////////////////////////
class LeastSquaresPolishing : public FinalModelPolisher {
class NonMinimalPolisher : public FinalModelPolisher {
public:
static Ptr<LeastSquaresPolishing> create (const Ptr<Estimator> &estimator_,
const Ptr<Quality> &quality_, int lsq_iterations);
static Ptr<NonMinimalPolisher> create(const Ptr<Quality> &quality_, const Ptr<NonMinimalSolver> &solver_,
Ptr<WeightFunction> weight_fnc_, int max_iters_, double iou_thr_);
};
class CovarianceSolver : public NonMinimalSolver {
public:
~CovarianceSolver() override = default;
int estimate (const std::vector<int> &, int , std::vector<Mat> &,
const std::vector<double> &) const override {
assert(false && "not implemeted!");
return 0;
}
virtual int estimate (const std::vector<bool> &new_mask, std::vector<Mat> &models,
const std::vector<double> &weights) override = 0;
virtual void reset() = 0;
};
class CovarianceEpipolarSolver : public CovarianceSolver {
public:
static Ptr<CovarianceEpipolarSolver> create (const Mat &points, bool is_fundamental);
static Ptr<CovarianceEpipolarSolver> create (const Mat &points, const Matx33d &T1, const Matx33d &T2);
};
class CovarianceHomographySolver : public CovarianceSolver {
public:
static Ptr<CovarianceHomographySolver> create (const Mat &points);
static Ptr<CovarianceHomographySolver> create (const Mat &points, const Matx33d &T1, const Matx33d &T2);
};
class CovarianceAffineSolver : public CovarianceSolver {
public:
static Ptr<CovarianceAffineSolver> create (const Mat &points, const Matx33d &T1, const Matx33d &T2);
static Ptr<CovarianceAffineSolver> create (const Mat &points);
};
/////////////////////////////////// POSE LIB ////////////////////////////////////////
struct CameraPose {
cv::Matx33d R;
cv::Vec3d t;
double alpha = 1.0; // either focal length or scale
};
typedef std::vector<CameraPose> CameraPoseVector;
struct BundleOptions {
int max_iterations = 100;
enum LossType {
MLESAC,
} loss_type = LossType::MLESAC; // CAUCHY;
double loss_scale = 1.0;
double gradient_tol = 1e-8;
double step_tol = 1e-8;
double initial_lambda = 1e-3;
};
bool satisfyCheirality (const cv::Matx33d& R, const cv::Vec3d &t, const cv::Vec3d &x1, const cv::Vec3d &x2);
// Relative pose refinement. Minimizes Sampson error error. Assumes identity intrinsics (calibrated camera)
// Returns number of iterations.
int refine_relpose(const cv::Mat &correspondences_,
const std::vector<int> &sample_,
const int sample_size_,
CameraPose *pose,
const BundleOptions &opt = BundleOptions(),
const double *weights = nullptr);
/////////////////////////////////// RANSAC OUTPUT ///////////////////////////////////
class RansacOutput : public Algorithm {
public:
virtual ~RansacOutput() override = default;
static Ptr<RansacOutput> create(const Mat &model_,
const std::vector<bool> &inliers_mask_,
int time_mcs_, double score_, int number_inliers_, int number_iterations_,
int number_estimated_models_, int number_good_models_);
const std::vector<bool> &inliers_mask_, int number_inliers_,
int number_iterations_, ModelConfidence conf, const std::vector<float> &errors_);
// Return inliers' indices. size of vector = number of inliers
virtual const std::vector<int > &getInliers() = 0;
// Return inliers mask. Vector of points size. 1-inlier, 0-outlier.
virtual const std::vector<bool> &getInliersMask() const = 0;
virtual int getTimeMicroSeconds() const = 0;
virtual int getTimeMicroSeconds1() const = 0;
virtual int getTimeMilliSeconds2() const = 0;
virtual int getTimeSeconds3() const = 0;
virtual int getNumberOfInliers() const = 0;
virtual int getNumberOfMainIterations() const = 0;
virtual int getNumberOfGoodModels () const = 0;
virtual int getNumberOfEstimatedModels () const = 0;
virtual const Mat &getModel() const = 0;
virtual int getNumberOfIters() const = 0;
virtual ModelConfidence getConfidence() const = 0;
virtual const std::vector<float> &getResiduals() const = 0;
};
////////////////////////////////////////////// MODEL /////////////////////////////////////////////
@ -724,7 +883,6 @@ public:
// getters
virtual int getSampleSize () const = 0;
virtual bool isParallel() const = 0;
virtual int getMaxNumHypothesisToTestBeforeRejection() const = 0;
virtual PolishingMethod getFinalPolisher () const = 0;
virtual LocalOptimMethod getLO () const = 0;
virtual ErrorMetric getError () const = 0;
@ -744,6 +902,7 @@ public:
virtual int getCellSize () const = 0;
virtual int getGraphRadius() const = 0;
virtual double getRelaxCoef () const = 0;
virtual bool isNonRandomnessTest () const = 0;
virtual int getFinalLSQIterations () const = 0;
virtual int getDegreesOfFreedom () const = 0;
@ -753,6 +912,7 @@ public:
virtual double getC () const = 0;
virtual double getMaximumThreshold () const = 0;
virtual double getGraphCutSpatialCoherenceTerm () const = 0;
virtual double getKmlesac () const = 0;
virtual int getLOSampleSize () const = 0;
virtual int getLOThresholdMultiplier() const = 0;
virtual int getLOIterativeSampleSize() const = 0;
@ -760,9 +920,15 @@ public:
virtual int getLOInnerMaxIters() const = 0;
virtual const std::vector<int> &getGridCellNumber () const = 0;
virtual int getRandomGeneratorState () const = 0;
virtual int getMaxItersBeforeLO () const = 0;
virtual MethodSolver getRansacSolver () const = 0;
virtual int getPlaneAndParallaxIters () const = 0;
virtual int getLevMarqIters () const = 0;
virtual int getLevMarqItersLO () const = 0;
virtual bool isLarssonOptimization () const = 0;
virtual int getProsacMaxSamples() const = 0;
// setters
virtual void setNonRandomnessTest (bool set) = 0;
virtual void setLocalOptimization (LocalOptimMethod lo_) = 0;
virtual void setKNearestNeighhbors (int knn_) = 0;
virtual void setNeighborsType (NeighborSearchMethod neighbors) = 0;
@ -774,8 +940,8 @@ public:
virtual void setLOIterations (int iters) = 0;
virtual void setLOIterativeIters (int iters) = 0;
virtual void setLOSampleSize (int lo_sample_size) = 0;
virtual void setThresholdMultiplierLO (double thr_mult) = 0;
virtual void setRandomGeneratorState (int state) = 0;
virtual void setFinalLSQ (int iters) = 0;
virtual void maskRequired (bool required) = 0;
virtual bool isMaskRequired () const = 0;
@ -783,6 +949,9 @@ public:
double confidence_=0.95, int max_iterations_=5000, ScoreMethod score_ =ScoreMethod::SCORE_METHOD_MSAC);
};
double getLambda (std::vector<int> &supports, double cdf_thr, int points_size,
int sample_size, bool is_indendent_inliers, int &min_non_random_inliers);
Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method,
double ransacReprojThreshold, OutputArray mask,
const int maxIters, const double confidence);
@ -811,7 +980,7 @@ Mat estimateAffine2D(InputArray from, InputArray to, OutputArray inliers,
void saveMask (OutputArray mask, const std::vector<bool> &inliers_mask);
void setParameters (Ptr<Model> &params, EstimationMethod estimator, const UsacParams &usac_params,
bool mask_need);
bool run (const Ptr<const Model> &params, InputArray points1, InputArray points2, int state,
bool run (const Ptr<const Model> &params, InputArray points1, InputArray points2,
Ptr<RansacOutput> &ransac_output, InputArray K1_, InputArray K2_,
InputArray dist_coeff1, InputArray dist_coeff2);
}}

View File

@ -0,0 +1,308 @@
// Copyright (c) 2020, Viktor Larsson
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * 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.
//
// * 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 <COPYRIGHT HOLDER> 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 "../usac.hpp"
namespace cv { namespace usac {
class MlesacLoss {
public:
MlesacLoss(double threshold) : squared_thr(threshold * threshold), norm_thr(squared_thr*3), one_over_thr(1/norm_thr), inv_sq_thr(1/squared_thr) {}
double loss(double r2) const {
return r2 < norm_thr ? r2 * one_over_thr - 1 : 0;
}
double weight(double r2) const {
// use Cauchly weight
return 1.0 / (1.0 + r2 * inv_sq_thr);
}
const double squared_thr;
private:
const double norm_thr, one_over_thr, inv_sq_thr;
};
class RelativePoseJacobianAccumulator {
private:
const Mat* correspondences;
const std::vector<int> &sample;
const int sample_size;
const MlesacLoss &loss_fn;
const double *weights;
public:
RelativePoseJacobianAccumulator(
const Mat& correspondences_,
const std::vector<int> &sample_,
const int sample_size_,
const MlesacLoss &l,
const double *w = nullptr) :
correspondences(&correspondences_),
sample(sample_),
sample_size(sample_size_),
loss_fn(l),
weights(w) {}
Matx33d essential_from_motion(const CameraPose &pose) const {
return Matx33d(0.0, -pose.t(2), pose.t(1),
pose.t(2), 0.0, -pose.t(0),
-pose.t(1), pose.t(0), 0.0) * pose.R;
}
double residual(const CameraPose &pose) const {
const Matx33d E = essential_from_motion(pose);
const float m11=static_cast<float>(E(0,0)), m12=static_cast<float>(E(0,1)), m13=static_cast<float>(E(0,2));
const float m21=static_cast<float>(E(1,0)), m22=static_cast<float>(E(1,1)), m23=static_cast<float>(E(1,2));
const float m31=static_cast<float>(E(2,0)), m32=static_cast<float>(E(2,1)), m33=static_cast<float>(E(2,2));
const auto * const pts = (float *) correspondences->data;
double cost = 0.0;
for (int k = 0; k < sample_size; ++k) {
const int idx = 4*sample[k];
const float x1=pts[idx], y1=pts[idx+1], x2=pts[idx+2], y2=pts[idx+3];
const float F_pt1_x = m11 * x1 + m12 * y1 + m13,
F_pt1_y = m21 * x1 + m22 * y1 + m23;
const float pt2_F_x = x2 * m11 + y2 * m21 + m31,
pt2_F_y = x2 * m12 + y2 * m22 + m32;
const float pt2_F_pt1 = x2 * F_pt1_x + y2 * F_pt1_y + m31 * x1 + m32 * y1 + m33;
const float r2 = pt2_F_pt1 * pt2_F_pt1 / (F_pt1_x * F_pt1_x + F_pt1_y * F_pt1_y +
pt2_F_x * pt2_F_x + pt2_F_y * pt2_F_y);
if (weights == nullptr)
cost += loss_fn.loss(r2);
else cost += weights[k] * loss_fn.loss(r2);
}
return cost;
}
void accumulate(const CameraPose &pose, Matx<double, 5, 5> &JtJ, Matx<double, 5, 1> &Jtr, Matx<double, 3, 2> &tangent_basis) const {
const auto * const pts = (float *) correspondences->data;
// We start by setting up a basis for the updates in the translation (orthogonal to t)
// We find the minimum element of t and cross product with the corresponding basis vector.
// (this ensures that the first cross product is not close to the zero vector)
Vec3d tangent_basis_col0;
if (std::abs(pose.t(0)) < std::abs(pose.t(1))) {
// x < y
if (std::abs(pose.t(0)) < std::abs(pose.t(2))) {
tangent_basis_col0 = pose.t.cross(Vec3d(1,0,0));
} else {
tangent_basis_col0 = pose.t.cross(Vec3d(0,0,1));
}
} else {
// x > y
if (std::abs(pose.t(1)) < std::abs(pose.t(2))) {
tangent_basis_col0 = pose.t.cross(Vec3d(0,1,0));
} else {
tangent_basis_col0 = pose.t.cross(Vec3d(0,0,1));
}
}
tangent_basis_col0 /= norm(tangent_basis_col0);
Vec3d tangent_basis_col1 = pose.t.cross(tangent_basis_col0);
tangent_basis_col1 /= norm(tangent_basis_col1);
for (int i = 0; i < 3; i++) {
tangent_basis(i,0) = tangent_basis_col0(i);
tangent_basis(i,1) = tangent_basis_col1(i);
}
const Matx33d E = essential_from_motion(pose);
// Matrices contain the jacobians of E w.r.t. the rotation and translation parameters
// Each column is vec(E*skew(e_k)) where e_k is k:th basis vector
const Matx<double, 9, 3> dR = {0., -E(0,2), E(0,1),
0., -E(1,2), E(1,1),
0., -E(2,2), E(2,1),
E(0,2), 0., -E(0,0),
E(1,2), 0., -E(1,0),
E(2,2), 0., -E(2,0),
-E(0,1), E(0,0), 0.,
-E(1,1), E(1,0), 0.,
-E(2,1), E(2,0), 0.};
Matx<double, 9, 2> dt;
// Each column is vec(skew(tangent_basis[k])*R)
for (int i = 0; i <= 2; i+=1) {
const Vec3d r_i(pose.R(0,i), pose.R(1,i), pose.R(2,i));
for (int j = 0; j <= 1; j+= 1) {
const Vec3d v = (j == 0 ? tangent_basis_col0 : tangent_basis_col1).cross(r_i);
for (int k = 0; k < 3; k++) {
dt(3*i+k,j) = v[k];
}
}
}
for (int k = 0; k < sample_size; ++k) {
const auto point_idx = 4*sample[k];
const Vec3d pt1 (pts[point_idx], pts[point_idx+1], 1), pt2 (pts[point_idx+2], pts[point_idx+3], 1);
const double C = pt2.dot(E * pt1);
// J_C is the Jacobian of the epipolar constraint w.r.t. the image points
const Vec4d J_C ((E.col(0).t() * pt2)[0], (E.col(1).t() * pt2)[0], (E.row(0) * pt1)[0], (E.row(1) * pt1)[0]);
const double nJ_C = norm(J_C);
const double inv_nJ_C = 1.0 / nJ_C;
const double r = C * inv_nJ_C;
if (r*r > loss_fn.squared_thr) continue;
// Compute weight from robust loss function (used in the IRLS)
double weight = loss_fn.weight(r * r) / sample_size;
if (weights != nullptr)
weight = weights[k] * weight;
if(weight < DBL_EPSILON)
continue;
// Compute Jacobian of Sampson error w.r.t the fundamental/essential matrix (3x3)
Matx<double, 1, 9> dF (pt1(0) * pt2(0), pt1(0) * pt2(1), pt1(0), pt1(1) * pt2(0), pt1(1) * pt2(1), pt1(1), pt2(0), pt2(1), 1.0);
const double s = C * inv_nJ_C * inv_nJ_C;
dF(0) -= s * (J_C(2) * pt1(0) + J_C(0) * pt2(0));
dF(1) -= s * (J_C(3) * pt1(0) + J_C(0) * pt2(1));
dF(2) -= s * (J_C(0));
dF(3) -= s * (J_C(2) * pt1(1) + J_C(1) * pt2(0));
dF(4) -= s * (J_C(3) * pt1(1) + J_C(1) * pt2(1));
dF(5) -= s * (J_C(1));
dF(6) -= s * (J_C(2));
dF(7) -= s * (J_C(3));
dF *= inv_nJ_C;
// and then w.r.t. the pose parameters (rotation + tangent basis for translation)
const Matx13d dFdR = dF * dR;
const Matx12d dFdt = dF * dt;
const Matx<double, 1, 5> J (dFdR(0), dFdR(1), dFdR(2), dFdt(0), dFdt(1));
// Accumulate into JtJ and Jtr
Jtr += weight * C * inv_nJ_C * J.t();
JtJ(0, 0) += weight * (J(0) * J(0));
JtJ(1, 0) += weight * (J(1) * J(0));
JtJ(1, 1) += weight * (J(1) * J(1));
JtJ(2, 0) += weight * (J(2) * J(0));
JtJ(2, 1) += weight * (J(2) * J(1));
JtJ(2, 2) += weight * (J(2) * J(2));
JtJ(3, 0) += weight * (J(3) * J(0));
JtJ(3, 1) += weight * (J(3) * J(1));
JtJ(3, 2) += weight * (J(3) * J(2));
JtJ(3, 3) += weight * (J(3) * J(3));
JtJ(4, 0) += weight * (J(4) * J(0));
JtJ(4, 1) += weight * (J(4) * J(1));
JtJ(4, 2) += weight * (J(4) * J(2));
JtJ(4, 3) += weight * (J(4) * J(3));
JtJ(4, 4) += weight * (J(4) * J(4));
}
}
};
bool satisfyCheirality (const Matx33d& R, const Vec3d &t, const Vec3d &x1, const Vec3d &x2) {
// This code assumes that x1 and x2 are unit vectors
const auto Rx1 = R * x1;
// lambda_2 * x2 = R * ( lambda_1 * x1 ) + t
// [1 a; a 1] * [lambda1; lambda2] = [b1; b2]
// [lambda1; lambda2] = [1 -a; -a 1] * [b1; b2] / (1 - a*a)
const double a = -Rx1.dot(x2), b1 = -Rx1.dot(t), b2 = x2.dot(t);
// Note that we drop the factor 1.0/(1-a*a) since it is always positive.
return (b1 - a * b2 > 0) && (-a * b1 + b2 > 0);
}
int refine_relpose(const Mat &correspondences_,
const std::vector<int> &sample_,
const int sample_size_,
CameraPose *pose,
const BundleOptions &opt,
const double* weights) {
MlesacLoss loss_fn(opt.loss_scale);
RelativePoseJacobianAccumulator accum(correspondences_, sample_, sample_size_, loss_fn, weights);
// return lm_5dof_impl(accum, pose, opt);
Matx<double, 5, 5> JtJ;
Matx<double, 5, 1> Jtr;
Matx<double, 3, 2> tangent_basis;
Matx33d sw = Matx33d::zeros();
double lambda = opt.initial_lambda;
// Compute initial cost
double cost = accum.residual(*pose);
bool recompute_jac = true;
int iter;
for (iter = 0; iter < opt.max_iterations; ++iter) {
// We only recompute jacobian and residual vector if last step was successful
if (recompute_jac) {
std::fill(JtJ.val, JtJ.val+25, 0);
std::fill(Jtr.val, Jtr.val +5, 0);
accum.accumulate(*pose, JtJ, Jtr, tangent_basis);
if (norm(Jtr) < opt.gradient_tol)
break;
}
// Add dampening
JtJ(0, 0) += lambda;
JtJ(1, 1) += lambda;
JtJ(2, 2) += lambda;
JtJ(3, 3) += lambda;
JtJ(4, 4) += lambda;
Matx<double, 5, 1> sol;
Matx<double, 5, 5> JtJ_symm = JtJ;
for (int i = 0; i < 5; i++)
for (int j = i+1; j < 5; j++)
JtJ_symm(i,j) = JtJ(j,i);
const bool success = solve(-JtJ_symm, Jtr, sol);
if (!success || norm(sol) < opt.step_tol)
break;
Vec3d w (sol(0,0), sol(1,0), sol(2,0));
const double theta = norm(w);
w /= theta;
const double a = std::sin(theta);
const double b = std::cos(theta);
sw(0, 1) = -w(2);
sw(0, 2) = w(1);
sw(1, 2) = -w(0);
sw(1, 0) = w(2);
sw(2, 0) = -w(1);
sw(2, 1) = w(0);
CameraPose pose_new;
pose_new.R = pose->R + pose->R * (a * sw + (1 - b) * sw * sw);
// In contrast to the 6dof case, we don't apply R here
// (since this can already be added into tangent_basis)
pose_new.t = pose->t + Vec3d(Mat(tangent_basis * Matx21d(sol(3,0), sol(4,0))));
double cost_new = accum.residual(pose_new);
if (cost_new < cost) {
*pose = pose_new;
lambda /= 10;
cost = cost_new;
recompute_jac = true;
} else {
JtJ(0, 0) -= lambda;
JtJ(1, 1) -= lambda;
JtJ(2, 2) -= lambda;
JtJ(3, 3) -= lambda;
JtJ(4, 4) -= lambda;
lambda *= 10;
recompute_jac = false;
}
}
return iter;
}
}}

View File

@ -13,7 +13,7 @@ private:
const int min_sample_size;
public:
explicit EpipolarGeometryDegeneracyImpl (const Mat &points_, int sample_size_) :
points_mat(&points_), points ((float*) points_.data), min_sample_size (sample_size_) {}
points_mat(&points_), points ((float*) points_mat->data), min_sample_size (sample_size_) {}
/*
* 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.
@ -23,39 +23,27 @@ public:
* e × x ~+ x'^T F
*/
inline bool isModelValid(const Mat &F_, const std::vector<int> &sample) const override {
// F is of rank 2, taking cross product of two rows we obtain null vector of F
Vec3d ec_mat = F_.row(0).cross(F_.row(2));
auto * ec = ec_mat.val; // of size 3x1
// e is zero vector, recompute e
if (ec[0] <= 1.9984e-15 && ec[0] >= -1.9984e-15 &&
ec[1] <= 1.9984e-15 && ec[1] >= -1.9984e-15 &&
ec[2] <= 1.9984e-15 && ec[2] >= -1.9984e-15) {
ec_mat = F_.row(1).cross(F_.row(2));
ec = ec_mat.val;
}
const Vec3d ep = Utils::getRightEpipole(F_);
const auto * const e = ep.val; // of size 3x1
const auto * const F = (double *) F_.data;
// without loss of generality, let the first point in sample be in front of the camera.
int pt = 4*sample[0];
// s1 = F11 * x2 + F21 * y2 + F31 * 1
// s2 = e'_2 * 1 - e'_3 * y1
// 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])*(ec[1]-ec[2]*points[pt+1]);
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])*(ec[1]-ec[2]*points[pt+1])<0)
return false;
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;
}
Ptr<Degeneracy> clone(int /*state*/) const override {
return makePtr<EpipolarGeometryDegeneracyImpl>(*points_mat, min_sample_size);
}
};
void EpipolarGeometryDegeneracy::recoverRank (Mat &model, bool is_fundamental_mat) {
/*
@ -81,9 +69,10 @@ class HomographyDegeneracyImpl : public HomographyDegeneracy {
private:
const Mat * points_mat;
const float * const points;
const float TOLERANCE = 2 * FLT_EPSILON; // 2 from area of triangle
public:
explicit HomographyDegeneracyImpl (const Mat &points_) :
points_mat(&points_), points ((float *)points_.data) {}
points_mat(&points_), points ((float *)points_mat->data) {}
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];
@ -119,222 +108,580 @@ public:
// Checks if points are not collinear
// If area of triangle constructed with 3 points is less then 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| = (1/2) det |x2-x1 y2-y1| < threshold
// |x3 y3 1| |x3-x1 y3-y1 0| |x3-x1 y3-y1|
// (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)) * 0.5 < FLT_EPSILON) return false; //1,2,3
if (fabsf((x2-x1) * (y4-y1) - (y2-y1) * (x4-x1)) * 0.5 < FLT_EPSILON) return false; //1,2,4
if (fabsf((x3-x1) * (y4-y1) - (y3-y1) * (x4-x1)) * 0.5 < FLT_EPSILON) return false; //1,3,4
if (fabsf((x3-x2) * (y4-y2) - (y3-y2) * (x4-x2)) * 0.5 < FLT_EPSILON) return false; //2,3,4
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)) * 0.5 < FLT_EPSILON) return false; //1,2,3
if (fabsf((X2-X1) * (Y4-Y1) - (Y2-Y1) * (X4-X1)) * 0.5 < FLT_EPSILON) return false; //1,2,4
if (fabsf((X3-X1) * (Y4-Y1) - (Y3-Y1) * (X4-X1)) * 0.5 < FLT_EPSILON) return false; //1,3,4
if (fabsf((X3-X2) * (Y4-Y2) - (Y3-Y2) * (X4-X2)) * 0.5 < FLT_EPSILON) return false; //2,3,4
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<Degeneracy> clone(int /*state*/) const override {
return makePtr<HomographyDegeneracyImpl>(*points_mat);
}
};
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;
const float * const points;
const 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);
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<int> h_inliers;
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, sample_size;
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_, double homography_threshold_) :
rng (state), quality(quality_), points((float *) points_.data), points_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((float *) points_.data), points_mat(&points_),
h_reproj_error(ReprojectionErrorForward::create(points_)),
ep_deg (points_, sample_size_), homography_threshold (homography_threshold_),
points_size (quality_->getPointsSize()), sample_size (sample_size_) {
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.emplace_back(std::vector<int>{0, 2, 7});
h_sample.emplace_back(std::vector<int>{3, 5, 7});
h_sample.emplace_back(std::vector<int>{3, 6, 7});
h_sample.emplace_back(std::vector<int>{2, 4, 7});
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;
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) {
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) {
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;
// (xi × e')
const Vec3d P1e = P1.cross(e_prime), P2e = P2.cross(e_prime), P3e = P3.cross(e_prime);
// xi × (A xi))^T (xi × e) / ‖xi×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 recoverIfDegenerate (const std::vector<int> &sample, const Mat &F_best,
Mat &non_degenerate_model, Score &non_degenerate_model_score) override {
non_degenerate_model_score = Score(); // set worst case
// According to Two-view Geometry Estimation Unaffected by a Dominant Plane
// (http://cmp.felk.cvut.cz/~matas/papers/chum-degen-cvpr05.pdf)
// only 5 homographies enough to test
// triplets {1,2,3}, {4,5,6}, {1,2,7}, {4,5,7} and {3,6,7}
// H = A - e' (M^-1 b)^T
// A = [e']_x F
// b_i = (xi × (A xi))^T (xi × e)‖xi×e‖^2,
// M is a 3×3 matrix with rows x^T_i
// epipole e' is left nullspace of F s.t. e^T F=0,
// find e', null space of F^T
Vec3d e_prime = F_best.col(0).cross(F_best.col(2));
if (fabs(e_prime(0)) < 1e-10 && fabs(e_prime(1)) < 1e-10 &&
fabs(e_prime(2)) < 1e-10) // if e' is zero
e_prime = F_best.col(1).cross(F_best.col(2));
const Matx33d A = Math::getSkewSymmetric(e_prime) * Matx33d(F_best);
Vec3d xi_prime(0,0,1), xi(0,0,1), b;
Matx33d M(0,0,1,0,0,1,0,0,1); // last column of M is 1
bool is_model_degenerate = false;
for (const auto &h_i : h_sample) { // only 5 samples
for (int pt_i = 0; pt_i < 3; pt_i++) {
// find b and M
const int smpl = 4*sample[h_i[pt_i]];
xi[0] = points[smpl];
xi[1] = points[smpl+1];
xi_prime[0] = points[smpl+2];
xi_prime[1] = points[smpl+3];
// (xi × e')
const Vec3d xprime_X_eprime = xi_prime.cross(e_prime);
// (xi × (A xi))
const Vec3d xprime_X_Ax = xi_prime.cross(A * xi);
// xi × (A xi))^T (xi × e) / ‖xi×e‖^2,
b[pt_i] = xprime_X_Ax.dot(xprime_X_eprime) /
std::pow(norm(xprime_X_eprime), 2);
// M from x^T
M(pt_i, 0) = xi[0];
M(pt_i, 1) = xi[1];
}
// compute H
Matx33d H = A - e_prime * (M.inv() * b).t();
int inliers_out_plane = 0;
h_reproj_error->setModelParameters(Mat(H));
// find inliers from sample, points related to H, x' ~ Hx
for (int s = 0; s < sample_size; s++)
if (h_reproj_error->getError(sample[s]) > homography_threshold)
if (++inliers_out_plane > 2)
break;
// if there are at least 5 points lying on plane then F is degenerate
if (inliers_out_plane <= 2) {
is_model_degenerate = true;
// update homography by polishing on all inliers
int h_inls_cnt = 0;
const auto &h_errors = h_reproj_error->getErrors(Mat(H));
for (int pt = 0; pt < points_size; pt++)
if (h_errors[pt] < homography_threshold)
h_inliers[h_inls_cnt++] = pt;
if (h_non_min_solver->estimate(h_inliers, h_inls_cnt, h_models, weights) != 0)
H = Matx33d(h_models[0]);
Mat newF;
const Score newF_score = planeAndParallaxRANSAC(H, newF, h_errors);
if (newF_score.isBetter(non_degenerate_model_score)) {
// store non degenerate model
non_degenerate_model_score = newF_score;
newF.copyTo(non_degenerate_model);
}
}
}
return is_model_degenerate;
}
Ptr<Degeneracy> clone(int state) const override {
return makePtr<FundamentalDegeneracyImpl>(state, quality->clone(), *points_mat,
sample_size, homography_threshold);
}
private:
// RANSAC with plane-and-parallax to find new Fundamental matrix
Score planeAndParallaxRANSAC (const Matx33d &H, Mat &best_F, const std::vector<float> &h_errors) {
int max_iters = 100; // with 95% confidence assume at least 17% of inliers
Score best_score;
for (int iters = 0; iters < max_iters; iters++) {
// draw two random points
int h_outlier1 = rng.uniform(0, points_size);
int h_outlier2 = rng.uniform(0, points_size);
while (h_outlier1 == h_outlier2)
h_outlier2 = rng.uniform(0, points_size);
// find outliers of homography H
if (h_errors[h_outlier1] > homography_threshold &&
h_errors[h_outlier2] > homography_threshold) {
// do plane and parallax with outliers of H
// F = [(p1' x Hp1) x (p2' x Hp2)]_x H
const Matx33d F = Math::getSkewSymmetric(
(Vec3d(points[4*h_outlier1+2], points[4*h_outlier1+3], 1).cross // p1'
(H * Vec3d(points[4*h_outlier1 ], points[4*h_outlier1+1], 1))).cross // Hp1
(Vec3d(points[4*h_outlier2+2], points[4*h_outlier2+3], 1).cross // p2'
(H * Vec3d(points[4*h_outlier2 ], points[4*h_outlier2+1], 1))) // Hp2
) * H;
const Score score = quality->getScore(Mat(F));
if (score.isBetter(best_score)) {
best_score = score;
best_F = Mat(F);
const double predicted_iters = log_conf / log(1 - std::pow
(static_cast<double>(score.inlier_number) / points_size, 2));
if (! std::isinf(predicted_iters) && predicted_iters < max_iters)
max_iters = static_cast<int>(predicted_iters);
}
}
}
return best_score;
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_, double homography_threshold_) {
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_,
homography_threshold_);
max_iters_plane_and_parallax, homography_threshold_, f_inlier_thr_sqr, true_K1, true_K2);
}
class EssentialDegeneracyImpl : public EssentialDegeneracy {
private:
const Mat * points_mat;
const int sample_size;
const EpipolarGeometryDegeneracyImpl ep_deg;
public:
explicit EssentialDegeneracyImpl (const Mat &points, int sample_size_) :
points_mat(&points), sample_size(sample_size_), ep_deg (points, 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<Degeneracy> clone(int /*state*/) const override {
return makePtr<EssentialDegeneracyImpl>(*points_mat, sample_size);
}
};
Ptr<EssentialDegeneracy> EssentialDegeneracy::create (const Mat &points_, int sample_size_) {
return makePtr<EssentialDegeneracyImpl>(points_, sample_size_);
}
}}
}}

View File

@ -46,29 +46,25 @@
#endif
namespace cv { namespace usac {
// This is the estimator class for estimating a homography matrix between two images. A model estimation method and error calculation method are implemented
class DLSPnPImpl : public DLSPnP {
#if defined(HAVE_LAPACK) || defined(HAVE_EIGEN)
private:
const Mat * points_mat, * calib_norm_points_mat;
const Matx33d * K_mat;
#if defined(HAVE_LAPACK) || defined(HAVE_EIGEN)
const Matx33d &K;
const Matx33d K;
const float * const calib_norm_points, * const points;
#endif
public:
explicit DLSPnPImpl (const Mat &points_, const Mat &calib_norm_points_, const Matx33d &K_) :
points_mat(&points_), calib_norm_points_mat(&calib_norm_points_), K_mat (&K_)
#if defined(HAVE_LAPACK) || defined(HAVE_EIGEN)
, K(K_), calib_norm_points((float*)calib_norm_points_.data), points((float*)points_.data)
explicit DLSPnPImpl (const Mat &points_, const Mat &calib_norm_points_, const Mat &K_)
: points_mat(&points_), calib_norm_points_mat(&calib_norm_points_), K(K_),
calib_norm_points((float*)calib_norm_points_mat->data), points((float*)points_mat->data) {}
#else
public:
explicit DLSPnPImpl (const Mat &, const Mat &, const Mat &) {}
#endif
{}
// return minimal sample size required for non-minimal estimation.
int getMinimumRequiredSampleSize() const override { return 3; }
// return maximum number of possible solutions.
int getMaxNumberOfSolutions () const override { return 27; }
Ptr<NonMinimalSolver> clone () const override {
return makePtr<DLSPnPImpl>(*points_mat, *calib_norm_points_mat, *K_mat);
}
#if defined(HAVE_LAPACK) || defined(HAVE_EIGEN)
int estimate(const std::vector<int> &sample, int sample_number,
std::vector<Mat> &models_, const std::vector<double> &/*weights_*/) const override {
@ -170,7 +166,6 @@ public:
for (int i = 0; i < sample_number; i++)
pts_random_shuffle[i] = i;
randShuffle(pts_random_shuffle);
for (int i = 0; i < 27; i++) {
// If the rotation solutions are real, treat this as a valid candidate
// rotation.
@ -227,6 +222,12 @@ public:
#endif
}
int estimate (const std::vector<bool> &/*mask*/, std::vector<Mat> &/*models*/,
const std::vector<double> &/*weights*/) override {
return 0;
}
void enforceRankConstraint (bool /*enforce*/) override {}
protected:
#if defined(HAVE_LAPACK) || defined(HAVE_EIGEN)
const int indices[1968] = {
@ -871,7 +872,7 @@ protected:
2 * D[74] - 2 * D[78]); // s1^3
}
};
Ptr<DLSPnP> DLSPnP::create(const Mat &points_, const Mat &calib_norm_pts, const Matx33d &K) {
Ptr<DLSPnP> DLSPnP::create(const Mat &points_, const Mat &calib_norm_pts, const Mat &K) {
return makePtr<DLSPnPImpl>(points_, calib_norm_pts, K);
}
}}

View File

@ -11,28 +11,25 @@
#endif
namespace cv { namespace usac {
// Essential matrix solver:
/*
* H. Stewenius, C. Engels, and D. Nister. Recent developments on direct relative orientation.
* ISPRS J. of Photogrammetry and Remote Sensing, 60:284,294, 2006
* http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.61.9329&rep=rep1&type=pdf
*
* D. Nister. An efficient solution to the five-point relative pose problem
* IEEE Transactions on Pattern Analysis and Machine Intelligence
* https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.86.8769&rep=rep1&type=pdf
*/
class EssentialMinimalSolverStewenius5ptsImpl : public EssentialMinimalSolverStewenius5pts {
class EssentialMinimalSolver5ptsImpl : public EssentialMinimalSolver5pts {
private:
// Points must be calibrated K^-1 x
const Mat * points_mat;
#if defined(HAVE_EIGEN) || defined(HAVE_LAPACK)
const float * const pts;
#endif
const bool use_svd, is_nister;
public:
explicit EssentialMinimalSolverStewenius5ptsImpl (const Mat &points_) :
points_mat(&points_)
#if defined(HAVE_EIGEN) || defined(HAVE_LAPACK)
, pts((float*)points_.data)
#endif
{}
explicit EssentialMinimalSolver5ptsImpl (const Mat &points_, bool use_svd_=false, bool is_nister_=false) :
points_mat(&points_), pts((float*)points_mat->data), use_svd(use_svd_), is_nister(is_nister_) {}
#if defined(HAVE_LAPACK) || defined(HAVE_EIGEN)
int estimate (const std::vector<int> &sample, std::vector<Mat> &models) const override {
// (1) Extract 4 null vectors from linear equations of epipolar constraint
std::vector<double> coefficients(45); // 5 pts=rows, 9 columns
@ -53,25 +50,35 @@ public:
const int num_cols = 9, num_e_mat = 4;
double ee[36]; // 9*4
// eliminate linear equations
if (!Math::eliminateUpperTriangular(coefficients, 5, num_cols))
return 0;
for (int i = 0; i < num_e_mat; i++)
for (int j = 5; j < num_cols; j++)
ee[num_cols * i + j] = (i + 5 == j) ? 1 : 0;
// use back-substitution
for (int e = 0; e < num_e_mat; e++) {
const int curr_e = num_cols * e;
// start from the last row
for (int i = 4; i >= 0; i--) {
const int row_i = i * num_cols;
double acc = 0;
for (int j = i + 1; j < num_cols; j++)
acc -= coefficients[row_i + j] * ee[curr_e + j];
ee[curr_e + i] = acc / coefficients[row_i + i];
// due to numerical errors return 0 solutions
if (std::isnan(ee[curr_e + i]))
return 0;
if (use_svd) {
Matx<double, 5, 9> coeffs (&coefficients[0]);
Mat D, U, Vt;
SVDecomp(coeffs, D, U, Vt, SVD::FULL_UV + SVD::MODIFY_A);
const auto * const vt = (double *) Vt.data;
for (int i = 0; i < num_e_mat; i++)
for (int j = 0; j < num_cols; j++)
ee[i * num_cols + j] = vt[(8-i)*num_cols+j];
} else {
// eliminate linear equations
if (!Math::eliminateUpperTriangular(coefficients, 5, num_cols))
return 0;
for (int i = 0; i < num_e_mat; i++)
for (int j = 5; j < num_cols; j++)
ee[num_cols * i + j] = (i + 5 == j) ? 1 : 0;
// use back-substitution
for (int e = 0; e < num_e_mat; e++) {
const int curr_e = num_cols * e;
// start from the last row
for (int i = 4; i >= 0; i--) {
const int row_i = i * num_cols;
double acc = 0;
for (int j = i + 1; j < num_cols; j++)
acc -= coefficients[row_i + j] * ee[curr_e + j];
ee[curr_e + i] = acc / coefficients[row_i + i];
// due to numerical errors return 0 solutions
if (std::isnan(ee[curr_e + i]))
return 0;
}
}
}
@ -80,130 +87,215 @@ public:
{null_space.col(0), null_space.col(3), null_space.col(6)},
{null_space.col(1), null_space.col(4), null_space.col(7)},
{null_space.col(2), null_space.col(5), null_space.col(8)}};
Mat_<double> constraint_mat(10, 20);
Matx<double, 1, 10> eet[3][3];
// (2) Use the rank constraint and the trace constraint to build ten third-order polynomial
// equations in the three unknowns. The monomials are ordered in GrLex order and
// represented in a 10×20 matrix, where each row corresponds to an equation and each column
// corresponds to a monomial
Matx<double, 1, 10> eet[3][3];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
// compute EE Transpose
// Shorthand for multiplying the Essential matrix with its transpose.
eet[i][j] = 2 * (multPolysDegOne(null_space_mat[i][0].val, null_space_mat[j][0].val) +
multPolysDegOne(null_space_mat[i][1].val, null_space_mat[j][1].val) +
multPolysDegOne(null_space_mat[i][2].val, null_space_mat[j][2].val));
if (is_nister) {
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
// compute EE Transpose
// Shorthand for multiplying the Essential matrix with its transpose.
eet[i][j] = multPolysDegOne(null_space_mat[i][0].val, null_space_mat[j][0].val) +
multPolysDegOne(null_space_mat[i][1].val, null_space_mat[j][1].val) +
multPolysDegOne(null_space_mat[i][2].val, null_space_mat[j][2].val);
const Matx<double, 1, 10> trace = eet[0][0] + eet[1][1] + eet[2][2];
Mat_<double> constraint_mat(10, 20);
// Trace constraint
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
Mat(multPolysDegOneAndTwo(eet[i][0].val, null_space_mat[0][j].val) +
multPolysDegOneAndTwo(eet[i][1].val, null_space_mat[1][j].val) +
multPolysDegOneAndTwo(eet[i][2].val, null_space_mat[2][j].val) -
0.5 * multPolysDegOneAndTwo(trace.val, null_space_mat[i][j].val))
.copyTo(constraint_mat.row(3 * i + j));
const Matx<double, 1, 10> trace = 0.5*(eet[0][0] + eet[1][1] + eet[2][2]);
// Trace constraint
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
Mat(multPolysDegOneAndTwoNister(i == 0 ? (eet[i][0] - trace).val : eet[i][0].val, null_space_mat[0][j].val) +
multPolysDegOneAndTwoNister(i == 1 ? (eet[i][1] - trace).val : eet[i][1].val, null_space_mat[1][j].val) +
multPolysDegOneAndTwoNister(i == 2 ? (eet[i][2] - trace).val : eet[i][2].val, null_space_mat[2][j].val)).copyTo(constraint_mat.row(1+3 * j + i));
// Rank = zero determinant constraint
Mat(multPolysDegOneAndTwo(
(multPolysDegOne(null_space_mat[0][1].val, null_space_mat[1][2].val) -
multPolysDegOne(null_space_mat[0][2].val, null_space_mat[1][1].val)).val,
null_space_mat[2][0].val) +
// Rank = zero determinant constraint
Mat(multPolysDegOneAndTwoNister(
(multPolysDegOne(null_space_mat[0][1].val, null_space_mat[1][2].val) -
multPolysDegOne(null_space_mat[0][2].val, null_space_mat[1][1].val)).val,
null_space_mat[2][0].val) +
multPolysDegOneAndTwoNister(
(multPolysDegOne(null_space_mat[0][2].val, null_space_mat[1][0].val) -
multPolysDegOne(null_space_mat[0][0].val, null_space_mat[1][2].val)).val,
null_space_mat[2][1].val) +
multPolysDegOneAndTwoNister(
(multPolysDegOne(null_space_mat[0][0].val, null_space_mat[1][1].val) -
multPolysDegOne(null_space_mat[0][1].val, null_space_mat[1][0].val)).val,
null_space_mat[2][2].val)).copyTo(constraint_mat.row(0));
Matx<double, 10, 10> Acoef = constraint_mat.colRange(0, 10),
Bcoef = constraint_mat.colRange(10, 20), A_;
if (!solve(Acoef, Bcoef, A_, DECOMP_LU)) return 0;
double b[3 * 13];
const auto * const a = A_.val;
for (int i = 0; i < 3; i++) {
const int r1_idx = i * 2 + 4, r2_idx = i * 2 + 5; // process from 4th row
for (int j = 0, r1_j = 0, r2_j = 0; j < 13; j++)
b[i*13+j] = ((j == 0 || j == 4 || j == 8) ? 0 : a[r1_idx*A_.cols+r1_j++]) - ((j == 3 || j == 7 || j == 12) ? 0 : a[r2_idx*A_.cols+r2_j++]);
}
std::vector<double> c(11), rs;
// filling coefficients of 10-degree polynomial satysfying zero-determinant constraint of essential matrix, ie., det(E) = 0
// based on "An Efficient Solution to the Five-Point Relative Pose Problem" (David Nister)
// same as in five-point.cpp
c[10] = (b[0]*b[17]*b[34]+b[26]*b[4]*b[21]-b[26]*b[17]*b[8]-b[13]*b[4]*b[34]-b[0]*b[21]*b[30]+b[13]*b[30]*b[8]);
c[9] = (b[26]*b[4]*b[22]+b[14]*b[30]*b[8]+b[13]*b[31]*b[8]+b[1]*b[17]*b[34]-b[13]*b[5]*b[34]+b[26]*b[5]*b[21]-b[0]*b[21]*b[31]-b[26]*b[17]*b[9]-b[1]*b[21]*b[30]+b[27]*b[4]*b[21]+b[0]*b[17]*b[35]-b[0]*b[22]*b[30]+b[13]*b[30]*b[9]+b[0]*b[18]*b[34]-b[27]*b[17]*b[8]-b[14]*b[4]*b[34]-b[13]*b[4]*b[35]-b[26]*b[18]*b[8]);
c[8] = (b[14]*b[30]*b[9]+b[14]*b[31]*b[8]+b[13]*b[31]*b[9]-b[13]*b[4]*b[36]-b[13]*b[5]*b[35]+b[15]*b[30]*b[8]-b[13]*b[6]*b[34]+b[13]*b[30]*b[10]+b[13]*b[32]*b[8]-b[14]*b[4]*b[35]-b[14]*b[5]*b[34]+b[26]*b[4]*b[23]+b[26]*b[5]*b[22]+b[26]*b[6]*b[21]-b[26]*b[17]*b[10]-b[15]*b[4]*b[34]-b[26]*b[18]*b[9]-b[26]*b[19]*b[8]+b[27]*b[4]*b[22]+b[27]*b[5]*b[21]-b[27]*b[17]*b[9]-b[27]*b[18]*b[8]-b[1]*b[21]*b[31]-b[0]*b[23]*b[30]-b[0]*b[21]*b[32]+b[28]*b[4]*b[21]-b[28]*b[17]*b[8]+b[2]*b[17]*b[34]+b[0]*b[18]*b[35]-b[0]*b[22]*b[31]+b[0]*b[17]*b[36]+b[0]*b[19]*b[34]-b[1]*b[22]*b[30]+b[1]*b[18]*b[34]+b[1]*b[17]*b[35]-b[2]*b[21]*b[30]);
c[7] = (b[14]*b[30]*b[10]+b[14]*b[32]*b[8]-b[3]*b[21]*b[30]+b[3]*b[17]*b[34]+b[13]*b[32]*b[9]+b[13]*b[33]*b[8]-b[13]*b[4]*b[37]-b[13]*b[5]*b[36]+b[15]*b[30]*b[9]+b[15]*b[31]*b[8]-b[16]*b[4]*b[34]-b[13]*b[6]*b[35]-b[13]*b[7]*b[34]+b[13]*b[30]*b[11]+b[13]*b[31]*b[10]+b[14]*b[31]*b[9]-b[14]*b[4]*b[36]-b[14]*b[5]*b[35]-b[14]*b[6]*b[34]+b[16]*b[30]*b[8]-b[26]*b[20]*b[8]+b[26]*b[4]*b[24]+b[26]*b[5]*b[23]+b[26]*b[6]*b[22]+b[26]*b[7]*b[21]-b[26]*b[17]*b[11]-b[15]*b[4]*b[35]-b[15]*b[5]*b[34]-b[26]*b[18]*b[10]-b[26]*b[19]*b[9]+b[27]*b[4]*b[23]+b[27]*b[5]*b[22]+b[27]*b[6]*b[21]-b[27]*b[17]*b[10]-b[27]*b[18]*b[9]-b[27]*b[19]*b[8]+b[0]*b[17]*b[37]-b[0]*b[23]*b[31]-b[0]*b[24]*b[30]-b[0]*b[21]*b[33]-b[29]*b[17]*b[8]+b[28]*b[4]*b[22]+b[28]*b[5]*b[21]-b[28]*b[17]*b[9]-b[28]*b[18]*b[8]+b[29]*b[4]*b[21]+b[1]*b[19]*b[34]-b[2]*b[21]*b[31]+b[0]*b[20]*b[34]+b[0]*b[19]*b[35]+b[0]*b[18]*b[36]-b[0]*b[22]*b[32]-b[1]*b[23]*b[30]-b[1]*b[21]*b[32]+b[1]*b[18]*b[35]-b[1]*b[22]*b[31]-b[2]*b[22]*b[30]+b[2]*b[17]*b[35]+b[1]*b[17]*b[36]+b[2]*b[18]*b[34]);
c[6] = (-b[14]*b[6]*b[35]-b[14]*b[7]*b[34]-b[3]*b[22]*b[30]-b[3]*b[21]*b[31]+b[3]*b[17]*b[35]+b[3]*b[18]*b[34]+b[13]*b[32]*b[10]+b[13]*b[33]*b[9]-b[13]*b[4]*b[38]-b[13]*b[5]*b[37]-b[15]*b[6]*b[34]+b[15]*b[30]*b[10]+b[15]*b[32]*b[8]-b[16]*b[4]*b[35]-b[13]*b[6]*b[36]-b[13]*b[7]*b[35]+b[13]*b[31]*b[11]+b[13]*b[30]*b[12]+b[14]*b[32]*b[9]+b[14]*b[33]*b[8]-b[14]*b[4]*b[37]-b[14]*b[5]*b[36]+b[16]*b[30]*b[9]+b[16]*b[31]*b[8]-b[26]*b[20]*b[9]+b[26]*b[4]*b[25]+b[26]*b[5]*b[24]+b[26]*b[6]*b[23]+b[26]*b[7]*b[22]-b[26]*b[17]*b[12]+b[14]*b[30]*b[11]+b[14]*b[31]*b[10]+b[15]*b[31]*b[9]-b[15]*b[4]*b[36]-b[15]*b[5]*b[35]-b[26]*b[18]*b[11]-b[26]*b[19]*b[10]-b[27]*b[20]*b[8]+b[27]*b[4]*b[24]+b[27]*b[5]*b[23]+b[27]*b[6]*b[22]+b[27]*b[7]*b[21]-b[27]*b[17]*b[11]-b[27]*b[18]*b[10]-b[27]*b[19]*b[9]-b[16]*b[5]*b[34]-b[29]*b[17]*b[9]-b[29]*b[18]*b[8]+b[28]*b[4]*b[23]+b[28]*b[5]*b[22]+b[28]*b[6]*b[21]-b[28]*b[17]*b[10]-b[28]*b[18]*b[9]-b[28]*b[19]*b[8]+b[29]*b[4]*b[22]+b[29]*b[5]*b[21]-b[2]*b[23]*b[30]+b[2]*b[18]*b[35]-b[1]*b[22]*b[32]-b[2]*b[21]*b[32]+b[2]*b[19]*b[34]+b[0]*b[19]*b[36]-b[0]*b[22]*b[33]+b[0]*b[20]*b[35]-b[0]*b[23]*b[32]-b[0]*b[25]*b[30]+b[0]*b[17]*b[38]+b[0]*b[18]*b[37]-b[0]*b[24]*b[31]+b[1]*b[17]*b[37]-b[1]*b[23]*b[31]-b[1]*b[24]*b[30]-b[1]*b[21]*b[33]+b[1]*b[20]*b[34]+b[1]*b[19]*b[35]+b[1]*b[18]*b[36]+b[2]*b[17]*b[36]-b[2]*b[22]*b[31]);
c[5] = (-b[14]*b[6]*b[36]-b[14]*b[7]*b[35]+b[14]*b[31]*b[11]-b[3]*b[23]*b[30]-b[3]*b[21]*b[32]+b[3]*b[18]*b[35]-b[3]*b[22]*b[31]+b[3]*b[17]*b[36]+b[3]*b[19]*b[34]+b[13]*b[32]*b[11]+b[13]*b[33]*b[10]-b[13]*b[5]*b[38]-b[15]*b[6]*b[35]-b[15]*b[7]*b[34]+b[15]*b[30]*b[11]+b[15]*b[31]*b[10]+b[16]*b[31]*b[9]-b[13]*b[6]*b[37]-b[13]*b[7]*b[36]+b[13]*b[31]*b[12]+b[14]*b[32]*b[10]+b[14]*b[33]*b[9]-b[14]*b[4]*b[38]-b[14]*b[5]*b[37]-b[16]*b[6]*b[34]+b[16]*b[30]*b[10]+b[16]*b[32]*b[8]-b[26]*b[20]*b[10]+b[26]*b[5]*b[25]+b[26]*b[6]*b[24]+b[26]*b[7]*b[23]+b[14]*b[30]*b[12]+b[15]*b[32]*b[9]+b[15]*b[33]*b[8]-b[15]*b[4]*b[37]-b[15]*b[5]*b[36]+b[29]*b[5]*b[22]+b[29]*b[6]*b[21]-b[26]*b[18]*b[12]-b[26]*b[19]*b[11]-b[27]*b[20]*b[9]+b[27]*b[4]*b[25]+b[27]*b[5]*b[24]+b[27]*b[6]*b[23]+b[27]*b[7]*b[22]-b[27]*b[17]*b[12]-b[27]*b[18]*b[11]-b[27]*b[19]*b[10]-b[28]*b[20]*b[8]-b[16]*b[4]*b[36]-b[16]*b[5]*b[35]-b[29]*b[17]*b[10]-b[29]*b[18]*b[9]-b[29]*b[19]*b[8]+b[28]*b[4]*b[24]+b[28]*b[5]*b[23]+b[28]*b[6]*b[22]+b[28]*b[7]*b[21]-b[28]*b[17]*b[11]-b[28]*b[18]*b[10]-b[28]*b[19]*b[9]+b[29]*b[4]*b[23]-b[2]*b[22]*b[32]-b[2]*b[21]*b[33]-b[1]*b[24]*b[31]+b[0]*b[18]*b[38]-b[0]*b[24]*b[32]+b[0]*b[19]*b[37]+b[0]*b[20]*b[36]-b[0]*b[25]*b[31]-b[0]*b[23]*b[33]+b[1]*b[19]*b[36]-b[1]*b[22]*b[33]+b[1]*b[20]*b[35]+b[2]*b[19]*b[35]-b[2]*b[24]*b[30]-b[2]*b[23]*b[31]+b[2]*b[20]*b[34]+b[2]*b[17]*b[37]-b[1]*b[25]*b[30]+b[1]*b[18]*b[37]+b[1]*b[17]*b[38]-b[1]*b[23]*b[32]+b[2]*b[18]*b[36]);
c[4] = (-b[14]*b[6]*b[37]-b[14]*b[7]*b[36]+b[14]*b[31]*b[12]+b[3]*b[17]*b[37]-b[3]*b[23]*b[31]-b[3]*b[24]*b[30]-b[3]*b[21]*b[33]+b[3]*b[20]*b[34]+b[3]*b[19]*b[35]+b[3]*b[18]*b[36]-b[3]*b[22]*b[32]+b[13]*b[32]*b[12]+b[13]*b[33]*b[11]-b[15]*b[6]*b[36]-b[15]*b[7]*b[35]+b[15]*b[31]*b[11]+b[15]*b[30]*b[12]+b[16]*b[32]*b[9]+b[16]*b[33]*b[8]-b[13]*b[6]*b[38]-b[13]*b[7]*b[37]+b[14]*b[32]*b[11]+b[14]*b[33]*b[10]-b[14]*b[5]*b[38]-b[16]*b[6]*b[35]-b[16]*b[7]*b[34]+b[16]*b[30]*b[11]+b[16]*b[31]*b[10]-b[26]*b[19]*b[12]-b[26]*b[20]*b[11]+b[26]*b[6]*b[25]+b[26]*b[7]*b[24]+b[15]*b[32]*b[10]+b[15]*b[33]*b[9]-b[15]*b[4]*b[38]-b[15]*b[5]*b[37]+b[29]*b[5]*b[23]+b[29]*b[6]*b[22]+b[29]*b[7]*b[21]-b[27]*b[20]*b[10]+b[27]*b[5]*b[25]+b[27]*b[6]*b[24]+b[27]*b[7]*b[23]-b[27]*b[18]*b[12]-b[27]*b[19]*b[11]-b[28]*b[20]*b[9]-b[16]*b[4]*b[37]-b[16]*b[5]*b[36]+b[0]*b[19]*b[38]-b[0]*b[24]*b[33]+b[0]*b[20]*b[37]-b[29]*b[17]*b[11]-b[29]*b[18]*b[10]-b[29]*b[19]*b[9]+b[28]*b[4]*b[25]+b[28]*b[5]*b[24]+b[28]*b[6]*b[23]+b[28]*b[7]*b[22]-b[28]*b[17]*b[12]-b[28]*b[18]*b[11]-b[28]*b[19]*b[10]-b[29]*b[20]*b[8]+b[29]*b[4]*b[24]+b[2]*b[18]*b[37]-b[0]*b[25]*b[32]+b[1]*b[18]*b[38]-b[1]*b[24]*b[32]+b[1]*b[19]*b[37]+b[1]*b[20]*b[36]-b[1]*b[25]*b[31]+b[2]*b[17]*b[38]+b[2]*b[19]*b[36]-b[2]*b[24]*b[31]-b[2]*b[22]*b[33]-b[2]*b[23]*b[32]+b[2]*b[20]*b[35]-b[1]*b[23]*b[33]-b[2]*b[25]*b[30]);
c[3] = (-b[14]*b[6]*b[38]-b[14]*b[7]*b[37]+b[3]*b[19]*b[36]-b[3]*b[22]*b[33]+b[3]*b[20]*b[35]-b[3]*b[23]*b[32]-b[3]*b[25]*b[30]+b[3]*b[17]*b[38]+b[3]*b[18]*b[37]-b[3]*b[24]*b[31]-b[15]*b[6]*b[37]-b[15]*b[7]*b[36]+b[15]*b[31]*b[12]+b[16]*b[32]*b[10]+b[16]*b[33]*b[9]+b[13]*b[33]*b[12]-b[13]*b[7]*b[38]+b[14]*b[32]*b[12]+b[14]*b[33]*b[11]-b[16]*b[6]*b[36]-b[16]*b[7]*b[35]+b[16]*b[31]*b[11]+b[16]*b[30]*b[12]+b[15]*b[32]*b[11]+b[15]*b[33]*b[10]-b[15]*b[5]*b[38]+b[29]*b[5]*b[24]+b[29]*b[6]*b[23]-b[26]*b[20]*b[12]+b[26]*b[7]*b[25]-b[27]*b[19]*b[12]-b[27]*b[20]*b[11]+b[27]*b[6]*b[25]+b[27]*b[7]*b[24]-b[28]*b[20]*b[10]-b[16]*b[4]*b[38]-b[16]*b[5]*b[37]+b[29]*b[7]*b[22]-b[29]*b[17]*b[12]-b[29]*b[18]*b[11]-b[29]*b[19]*b[10]+b[28]*b[5]*b[25]+b[28]*b[6]*b[24]+b[28]*b[7]*b[23]-b[28]*b[18]*b[12]-b[28]*b[19]*b[11]-b[29]*b[20]*b[9]+b[29]*b[4]*b[25]-b[2]*b[24]*b[32]+b[0]*b[20]*b[38]-b[0]*b[25]*b[33]+b[1]*b[19]*b[38]-b[1]*b[24]*b[33]+b[1]*b[20]*b[37]-b[2]*b[25]*b[31]+b[2]*b[20]*b[36]-b[1]*b[25]*b[32]+b[2]*b[19]*b[37]+b[2]*b[18]*b[38]-b[2]*b[23]*b[33]);
c[2] = (b[3]*b[18]*b[38]-b[3]*b[24]*b[32]+b[3]*b[19]*b[37]+b[3]*b[20]*b[36]-b[3]*b[25]*b[31]-b[3]*b[23]*b[33]-b[15]*b[6]*b[38]-b[15]*b[7]*b[37]+b[16]*b[32]*b[11]+b[16]*b[33]*b[10]-b[16]*b[5]*b[38]-b[16]*b[6]*b[37]-b[16]*b[7]*b[36]+b[16]*b[31]*b[12]+b[14]*b[33]*b[12]-b[14]*b[7]*b[38]+b[15]*b[32]*b[12]+b[15]*b[33]*b[11]+b[29]*b[5]*b[25]+b[29]*b[6]*b[24]-b[27]*b[20]*b[12]+b[27]*b[7]*b[25]-b[28]*b[19]*b[12]-b[28]*b[20]*b[11]+b[29]*b[7]*b[23]-b[29]*b[18]*b[12]-b[29]*b[19]*b[11]+b[28]*b[6]*b[25]+b[28]*b[7]*b[24]-b[29]*b[20]*b[10]+b[2]*b[19]*b[38]-b[1]*b[25]*b[33]+b[2]*b[20]*b[37]-b[2]*b[24]*b[33]-b[2]*b[25]*b[32]+b[1]*b[20]*b[38]);
c[1] = (b[29]*b[7]*b[24]-b[29]*b[20]*b[11]+b[2]*b[20]*b[38]-b[2]*b[25]*b[33]-b[28]*b[20]*b[12]+b[28]*b[7]*b[25]-b[29]*b[19]*b[12]-b[3]*b[24]*b[33]+b[15]*b[33]*b[12]+b[3]*b[19]*b[38]-b[16]*b[6]*b[38]+b[3]*b[20]*b[37]+b[16]*b[32]*b[12]+b[29]*b[6]*b[25]-b[16]*b[7]*b[37]-b[3]*b[25]*b[32]-b[15]*b[7]*b[38]+b[16]*b[33]*b[11]);
c[0] = -b[29]*b[20]*b[12]+b[29]*b[7]*b[25]+b[16]*b[33]*b[12]-b[16]*b[7]*b[38]+b[3]*b[20]*b[38]-b[3]*b[25]*b[33];
const auto poly_solver = SolverPoly::create();
const int num_roots = poly_solver->getRealRoots(c, rs);
models = std::vector<Mat>(); models.reserve(num_roots);
for (int i = 0; i < num_roots; i++) {
const double z1 = rs[i], z2 = z1*z1, z3 = z2*z1, z4 = z3*z1;
double bz[9], norm_bz = 0;
for (int j = 0; j < 3; j++) {
double * const br = b + j * 13, * Bz = bz + 3*j;
Bz[0] = br[0] * z3 + br[1] * z2 + br[2] * z1 + br[3];
Bz[1] = br[4] * z3 + br[5] * z2 + br[6] * z1 + br[7];
Bz[2] = br[8] * z4 + br[9] * z3 + br[10] * z2 + br[11] * z1 + br[12];
norm_bz += Bz[0]*Bz[0] + Bz[1]*Bz[1] + Bz[2]*Bz[2];
}
Matx33d Bz(bz);
// Bz is rank 2, matrix, so epipole is its null-vector
Vec3d xy1 = Utils::getRightEpipole(Mat(Bz * (1/sqrt(norm_bz))));
if (fabs(xy1(2)) < 1e-10) continue;
Mat_<double> E(3,3);
double * e_arr = (double *)E.data, x = xy1(0) / xy1(2), y = xy1(1) / xy1(2);
for (int e_i = 0; e_i < 9; e_i++)
e_arr[e_i] = ee[e_i] * x + ee[9+e_i] * y + ee[18+e_i]*z1 + ee[27+e_i];
models.emplace_back(E);
}
} else {
#if defined(HAVE_EIGEN) || defined(HAVE_LAPACK)
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
// compute EE Transpose
// Shorthand for multiplying the Essential matrix with its transpose.
eet[i][j] = 2 * (multPolysDegOne(null_space_mat[i][0].val, null_space_mat[j][0].val) +
multPolysDegOne(null_space_mat[i][1].val, null_space_mat[j][1].val) +
multPolysDegOne(null_space_mat[i][2].val, null_space_mat[j][2].val));
const Matx<double, 1, 10> trace = eet[0][0] + eet[1][1] + eet[2][2];
// Trace constraint
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
Mat(multPolysDegOneAndTwo(eet[i][0].val, null_space_mat[0][j].val) +
multPolysDegOneAndTwo(eet[i][1].val, null_space_mat[1][j].val) +
multPolysDegOneAndTwo(eet[i][2].val, null_space_mat[2][j].val) -
0.5 * multPolysDegOneAndTwo(trace.val, null_space_mat[i][j].val))
.copyTo(constraint_mat.row(3 * i + j));
// Rank = zero determinant constraint
Mat(multPolysDegOneAndTwo(
(multPolysDegOne(null_space_mat[0][1].val, null_space_mat[1][2].val) -
multPolysDegOne(null_space_mat[0][2].val, null_space_mat[1][1].val)).val,
null_space_mat[2][0].val) +
multPolysDegOneAndTwo(
(multPolysDegOne(null_space_mat[0][2].val, null_space_mat[1][0].val) -
multPolysDegOne(null_space_mat[0][0].val, null_space_mat[1][2].val)).val,
null_space_mat[2][1].val) +
null_space_mat[2][1].val) +
multPolysDegOneAndTwo(
(multPolysDegOne(null_space_mat[0][0].val, null_space_mat[1][1].val) -
multPolysDegOne(null_space_mat[0][1].val, null_space_mat[1][0].val)).val,
null_space_mat[2][2].val)).copyTo(constraint_mat.row(9));
null_space_mat[2][2].val)).copyTo(constraint_mat.row(9));
#ifdef HAVE_EIGEN
const Eigen::Matrix<double, 10, 20, Eigen::RowMajor> constraint_mat_eig((double *) constraint_mat.data);
// (3) Compute the Gröbner basis. This turns out to be as simple as performing a
// Gauss-Jordan elimination on the 10×20 matrix
const Eigen::Matrix<double, 10, 10> eliminated_mat_eig = constraint_mat_eig.block<10, 10>(0, 0)
.fullPivLu().solve(constraint_mat_eig.block<10, 10>(0, 10));
const Eigen::Matrix<double, 10, 20, Eigen::RowMajor> constraint_mat_eig((double *) constraint_mat.data);
// (3) Compute the Gröbner basis. This turns out to be as simple as performing a
// Gauss-Jordan elimination on the 10×20 matrix
const Eigen::Matrix<double, 10, 10> eliminated_mat_eig = constraint_mat_eig.block<10, 10>(0, 0)
.fullPivLu().solve(constraint_mat_eig.block<10, 10>(0, 10));
// (4) Compute the 10×10 action matrix for multiplication by one of the un-knowns.
// This is a simple matter of extracting the correct elements fromthe eliminated
// 10×20 matrix and organising them to form the action matrix.
Eigen::Matrix<double, 10, 10> action_mat_eig = Eigen::Matrix<double, 10, 10>::Zero();
action_mat_eig.block<3, 10>(0, 0) = eliminated_mat_eig.block<3, 10>(0, 0);
action_mat_eig.block<2, 10>(3, 0) = eliminated_mat_eig.block<2, 10>(4, 0);
action_mat_eig.row(5) = eliminated_mat_eig.row(7);
action_mat_eig(6, 0) = -1.0;
action_mat_eig(7, 1) = -1.0;
action_mat_eig(8, 3) = -1.0;
action_mat_eig(9, 6) = -1.0;
// (4) Compute the 10×10 action matrix for multiplication by one of the unknowns.
// This is a simple matter of extracting the correct elements from the eliminated
// 10×20 matrix and organising them to form the action matrix.
Eigen::Matrix<double, 10, 10> action_mat_eig = Eigen::Matrix<double, 10, 10>::Zero();
action_mat_eig.block<3, 10>(0, 0) = eliminated_mat_eig.block<3, 10>(0, 0);
action_mat_eig.block<2, 10>(3, 0) = eliminated_mat_eig.block<2, 10>(4, 0);
action_mat_eig.row(5) = eliminated_mat_eig.row(7);
action_mat_eig(6, 0) = -1.0;
action_mat_eig(7, 1) = -1.0;
action_mat_eig(8, 3) = -1.0;
action_mat_eig(9, 6) = -1.0;
// (5) Compute the left eigenvectors of the action matrix
Eigen::EigenSolver<Eigen::Matrix<double, 10, 10>> eigensolver(action_mat_eig);
const Eigen::VectorXcd &eigenvalues = eigensolver.eigenvalues();
const auto * const eig_vecs_ = (double *) eigensolver.eigenvectors().real().data();
// (5) Compute the left eigenvectors of the action matrix
Eigen::EigenSolver<Eigen::Matrix<double, 10, 10>> eigensolver(action_mat_eig);
const Eigen::VectorXcd &eigenvalues = eigensolver.eigenvalues();
const auto * const eig_vecs_ = (double *) eigensolver.eigenvectors().real().data();
#else
Matx<double, 10, 10> A = constraint_mat.colRange(0, 10),
B = constraint_mat.colRange(10, 20), eliminated_mat;
if (!solve(A, B, eliminated_mat, DECOMP_LU)) return 0;
Matx<double, 10, 10> A = constraint_mat.colRange(0, 10),
B = constraint_mat.colRange(10, 20), eliminated_mat;
if (!solve(A, B, eliminated_mat, DECOMP_LU)) return 0;
Mat eliminated_mat_dyn = Mat(eliminated_mat);
Mat action_mat = Mat_<double>::zeros(10, 10);
eliminated_mat_dyn.rowRange(0,3).copyTo(action_mat.rowRange(0,3));
eliminated_mat_dyn.rowRange(4,6).copyTo(action_mat.rowRange(3,5));
eliminated_mat_dyn.row(7).copyTo(action_mat.row(5));
auto * action_mat_data = (double *) action_mat.data;
action_mat_data[60] = -1.0; // 6 row, 0 col
action_mat_data[71] = -1.0; // 7 row, 1 col
action_mat_data[83] = -1.0; // 8 row, 3 col
action_mat_data[96] = -1.0; // 9 row, 6 col
Mat eliminated_mat_dyn = Mat(eliminated_mat);
Mat action_mat = Mat_<double>::zeros(10, 10);
eliminated_mat_dyn.rowRange(0,3).copyTo(action_mat.rowRange(0,3));
eliminated_mat_dyn.rowRange(4,6).copyTo(action_mat.rowRange(3,5));
eliminated_mat_dyn.row(7).copyTo(action_mat.row(5));
auto * action_mat_data = (double *) action_mat.data;
action_mat_data[60] = -1.0; // 6 row, 0 col
action_mat_data[71] = -1.0; // 7 row, 1 col
action_mat_data[83] = -1.0; // 8 row, 3 col
action_mat_data[96] = -1.0; // 9 row, 6 col
int mat_order = 10, info, lda = 10, ldvl = 10, ldvr = 1, lwork = 100;
double wr[10], wi[10] = {0}, eig_vecs[100], work[100]; // 10 = mat_order, 100 = lwork
char jobvl = 'V', jobvr = 'N'; // only left eigen vectors are computed
OCV_LAPACK_FUNC(dgeev)(&jobvl, &jobvr, &mat_order, action_mat_data, &lda, wr, wi, eig_vecs, &ldvl,
nullptr, &ldvr, work, &lwork, &info);
if (info != 0) return 0;
int mat_order = 10, info, lda = 10, ldvl = 10, ldvr = 1, lwork = 100;
double wr[10], wi[10] = {0}, eig_vecs[100], work[100]; // 10 = mat_order, 100 = lwork
char jobvl = 'V', jobvr = 'N'; // only left eigen vectors are computed
OCV_LAPACK_FUNC(dgeev)(&jobvl, &jobvr, &mat_order, action_mat_data, &lda, wr, wi, eig_vecs, &ldvl,
nullptr, &ldvr, work, &lwork, &info);
if (info != 0) return 0;
#endif
models = std::vector<Mat>(); models.reserve(10);
models = std::vector<Mat>(); models.reserve(10);
// Read off the values for the three unknowns at all the solution points and
// back-substitute to obtain the solutions for the essential matrix.
for (int i = 0; i < 10; i++)
// process only real solutions
// Read off the values for the three unknowns at all the solution points and
// back-substitute to obtain the solutions for the essential matrix.
for (int i = 0; i < 10; i++)
// process only real solutions
#ifdef HAVE_EIGEN
if (eigenvalues(i).imag() == 0) {
Mat_<double> model(3, 3);
auto * model_data = (double *) model.data;
const int eig_i = 20 * i + 12; // eigen stores imaginary values too
for (int j = 0; j < 9; j++)
model_data[j] = ee[j ] * eig_vecs_[eig_i ] + ee[j+9 ] * eig_vecs_[eig_i+2] +
ee[j+18] * eig_vecs_[eig_i+4] + ee[j+27] * eig_vecs_[eig_i+6];
if (eigenvalues(i).imag() == 0) {
Mat_<double> model(3, 3);
auto * model_data = (double *) model.data;
const int eig_i = 20 * i + 12; // eigen stores imaginary values too
for (int j = 0; j < 9; j++)
model_data[j] = ee[j ] * eig_vecs_[eig_i ] + ee[j+9 ] * eig_vecs_[eig_i+2] +
ee[j+18] * eig_vecs_[eig_i+4] + ee[j+27] * eig_vecs_[eig_i+6];
#else
if (wi[i] == 0) {
Mat_<double> model (3,3);
auto * model_data = (double *) model.data;
const int eig_i = 10 * i + 6;
for (int j = 0; j < 9; j++)
model_data[j] = ee[j ]*eig_vecs[eig_i ] + ee[j+9 ]*eig_vecs[eig_i+1] +
ee[j+18]*eig_vecs[eig_i+2] + ee[j+27]*eig_vecs[eig_i+3];
if (wi[i] == 0) {
Mat_<double> model (3,3);
auto * model_data = (double *) model.data;
const int eig_i = 10 * i + 6;
for (int j = 0; j < 9; j++)
model_data[j] = ee[j ]*eig_vecs[eig_i ] + ee[j+9 ]*eig_vecs[eig_i+1] +
ee[j+18]*eig_vecs[eig_i+2] + ee[j+27]*eig_vecs[eig_i+3];
#endif
models.emplace_back(model);
}
models.emplace_back(model);
}
#else
CV_Error(cv::Error::StsNotImplemented, "To run essential matrix estimation of Stewenius method you need to have either Eigen or LAPACK installed! Or switch to Nister algorithm");
return 0;
#endif
}
return static_cast<int>(models.size());
#else
int estimate (const std::vector<int> &/*sample*/, std::vector<Mat> &/*models*/) const override {
CV_Error(cv::Error::StsNotImplemented, "To use essential matrix solver LAPACK or Eigen has to be installed!");
#endif
}
// number of possible solutions is 0,2,4,6,8,10
int getMaxNumberOfSolutions () const override { return 10; }
int getSampleSize() const override { return 5; }
Ptr<MinimalSolver> clone () const override {
return makePtr<EssentialMinimalSolverStewenius5ptsImpl>(*points_mat);
}
private:
/*
* Multiply two polynomials of degree one with unknowns x y z
@ -236,105 +328,19 @@ private:
p[5]*q[3]+p[8]*q[2], p[6]*q[3]+p[9]*q[0], p[7]*q[3]+p[9]*q[1], p[8]*q[3]+p[9]*q[2],
p[9]*q[3]});
}
};
Ptr<EssentialMinimalSolverStewenius5pts> EssentialMinimalSolverStewenius5pts::create
(const Mat &points_) {
return makePtr<EssentialMinimalSolverStewenius5ptsImpl>(points_);
}
class EssentialNonMinimalSolverImpl : public EssentialNonMinimalSolver {
private:
const Mat * points_mat;
const float * const points;
public:
/*
* Input calibrated points K^-1 x.
* Linear 8 points algorithm is used for estimation.
*/
explicit EssentialNonMinimalSolverImpl (const Mat &points_) :
points_mat(&points_), points ((float *) points_.data) {}
int estimate (const std::vector<int> &sample, int sample_size, std::vector<Mat>
&models, const std::vector<double> &weights) const override {
if (sample_size < getMinimumRequiredSampleSize())
return 0;
// ------- 8 points algorithm with Eigen and covariance matrix --------------
double a[9] = {0, 0, 0, 0, 0, 0, 0, 0, 1};
double AtA[81] = {0}; // 9x9
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
const int pidx = 4*sample[i];
const double x1 = points[pidx ], y1 = points[pidx+1],
x2 = points[pidx+2], y2 = points[pidx+3];
a[0] = x2*x1;
a[1] = x2*y1;
a[2] = x2;
a[3] = y2*x1;
a[4] = y2*y1;
a[5] = y2;
a[6] = x1;
a[7] = y1;
// calculate covariance for eigen
for (int row = 0; row < 9; row++)
for (int col = row; col < 9; col++)
AtA[row*9+col] += a[row]*a[col];
}
} else {
for (int i = 0; i < sample_size; i++) {
const int smpl = 4*sample[i];
const double weight = weights[i];
const double x1 = points[smpl ], y1 = points[smpl+1],
x2 = points[smpl+2], y2 = points[smpl+3];
const double weight_times_x2 = weight * x2,
weight_times_y2 = weight * y2;
a[0] = weight_times_x2 * x1;
a[1] = weight_times_x2 * y1;
a[2] = weight_times_x2;
a[3] = weight_times_y2 * x1;
a[4] = weight_times_y2 * y1;
a[5] = weight_times_y2;
a[6] = weight * x1;
a[7] = weight * y1;
a[8] = weight;
// calculate covariance for eigen
for (int row = 0; row < 9; row++)
for (int col = row; col < 9; col++)
AtA[row*9+col] += a[row]*a[col];
}
}
// copy symmetric part of covariance matrix
for (int j = 1; j < 9; j++)
for (int z = 0; z < j; z++)
AtA[j*9+z] = AtA[z*9+j];
#ifdef HAVE_EIGEN
models = std::vector<Mat>{ Mat_<double>(3,3) };
const Eigen::JacobiSVD<Eigen::Matrix<double, 9, 9>> svd((Eigen::Matrix<double, 9, 9>(AtA)),
Eigen::ComputeFullV);
// extract the last nullspace
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)models[0].data) = svd.matrixV().col(8);
#else
Matx<double, 9, 9> AtA_(AtA), U, Vt;
Vec<double, 9> W;
SVD::compute(AtA_, W, U, Vt, SVD::FULL_UV + SVD::MODIFY_A);
models = std::vector<Mat> { Mat_<double>(3, 3, Vt.val + 72 /*=8*9*/) };
#endif
FundamentalDegeneracy::recoverRank(models[0], false /*E*/);
return 1;
}
int getMinimumRequiredSampleSize() const override { return 8; }
int getMaxNumberOfSolutions () const override { return 1; }
Ptr<NonMinimalSolver> clone () const override {
return makePtr<EssentialNonMinimalSolverImpl>(*points_mat);
static inline Matx<double, 1, 20> multPolysDegOneAndTwoNister(const double * const p,
const double * const q) {
// permutation {0, 3, 1, 2, 4, 10, 6, 12, 5, 11, 7, 13, 16, 8, 14, 17, 9, 15, 18, 19};
return Matx<double, 1, 20>
({p[0]*q[0], p[2]*q[1], p[0]*q[1]+p[1]*q[0], p[1]*q[1]+p[2]*q[0], p[0]*q[2]+p[3]*q[0],
p[0]*q[3]+p[6]*q[0], p[2]*q[2]+p[4]*q[1], p[2]*q[3]+p[7]*q[1], p[1]*q[2]+p[3]*q[1]+p[4]*q[0],
p[1]*q[3]+p[6]*q[1]+p[7]*q[0], p[3]*q[2]+p[5]*q[0], p[3]*q[3]+p[6]*q[2]+p[8]*q[0],
p[6]*q[3]+p[9]*q[0], p[4]*q[2]+p[5]*q[1], p[4]*q[3]+p[7]*q[2]+p[8]*q[1], p[7]*q[3]+p[9]*q[1],
p[5]*q[2], p[5]*q[3]+p[8]*q[2], p[8]*q[3]+p[9]*q[2], p[9]*q[3]});
}
};
Ptr<EssentialNonMinimalSolver> EssentialNonMinimalSolver::create (const Mat &points_) {
return makePtr<EssentialNonMinimalSolverImpl>(points_);
Ptr<EssentialMinimalSolver5pts> EssentialMinimalSolver5pts::create
(const Mat &points_, bool use_svd, bool is_nister) {
return makePtr<EssentialMinimalSolver5ptsImpl>(points_, use_svd, is_nister);
}
}}

View File

@ -36,10 +36,7 @@ public:
int getNonMinimalSampleSize () const override {
return non_min_solver->getMinimumRequiredSampleSize();
}
Ptr<Estimator> clone() const override {
return makePtr<HomographyEstimatorImpl>(min_solver->clone(), non_min_solver->clone(),
degeneracy->clone(0 /*we don't need state here*/));
}
void enforceRankConstraint (bool /*enforce*/) override {}
};
Ptr<HomographyEstimator> HomographyEstimator::create (const Ptr<MinimalSolver> &min_solver_,
const Ptr<NonMinimalSolver> &non_min_solver_, const Ptr<Degeneracy> &degeneracy_) {
@ -80,13 +77,12 @@ public:
int getNonMinimalSampleSize () const override {
return non_min_solver->getMinimumRequiredSampleSize();
}
void enforceRankConstraint (bool enforce) override {
non_min_solver->enforceRankConstraint(enforce);
}
int getMaxNumSolutionsNonMinimal () const override {
return non_min_solver->getMaxNumberOfSolutions();
}
Ptr<Estimator> clone() const override {
return makePtr<FundamentalEstimatorImpl>(min_solver->clone(), non_min_solver->clone(),
degeneracy->clone(0));
}
};
Ptr<FundamentalEstimator> FundamentalEstimator::create (const Ptr<MinimalSolver> &min_solver_,
const Ptr<NonMinimalSolver> &non_min_solver_, const Ptr<Degeneracy> &degeneracy_) {
@ -106,7 +102,7 @@ public:
inline int
estimateModels(const std::vector<int> &sample, std::vector<Mat> &models) const override {
std::vector<Mat> E;
std::vector<Mat> E;
const int models_count = min_solver->estimate(sample, E);
int valid_models_count = 0;
for (int i = 0; i < models_count; i++)
@ -115,6 +111,10 @@ public:
return valid_models_count;
}
int estimateModelNonMinimalSample (const Mat &model, const std::vector<int> &sample, int sample_size, std::vector<Mat>
&models, const std::vector<double> &weights) const override {
return non_min_solver->estimate(model, sample, sample_size, models, weights);
}
int estimateModelNonMinimalSample(const std::vector<int> &sample, int sample_size,
std::vector<Mat> &models, const std::vector<double> &weights) const override {
return non_min_solver->estimate(sample, sample_size, models, weights);
@ -128,13 +128,12 @@ public:
int getNonMinimalSampleSize () const override {
return non_min_solver->getMinimumRequiredSampleSize();
}
void enforceRankConstraint (bool enforce) override {
non_min_solver->enforceRankConstraint(enforce);
}
int getMaxNumSolutionsNonMinimal () const override {
return non_min_solver->getMaxNumberOfSolutions();
}
Ptr<Estimator> clone() const override {
return makePtr<EssentialEstimatorImpl>(min_solver->clone(), non_min_solver->clone(),
degeneracy->clone(0));
}
};
Ptr<EssentialEstimator> EssentialEstimator::create (const Ptr<MinimalSolver> &min_solver_,
const Ptr<NonMinimalSolver> &non_min_solver_, const Ptr<Degeneracy> &degeneracy_) {
@ -170,9 +169,7 @@ public:
int getMaxNumSolutionsNonMinimal () const override {
return non_min_solver->getMaxNumberOfSolutions();
}
Ptr<Estimator> clone() const override {
return makePtr<AffineEstimatorImpl>(min_solver->clone(), non_min_solver->clone());
}
void enforceRankConstraint (bool /*enforce*/) override {}
};
Ptr<AffineEstimator> AffineEstimator::create (const Ptr<MinimalSolver> &min_solver_,
const Ptr<NonMinimalSolver> &non_min_solver_) {
@ -208,9 +205,7 @@ public:
int getMaxNumSolutionsNonMinimal () const override {
return non_min_solver->getMaxNumberOfSolutions();
}
Ptr<Estimator> clone() const override {
return makePtr<PnPEstimatorImpl>(min_solver->clone(), non_min_solver->clone());
}
void enforceRankConstraint (bool /*enforce*/) override {}
};
Ptr<PnPEstimator> PnPEstimator::create (const Ptr<MinimalSolver> &min_solver_,
const Ptr<NonMinimalSolver> &non_min_solver_) {
@ -253,9 +248,9 @@ public:
minv21=(float)minv[3]; minv22=(float)minv[4]; minv23=(float)minv[5];
minv31=(float)minv[6]; minv32=(float)minv[7]; minv33=(float)minv[8];
}
inline float getError (int point_idx) const override {
const int smpl = 4*point_idx;
const float x1=points[smpl], y1=points[smpl+1], x2=points[smpl+2], y2=points[smpl+3];
inline float getError (int idx) const override {
idx *= 4;
const float x1=points[idx], y1=points[idx+1], x2=points[idx+2], y2=points[idx+3];
const float est_z2 = 1 / (m31 * x1 + m32 * y1 + m33),
dx2 = x2 - (m11 * x1 + m12 * y1 + m13) * est_z2,
dy2 = y2 - (m21 * x1 + m22 * y1 + m23) * est_z2;
@ -279,9 +274,6 @@ public:
}
return errors;
}
Ptr<Error> clone () const override {
return makePtr<ReprojectionErrorSymmetricImpl>(*points_mat);
}
};
Ptr<ReprojectionErrorSymmetric>
ReprojectionErrorSymmetric::create(const Mat &points) {
@ -314,9 +306,9 @@ public:
m21=static_cast<float>(m[3]); m22=static_cast<float>(m[4]); m23=static_cast<float>(m[5]);
m31=static_cast<float>(m[6]); m32=static_cast<float>(m[7]); m33=static_cast<float>(m[8]);
}
inline float getError (int point_idx) const override {
const int smpl = 4*point_idx;
const float x1 = points[smpl], y1 = points[smpl+1], x2 = points[smpl+2], y2 = points[smpl+3];
inline float getError (int idx) const override {
idx *= 4;
const float x1 = points[idx], y1 = points[idx+1], x2 = points[idx+2], y2 = points[idx+3];
const float est_z2 = 1 / (m31 * x1 + m32 * y1 + m33),
dx2 = x2 - (m11 * x1 + m12 * y1 + m13) * est_z2,
dy2 = y2 - (m21 * x1 + m22 * y1 + m23) * est_z2;
@ -334,9 +326,6 @@ public:
}
return errors;
}
Ptr<Error> clone () const override {
return makePtr<ReprojectionErrorForwardImpl>(*points_mat);
}
};
Ptr<ReprojectionErrorForward>
ReprojectionErrorForward::create(const Mat &points) {
@ -379,9 +368,9 @@ public:
* [ F(3,1) F(3,2) F(3,3) ] [ 1 ]
*
*/
inline float getError (int point_idx) const override {
const int smpl = 4*point_idx;
const float x1=points[smpl], y1=points[smpl+1], x2=points[smpl+2], y2=points[smpl+3];
inline float getError (int idx) const override {
idx *= 4;
const float x1=points[idx], y1=points[idx+1], x2=points[idx+2], y2=points[idx+3];
const float F_pt1_x = m11 * x1 + m12 * y1 + m13,
F_pt1_y = m21 * x1 + m22 * y1 + m23;
const float pt2_F_x = x2 * m11 + y2 * m21 + m31,
@ -405,9 +394,6 @@ public:
}
return errors;
}
Ptr<Error> clone () const override {
return makePtr<SampsonErrorImpl>(*points_mat);
}
};
Ptr<SampsonError>
SampsonError::create(const Mat &points) {
@ -440,9 +426,9 @@ public:
m31=static_cast<float>(m[6]); m32=static_cast<float>(m[7]); m33=static_cast<float>(m[8]);
}
inline float getError (int point_idx) const override {
const int smpl = 4*point_idx;
const float x1=points[smpl], y1=points[smpl+1], x2=points[smpl+2], y2=points[smpl+3];
inline float getError (int idx) const override {
idx *= 4;
const float x1=points[idx], y1=points[idx+1], x2=points[idx+2], y2=points[idx+3];
// pt2^T * E, line 1 = [l1 l2]
const float l1 = x2 * m11 + y2 * m21 + m31,
l2 = x2 * m12 + y2 * m22 + m32;
@ -468,9 +454,6 @@ public:
}
return errors;
}
Ptr<Error> clone () const override {
return makePtr<SymmetricGeometricDistanceImpl>(*points_mat);
}
};
Ptr<SymmetricGeometricDistance>
SymmetricGeometricDistance::create(const Mat &points) {
@ -504,10 +487,10 @@ public:
p31 = (float)p[8]; p32 = (float)p[9]; p33 = (float)p[10]; p34 = (float)p[11];
}
inline float getError (int point_idx) const override {
const int smpl = 5*point_idx;
const float u = points[smpl ], v = points[smpl+1],
x = points[smpl+2], y = points[smpl+3], z = points[smpl+4];
inline float getError (int idx) const override {
idx *= 5;
const float u = points[idx ], v = points[idx+1],
x = points[idx+2], y = points[idx+3], z = points[idx+4];
const float depth = 1 / (p31 * x + p32 * y + p33 * z + p34);
const float du = u - (p11 * x + p12 * y + p13 * z + p14) * depth;
const float dv = v - (p21 * x + p22 * y + p23 * z + p24) * depth;
@ -526,9 +509,6 @@ public:
}
return errors;
}
Ptr<Error> clone () const override {
return makePtr<ReprojectionErrorPmatrixImpl>(*points_mat);
}
};
Ptr<ReprojectionErrorPmatrix> ReprojectionErrorPmatrix::create(const Mat &points) {
return makePtr<ReprojectionErrorPmatrixImpl>(points);
@ -565,9 +545,9 @@ public:
m11 = (float)m[0]; m12 = (float)m[1]; m13 = (float)m[2];
m21 = (float)m[3]; m22 = (float)m[4]; m23 = (float)m[5];
}
inline float getError (int point_idx) const override {
const int smpl = 4*point_idx;
const float x1=points[smpl], y1=points[smpl+1], x2=points[smpl+2], y2=points[smpl+3];
inline float getError (int idx) const override {
idx *= 4;
const float x1=points[idx], y1=points[idx+1], x2=points[idx+2], y2=points[idx+3];
const float dx2 = x2 - (m11 * x1 + m12 * y1 + m13), dy2 = y2 - (m21 * x1 + m22 * y1 + m23);
return dx2 * dx2 + dy2 * dy2;
}
@ -581,9 +561,6 @@ public:
}
return errors;
}
Ptr<Error> clone () const override {
return makePtr<ReprojectionDistanceAffineImpl>(*points_mat);
}
};
Ptr<ReprojectionErrorAffine>
ReprojectionErrorAffine::create(const Mat &points) {
@ -606,13 +583,11 @@ public:
int smpl;
for (int i = 0; i < sample_size; i++) {
smpl = 4 * sample[i];
mean_pts1_x += points[smpl ];
mean_pts1_y += points[smpl + 1];
mean_pts2_x += points[smpl + 2];
mean_pts2_y += points[smpl + 3];
}
mean_pts1_x /= sample_size; mean_pts1_y /= sample_size;
mean_pts2_x /= sample_size; mean_pts2_y /= sample_size;
@ -652,9 +627,9 @@ public:
auto * norm_points_ptr = (float *) norm_points.data;
// Normalize points: Npts = T*pts 3x3 * 3xN
const float avg_dist1f = (float)avg_dist1, avg_dist2f = (float)avg_dist2;
const float transl_x1f = (float)transl_x1, transl_y1f = (float)transl_y1;
const float transl_x2f = (float)transl_x2, transl_y2f = (float)transl_y2;
const auto avg_dist1f = (float)avg_dist1, avg_dist2f = (float)avg_dist2;
const auto transl_x1f = (float)transl_x1, transl_y1f = (float)transl_y1;
const auto transl_x2f = (float)transl_x2, transl_y2f = (float)transl_y2;
for (int i = 0; i < sample_size; i++) {
smpl = 4 * sample[i];
(*norm_points_ptr++) = avg_dist1f * points[smpl ] + transl_x1f;

View File

@ -4,19 +4,20 @@
#include "../precomp.hpp"
#include "../usac.hpp"
#include "../polynom_solver.h"
#ifdef HAVE_EIGEN
#include <Eigen/Eigen>
#endif
namespace cv { namespace usac {
// Fundamental Matrix Solver:
class FundamentalMinimalSolver7ptsImpl: public FundamentalMinimalSolver7pts {
private:
const Mat * points_mat;
const float * const points;
const Mat * points_mat; // pointer to OpenCV Mat
const float * const points; // pointer to points_mat->data for faster data access
const bool use_ge;
public:
explicit FundamentalMinimalSolver7ptsImpl (const Mat &points_) :
points_mat (&points_), points ((float *) points_.data) {}
explicit FundamentalMinimalSolver7ptsImpl (const Mat &points_, bool use_ge_) :
points_mat (&points_), points ((float *) points_mat->data), use_ge(use_ge_) {}
int estimate (const std::vector<int> &sample, std::vector<Mat> &models) const override {
const int m = 7, n = 9; // rows, cols
@ -38,52 +39,57 @@ public:
(*a_++) = y1;
(*a_++) = 1;
}
if (!Math::eliminateUpperTriangular(a, m, n))
return 0;
/*
[a11 a12 a13 a14 a15 a16 a17 a18 a19]
[ 0 a22 a23 a24 a25 a26 a27 a28 a29]
[ 0 0 a33 a34 a35 a36 a37 a38 a39]
[ 0 0 0 a44 a45 a46 a47 a48 a49]
[ 0 0 0 0 a55 a56 a57 a58 a59]
[ 0 0 0 0 0 a66 a67 a68 a69]
[ 0 0 0 0 0 0 a77 a78 a79]
f9 = 1
*/
double f1[9], f2[9];
f1[8] = 1.;
f1[7] = 0.;
f1[6] = -a[6*n+8] / a[6*n+6];
f2[8] = 1.;
f2[7] = -a[6*n+8] / a[6*n+7];
f2[6] = 0.;
// start from the last row
for (int i = m-2; i >= 0; i--) {
const int row_i = i*n;
double acc1 = 0, acc2 = 0;
for (int j = i+1; j < n; j++) {
acc1 -= a[row_i + j] * f1[j];
acc2 -= a[row_i + j] * f2[j];
}
f1[i] = acc1 / a[row_i + i];
f2[i] = acc2 / a[row_i + i];
// due to numerical errors return 0 solutions
if (std::isnan(f1[i]) || std::isnan(f2[i]))
if (use_ge) {
if (!Math::eliminateUpperTriangular(a, m, n))
return 0;
/*
[a11 a12 a13 a14 a15 a16 a17 a18 a19]
[ 0 a22 a23 a24 a25 a26 a27 a28 a29]
[ 0 0 a33 a34 a35 a36 a37 a38 a39]
[ 0 0 0 a44 a45 a46 a47 a48 a49]
[ 0 0 0 0 a55 a56 a57 a58 a59]
[ 0 0 0 0 0 a66 a67 a68 a69]
[ 0 0 0 0 0 0 a77 a78 a79]
*/
f1[8] = 1.;
f1[7] = 0.;
f1[6] = -a[6*n+8] / a[6*n+6];
f2[8] = 0.;
f2[7] = -a[6*n+6] / a[6*n+7];
f2[6] = 1;
// start from the last row
for (int i = m-2; i >= 0; i--) {
const int row_i = i*n;
double acc1 = 0, acc2 = 0;
for (int j = i+1; j < n; j++) {
acc1 -= a[row_i + j] * f1[j];
acc2 -= a[row_i + j] * f2[j];
}
f1[i] = acc1 / a[row_i + i];
f2[i] = acc2 / a[row_i + i];
if (std::isnan(f1[i]) || std::isnan(f2[i]))
return 0; // due to numerical errors return 0 solutions
}
} else {
Mat U, Vt, D;
cv::Matx<double, 7, 9> A(&a[0]);
SVD::compute(A, D, U, Vt, SVD::FULL_UV+SVD::MODIFY_A);
const auto * const vt = (double *) Vt.data;
int i1 = 8*9, i2 = 7*9;
for (int i = 0; i < 9; i++) {
f1[i] = vt[i1+i];
f2[i] = vt[i2+i];
}
}
// OpenCV:
double c[4] = { 0 }, r[3] = { 0 };
double t0 = 0, t1 = 0, t2 = 0;
Mat_<double> coeffs (1, 4, c);
Mat_<double> roots (1, 3, r);
for (int i = 0; i < 9; i++)
f1[i] -= f2[i];
@ -117,7 +123,7 @@ public:
c[0] = f1[0]*t0 - f1[1]*t1 + f1[2]*t2;
// solve the cubic equation; there can be 1 to 3 roots ...
int nroots = solveCubic (coeffs, roots);
const int nroots = solve_deg3(c[0], c[1], c[2], c[3], r[0], r[1], r[2]);
if (nroots < 1) return 0;
models = std::vector<Mat>(nroots);
@ -145,12 +151,9 @@ public:
int getMaxNumberOfSolutions () const override { return 3; }
int getSampleSize() const override { return 7; }
Ptr<MinimalSolver> clone () const override {
return makePtr<FundamentalMinimalSolver7ptsImpl>(*points_mat);
}
};
Ptr<FundamentalMinimalSolver7pts> FundamentalMinimalSolver7pts::create(const Mat &points_) {
return makePtr<FundamentalMinimalSolver7ptsImpl>(points_);
Ptr<FundamentalMinimalSolver7pts> FundamentalMinimalSolver7pts::create(const Mat &points, bool use_ge) {
return makePtr<FundamentalMinimalSolver7ptsImpl>(points, use_ge);
}
class FundamentalMinimalSolver8ptsImpl : public FundamentalMinimalSolver8pts {
@ -159,10 +162,8 @@ private:
const float * const points;
public:
explicit FundamentalMinimalSolver8ptsImpl (const Mat &points_) :
points_mat (&points_), points ((float*) points_.data)
{
CV_DbgAssert(points);
}
points_mat (&points_), points ((float*) points_mat->data)
{ CV_DbgAssert(points); }
int estimate (const std::vector<int> &sample, std::vector<Mat> &models) const override {
const int m = 8, n = 9; // rows, cols
@ -225,22 +226,30 @@ public:
int getMaxNumberOfSolutions () const override { return 1; }
int getSampleSize() const override { return 8; }
Ptr<MinimalSolver> clone () const override {
return makePtr<FundamentalMinimalSolver8ptsImpl>(*points_mat);
}
};
Ptr<FundamentalMinimalSolver8pts> FundamentalMinimalSolver8pts::create(const Mat &points_) {
return makePtr<FundamentalMinimalSolver8ptsImpl>(points_);
}
class FundamentalNonMinimalSolverImpl : public FundamentalNonMinimalSolver {
class EpipolarNonMinimalSolverImpl : public EpipolarNonMinimalSolver {
private:
const Mat * points_mat;
const Ptr<NormTransform> normTr;
const bool do_norm;
Matx33d _T1, _T2;
Ptr<NormTransform> normTr = nullptr;
bool enforce_rank = true, is_fundamental, use_ge;
public:
explicit FundamentalNonMinimalSolverImpl (const Mat &points_) :
points_mat(&points_), normTr (NormTransform::create(points_)) {}
explicit EpipolarNonMinimalSolverImpl (const Mat &points_, const Matx33d &T1, const Matx33d &T2, bool use_ge_)
: points_mat(&points_), do_norm(false), _T1(T1), _T2(T2), use_ge(use_ge_) {
is_fundamental = true;
}
explicit EpipolarNonMinimalSolverImpl (const Mat &points_, bool is_fundamental_) :
points_mat(&points_), do_norm(is_fundamental_), use_ge(false) {
is_fundamental = is_fundamental_;
if (is_fundamental)
normTr = NormTransform::create(points_);
}
void enforceRankConstraint (bool enforce) override { enforce_rank = enforce; }
int estimate (const std::vector<int> &sample, int sample_size, std::vector<Mat>
&models, const std::vector<double> &weights) const override {
if (sample_size < getMinimumRequiredSampleSize())
@ -248,18 +257,210 @@ public:
Matx33d T1, T2;
Mat norm_points;
normTr->getNormTransformation(norm_points, sample, sample_size, T1, T2);
const auto * const norm_pts = (float *) norm_points.data;
if (do_norm)
normTr->getNormTransformation(norm_points, sample, sample_size, T1, T2);
const auto * const norm_pts = do_norm ? (float *) norm_points.data : (float *) points_mat->data;
// ------- 8 points algorithm with Eigen and covariance matrix --------------
if (use_ge) {
double a[8];
std::vector<double> AtAb(72, 0); // 8x9
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
const int idx = do_norm ? 4*i : 4*sample[i];
const double x1 = norm_pts[idx], y1 = norm_pts[idx+1], x2 = norm_pts[idx+2], y2 = norm_pts[idx+3];
a[0] = x2*x1;
a[1] = x2*y1;
a[2] = x2;
a[3] = y2*x1;
a[4] = y2*y1;
a[5] = y2;
a[6] = x1;
a[7] = y1;
// calculate covariance for eigen
for (int row = 0; row < 8; row++) {
for (int col = row; col < 8; col++)
AtAb[row * 9 + col] += a[row] * a[col];
AtAb[row * 9 + 8] += a[row];
}
}
} else { // use weights
for (int i = 0; i < sample_size; i++) {
const auto weight = weights[i];
if (weight < FLT_EPSILON) continue;
const int idx = do_norm ? 4*i : 4*sample[i];
const double x1 = norm_pts[idx], y1 = norm_pts[idx+1], x2 = norm_pts[idx+2], y2 = norm_pts[idx+3];
const double weight_times_x2 = weight * x2, weight_times_y2 = weight * y2;
a[0] = weight_times_x2 * x1;
a[1] = weight_times_x2 * y1;
a[2] = weight_times_x2;
a[3] = weight_times_y2 * x1;
a[4] = weight_times_y2 * y1;
a[5] = weight_times_y2;
a[6] = weight * x1;
a[7] = weight * y1;
// calculate covariance for eigen
for (int row = 0; row < 8; row++) {
for (int col = row; col < 8; col++)
AtAb[row * 9 + col] += a[row] * a[col];
AtAb[row * 9 + 8] += a[row];
}
}
}
// copy symmetric part of covariance matrix
for (int j = 1; j < 8; j++)
for (int z = 0; z < j; z++)
AtAb[j*9+z] = AtAb[z*9+j];
Math::eliminateUpperTriangular(AtAb, 8, 9);
models = std::vector<Mat>{ Mat_<double>(3,3) };
auto * f = (double *) models[0].data;
f[8] = 1.;
const int m = 8, n = 9;
// start from the last row
for (int i = m-1; i >= 0; i--) {
double acc = 0;
for (int j = i+1; j < n; j++)
acc -= AtAb[i*n+j]*f[j];
f[i] = acc / AtAb[i*n+i];
// due to numerical errors return 0 solutions
if (std::isnan(f[i]))
return 0;
}
} else {
// ------- 8 points algorithm with Eigen and covariance matrix --------------
double a[9] = {0, 0, 0, 0, 0, 0, 0, 0, 1}, AtA[81] = {0}; // 9x9
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
const int idx = do_norm ? 4*i : 4*sample[i];
const auto x1 = norm_pts[idx], y1 = norm_pts[idx+1], x2 = norm_pts[idx+2], y2 = norm_pts[idx+3];
a[0] = x2*x1;
a[1] = x2*y1;
a[2] = x2;
a[3] = y2*x1;
a[4] = y2*y1;
a[5] = y2;
a[6] = x1;
a[7] = y1;
// calculate covariance matrix
for (int row = 0; row < 9; row++)
for (int col = row; col < 9; col++)
AtA[row*9+col] += a[row]*a[col];
}
} else { // use weights
for (int i = 0; i < sample_size; i++) {
const auto weight = weights[i];
if (weight < FLT_EPSILON) continue;
const int smpl = do_norm ? 4*i : 4*sample[i];
const auto x1 = norm_pts[smpl], y1 = norm_pts[smpl+1], x2 = norm_pts[smpl+2], y2 = norm_pts[smpl+3];
const double weight_times_x2 = weight * x2, weight_times_y2 = weight * y2;
a[0] = weight_times_x2 * x1;
a[1] = weight_times_x2 * y1;
a[2] = weight_times_x2;
a[3] = weight_times_y2 * x1;
a[4] = weight_times_y2 * y1;
a[5] = weight_times_y2;
a[6] = weight * x1;
a[7] = weight * y1;
a[8] = weight;
for (int row = 0; row < 9; row++)
for (int col = row; col < 9; col++)
AtA[row*9+col] += a[row]*a[col];
}
}
for (int j = 1; j < 9; j++)
for (int z = 0; z < j; z++)
AtA[j*9+z] = AtA[z*9+j];
#ifdef HAVE_EIGEN
models = std::vector<Mat>{ Mat_<double>(3,3) };
// extract the last null-vector
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)models[0].data) = Eigen::JacobiSVD
<Eigen::Matrix<double, 9, 9>> ((Eigen::Matrix<double, 9, 9>(AtA)),
Eigen::ComputeFullV).matrixV().col(8);
#else
Matx<double, 9, 9> AtA_(AtA), U, Vt;
Vec<double, 9> W;
SVD::compute(AtA_, W, U, Vt, SVD::FULL_UV + SVD::MODIFY_A);
models = std::vector<Mat> { Mat_<double>(3, 3, Vt.val + 72 /*=8*9*/) };
#endif
}
if (enforce_rank)
FundamentalDegeneracy::recoverRank(models[0], is_fundamental);
if (is_fundamental) {
const auto * const f = (double *) models[0].data;
const auto * const t1 = do_norm ? T1.val : _T1.val, * t2 = do_norm ? T2.val : _T2.val;
// F = T2^T F T1
models[0] = Mat(Matx33d(t1[0]*t2[0]*f[0],t1[0]*t2[0]*f[1], t2[0]*f[2] + t2[0]*f[0]*t1[2] +
t2[0]*f[1]*t1[5], t1[0]*t2[0]*f[3],t1[0]*t2[0]*f[4], t2[0]*f[5] + t2[0]*f[3]*t1[2] +
t2[0]*f[4]*t1[5], t1[0]*(f[6] + f[0]*t2[2] + f[3]*t2[5]), t1[0]*(f[7] + f[1]*t2[2] +
f[4]*t2[5]), f[8] + t1[2]*(f[6] + f[0]*t2[2] + f[3]*t2[5]) + t1[5]*(f[7] + f[1]*t2[2] +
f[4]*t2[5]) + f[2]*t2[2] + f[5]*t2[5]));
}
return 1;
}
int estimate (const std::vector<bool> &/*mask*/, std::vector<Mat> &/*models*/,
const std::vector<double> &/*weights*/) override {
return 0;
}
int getMinimumRequiredSampleSize() const override { return 8; }
int getMaxNumberOfSolutions () const override { return 1; }
};
Ptr<EpipolarNonMinimalSolver> EpipolarNonMinimalSolver::create(const Mat &points_, bool is_fundamental) {
return makePtr<EpipolarNonMinimalSolverImpl>(points_, is_fundamental);
}
Ptr<EpipolarNonMinimalSolver> EpipolarNonMinimalSolver::create(const Mat &points_, const Matx33d &T1, const Matx33d &T2, bool use_ge) {
return makePtr<EpipolarNonMinimalSolverImpl>(points_, T1, T2, use_ge);
}
class CovarianceEpipolarSolverImpl : public CovarianceEpipolarSolver {
private:
Mat norm_pts;
Matx33d T1, T2;
float * norm_points;
std::vector<bool> mask;
int points_size;
double covariance[81] = {0}, * t1, * t2;
bool is_fundamental, enforce_rank = true;
public:
explicit CovarianceEpipolarSolverImpl (const Mat &norm_points_, const Matx33d &T1_, const Matx33d &T2_)
: norm_pts(norm_points_), T1(T1_), T2(T2_) {
points_size = norm_points_.rows;
norm_points = (float *) norm_pts.data;
t1 = T1.val; t2 = T2.val;
mask = std::vector<bool>(points_size, false);
is_fundamental = true;
}
explicit CovarianceEpipolarSolverImpl (const Mat &points_, bool is_fundamental_) {
points_size = points_.rows;
is_fundamental = is_fundamental_;
if (is_fundamental) { // normalize image points only for fundmantal matrix
std::vector<int> sample(points_size);
for (int i = 0; i < points_size; i++) sample[i] = i;
const Ptr<NormTransform> normTr = NormTransform::create(points_);
normTr->getNormTransformation(norm_pts, sample, points_size, T1, T2);
t1 = T1.val; t2 = T2.val;
} else norm_pts = points_; // otherwise points are normalized by intrinsics
norm_points = (float *)norm_pts.data;
mask = std::vector<bool>(points_size, false);
}
void enforceRankConstraint (bool enforce_) override { enforce_rank = enforce_; }
void reset () override {
std::fill(covariance, covariance+81, 0);
std::fill(mask.begin(), mask.end(), false);
}
/*
* Find fundamental matrix using 8-point algorithm with covariance matrix and PCA
*/
int estimate (const std::vector<bool> &new_mask, std::vector<Mat> &models,
const std::vector<double> &/*weights*/) override {
double a[9] = {0, 0, 0, 0, 0, 0, 0, 0, 1};
double AtA[81] = {0}; // 9x9
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
const int norm_points_idx = 4*i;
const double x1 = norm_pts[norm_points_idx ], y1 = norm_pts[norm_points_idx+1],
x2 = norm_pts[norm_points_idx+2], y2 = norm_pts[norm_points_idx+3];
for (int i = 0; i < points_size; i++) {
if (mask[i] != new_mask[i]) {
const int smpl = 4*i;
const double x1 = norm_points[smpl ], y1 = norm_points[smpl+1],
x2 = norm_points[smpl+2], y2 = norm_points[smpl+3];
a[0] = x2*x1;
a[1] = x2*y1;
a[2] = x2;
@ -269,71 +470,129 @@ public:
a[6] = x1;
a[7] = y1;
// calculate covariance for eigen
for (int row = 0; row < 9; row++)
for (int col = row; col < 9; col++)
AtA[row*9+col] += a[row]*a[col];
}
} else {
for (int i = 0; i < sample_size; i++) {
const int smpl = 4*i;
const double weight = weights[i];
const double x1 = norm_pts[smpl ], y1 = norm_pts[smpl+1],
x2 = norm_pts[smpl+2], y2 = norm_pts[smpl+3];
const double weight_times_x2 = weight * x2,
weight_times_y2 = weight * y2;
a[0] = weight_times_x2 * x1;
a[1] = weight_times_x2 * y1;
a[2] = weight_times_x2;
a[3] = weight_times_y2 * x1;
a[4] = weight_times_y2 * y1;
a[5] = weight_times_y2;
a[6] = weight * x1;
a[7] = weight * y1;
a[8] = weight;
// calculate covariance for eigen
for (int row = 0; row < 9; row++)
for (int col = row; col < 9; col++)
AtA[row*9+col] += a[row]*a[col];
if (mask[i]) // if mask[i] is true then new_mask[i] must be false
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
covariance[j*9+z] -= a[j]*a[z];
else
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
covariance[j*9+z] += a[j]*a[z];
}
}
mask = new_mask;
// copy symmetric part of covariance matrix
for (int j = 1; j < 9; j++)
for (int z = 0; z < j; z++)
AtA[j*9+z] = AtA[z*9+j];
covariance[j*9+z] = covariance[z*9+j];
#ifdef HAVE_EIGEN
models = std::vector<Mat>{ Mat_<double>(3,3) };
const Eigen::JacobiSVD<Eigen::Matrix<double, 9, 9>> svd((Eigen::Matrix<double, 9, 9>(AtA)),
Eigen::ComputeFullV);
// extract the last nullspace
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)models[0].data) = svd.matrixV().col(8);
// extract the last null-vector
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)models[0].data) = Eigen::JacobiSVD
<Eigen::Matrix<double, 9, 9>> ((Eigen::Matrix<double, 9, 9>(covariance)),
Eigen::ComputeFullV).matrixV().col(8);
#else
Matx<double, 9, 9> AtA_(AtA), U, Vt;
Vec<double, 9> W;
SVD::compute(AtA_, W, U, Vt, SVD::FULL_UV + SVD::MODIFY_A);
models = std::vector<Mat> { Mat_<double>(3, 3, Vt.val + 72 /*=8*9*/) };
Matx<double, 9, 9> AtA_(covariance), U, Vt;
Vec<double, 9> W;
SVD::compute(AtA_, W, U, Vt, SVD::FULL_UV + SVD::MODIFY_A);
models = std::vector<Mat> { Mat_<double>(3, 3, Vt.val + 72 /*=8*9*/) };
#endif
FundamentalDegeneracy::recoverRank(models[0], true/*F*/);
if (enforce_rank)
FundamentalDegeneracy::recoverRank(models[0], is_fundamental);
if (is_fundamental) {
const auto * const f = (double *) models[0].data;
// F = T2^T F T1
models[0] = Mat(Matx33d(t1[0]*t2[0]*f[0],t1[0]*t2[0]*f[1], t2[0]*f[2] + t2[0]*f[0]*t1[2] +
t2[0]*f[1]*t1[5], t1[0]*t2[0]*f[3],t1[0]*t2[0]*f[4], t2[0]*f[5] + t2[0]*f[3]*t1[2] +
t2[0]*f[4]*t1[5], t1[0]*(f[6] + f[0]*t2[2] + f[3]*t2[5]), t1[0]*(f[7] + f[1]*t2[2] +
f[4]*t2[5]), f[8] + t1[2]*(f[6] + f[0]*t2[2] + f[3]*t2[5]) + t1[5]*(f[7] + f[1]*t2[2] +
f[4]*t2[5]) + f[2]*t2[2] + f[5]*t2[5]));
}
return 1;
}
int getMinimumRequiredSampleSize() const override { return 8; }
int getMaxNumberOfSolutions () const override { return 1; }
};
Ptr<CovarianceEpipolarSolver> CovarianceEpipolarSolver::create (const Mat &points, bool is_fundamental) {
return makePtr<CovarianceEpipolarSolverImpl>(points, is_fundamental);
}
Ptr<CovarianceEpipolarSolver> CovarianceEpipolarSolver::create (const Mat &points, const Matx33d &T1, const Matx33d &T2) {
return makePtr<CovarianceEpipolarSolverImpl>(points, T1, T2);
}
// Transpose T2 (in T2 the lower diagonal is zero)
T2(2, 0) = T2(0, 2); T2(2, 1) = T2(1, 2);
T2(0, 2) = 0; T2(1, 2) = 0;
class LarssonOptimizerImpl : public LarssonOptimizer {
private:
const Mat &calib_points;
Matx33d K1, K2, K2_t, K1_inv, K2_inv_t;
bool is_fundamental;
BundleOptions opt;
public:
LarssonOptimizerImpl (const Mat &calib_points_, const Matx33d &K1_, const Matx33d &K2_, int max_iters_, bool is_fundamental_) :
calib_points(calib_points_), K1(K1_), K2(K2_){
is_fundamental = is_fundamental_;
opt.max_iterations = max_iters_;
opt.loss_scale = Utils::getCalibratedThreshold(std::max(1.5, opt.loss_scale), Mat(K1), Mat(K2));
if (is_fundamental) {
K1_inv = K1.inv();
K2_t = K2.t();
K2_inv_t = K2_t.inv();
}
}
models[0] = T2 * models[0] * T1;
int estimate (const Mat &model, const std::vector<int> &sample, int sample_size, std::vector<Mat>
&models, const std::vector<double> &weights) const override {
if (sample_size < 5) return 0;
const Matx33d E = is_fundamental ? K2_t * Matx33d(model) * K1 : model;
RNG rng (sample_size);
cv::Matx33d R1, R2; cv::Vec3d t;
cv::decomposeEssentialMat(E, R1, R2, t);
int positive_depth[4] = {0};
const auto * const pts_ = (float *) calib_points.data;
// a few point are enough to test
// actually due to Sampson error minimization, the input R,t do not really matter
// for a correct pair there is a sligthly faster convergence
for (int i = 0; i < 3; i++) { // could be 1 point
const int rand_inl = 4 * sample[rng.uniform(0, sample_size)];
Vec3d p1 (pts_[rand_inl], pts_[rand_inl+1], 1), p2(pts_[rand_inl+2], pts_[rand_inl+3], 1);
p1 /= norm(p1); p2 /= norm(p2);
if (satisfyCheirality(R1, t, p1, p2)) positive_depth[0]++;
if (satisfyCheirality(R1, -t, p1, p2)) positive_depth[1]++;
if (satisfyCheirality(R2, t, p1, p2)) positive_depth[2]++;
if (satisfyCheirality(R2, -t, p1, p2)) positive_depth[3]++;
}
int corr_idx = 0, max_good_pts = positive_depth[0];
for (int i = 1; i < 4; i++) {
if (max_good_pts < positive_depth[i]) {
max_good_pts = positive_depth[i];
corr_idx = i;
}
}
CameraPose pose;
pose.R = corr_idx < 2 ? R1 : R2;
pose.t = corr_idx % 2 == 1 ? -t : t;
refine_relpose(calib_points, sample, sample_size, &pose, opt, &weights[0]);
Matx33d model_new = Math::getSkewSymmetric(pose.t) * pose.R;
if (is_fundamental)
model_new = K2_inv_t * model_new * K1_inv;
models = std::vector<Mat> { Mat(model_new) };
return 1;
}
int getMinimumRequiredSampleSize() const override { return 8; }
int getMaxNumberOfSolutions () const override { return 1; }
Ptr<NonMinimalSolver> clone () const override {
return makePtr<FundamentalNonMinimalSolverImpl>(*points_mat);
int estimate (const std::vector<int>&, int, std::vector<Mat>&, const std::vector<double>&) const override {
return 0;
}
int estimate (const std::vector<bool> &/*mask*/, std::vector<Mat> &/*models*/,
const std::vector<double> &/*weights*/) override {
return 0;
}
void enforceRankConstraint (bool /*enforce*/) override {}
int getMinimumRequiredSampleSize() const override { return 5; }
int getMaxNumberOfSolutions () const override { return 1; }
};
Ptr<FundamentalNonMinimalSolver> FundamentalNonMinimalSolver::create(const Mat &points_) {
return makePtr<FundamentalNonMinimalSolverImpl>(points_);
Ptr<LarssonOptimizer> LarssonOptimizer::create(const Mat &calib_points_, const Matx33d &K1, const Matx33d &K2, int max_iters_, bool is_fundamental) {
return makePtr<LarssonOptimizerImpl>(calib_points_, K1, K2, max_iters_, is_fundamental);
}
}}

View File

@ -7,101 +7,146 @@
namespace cv { namespace usac {
GammaValues::GammaValues()
: max_range_complete(4.62)
, max_range_gamma(1.52)
, max_size_table(3000)
{
/*
* Gamma values for degrees of freedom n = 2 and sigma quantile 99% of chi distribution
* (squared root of chi-squared distribution), in the range <0; 4.62> for complete values
* and <0, 1.52> for gamma values.
* Number of anchor points is 50. Other values are approximated using linear interpolation
*/
const int number_of_anchor_points = 50;
std::vector<double> gamma_complete_anchor = std::vector<double>
{1.7724538509055159, 1.182606138403832, 0.962685372890749, 0.8090013493715409,
0.6909325812483967, 0.5961199186942078, 0.5179833984918483, 0.45248091153099873,
0.39690029823142897, 0.34930995878395804, 0.3082742109224103, 0.2726914551904204,
0.2416954924567404, 0.21459196516027726, 0.190815580770884, 0.16990026519723456,
0.15145770273372564, 0.13516150988807635, 0.12073530906427948, 0.10794357255251595,
0.0965844793065712, 0.08648426334883624, 0.07749268706639856, 0.06947937608738222,
0.062330823249820304, 0.05594791865006951, 0.05024389794830681, 0.045142626552664405,
0.040577155977706246, 0.03648850256745103, 0.03282460924226794, 0.029539458909083157,
0.02659231432268328, 0.023947063970062663, 0.021571657306774475, 0.01943761564987864,
0.017519607407598645, 0.015795078236273064, 0.014243928262247118, 0.012848229767187478,
0.011591979769030827, 0.010460882783057988, 0.009442159753944173, 0.008524379737926344,
0.007697311406424555, 0.006951791856026042, 0.006279610558635573, 0.005673406581042374,
0.005126577454218803, 0.004633198286725555};
class GammaValuesImpl : public GammaValues {
std::vector<double> gamma_complete, gamma_incomplete, gamma;
double scale_complete_values, scale_gamma_values;
int max_size_table, DoF;
public:
GammaValuesImpl (int DoF_, int max_size_table_) {
max_size_table = max_size_table_;
max_size_table = max_size_table_;
DoF = DoF_;
/*
* Gamma values for degrees of freedom n = 2 and sigma quantile 99% of chi distribution
* (squared root of chi-squared distribution), in the range <0; 4.62> for complete values
* and <0, 1.52> for gamma values.
* Number of anchor points is 50. Other values are approximated using linear interpolation
*/
const int number_of_anchor_points = 50;
std::vector<double> gamma_complete_anchor, gamma_incomplete_anchor, gamma_anchor;
if (DoF == 2) {
const double max_thr = 7.5, gamma_quantile = 3.04;
scale_complete_values = max_size_table_ / max_thr;
scale_gamma_values = gamma_quantile * max_size_table_ / max_thr ;
std::vector<double> gamma_incomplete_anchor = std::vector<double>
{0.0, 0.01773096912803939, 0.047486924846289004, 0.08265437835139826, 0.120639343491371,
0.15993024714868515, 0.19954558593754865, 0.23881753504915218, 0.2772830648361923,
0.3146208784488923, 0.3506114446939783, 0.385110056889967, 0.41802785670077697,
0.44931803198258047, 0.47896553567848993, 0.5069792897777948, 0.5333861945970247,
0.5582264802664578, 0.581550074874317, 0.6034137543595729, 0.6238789008764282,
0.6430097394182639, 0.6608719532994989, 0.6775316015953519, 0.6930542783709592,
0.7075044661695132, 0.7209450459078338, 0.733436932830201, 0.7450388140484766,
0.7558069678435577, 0.7657951486073097, 0.7750545242776943, 0.7836336555215403,
0.7915785078697124, 0.798932489600361, 0.8057365094688473, 0.8120290494534339,
0.8178462485678104, 0.8232219945197348, 0.8281880205973585, 0.8327740056635289,
0.8370076755516281, 0.8409149044990385, 0.8445198155381767, 0.8478448790000731,
0.8509110084798414, 0.8537376537738418, 0.8563428904304485, 0.8587435056647642,
0.8609550804762539};
gamma_complete_anchor = std::vector<double>
{1.77245385e+00, 1.02824699e+00, 7.69267629e-01, 5.99047749e-01,
4.75998050e-01, 3.83008633e-01, 3.10886473e-01, 2.53983661e-01,
2.08540472e-01, 1.71918718e-01, 1.42197872e-01, 1.17941854e-01,
9.80549104e-02, 8.16877552e-02, 6.81739145e-02, 5.69851046e-02,
4.76991202e-02, 3.99417329e-02, 3.35126632e-02, 2.81470710e-02,
2.36624697e-02, 1.99092598e-02, 1.67644090e-02, 1.41264487e-02,
1.19114860e-02, 1.00500046e-02, 8.48428689e-03, 7.16632498e-03,
6.05612291e-03, 5.12031042e-03, 4.33100803e-03, 3.66489504e-03,
3.10244213e-03, 2.62514027e-03, 2.22385863e-03, 1.88454040e-03,
1.59749690e-03, 1.35457835e-03, 1.14892453e-03, 9.74756909e-04,
8.27205063e-04, 7.02161552e-04, 5.96160506e-04, 5.06275903e-04,
4.30036278e-04, 3.65353149e-04, 3.10460901e-04, 2.63866261e-04,
2.24305797e-04, 1.90558599e-04};
std::vector<double> gamma_anchor = std::vector<double>
{1.7724538509055159, 1.427187162582056, 1.2890382454046982, 1.186244737282388,
1.1021938955410173, 1.0303674512016956, 0.9673796229113404, 0.9111932804012203,
0.8604640514722175, 0.814246149432561, 0.7718421763436497, 0.7327190195355812,
0.6964573670982434, 0.6627197089339725, 0.6312291454822467, 0.6017548373556638,
0.5741017071093776, 0.5481029597580317, 0.523614528104858, 0.5005108666212138,
0.478681711577816, 0.4580295473431646, 0.43846759792922513, 0.41991821541471996,
0.40231157253054745, 0.38558459136185, 0.3696800574963841, 0.3545458813847714,
0.340134477710645, 0.32640224021796493, 0.3133090943985706, 0.3008181141790485,
0.28889519159238314, 0.2775087506098113, 0.2666294980086962, 0.2562302054837794,
0.24628551826026082, 0.2367717863030556, 0.22766691488600885, 0.21895023182476064,
0.2106023691144937, 0.2026051570714723, 0.19494152937027823, 0.18759543761063277,
0.1805517742482484, 0.17379630289125447, 0.16731559510356395, 0.1610969729740903,
0.1551284568099053, 0.14939871739550692};
gamma_incomplete_anchor = std::vector<double>
{0. , 0.0364325 , 0.09423626, 0.15858163, 0.22401622, 0.28773243,
0.34820493, 0.40463362, 0.45665762, 0.50419009, 0.54731575, 0.58622491,
0.62116968, 0.65243473, 0.68031763, 0.70511575, 0.72711773, 0.74668782,
0.76389332, 0.77907386, 0.79244816, 0.80421561, 0.81455692, 0.8236351 ,
0.83159653, 0.83857228, 0.84467927, 0.85002158, 0.85469163, 0.85877132,
0.86233307, 0.86544086, 0.86815108, 0.87052421, 0.87258093, 0.87437198,
0.87593103, 0.87728759, 0.87846752, 0.87949345, 0.88038518, 0.88116002,
0.88183307, 0.88241755, 0.88292497, 0.88336537, 0.88374751, 0.88407901,
0.88436652, 0.88461696};
// allocate tables
gamma_complete = std::vector<double>(max_size_table);
gamma_incomplete = std::vector<double>(max_size_table);
gamma = std::vector<double>(max_size_table);
gamma_anchor = std::vector<double>
{1.77245385e+00, 5.93375722e-01, 3.05833272e-01, 1.68019955e-01,
9.52188705e-02, 5.49876141e-02, 3.21629603e-02, 1.89881161e-02,
1.12897384e-02, 6.75016002e-03, 4.05426969e-03, 2.44422283e-03,
1.47822780e-03, 8.96425642e-04, 5.44879754e-04, 3.31873268e-04,
2.02499478e-04, 1.23458651e-04, 7.55593392e-05, 4.63032752e-05,
2.84078946e-05, 1.74471428e-05, 1.07257506e-05, 6.59955061e-06,
4.06400013e-06, 2.50448635e-06, 1.54449028e-06, 9.53085308e-07,
5.88490160e-07, 3.63571768e-07, 2.24734099e-07, 1.38982938e-07,
8.59913580e-08, 5.31026827e-08, 3.28834964e-08, 2.03707922e-08,
1.26240063e-08, 7.82595771e-09, 4.85312084e-09, 3.01051575e-09,
1.86805770e-09, 1.15947962e-09, 7.19869372e-10, 4.47050615e-10,
2.77694421e-10, 1.72536278e-10, 1.07224039e-10, 6.66497131e-11,
4.14376355e-11, 2.57079508e-11};
} else if (DoF == 4) {
const double max_thr = 2.5, gamma_quantile = 3.64;
scale_complete_values = max_size_table_ / max_thr;
scale_gamma_values = gamma_quantile * max_size_table_ / max_thr ;
gamma_complete_anchor = std::vector<double>
{0.88622693, 0.87877828, 0.86578847, 0.84979442, 0.83179176, 0.81238452,
0.79199067, 0.77091934, 0.74940836, 0.72764529, 0.70578051, 0.68393585,
0.66221071, 0.64068639, 0.61942952, 0.59849449, 0.57792561, 0.55766078,
0.53792634, 0.51864482, 0.49983336, 0.48150466, 0.46366759, 0.44632776,
0.42948797, 0.41314862, 0.39730804, 0.38196282, 0.36710806, 0.35273761,
0.33884422, 0.32541979, 0.31245545, 0.29988151, 0.28781065, 0.2761701 ,
0.26494924, 0.25413723, 0.24372308, 0.23369573, 0.22404405, 0.21475696,
0.2058234 , 0.19723241, 0.18897314, 0.18103488, 0.17340708, 0.16607937,
0.15904157, 0.15225125};
gamma_incomplete_anchor = std::vector<double>
{0.00000000e+00, 2.26619558e-04, 1.23631005e-03, 3.28596265e-03,
6.50682297e-03, 1.09662062e-02, 1.66907233e-02, 2.36788942e-02,
3.19091043e-02, 4.13450655e-02, 5.19397673e-02, 6.36384378e-02,
7.63808171e-02, 9.01029320e-02, 1.04738496e-01, 1.20220023e-01,
1.36479717e-01, 1.53535010e-01, 1.71152805e-01, 1.89349599e-01,
2.08062142e-01, 2.27229225e-01, 2.46791879e-01, 2.66693534e-01,
2.86880123e-01, 3.07300152e-01, 3.27904735e-01, 3.48647611e-01,
3.69485130e-01, 3.90376227e-01, 4.11282379e-01, 4.32167556e-01,
4.52998149e-01, 4.73844336e-01, 4.94473655e-01, 5.14961263e-01,
5.35282509e-01, 5.55414767e-01, 5.75337352e-01, 5.95031429e-01,
6.14479929e-01, 6.33667460e-01, 6.52580220e-01, 6.71205917e-01,
6.89533681e-01, 7.07553988e-01, 7.25258581e-01, 7.42640393e-01,
7.59693477e-01, 7.76494059e-01};
gamma_anchor = std::vector<double>
{8.86226925e-01, 8.38460922e-01, 7.64931722e-01, 6.85680218e-01,
6.07663201e-01, 5.34128389e-01, 4.66574835e-01, 4.05560768e-01,
3.51114357e-01, 3.02965249e-01, 2.60682396e-01, 2.23758335e-01,
1.91661077e-01, 1.63865725e-01, 1.39873108e-01, 1.19220033e-01,
1.01484113e-01, 8.62162923e-02, 7.32253576e-02, 6.21314285e-02,
5.26713657e-02, 4.46151697e-02, 3.77626859e-02, 3.19403783e-02,
2.69982683e-02, 2.28070945e-02, 1.92557199e-02, 1.62487939e-02,
1.37046640e-02, 1.15535264e-02, 9.73579631e-03, 8.20068208e-03,
6.90494092e-03, 5.80688564e-03, 4.88587254e-03, 4.10958296e-03,
3.45555079e-03, 2.90474053e-03, 2.44103551e-03, 2.05079975e-03,
1.72250366e-03, 1.44640449e-03, 1.21427410e-03, 1.01916714e-03,
8.55224023e-04, 7.17503448e-04, 6.01840372e-04, 5.04725511e-04,
4.23203257e-04, 3.54478559e-04};
} else CV_Error(cv::Error::StsNotImplemented, "Not implemented for specific DoF!");
// allocate tables
gamma_complete = std::vector<double>(max_size_table);
gamma_incomplete = std::vector<double>(max_size_table);
gamma = std::vector<double>(max_size_table);
const int step = (int)((double)max_size_table / (number_of_anchor_points-1));
int arr_cnt = 0;
for (int i = 0; i < number_of_anchor_points-1; i++) {
const double complete_x0 = gamma_complete_anchor[i], step_complete = (gamma_complete_anchor[i+1] - complete_x0) / step;
const double incomplete_x0 = gamma_incomplete_anchor[i], step_incomplete = (gamma_incomplete_anchor[i+1] - incomplete_x0) / step;
const double gamma_x0 = gamma_anchor[i], step_gamma = (gamma_anchor[i+1] - gamma_x0) / step;
// do linear interpolation of gamma values
const int step = (int)((double)max_size_table / (number_of_anchor_points-1));
int arr_cnt = 0;
for (int i = 0; i < number_of_anchor_points-1; i++) {
const double complete_x0 = gamma_complete_anchor[i], step_complete = (gamma_complete_anchor[i+1] - complete_x0) / step;
const double incomplete_x0 = gamma_incomplete_anchor[i], step_incomplete = (gamma_incomplete_anchor[i+1] - incomplete_x0) / step;
const double gamma_x0 = gamma_anchor[i], step_gamma = (gamma_anchor[i+1] - gamma_x0) / step;
for (int j = 0; j < step; j++) {
gamma_complete[arr_cnt] = complete_x0 + j * step_complete;
gamma_incomplete[arr_cnt] = incomplete_x0 + j * step_incomplete;
gamma[arr_cnt++] = gamma_x0 + j * step_gamma;
}
}
if (arr_cnt < max_size_table) {
// if array was not totally filled (in some cases can happen) then copy last values
std::fill(gamma_complete.begin()+arr_cnt, gamma_complete.end(), gamma_complete[arr_cnt-1]);
std::fill(gamma_incomplete.begin()+arr_cnt, gamma_incomplete.end(), gamma_incomplete[arr_cnt-1]);
std::fill(gamma.begin()+arr_cnt, gamma.end(), gamma[arr_cnt-1]);
for (int j = 0; j < step; j++) {
gamma_complete[arr_cnt] = complete_x0 + j * step_complete;
gamma_incomplete[arr_cnt] = incomplete_x0 + j * step_incomplete;
gamma[arr_cnt++] = gamma_x0 + j * step_gamma;
}
}
if (arr_cnt < max_size_table) {
// if array was not totally filled (in some cases can happen) then copy last values
std::fill(gamma_complete.begin()+arr_cnt, gamma_complete.end(), gamma_complete[arr_cnt-1]);
std::fill(gamma_incomplete.begin()+arr_cnt, gamma_incomplete.end(), gamma_incomplete[arr_cnt-1]);
std::fill(gamma.begin()+arr_cnt, gamma.end(), gamma[arr_cnt-1]);
}
}
const std::vector<double> &getCompleteGammaValues() const override { return gamma_complete; }
const std::vector<double> &getIncompleteGammaValues() const override { return gamma_incomplete; }
const std::vector<double> &getGammaValues() const override { return gamma; }
double getScaleOfGammaCompleteValues () const override { return scale_complete_values; }
double getScaleOfGammaValues () const override { return scale_gamma_values; }
int getTableSize () const override { return max_size_table; }
};
Ptr<GammaValues> GammaValues::create(int DoF, int max_size_table) {
return makePtr<GammaValuesImpl>(DoF, max_size_table);
}
const std::vector<double>& GammaValues::getCompleteGammaValues() const { return gamma_complete; }
const std::vector<double>& GammaValues::getIncompleteGammaValues() const { return gamma_incomplete; }
const std::vector<double>& GammaValues::getGammaValues() const { return gamma; }
double GammaValues::getScaleOfGammaCompleteValues () const { return gamma_complete.size() / max_range_complete; }
double GammaValues::getScaleOfGammaValues () const { return gamma.size() / max_range_gamma; }
int GammaValues::getTableSize () const { return max_size_table; }
/* static */
const GammaValues& GammaValues::getSingleton()
{
static GammaValues g_gammaValues;
return g_gammaValues;
}
}} // namespace

View File

@ -9,13 +9,14 @@
#endif
namespace cv { namespace usac {
class HomographyMinimalSolver4ptsGEMImpl : public HomographyMinimalSolver4ptsGEM {
class HomographyMinimalSolver4ptsImpl : public HomographyMinimalSolver4pts {
private:
const Mat * points_mat;
const float * const points;
const bool use_ge;
public:
explicit HomographyMinimalSolver4ptsGEMImpl (const Mat &points_) :
points_mat(&points_), points ((float*) points_.data) {}
explicit HomographyMinimalSolver4ptsImpl (const Mat &points_, bool use_ge_) :
points_mat(&points_), points ((float*) points_mat->data), use_ge(use_ge_) {}
int estimate (const std::vector<int>& sample, std::vector<Mat> &models) const override {
int m = 8, n = 9;
@ -23,7 +24,7 @@ public:
int cnt = 0;
for (int i = 0; i < 4; i++) {
const int smpl = 4*sample[i];
const double x1 = points[smpl], y1 = points[smpl+1], x2 = points[smpl+2], y2 = points[smpl+3];
const auto x1 = points[smpl], y1 = points[smpl+1], x2 = points[smpl+2], y2 = points[smpl+3];
A[cnt++] = -x1;
A[cnt++] = -y1;
@ -42,49 +43,53 @@ public:
A[cnt++] = y2;
}
if (!Math::eliminateUpperTriangular(A, m, n))
return 0;
models = std::vector<Mat>{ Mat_<double>(3,3) };
auto * h = (double *) models[0].data;
h[8] = 1.;
// start from the last row
for (int i = m-1; i >= 0; i--) {
double acc = 0;
for (int j = i+1; j < n; j++)
acc -= A[i*n+j]*h[j];
h[i] = acc / A[i*n+i];
// due to numerical errors return 0 solutions
if (std::isnan(h[i]))
if (use_ge) {
if (!Math::eliminateUpperTriangular(A, m, n))
return 0;
models = std::vector<Mat>{ Mat_<double>(3,3) };
auto * h = (double *) models[0].data;
h[8] = 1.;
// start from the last row
for (int i = m-1; i >= 0; i--) {
double acc = 0;
for (int j = i+1; j < n; j++)
acc -= A[i*n+j]*h[j];
h[i] = acc / A[i*n+i];
// due to numerical errors return 0 solutions
if (std::isnan(h[i]))
return 0;
}
} else {
Mat U, Vt, D;
cv::Matx<double, 8, 9> A_svd(&A[0]);
SVD::compute(A_svd, D, U, Vt, SVD::FULL_UV+SVD::MODIFY_A);
models = std::vector<Mat> { Vt.row(Vt.rows-1).reshape(0, 3) };
}
return 1;
}
int getMaxNumberOfSolutions () const override { return 1; }
int getSampleSize() const override { return 4; }
Ptr<MinimalSolver> clone () const override {
return makePtr<HomographyMinimalSolver4ptsGEMImpl>(*points_mat);
}
};
Ptr<HomographyMinimalSolver4ptsGEM> HomographyMinimalSolver4ptsGEM::create(const Mat &points_) {
return makePtr<HomographyMinimalSolver4ptsGEMImpl>(points_);
Ptr<HomographyMinimalSolver4pts> HomographyMinimalSolver4pts::create(const Mat &points, bool use_ge) {
return makePtr<HomographyMinimalSolver4ptsImpl>(points, use_ge);
}
class HomographyNonMinimalSolverImpl : public HomographyNonMinimalSolver {
private:
const Mat * points_mat;
const Ptr<NormTransform> normTr;
const bool do_norm, use_ge;
Ptr<NormTransform> normTr;
Matx33d _T1, _T2;
public:
explicit HomographyNonMinimalSolverImpl (const Mat &points_) :
points_mat(&points_), normTr (NormTransform::create(points_)) {}
explicit HomographyNonMinimalSolverImpl (const Mat &norm_points_, const Matx33d &T1, const Matx33d &T2, bool use_ge_) :
points_mat(&norm_points_), do_norm(false), use_ge(use_ge_), _T1(T1), _T2(T2) {}
explicit HomographyNonMinimalSolverImpl (const Mat &points_, bool use_ge_) :
points_mat(&points_), do_norm(true), use_ge(use_ge_), normTr (NormTransform::create(points_)) {}
/*
* Find Homography matrix using (weighted) non-minimal estimation.
* Use Principal Component Analysis. Use normalized points.
*/
int estimate (const std::vector<int> &sample, int sample_size, std::vector<Mat> &models,
const std::vector<double> &weights) const override {
if (sample_size < getMinimumRequiredSampleSize())
@ -92,21 +97,226 @@ public:
Matx33d T1, T2;
Mat norm_points_;
normTr->getNormTransformation(norm_points_, sample, sample_size, T1, T2);
if (do_norm)
normTr->getNormTransformation(norm_points_, sample, sample_size, T1, T2);
const auto * const npts = do_norm ? (float *) norm_points_.data : (float *) points_mat->data;
/*
* @norm_points is matrix 4 x inlier_size
* @weights is vector of inliers_size
* weights[i] is weight of i-th inlier
*/
const auto * const norm_points = (float *) norm_points_.data;
Mat H;
if (use_ge) {
double a1[8] = {0, 0, -1, 0, 0, 0, 0, 0},
a2[8] = {0, 0, 0, 0, 0, -1, 0, 0};
std::vector<double> AtAb(72, 0); // 8x9
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
const int idx = do_norm ? 4*i : 4*sample[i];
const double x1 = npts[idx], y1 = npts[idx+1], x2 = npts[idx+2], y2 = npts[idx+3];
a1[0] = -x1;
a1[1] = -y1;
a1[6] = x2*x1;
a1[7] = x2*y1;
a2[3] = -x1;
a2[4] = -y1;
a2[6] = y2*x1;
a2[7] = y2*y1;
// calculate covariance for eigen
for (int j = 0; j < 8; j++) {
for (int z = j; z < 8; z++)
AtAb[j * 9 + z] += a1[j]*a1[z] + a2[j]*a2[z];
AtAb[j * 9 + 8] += a1[j]*x2 + a2[j]*y2;
}
}
} else { // use weights
for (int i = 0; i < sample_size; i++) {
const double weight = weights[i];
if (weight < FLT_EPSILON) continue;
const int idx = do_norm ? 4*i : 4*sample[i];
const double x1 = npts[idx], y1 = npts[idx+1], x2 = npts[idx+2], y2 = npts[idx+3];
const double minus_weight_times_x1 = -weight * x1,
minus_weight_times_y1 = -weight * y1,
weight_times_x2 = weight * x2,
weight_times_y2 = weight * y2;
a1[0] = minus_weight_times_x1;
a1[1] = minus_weight_times_y1;
a1[2] = -weight;
a1[6] = weight_times_x2 * x1;
a1[7] = weight_times_x2 * y1;
a2[3] = minus_weight_times_x1;
a2[4] = minus_weight_times_y1;
a2[5] = -weight;
a2[6] = weight_times_y2 * x1;
a2[7] = weight_times_y2 * y1;
for (int j = 0; j < 8; j++) {
for (int z = j; z < 8; z++)
AtAb[j * 9 + z] += a1[j]*a1[z] + a2[j]*a2[z];
AtAb[j * 9 + 8] += a1[j]*weight_times_x2 + a2[j]*weight_times_y2;
}
}
}
for (int j = 1; j < 8; j++)
for (int z = 0; z < j; z++)
AtAb[j*9+z] = AtAb[z*9+j];
if (!Math::eliminateUpperTriangular(AtAb, 8, 9))
return 0;
H = Mat_<double>(3,3);
auto * h = (double *) H.data;
h[8] = 1.;
const int m = 8, n = 9;
// start from the last row
for (int i = m-1; i >= 0; i--) {
double acc = 0;
for (int j = i+1; j < n; j++)
acc -= AtAb[i*n+j]*h[j];
h[i] = acc / AtAb[i*n+i];
if (std::isnan(h[i]))
return 0; // numerical imprecision
}
} else {
double a1[9] = {0, 0, -1, 0, 0, 0, 0, 0, 0},
a2[9] = {0, 0, 0, 0, 0, -1, 0, 0, 0}, AtA[81] = {0};
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
const int smpl = do_norm ? 4*i : 4*sample[i];
const auto x1 = npts[smpl], y1 = npts[smpl+1], x2 = npts[smpl+2], y2 = npts[smpl+3];
a1[0] = -x1;
a1[1] = -y1;
a1[6] = x2*x1;
a1[7] = x2*y1;
a1[8] = x2;
a2[3] = -x1;
a2[4] = -y1;
a2[6] = y2*x1;
a2[7] = y2*y1;
a2[8] = y2;
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
AtA[j*9+z] += a1[j]*a1[z] + a2[j]*a2[z];
}
} else { // use weights
for (int i = 0; i < sample_size; i++) {
const double weight = weights[i];
if (weight < FLT_EPSILON) continue;
const int smpl = do_norm ? 4*i : 4*sample[i];
const auto x1 = npts[smpl], y1 = npts[smpl+1], x2 = npts[smpl+2], y2 = npts[smpl+3];
const double minus_weight_times_x1 = -weight * x1,
minus_weight_times_y1 = -weight * y1,
weight_times_x2 = weight * x2,
weight_times_y2 = weight * y2;
a1[0] = minus_weight_times_x1;
a1[1] = minus_weight_times_y1;
a1[2] = -weight;
a1[6] = weight_times_x2 * x1;
a1[7] = weight_times_x2 * y1;
a1[8] = weight_times_x2;
a2[3] = minus_weight_times_x1;
a2[4] = minus_weight_times_y1;
a2[5] = -weight;
a2[6] = weight_times_y2 * x1;
a2[7] = weight_times_y2 * y1;
a2[8] = weight_times_y2;
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
AtA[j*9+z] += a1[j]*a1[z] + a2[j]*a2[z];
}
}
// copy symmetric part of covariance matrix
for (int j = 1; j < 9; j++)
for (int z = 0; z < j; z++)
AtA[j*9+z] = AtA[z*9+j];
#ifdef HAVE_EIGEN
H = Mat_<double>(3,3);
// extract the last null-vector
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)H.data) = Eigen::Matrix<double, 9, 9>
(Eigen::HouseholderQR<Eigen::Matrix<double, 9, 9>> (
(Eigen::Matrix<double, 9, 9> (AtA))).householderQ()).col(8);
#else
Matx<double, 9, 9> Vt;
Vec<double, 9> D;
if (! eigen(Matx<double, 9, 9>(AtA), D, Vt)) return 0;
H = Mat_<double>(3, 3, Vt.val + 72/*=8*9*/);
#endif
}
const auto * const h = (double *) H.data;
const auto * const t1 = do_norm ? T1.val : _T1.val, * const t2 = do_norm ? T2.val : _T2.val;
// H = T2^-1 H T1
models = std::vector<Mat>{ Mat(Matx33d(t1[0]*(h[0]/t2[0] - (h[6]*t2[2])/t2[0]),
t1[0]*(h[1]/t2[0] - (h[7]*t2[2])/t2[0]), h[2]/t2[0] + t1[2]*(h[0]/t2[0] -
(h[6]*t2[2])/t2[0]) + t1[5]*(h[1]/t2[0] - (h[7]*t2[2])/t2[0]) - (h[8]*t2[2])/t2[0],
t1[0]*(h[3]/t2[0] - (h[6]*t2[5])/t2[0]), t1[0]*(h[4]/t2[0] - (h[7]*t2[5])/t2[0]),
h[5]/t2[0] + t1[2]*(h[3]/t2[0] - (h[6]*t2[5])/t2[0]) + t1[5]*(h[4]/t2[0] -
(h[7]*t2[5])/t2[0]) - (h[8]*t2[5])/t2[0], t1[0]*h[6], t1[0]*h[7],
h[8] + h[6]*t1[2] + h[7]*t1[5])) };
return 1;
}
int estimate (const std::vector<bool> &/*mask*/, std::vector<Mat> &/*models*/,
const std::vector<double> &/*weights*/) override {
return 0;
}
int getMinimumRequiredSampleSize() const override { return 4; }
int getMaxNumberOfSolutions () const override { return 1; }
void enforceRankConstraint (bool /*enforce*/) override {}
};
Ptr<HomographyNonMinimalSolver> HomographyNonMinimalSolver::create(const Mat &points_, bool use_ge_) {
return makePtr<HomographyNonMinimalSolverImpl>(points_, use_ge_);
}
Ptr<HomographyNonMinimalSolver> HomographyNonMinimalSolver::create(const Mat &points_, const Matx33d &T1, const Matx33d &T2, bool use_ge) {
return makePtr<HomographyNonMinimalSolverImpl>(points_, T1, T2, use_ge);
}
class CovarianceHomographySolverImpl : public CovarianceHomographySolver {
private:
Mat norm_pts;
Matx33d T1, T2;
float * norm_points;
std::vector<bool> mask;
int points_size;
double covariance[81] = {0}, * t1, * t2;
public:
explicit CovarianceHomographySolverImpl (const Mat &norm_points_, const Matx33d &T1_, const Matx33d &T2_)
: norm_pts(norm_points_), T1(T1_), T2(T2_) {
points_size = norm_points_.rows;
norm_points = (float *) norm_pts.data;
t1 = T1.val; t2 = T2.val;
mask = std::vector<bool>(points_size, false);
}
explicit CovarianceHomographySolverImpl (const Mat &points_) {
points_size = points_.rows;
// normalize points
std::vector<int> sample(points_size);
for (int i = 0; i < points_size; i++) sample[i] = i;
const Ptr<NormTransform> normTr = NormTransform::create(points_);
normTr->getNormTransformation(norm_pts, sample, points_size, T1, T2);
norm_points = (float *) norm_pts.data;
t1 = T1.val; t2 = T2.val;
mask = std::vector<bool>(points_size, false);
}
void reset () override {
// reset covariance matrix to zero and mask to false
std::fill(covariance, covariance+81, 0);
std::fill(mask.begin(), mask.end(), false);
}
/*
* Find homography using 4-point algorithm with covariance matrix and PCA
*/
int estimate (const std::vector<bool> &new_mask, std::vector<Mat> &models,
const std::vector<double> &/*weights*/) override {
double a1[9] = {0, 0, -1, 0, 0, 0, 0, 0, 0},
a2[9] = {0, 0, 0, 0, 0, -1, 0, 0, 0},
AtA[81] = {0};
a2[9] = {0, 0, 0, 0, 0, -1, 0, 0, 0};
if (weights.empty()) {
for (int i = 0; i < sample_size; i++) {
for (int i = 0; i < points_size; i++) {
if (mask[i] != new_mask[i]) {
const int smpl = 4*i;
const double x1 = norm_points[smpl ], y1 = norm_points[smpl+1],
x2 = norm_points[smpl+2], y2 = norm_points[smpl+3];
@ -123,71 +333,57 @@ public:
a2[7] = y2*y1;
a2[8] = y2;
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
AtA[j*9+z] += a1[j]*a1[z] + a2[j]*a2[z];
}
} else {
for (int i = 0; i < sample_size; i++) {
const int smpl = 4*i;
const double weight = weights[i];
const double x1 = norm_points[smpl ], y1 = norm_points[smpl+1],
x2 = norm_points[smpl+2], y2 = norm_points[smpl+3];
const double minus_weight_times_x1 = -weight * x1,
minus_weight_times_y1 = -weight * y1,
weight_times_x2 = weight * x2,
weight_times_y2 = weight * y2;
a1[0] = minus_weight_times_x1;
a1[1] = minus_weight_times_y1;
a1[2] = -weight;
a1[6] = weight_times_x2 * x1;
a1[7] = weight_times_x2 * y1;
a1[8] = weight_times_x2;
a2[3] = minus_weight_times_x1;
a2[4] = minus_weight_times_y1;
a2[5] = -weight;
a2[6] = weight_times_y2 * x1;
a2[7] = weight_times_y2 * y1;
a2[8] = weight_times_y2;
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
AtA[j*9+z] += a1[j]*a1[z] + a2[j]*a2[z];
if (mask[i]) // if mask[i] is true then new_mask[i] must be false
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
covariance[j*9+z] +=-a1[j]*a1[z] - a2[j]*a2[z];
else
for (int j = 0; j < 9; j++)
for (int z = j; z < 9; z++)
covariance[j*9+z] += a1[j]*a1[z] + a2[j]*a2[z];
}
}
mask = new_mask;
// copy symmetric part of covariance matrix
for (int j = 1; j < 9; j++)
for (int z = 0; z < j; z++)
AtA[j*9+z] = AtA[z*9+j];
covariance[j*9+z] = covariance[z*9+j];
#ifdef HAVE_EIGEN
Mat H = Mat_<double>(3,3);
Eigen::HouseholderQR<Eigen::Matrix<double, 9, 9>> qr((Eigen::Matrix<double, 9, 9> (AtA)));
const Eigen::Matrix<double, 9, 9> &Q = qr.householderQ();
// extract the last nullspace
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)H.data) = Q.col(8);
// extract the last null-vector
Eigen::Map<Eigen::Matrix<double, 9, 1>>((double *)H.data) = Eigen::Matrix<double, 9, 9>
(Eigen::HouseholderQR<Eigen::Matrix<double, 9, 9>> (
(Eigen::Matrix<double, 9, 9> (covariance))).householderQ()).col(8);
#else
Matx<double, 9, 9> Vt;
Vec<double, 9> D;
if (! eigen(Matx<double, 9, 9>(AtA), D, Vt)) return 0;
Mat H = Mat_<double>(3, 3, Vt.val + 72/*=8*9*/);
Matx<double, 9, 9> Vt;
Vec<double, 9> D;
if (! eigen(Matx<double, 9, 9>(covariance), D, Vt)) return 0;
Mat H = Mat_<double>(3, 3, Vt.val + 72/*=8*9*/);
#endif
models = std::vector<Mat>{ T2.inv() * H * T1 };
const auto * const h = (double *) H.data;
// H = T2^-1 H T1
models = std::vector<Mat>{ Mat(Matx33d(t1[0]*(h[0]/t2[0] - (h[6]*t2[2])/t2[0]),
t1[0]*(h[1]/t2[0] - (h[7]*t2[2])/t2[0]), h[2]/t2[0] + t1[2]*(h[0]/t2[0] -
(h[6]*t2[2])/t2[0]) + t1[5]*(h[1]/t2[0] - (h[7]*t2[2])/t2[0]) - (h[8]*t2[2])/t2[0],
t1[0]*(h[3]/t2[0] - (h[6]*t2[5])/t2[0]), t1[0]*(h[4]/t2[0] - (h[7]*t2[5])/t2[0]),
h[5]/t2[0] + t1[2]*(h[3]/t2[0] - (h[6]*t2[5])/t2[0]) + t1[5]*(h[4]/t2[0] -
(h[7]*t2[5])/t2[0]) - (h[8]*t2[5])/t2[0], t1[0]*h[6], t1[0]*h[7],
h[8] + h[6]*t1[2] + h[7]*t1[5])) };
return 1;
}
void enforceRankConstraint (bool /*enforce*/) override {}
int getMinimumRequiredSampleSize() const override { return 4; }
int getMaxNumberOfSolutions () const override { return 1; }
Ptr<NonMinimalSolver> clone () const override {
return makePtr<HomographyNonMinimalSolverImpl>(*points_mat);
}
};
Ptr<HomographyNonMinimalSolver> HomographyNonMinimalSolver::create(const Mat &points_) {
return makePtr<HomographyNonMinimalSolverImpl>(points_);
Ptr<CovarianceHomographySolver> CovarianceHomographySolver::create (const Mat &points) {
return makePtr<CovarianceHomographySolverImpl>(points);
}
Ptr<CovarianceHomographySolver> CovarianceHomographySolver::create (const Mat &points, const Matx33d &T1, const Matx33d &T2) {
return makePtr<CovarianceHomographySolverImpl>(points, T1, T2);
}
class AffineMinimalSolverImpl : public AffineMinimalSolver {
@ -196,7 +392,7 @@ private:
const float * const points;
public:
explicit AffineMinimalSolverImpl (const Mat &points_) :
points_mat(&points_), points((float *) points_.data) {}
points_mat(&points_), points((float *) points_mat->data) {}
/*
Affine transformation
x1 y1 1 0 0 0 a u1
@ -232,9 +428,6 @@ public:
}
int getSampleSize() const override { return 3; }
int getMaxNumberOfSolutions () const override { return 1; }
Ptr<MinimalSolver> clone () const override {
return makePtr<AffineMinimalSolverImpl>(*points_mat);
}
};
Ptr<AffineMinimalSolver> AffineMinimalSolver::create(const Mat &points_) {
return makePtr<AffineMinimalSolverImpl>(points_);
@ -243,22 +436,32 @@ Ptr<AffineMinimalSolver> AffineMinimalSolver::create(const Mat &points_) {
class AffineNonMinimalSolverImpl : public AffineNonMinimalSolver {
private:
const Mat * points_mat;
const float * const points;
// const NormTransform<double> norm_transform;
Ptr<NormTransform> normTr;
Matx33d _T1, _T2;
bool do_norm;
public:
explicit AffineNonMinimalSolverImpl (const Mat &points_) :
points_mat(&points_), points((float*) points_.data)
/*, norm_transform(points_)*/ {}
explicit AffineNonMinimalSolverImpl (const Mat &points_, InputArray T1, InputArray T2) :
points_mat(&points_) {
if (!T1.empty() && !T2.empty()) {
do_norm = false;
_T1 = T1.getMat();
_T2 = T2.getMat();
} else {
do_norm = true;
normTr = NormTransform::create(points_);
}
}
int estimate (const std::vector<int> &sample, int sample_size, std::vector<Mat> &models,
const std::vector<double> &weights) const override {
// surprisingly normalization of points does not improve the output model
// Mat norm_points_, T1, T2;
// norm_transform.getNormTransformation(norm_points_, sample, sample_size, T1, T2);
// const auto * const n_pts = (double *) norm_points_.data;
if (sample_size < getMinimumRequiredSampleSize())
return 0;
Matx33d T1, T2;
Mat norm_points_;
if (do_norm)
normTr->getNormTransformation(norm_points_, sample, sample_size, T1, T2);
const auto * const pts = normTr ? (float *) norm_points_.data : (float *) points_mat->data;
// do Least Squares
// Ax = b -> A^T Ax = A^T b
// x = (A^T A)^-1 A^T b
@ -268,12 +471,8 @@ public:
if (weights.empty())
for (int p = 0; p < sample_size; p++) {
// if (weights != nullptr) weight = weights[sample[p]];
const int smpl = 4*sample[p];
const double x1=points[smpl], y1=points[smpl+1], x2=points[smpl+2], y2=points[smpl+3];
// const double x1=n_pts[smpl], y1=n_pts[smpl+1], x2=n_pts[smpl+2], y2=n_pts[smpl+3];
const int idx = do_norm ? 4*p : 4*sample[p];
const auto x1=pts[idx], y1=pts[idx+1], x2=pts[idx+2], y2=pts[idx+3];
r1[0] = x1;
r1[1] = y1;
@ -288,12 +487,13 @@ public:
}
else
for (int p = 0; p < sample_size; p++) {
const int smpl = 4*sample[p];
const double weight = weights[p];
const double weight_times_x1 = weight * points[smpl ],
weight_times_y1 = weight * points[smpl+1],
weight_times_x2 = weight * points[smpl+2],
weight_times_y2 = weight * points[smpl+3];
const auto weight = weights[p];
if (weight < FLT_EPSILON) continue;
const int idx = do_norm ? 4*p : 4*sample[p];
const double weight_times_x1 = weight * pts[idx ],
weight_times_y1 = weight * pts[idx+1],
weight_times_x2 = weight * pts[idx+2],
weight_times_y2 = weight * pts[idx+3];
r1[0] = weight_times_x1;
r1[1] = weight_times_y1;
@ -318,21 +518,126 @@ public:
Vec6d aff;
if (!solve(Matx66d(AtA), Vec6d(Ab), aff))
return 0;
models[0] = Mat(Matx33d(aff(0), aff(1), aff(2),
aff(3), aff(4), aff(5),
0, 0, 1));
// models[0] = T2.inv() * models[0] * T1;
const double h[9] = {aff(0), aff(1), aff(2),
aff(3), aff(4), aff(5),
0, 0, 1};
const auto * const t1 = normTr ? T1.val : _T1.val, * const t2 = normTr ? T2.val : _T2.val;
// A = T2^-1 A T1
models = std::vector<Mat>{ Mat(Matx33d(t1[0]*(h[0]/t2[0] - (h[6]*t2[2])/t2[0]),
t1[0]*(h[1]/t2[0] - (h[7]*t2[2])/t2[0]), h[2]/t2[0] + t1[2]*(h[0]/t2[0] -
(h[6]*t2[2])/t2[0]) + t1[5]*(h[1]/t2[0] - (h[7]*t2[2])/t2[0]) - (h[8]*t2[2])/t2[0],
t1[0]*(h[3]/t2[0] - (h[6]*t2[5])/t2[0]), t1[0]*(h[4]/t2[0] - (h[7]*t2[5])/t2[0]),
h[5]/t2[0] + t1[2]*(h[3]/t2[0] - (h[6]*t2[5])/t2[0]) + t1[5]*(h[4]/t2[0] -
(h[7]*t2[5])/t2[0]) - (h[8]*t2[5])/t2[0], t1[0]*h[6], t1[0]*h[7],
h[8] + h[6]*t1[2] + h[7]*t1[5])) };
return 1;
}
int estimate (const std::vector<bool> &/*mask*/, std::vector<Mat> &/*models*/,
const std::vector<double> &/*weights*/) override {
return 0;
}
void enforceRankConstraint (bool /*enforce*/) override {}
int getMinimumRequiredSampleSize() const override { return 3; }
int getMaxNumberOfSolutions () const override { return 1; }
Ptr<NonMinimalSolver> clone () const override {
return makePtr<AffineNonMinimalSolverImpl>(*points_mat);
}
};
Ptr<AffineNonMinimalSolver> AffineNonMinimalSolver::create(const Mat &points_) {
return makePtr<AffineNonMinimalSolverImpl>(points_);
Ptr<AffineNonMinimalSolver> AffineNonMinimalSolver::create(const Mat &points_, InputArray T1, InputArray T2) {
return makePtr<AffineNonMinimalSolverImpl>(points_, T1, T2);
}
class CovarianceAffineSolverImpl : public CovarianceAffineSolver {
private:
Mat norm_pts;
Matx33d T1, T2;
float * norm_points;
std::vector<bool> mask;
int points_size;
double covariance[36] = {0}, Ab[6] = {0}, * t1, * t2;
public:
explicit CovarianceAffineSolverImpl (const Mat &norm_points_, const Matx33d &T1_, const Matx33d &T2_)
: norm_pts(norm_points_), T1(T1_), T2(T2_) {
points_size = norm_points_.rows;
norm_points = (float *) norm_pts.data;
t1 = T1.val; t2 = T2.val;
mask = std::vector<bool>(points_size, false);
}
explicit CovarianceAffineSolverImpl (const Mat &points_) {
points_size = points_.rows;
// normalize points
std::vector<int> sample(points_size);
for (int i = 0; i < points_size; i++) sample[i] = i;
const Ptr<NormTransform> normTr = NormTransform::create(points_);
normTr->getNormTransformation(norm_pts, sample, points_size, T1, T2);
norm_points = (float *) norm_pts.data;
t1 = T1.val; t2 = T2.val;
mask = std::vector<bool>(points_size, false);
}
void reset () override {
std::fill(covariance, covariance+36, 0);
std::fill(Ab, Ab+6, 0);
std::fill(mask.begin(), mask.end(), false);
}
/*
* Find affine transformation using linear method with covariance matrix and PCA
*/
int estimate (const std::vector<bool> &new_mask, std::vector<Mat> &models,
const std::vector<double> &) override {
double r1[6] = {0, 0, 1, 0, 0, 0}; // row 1 of A
double r2[6] = {0, 0, 0, 0, 0, 1}; // row 2 of A
for (int i = 0; i < points_size; i++) {
if (mask[i] != new_mask[i]) {
const int smpl = 4*i;
const double x1 = norm_points[smpl ], y1 = norm_points[smpl+1],
x2 = norm_points[smpl+2], y2 = norm_points[smpl+3];
r1[0] = x1;
r1[1] = y1;
r2[3] = x1;
r2[4] = y1;
if (mask[i]) // if mask[i] is true then new_mask[i] must be false
for (int j = 0; j < 6; j++) {
for (int z = j; z < 6; z++)
covariance[j*6+z] +=-r1[j]*r1[z] - r2[j]*r2[z];
Ab[j] +=-r1[j]*x2 - r2[j]*y2;
}
else
for (int j = 0; j < 6; j++) {
for (int z = j; z < 6; z++)
covariance[j*6+z] += r1[j]*r1[z] + r2[j]*r2[z];
Ab[j] += r1[j]*x2 + r2[j]*y2;
}
}
}
mask = new_mask;
// copy symmetric part of covariance matrix
for (int j = 1; j < 6; j++)
for (int z = 0; z < j; z++)
covariance[j*6+z] = covariance[z*6+j];
Vec6d aff;
if (!solve(Matx66d(covariance), Vec6d(Ab), aff))
return 0;
double a[9] = { aff(0), aff(1), aff(2), aff(3), aff(4), aff(5), 0, 0, 1 };
models = std::vector<Mat>{ Mat(Matx33d(t1[0]*(a[0]/t2[0] - (a[6]*t2[2])/t2[0]),
t1[0]*(a[1]/t2[0] - (a[7]*t2[2])/t2[0]), a[2]/t2[0] + t1[2]*(a[0]/t2[0] -
(a[6]*t2[2])/t2[0]) + t1[5]*(a[1]/t2[0] - (a[7]*t2[2])/t2[0]) - (a[8]*t2[2])/t2[0],
t1[0]*(a[3]/t2[0] - (a[6]*t2[5])/t2[0]), t1[0]*(a[4]/t2[0] - (a[7]*t2[5])/t2[0]),
a[5]/t2[0] + t1[2]*(a[3]/t2[0] - (a[6]*t2[5])/t2[0]) + t1[5]*(a[4]/t2[0] -
(a[7]*t2[5])/t2[0]) - (a[8]*t2[5])/t2[0], t1[0]*a[6], t1[0]*a[7],
a[8] + a[6]*t1[2] + a[7]*t1[5])) };
return 1;
}
void enforceRankConstraint (bool /*enforce*/) override {}
int getMinimumRequiredSampleSize() const override { return 3; }
int getMaxNumberOfSolutions () const override { return 1; }
};
Ptr<CovarianceAffineSolver> CovarianceAffineSolver::create (const Mat &points, const Matx33d &T1, const Matx33d &T2) {
return makePtr<CovarianceAffineSolverImpl>(points, T1, T2);
}
Ptr<CovarianceAffineSolver> CovarianceAffineSolver::create (const Mat &points) {
return makePtr<CovarianceAffineSolverImpl>(points);
}
}}

View File

@ -22,14 +22,18 @@ protected:
std::vector<double> energies, weights;
std::set<int> used_edges;
std::vector<Mat> gc_models;
Ptr<Termination> termination;
int num_lo_optimizations = 0, current_ransac_iter = 0;
public:
void setCurrentRANSACiter (int ransac_iter) override { current_ransac_iter = ransac_iter; }
// In lo_sampler_ the sample size should be set and be equal gc_sample_size_
GraphCutImpl (const Ptr<Estimator> &estimator_, const Ptr<Error> &error_, const Ptr<Quality> &quality_,
GraphCutImpl (const Ptr<Estimator> &estimator_, const Ptr<Quality> &quality_,
const Ptr<NeighborhoodGraph> &neighborhood_graph_, const Ptr<RandomGenerator> &lo_sampler_,
double threshold_, double spatial_coherence_term, int gc_inner_iteration_number_) :
double threshold_, double spatial_coherence_term, int gc_inner_iteration_number_, Ptr<Termination> termination_) :
neighborhood_graph (neighborhood_graph_), estimator (estimator_), quality (quality_),
lo_sampler (lo_sampler_), error (error_) {
lo_sampler (lo_sampler_), error (quality_->getErrorFnc()), termination(termination_) {
points_size = quality_->getPointsSize();
spatial_coherence = spatial_coherence_term;
@ -81,9 +85,13 @@ public:
gc_models[model_idx].copyTo(new_model);
}
}
if (termination != nullptr && is_best_model_updated && current_ransac_iter > termination->update(best_model, best_model_score.inlier_number)) {
is_best_model_updated = false; // to break outer loop
}
} // end of inner GC local optimization
} // end of while loop
return true;
}
@ -91,7 +99,6 @@ private:
// find inliers using graph cut algorithm.
int labeling (const Mat& model) {
const auto &errors = error->getErrors(model);
detail::GCGraph<double> graph;
for (int pt = 0; pt < points_size; pt++)
@ -151,10 +158,8 @@ private:
has_edges = true;
}
}
if (!has_edges)
if (! has_edges)
return quality->getInliers(model, labeling_inliers);
graph.maxFlow();
int inlier_number = 0;
@ -163,23 +168,17 @@ private:
labeling_inliers[inlier_number++] = pt;
return inlier_number;
}
Ptr<LocalOptimization> clone(int state) const override {
return makePtr<GraphCutImpl>(estimator->clone(), error->clone(), quality->clone(),
neighborhood_graph,lo_sampler->clone(state), sqr_trunc_thr / 2.25,
spatial_coherence, lo_inner_iterations);
}
int getNumLOoptimizations () const override { return num_lo_optimizations; }
};
Ptr<GraphCut> GraphCut::create(const Ptr<Estimator> &estimator_, const Ptr<Error> &error_,
Ptr<GraphCut> GraphCut::create(const Ptr<Estimator> &estimator_,
const Ptr<Quality> &quality_, const Ptr<NeighborhoodGraph> &neighborhood_graph_,
const Ptr<RandomGenerator> &lo_sampler_, double threshold_,
double spatial_coherence_term, int gc_inner_iteration_number) {
return makePtr<GraphCutImpl>(estimator_, error_, quality_, neighborhood_graph_, lo_sampler_,
threshold_, spatial_coherence_term, gc_inner_iteration_number);
double spatial_coherence_term, int gc_inner_iteration_number, Ptr<Termination> termination_) {
return makePtr<GraphCutImpl>(estimator_, quality_, neighborhood_graph_, lo_sampler_,
threshold_, spatial_coherence_term, gc_inner_iteration_number, termination_);
}
/*
* http://cmp.felk.cvut.cz/~matas/papers/chum-dagm03.pdf
*/
// http://cmp.felk.cvut.cz/~matas/papers/chum-dagm03.pdf
class InnerIterativeLocalOptimizationImpl : public InnerIterativeLocalOptimization {
private:
const Ptr<Estimator> estimator;
@ -197,23 +196,18 @@ private:
double threshold, new_threshold, threshold_step;
std::vector<double> weights;
public:
InnerIterativeLocalOptimizationImpl (const Ptr<Estimator> &estimator_, const Ptr<Quality> &quality_,
const Ptr<RandomGenerator> &lo_sampler_, int pts_size,
double threshold_, bool is_iterative_, int lo_iter_sample_size_,
int lo_inner_iterations_=10, int lo_iter_max_iterations_=5,
double threshold_multiplier_=4)
: estimator (estimator_), quality (quality_), lo_sampler (lo_sampler_)
, lo_iter_sample_size(0)
, new_threshold(0), threshold_step(0)
{
, lo_iter_sample_size(0), new_threshold(0), threshold_step(0) {
lo_inner_max_iterations = lo_inner_iterations_;
lo_iter_max_iterations = lo_iter_max_iterations_;
threshold = threshold_;
lo_sample_size = lo_sampler->getSubsetSize();
is_iterative = is_iterative_;
if (is_iterative) {
lo_iter_sample_size = lo_iter_sample_size_;
@ -225,7 +219,6 @@ public:
// In the last iteration there be original threshold θ.
threshold_step = (new_threshold - threshold) / lo_iter_max_iterations_;
}
lo_models = std::vector<Mat>(estimator->getMaxNumSolutionsNonMinimal());
// Allocate max memory to avoid reallocation
@ -327,12 +320,6 @@ public:
}
return true;
}
Ptr<LocalOptimization> clone(int state) const override {
return makePtr<InnerIterativeLocalOptimizationImpl>(estimator->clone(), quality->clone(),
lo_sampler->clone(state),(int)inliers_of_best_model.size(), threshold, is_iterative,
lo_iter_sample_size, lo_inner_max_iterations, lo_iter_max_iterations,
new_threshold / threshold);
}
};
Ptr<InnerIterativeLocalOptimization> InnerIterativeLocalOptimization::create
(const Ptr<Estimator> &estimator_, const Ptr<Quality> &quality_,
@ -345,293 +332,275 @@ Ptr<InnerIterativeLocalOptimization> InnerIterativeLocalOptimization::create
lo_inner_iterations_, lo_iter_max_iterations_, threshold_multiplier_);
}
class SigmaConsensusImpl : public SigmaConsensus {
class SimpleLocalOptimizationImpl : public SimpleLocalOptimization {
private:
const Ptr<Estimator> estimator;
const Ptr<Quality> quality;
const Ptr<Error> error;
const Ptr<ModelVerifier> verifier;
const GammaValues& gamma_generator;
// The degrees of freedom of the data from which the model is estimated.
// E.g., for models coming from point correspondences (x1,y1,x2,y2), it is 4.
const int degrees_of_freedom;
// A 0.99 quantile of the Chi^2-distribution to convert sigma values to residuals
const double k;
// Calculating (DoF - 1) / 2 which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double dof_minus_one_per_two;
const double C;
// The size of a minimal sample used for the estimation
const int sample_size;
// Calculating 2^(DoF - 1) which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double two_ad_dof;
// Calculating C * 2^(DoF - 1) which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double C_times_two_ad_dof;
// Calculating the gamma value of (DoF - 1) / 2 which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double squared_sigma_max_2, one_over_sigma;
// Calculating the upper incomplete gamma value of (DoF - 1) / 2 with k^2 / 2.
const double gamma_k;
// Calculating the lower incomplete gamma value of (DoF - 1) / 2 which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double max_sigma_sqr;
const int points_size, number_of_irwls_iters;
const double maximum_threshold, max_sigma;
std::vector<double> sqr_residuals, sigma_weights;
std::vector<int> sqr_residuals_idxs;
// Models fit by weighted least-squares fitting
std::vector<Mat> sigma_models;
// Points used in the weighted least-squares fitting
std::vector<int> sigma_inliers;
// Weights used in the the weighted least-squares fitting
int max_lo_sample_size, stored_gamma_number_min1;
double scale_of_stored_gammas;
RNG rng;
const std::vector<double> &stored_gamma_values;
const Ptr<NonMinimalSolver> estimator;
const Ptr<Termination> termination;
const Ptr<RandomGenerator> random_generator;
const Ptr<WeightFunction> weight_fnc;
// unlike to @random_generator which has fixed subset size
// @random_generator_smaller_subset is used to draw smaller
// amount of points which depends on current number of inliers
Ptr<RandomGenerator> random_generator_smaller_subset;
int points_size, max_lo_iters, non_min_sample_size, current_ransac_iter;
std::vector<double> weights;
std::vector<int> inliers;
std::vector<cv::Mat> models;
double inlier_threshold_sqr;
int num_lo_optimizations = 0;
bool updated_lo = false;
public:
SigmaConsensusImpl (const Ptr<Estimator> &estimator_, const Ptr<Error> &error_,
const Ptr<Quality> &quality_, const Ptr<ModelVerifier> &verifier_,
int max_lo_sample_size_, int number_of_irwls_iters_, int DoF,
double sigma_quantile, double upper_incomplete_of_sigma_quantile, double C_,
double maximum_thr) : estimator (estimator_), quality(quality_),
error (error_), verifier(verifier_),
gamma_generator(GammaValues::getSingleton()),
degrees_of_freedom(DoF), k (sigma_quantile), C(C_),
sample_size(estimator_->getMinimalSampleSize()),
gamma_k (upper_incomplete_of_sigma_quantile), points_size (quality_->getPointsSize()),
number_of_irwls_iters (number_of_irwls_iters_),
maximum_threshold(maximum_thr), max_sigma (maximum_thr),
stored_gamma_values(gamma_generator.getGammaValues())
{
dof_minus_one_per_two = (degrees_of_freedom - 1.0) / 2.0;
two_ad_dof = std::pow(2.0, dof_minus_one_per_two);
C_times_two_ad_dof = C * two_ad_dof;
// Calculate 2 * \sigma_{max}^2 a priori
squared_sigma_max_2 = max_sigma * max_sigma * 2.0;
// Divide C * 2^(DoF - 1) by \sigma_{max} a priori
one_over_sigma = C_times_two_ad_dof / max_sigma;
max_sigma_sqr = squared_sigma_max_2 * 0.5;
sqr_residuals = std::vector<double>(points_size);
sqr_residuals_idxs = std::vector<int>(points_size);
sigma_inliers = std::vector<int>(points_size);
max_lo_sample_size = max_lo_sample_size_;
sigma_weights = std::vector<double>(points_size);
sigma_models = std::vector<Mat>(estimator->getMaxNumSolutionsNonMinimal());
stored_gamma_number_min1 = gamma_generator.getTableSize()-1;
scale_of_stored_gammas = gamma_generator.getScaleOfGammaValues();
SimpleLocalOptimizationImpl (const Ptr<Quality> &quality_, const Ptr<NonMinimalSolver> &estimator_,
const Ptr<Termination> termination_, const Ptr<RandomGenerator> &random_gen, Ptr<WeightFunction> weight_fnc_,
int max_lo_iters_, double inlier_threshold_sqr_, bool update_lo_) :
quality(quality_), error(quality_->getErrorFnc()), estimator(estimator_), termination(termination_),
random_generator(random_gen), weight_fnc(weight_fnc_) {
max_lo_iters = max_lo_iters_;
non_min_sample_size = random_generator->getSubsetSize();
current_ransac_iter = 0;
inliers = std::vector<int>(quality_->getPointsSize());
models = std::vector<cv::Mat>(estimator_->getMaxNumberOfSolutions());
points_size = quality_->getPointsSize();
inlier_threshold_sqr = inlier_threshold_sqr_;
if (weight_fnc != nullptr) weights = std::vector<double>(points_size);
random_generator_smaller_subset = nullptr;
updated_lo = update_lo_;
}
void setCurrentRANSACiter (int ransac_iter) override { current_ransac_iter = ransac_iter; }
int getNumLOoptimizations () const override { return num_lo_optimizations; }
bool refineModel (const Mat &best_model, const Score &best_model_score, Mat &new_model, Score &new_model_score) override {
new_model_score = best_model_score;
best_model.copyTo(new_model);
// https://github.com/danini/magsac
bool refineModel (const Mat &in_model, const Score &best_model_score,
Mat &new_model, Score &new_model_score) override {
int residual_cnt = 0;
int num_inliers;
if (weights.empty())
num_inliers = Quality::getInliers(error, best_model, inliers, inlier_threshold_sqr);
else num_inliers = weight_fnc->getInliersWeights(error->getErrors(best_model), inliers, weights);
auto update_generator = [&] (int num_inls) {
if (num_inls <= non_min_sample_size) {
// add new random generator if number of inliers is fewer than non-minimal sample size
const int new_sample_size = (int)(0.6*num_inls);
if (new_sample_size <= estimator->getMinimumRequiredSampleSize())
return false;
if (random_generator_smaller_subset == nullptr)
random_generator_smaller_subset = UniformRandomGenerator::create(num_inls/*state*/, quality->getPointsSize(), new_sample_size);
else random_generator_smaller_subset->setSubsetSize(new_sample_size);
}
return true;
};
if (!update_generator(num_inliers))
return false;
if (verifier->isModelGood(in_model)) {
if (verifier->hasErrors()) {
const std::vector<float> &errors = verifier->getErrors();
for (int point_idx = 0; point_idx < points_size; ++point_idx) {
// Calculate the residual of the current point
const auto residual = sqrtf(errors[point_idx]);
if (max_sigma > residual) {
// Store the residual of the current point and its index
sqr_residuals[residual_cnt] = residual;
sqr_residuals_idxs[residual_cnt++] = point_idx;
int max_lo_iters_ = max_lo_iters, last_update = 0, last_inliers_update = 0;
for (int iter = 0; iter < max_lo_iters_; iter++) {
int num_models;
if (num_inliers <= non_min_sample_size)
num_models = estimator->estimate(new_model, random_generator_smaller_subset->generateUniqueRandomSubset(inliers, num_inliers),
random_generator_smaller_subset->getSubsetSize(), models, weights);
else num_models = estimator->estimate(new_model, random_generator->generateUniqueRandomSubset(inliers, num_inliers), non_min_sample_size, models, weights);
for (int m = 0; m < num_models; m++) {
const auto score = quality->getScore(models[m]);
if (score.isBetter(new_model_score)) {
last_update = iter; last_inliers_update = new_model_score.inlier_number - score.inlier_number;
if (updated_lo) {
if (max_lo_iters_ < iter + 5 && last_inliers_update >= 1) {
max_lo_iters_ = iter + 5;
}
}
models[m].copyTo(new_model);
new_model_score = score;
if (termination != nullptr && current_ransac_iter > termination->update(new_model, new_model_score.inlier_number))
return true; // terminate LO if max number of iterations reached
if (score.inlier_number >= best_model_score.inlier_number ||
score.inlier_number > non_min_sample_size) {
// update inliers if new model has more than previous best model
if (weights.empty())
num_inliers = Quality::getInliers(error, best_model, inliers, inlier_threshold_sqr);
else num_inliers = weight_fnc->getInliersWeights(error->getErrors(best_model), inliers, weights);
if (!update_generator(num_inliers))
return true;
}
// Interrupt if there is no chance of being better
if (residual_cnt + points_size - point_idx < best_model_score.inlier_number)
return false;
}
} else {
error->setModelParameters(in_model);
for (int point_idx = 0; point_idx < points_size; ++point_idx) {
const double sqr_residual = error->getError(point_idx);
if (sqr_residual < max_sigma_sqr) {
// Store the residual of the current point and its index
sqr_residuals[residual_cnt] = sqr_residual;
sqr_residuals_idxs[residual_cnt++] = point_idx;
}
if (residual_cnt + points_size - point_idx < best_model_score.inlier_number)
return false;
}
}
} else return false;
in_model.copyTo(new_model);
new_model_score = Score();
// Do the iteratively re-weighted least squares fitting
for (int iterations = 0; iterations < number_of_irwls_iters; iterations++) {
int sigma_inliers_cnt = 0;
// If the current iteration is not the first, the set of possibly inliers
// (i.e., points closer than the maximum threshold) have to be recalculated.
if (iterations > 0) {
// error->setModelParameters(polished_model);
error->setModelParameters(new_model);
// Remove everything from the residual vector
residual_cnt = 0;
// Collect the points which are closer than the maximum threshold
for (int point_idx = 0; point_idx < points_size; ++point_idx) {
// Calculate the residual of the current point
const double sqr_residual = error->getError(point_idx);
if (sqr_residual < max_sigma_sqr) {
// Store the residual of the current point and its index
sqr_residuals[residual_cnt] = sqr_residual;
sqr_residuals_idxs[residual_cnt++] = point_idx;
}
}
sigma_inliers_cnt = 0;
}
// Calculate the weight of each point
for (int i = 0; i < residual_cnt; i++) {
// Get the position of the gamma value in the lookup table
int x = (int)round(scale_of_stored_gammas * sqr_residuals[i]
/ squared_sigma_max_2);
// If the sought gamma value is not stored in the lookup, return the closest element
if (x >= stored_gamma_number_min1 || x < 0 /*overflow*/) // actual number of gamma values is 1 more, so >=
x = stored_gamma_number_min1;
sigma_inliers[sigma_inliers_cnt] = sqr_residuals_idxs[i]; // store index of point for LSQ
sigma_weights[sigma_inliers_cnt++] = one_over_sigma * (stored_gamma_values[x] - gamma_k);
}
// random shuffle sigma inliers
if (sigma_inliers_cnt > max_lo_sample_size)
for (int i = sigma_inliers_cnt-1; i > 0; i--) {
const int idx = rng.uniform(0, i+1);
std::swap(sigma_inliers[i], sigma_inliers[idx]);
std::swap(sigma_weights[i], sigma_weights[idx]);
}
const int num_est_models = estimator->estimateModelNonMinimalSample
(sigma_inliers, std::min(max_lo_sample_size, sigma_inliers_cnt),
sigma_models, sigma_weights);
if (num_est_models == 0)
break; // break iterations
// Update the model parameters
Mat polished_model = sigma_models[0];
if (num_est_models > 1) {
// find best over other models
Score sigma_best_score = quality->getScore(polished_model);
for (int m = 1; m < num_est_models; m++) {
const Score sc = quality->getScore(sigma_models[m]);
if (sc.isBetter(sigma_best_score)) {
polished_model = sigma_models[m];
sigma_best_score = sc;
}
}
}
const Score polished_model_score = quality->getScore(polished_model);
if (polished_model_score.isBetter(new_model_score)){
new_model_score = polished_model_score;
polished_model.copyTo(new_model);
if (updated_lo && iter - last_update >= 10) {
break;
}
}
const Score in_model_score = quality->getScore(in_model);
if (in_model_score.isBetter(new_model_score)) {
new_model_score = in_model_score;
in_model.copyTo(new_model);
}
return true;
}
Ptr<LocalOptimization> clone(int state) const override {
return makePtr<SigmaConsensusImpl>(estimator->clone(), error->clone(), quality->clone(),
verifier->clone(state), max_lo_sample_size,
number_of_irwls_iters, degrees_of_freedom, k, gamma_k, C, maximum_threshold);
};
Ptr<SimpleLocalOptimization> SimpleLocalOptimization::create (const Ptr<Quality> &quality_,
const Ptr<NonMinimalSolver> &estimator_, const Ptr<Termination> termination_, const Ptr<RandomGenerator> &random_gen,
const Ptr<WeightFunction> weight_fnc, int max_lo_iters_, double inlier_thr_sqr, bool updated_lo) {
return makePtr<SimpleLocalOptimizationImpl> (quality_, estimator_, termination_, random_gen, weight_fnc, max_lo_iters_, inlier_thr_sqr, updated_lo);
}
class MagsacWeightFunctionImpl : public MagsacWeightFunction {
private:
const std::vector<double> &stored_gamma_values;
double C, max_sigma, max_sigma_sqr, scale_of_stored_gammas, one_over_sigma, gamma_k, squared_sigma_max_2, rescale_err;
int DoF;
unsigned int stored_gamma_number_min1;
public:
MagsacWeightFunctionImpl (const Ptr<GammaValues> &gamma_generator,
int DoF_, double upper_incomplete_of_sigma_quantile, double C_, double max_sigma_) :
stored_gamma_values (gamma_generator->getGammaValues()) {
gamma_k = upper_incomplete_of_sigma_quantile;
stored_gamma_number_min1 = static_cast<unsigned int>(gamma_generator->getTableSize()-1);
scale_of_stored_gammas = gamma_generator->getScaleOfGammaValues();
DoF = DoF_; C = C_;
max_sigma = max_sigma_;
squared_sigma_max_2 = max_sigma * max_sigma * 2.0;
one_over_sigma = C * pow(2.0, (DoF - 1.0) * 0.5) / max_sigma;
max_sigma_sqr = squared_sigma_max_2 * 0.5;
rescale_err = scale_of_stored_gammas / squared_sigma_max_2;
}
int getInliersWeights (const std::vector<float> &errors, std::vector<int> &inliers, std::vector<double> &weights) const override {
return getInliersWeights(errors, inliers, weights, one_over_sigma, rescale_err, max_sigma_sqr);
}
int getInliersWeights (const std::vector<float> &errors, std::vector<int> &inliers, std::vector<double> &weights, double thr_sqr) const override {
const auto _max_sigma = thr_sqr;
const auto _squared_sigma_max_2 = _max_sigma * _max_sigma * 2.0;
const auto _one_over_sigma = C * pow(2.0, (DoF - 1.0) * 0.5) / _max_sigma;
const auto _max_sigma_sqr = _squared_sigma_max_2 * 0.5;
const auto _rescale_err = scale_of_stored_gammas / _squared_sigma_max_2;
return getInliersWeights(errors, inliers, weights, _one_over_sigma, _rescale_err, _max_sigma_sqr);
}
double getThreshold () const override {
return max_sigma_sqr;
}
private:
int getInliersWeights (const std::vector<float> &errors, std::vector<int> &inliers, std::vector<double> &weights,
double _one_over_sigma, double _rescale_err, double _max_sigma_sqr) const {
int num_inliers = 0, p = 0;
for (const auto &e : errors) {
// Calculate the residual of the current point
if (e < _max_sigma_sqr) {
// Get the position of the gamma value in the lookup table
auto x = static_cast<unsigned int>(_rescale_err * e);
if (x > stored_gamma_number_min1)
x = stored_gamma_number_min1;
inliers[num_inliers] = p; // store index of point for LSQ
weights[num_inliers++] = _one_over_sigma * (stored_gamma_values[x] - gamma_k);
}
p++;
}
return num_inliers;
}
};
Ptr<SigmaConsensus>
SigmaConsensus::create(const Ptr<Estimator> &estimator_, const Ptr<Error> &error_,
const Ptr<Quality> &quality, const Ptr<ModelVerifier> &verifier_,
int max_lo_sample_size, int number_of_irwls_iters_, int DoF,
double sigma_quantile, double upper_incomplete_of_sigma_quantile, double C_,
double maximum_thr) {
return makePtr<SigmaConsensusImpl>(estimator_, error_, quality, verifier_,
max_lo_sample_size, number_of_irwls_iters_, DoF, sigma_quantile,
upper_incomplete_of_sigma_quantile, C_, maximum_thr);
Ptr<MagsacWeightFunction> MagsacWeightFunction::create(const Ptr<GammaValues> &gamma_generator_,
int DoF_, double upper_incomplete_of_sigma_quantile, double C_, double max_sigma_) {
return makePtr<MagsacWeightFunctionImpl>(gamma_generator_, DoF_, upper_incomplete_of_sigma_quantile, C_, max_sigma_);
}
/////////////////////////////////////////// FINAL MODEL POLISHER ////////////////////////
class LeastSquaresPolishingImpl : public LeastSquaresPolishing {
class NonMinimalPolisherImpl : public NonMinimalPolisher {
private:
const Ptr<Estimator> estimator;
const Ptr<Quality> quality;
int lsq_iterations;
std::vector<int> inliers;
const Ptr<NonMinimalSolver> solver;
const Ptr<Error> error_fnc;
const Ptr<WeightFunction> weight_fnc;
std::vector<bool> mask, mask_best;
std::vector<Mat> models;
std::vector<double> weights;
std::vector<float> errors_best;
std::vector<int> inliers;
double threshold, iou_thr, max_thr;
int max_iters, points_size;
bool is_covariance, CHANGE_WEIGHTS = true;
public:
LeastSquaresPolishingImpl(const Ptr<Estimator> &estimator_, const Ptr<Quality> &quality_,
int lsq_iterations_) :
estimator(estimator_), quality(quality_) {
lsq_iterations = lsq_iterations_;
// allocate memory for inliers array and models
inliers = std::vector<int>(quality_->getPointsSize());
models = std::vector<Mat>(estimator->getMaxNumSolutionsNonMinimal());
NonMinimalPolisherImpl (const Ptr<Quality> &quality_, const Ptr<NonMinimalSolver> &solver_,
Ptr<WeightFunction> weight_fnc_, int max_iters_, double iou_thr_) :
quality(quality_), solver(solver_), error_fnc(quality_->getErrorFnc()), weight_fnc(weight_fnc_) {
max_iters = max_iters_;
points_size = quality_->getPointsSize();
threshold = quality_->getThreshold();
iou_thr = iou_thr_;
is_covariance = dynamic_cast<const cv::usac::CovarianceSolver*>(solver_.get()) != nullptr;
mask = std::vector<bool>(points_size);
mask_best = std::vector<bool>(points_size);
inliers = std::vector<int>(points_size);
if (weight_fnc != nullptr) {
weights = std::vector<double>(points_size);
max_thr = weight_fnc->getThreshold();
if (is_covariance)
CV_Error(cv::Error::StsBadArg, "Covariance polisher cannot be combined with weights!");
}
}
bool polishSoFarTheBestModel(const Mat &model, const Score &best_model_score,
Mat &new_model, Score &out_score) override {
// get inliers from input model
int inlier_number = quality->getInliers(model, inliers);
if (inlier_number < estimator->getMinimalSampleSize())
return false;
out_score = Score(); // set the worst case
// several all-inlier least-squares refines model better than only one but for
// big amount of points may be too time-consuming.
for (int lsq_iter = 0; lsq_iter < lsq_iterations; lsq_iter++) {
bool model_updated = false;
// estimate non minimal models with all inliers
const int num_models = estimator->estimateModelNonMinimalSample(inliers,
inlier_number, models, weights);
for (int model_idx = 0; model_idx < num_models; model_idx++) {
const Score score = quality->getScore(models[model_idx]);
if (best_model_score.isBetter(score))
continue;
if (score.isBetter(out_score)) {
models[model_idx].copyTo(new_model);
out_score = score;
model_updated = true;
bool polishSoFarTheBestModel (const Mat &model, const Score &best_model_score,
Mat &new_model, Score &new_model_score) override {
int num_inliers = 0;
if (weights.empty()) {
quality->getInliers(model, mask_best);
if (!is_covariance)
for (int p = 0; p < points_size; p++)
if (mask_best[p]) inliers[num_inliers++] = p;
} else {
errors_best = error_fnc->getErrors(model);
num_inliers = weight_fnc->getInliersWeights(errors_best, inliers, weights, max_thr);
}
new_model_score = best_model_score;
model.copyTo(new_model);
int last_update = -1;
for (int iter = 0; iter < max_iters; iter++) {
int num_sols;
if (is_covariance) num_sols = solver->estimate(mask_best, models, weights);
else num_sols = solver->estimate(new_model, inliers, num_inliers, models, weights);
Score prev_score;
for (int i = 0; i < num_sols; i++) {
const auto &errors = error_fnc->getErrors(models[i]);
const auto score = quality->getScore(errors);
if (score.isBetter(new_model_score)) {
last_update = iter;
models[i].copyTo(new_model);
errors_best = errors;
prev_score = new_model_score;
new_model_score = score;
}
}
if (!model_updated)
// if model was not updated at the first iteration then return false
// otherwise if all-inliers LSQ has not updated model then no sense
// to do it again -> return true (model was updated before).
return lsq_iter > 0;
// if number of inliers doesn't increase more than 5% then break
if (fabs(static_cast<double>(out_score.inlier_number) - static_cast<double>
(best_model_score.inlier_number)) / best_model_score.inlier_number < 0.05)
return true;
if (lsq_iter != lsq_iterations - 1)
// if not the last LSQ normalization then get inliers for next normalization
inlier_number = quality->getInliers(new_model, inliers);
if (weights.empty()) {
if (iter > last_update)
break;
else {
Quality::getInliers(errors_best, mask, threshold);
if (Utils::intersectionOverUnion(mask, mask_best) >= iou_thr)
return true;
mask_best = mask;
num_inliers = 0;
if (!is_covariance)
for (int p = 0; p < points_size; p++)
if (mask_best[p]) inliers[num_inliers++] = p;
}
} else {
if (iter > last_update) {
// new model is worse
if (CHANGE_WEIGHTS) {
// if failed more than 5 times then break
if (iter - std::max(0, last_update) >= 5)
break;
// try to change weights by changing threshold
if (fabs(new_model_score.score - prev_score.score) < FLT_EPSILON) {
// increase threshold if new model score is the same as the same as previous
max_thr *= 1.05; // increase by 5%
} else if (iter > last_update) {
// decrease max threshold if model is worse
max_thr *= 0.9; // decrease by 10%
}
} else break; // break if not changing weights
}
// generate new weights and inliers
num_inliers = weight_fnc->getInliersWeights(errors_best, inliers, weights, max_thr);
}
}
return true;
return last_update >= 0;
}
};
Ptr<LeastSquaresPolishing> LeastSquaresPolishing::create (const Ptr<Estimator> &estimator_,
const Ptr<Quality> &quality_, int lsq_iterations_) {
return makePtr<LeastSquaresPolishingImpl>(estimator_, quality_, lsq_iterations_);
Ptr<NonMinimalPolisher> NonMinimalPolisher::create(const Ptr<Quality> &quality_, const Ptr<NonMinimalSolver> &solver_,
Ptr<WeightFunction> weight_fnc_, int max_iters_, double iou_thr_) {
return makePtr<NonMinimalPolisherImpl>(quality_, solver_, weight_fnc_, max_iters_, iou_thr_);
}
}}

View File

@ -23,7 +23,7 @@ public:
int getMaxNumberOfSolutions () const override { return 1; }
explicit PnPMinimalSolver6PtsImpl (const Mat &points_) :
points_mat(&points_), points ((float*)points_.data) {}
points_mat(&points_), points ((float*)points_mat->data) {}
/*
DLT:
d x = P X, x = (u, v, 1), X = (X, Y, Z, 1), P = K[R t]
@ -73,6 +73,9 @@ public:
int estimate (const std::vector<int> &sample, std::vector<Mat> &models) const override {
std::vector<double> A1 (60, 0), A2(56, 0); // 5x12, 7x8
// std::vector<double> A_all(11*12, 0);
// int cnt3 = 0;
int cnt1 = 0, cnt2 = 0;
for (int i = 0; i < 6; i++) {
const int smpl = 5 * sample[i];
@ -100,14 +103,16 @@ public:
A2[cnt2++] = -v * Z;
A2[cnt2++] = -v;
}
// matrix is sparse -> do not test for singularity
Math::eliminateUpperTriangular(A1, 5, 12);
int offset = 4*12;
// add last eliminated row of A1
for (int i = 0; i < 8; i++)
A2[cnt2++] = A1[offset + i + 4/* skip 4 first cols*/];
// must be full-rank
if (!Math::eliminateUpperTriangular(A2, 7, 8))
return 0;
@ -139,13 +144,9 @@ public:
if (std::isnan(p[i]))
return 0;
}
models = std::vector<Mat>{P};
return 1;
}
Ptr<MinimalSolver> clone () const override {
return makePtr<PnPMinimalSolver6PtsImpl>(*points_mat);
}
};
Ptr<PnPMinimalSolver6Pts> PnPMinimalSolver6Pts::create(const Mat &points_) {
return makePtr<PnPMinimalSolver6PtsImpl>(points_);
@ -157,7 +158,7 @@ private:
const float * const points;
public:
explicit PnPNonMinimalSolverImpl (const Mat &points_) :
points_mat(&points_), points ((float*)points_.data){}
points_mat(&points_), points ((float*)points_mat->data){}
int estimate (const std::vector<int> &sample, int sample_size,
std::vector<Mat> &models, const std::vector<double> &weights) const override {
@ -236,7 +237,7 @@ public:
models = std::vector<Mat>{ Mat_<double>(3,4) };
Eigen::HouseholderQR<Eigen::Matrix<double, 12, 12>> qr((Eigen::Matrix<double, 12, 12>(AtA)));
const Eigen::Matrix<double, 12, 12> &Q = qr.householderQ();
// extract the last nullspace
// extract the last null-vector
Eigen::Map<Eigen::Matrix<double, 12, 1>>((double *)models[0].data) = Q.col(11);
#else
Matx<double, 12, 12> Vt;
@ -246,17 +247,37 @@ public:
#endif
return 1;
}
int estimate (const std::vector<bool> &/*mask*/, std::vector<Mat> &/*models*/,
const std::vector<double> &/*weights*/) override {
return 0;
}
void enforceRankConstraint (bool /*enforce*/) override {}
int getMinimumRequiredSampleSize() const override { return 6; }
int getMaxNumberOfSolutions () const override { return 1; }
Ptr<NonMinimalSolver> clone () const override {
return makePtr<PnPNonMinimalSolverImpl>(*points_mat);
}
};
Ptr<PnPNonMinimalSolver> PnPNonMinimalSolver::create(const Mat &points) {
return makePtr<PnPNonMinimalSolverImpl>(points);
}
class PnPSVDSolverImpl : public PnPSVDSolver {
private:
std::vector<double> w;
Ptr<PnPNonMinimalSolver> pnp_solver;
public:
int getSampleSize() const override { return 6; }
int getMaxNumberOfSolutions () const override { return 1; }
explicit PnPSVDSolverImpl (const Mat &points_) {
pnp_solver = PnPNonMinimalSolver::create(points_);
}
int estimate (const std::vector<int> &sample, std::vector<Mat> &models) const override {
return pnp_solver->estimate(sample, 6, models, w);
}
};
Ptr<PnPSVDSolver> PnPSVDSolver::create(const Mat &points_) {
return makePtr<PnPSVDSolverImpl>(points_);
}
class P3PSolverImpl : public P3PSolver {
private:
/*
@ -264,7 +285,7 @@ private:
* K^-1 [u v 1]^T / ||K^-1 [u v 1]^T||
*/
const Mat * points_mat, * calib_norm_points_mat;
const Matx33d * K_mat, &K;
const Matx33d K;
const float * const calib_norm_points, * const points;
const double VAL_THR = 1e-4;
public:
@ -272,9 +293,9 @@ public:
* @points_ is matrix N x 5
* u v x y z. (u,v) is image point, (x y z) is world point
*/
P3PSolverImpl (const Mat &points_, const Mat &calib_norm_points_, const Matx33d &K_) :
points_mat(&points_), calib_norm_points_mat(&calib_norm_points_), K_mat (&K_),
K(K_), calib_norm_points((float*)calib_norm_points_.data), points((float*)points_.data) {}
P3PSolverImpl (const Mat &points_, const Mat &calib_norm_points_, const Mat &K_) :
points_mat(&points_), calib_norm_points_mat(&calib_norm_points_), K(K_),
calib_norm_points((float*)calib_norm_points_mat->data), points((float*)points_mat->data) {}
int estimate (const std::vector<int> &sample, std::vector<Mat> &models) const override {
/*
@ -362,8 +383,8 @@ public:
zw[2] = Z3crZ1w(0); zw[5] = Z3crZ1w(1); zw[8] = Z3crZ1w(2);
const Matx33d R = Math::rotVec2RotMat(Math::rotMat2RotVec(Z * Zw.inv()));
Mat P, KR = Mat(K * R);
Matx33d KR = K * R;
Matx34d P;
hconcat(KR, -KR * (X1 - R.t() * nX1), P);
models.emplace_back(P);
}
@ -371,11 +392,8 @@ public:
}
int getSampleSize() const override { return 3; }
int getMaxNumberOfSolutions () const override { return 4; }
Ptr<MinimalSolver> clone () const override {
return makePtr<P3PSolverImpl>(*points_mat, *calib_norm_points_mat, *K_mat);
}
};
Ptr<P3PSolver> P3PSolver::create(const Mat &points_, const Mat &calib_norm_pts, const Matx33d &K) {
Ptr<P3PSolver> P3PSolver::create(const Mat &points_, const Mat &calib_norm_pts, const Mat &K) {
return makePtr<P3PSolverImpl>(points_, calib_norm_pts, K);
}
}}

View File

@ -25,6 +25,27 @@ int Quality::getInliers(const Ptr<Error> &error, const Mat &model, std::vector<b
}
return num_inliers;
}
int Quality::getInliers (const std::vector<float> &errors, std::vector<bool> &inliers, double threshold) {
std::fill(inliers.begin(), inliers.end(), false);
int cnt = 0, inls = 0;
for (const auto e : errors) {
if (e < threshold) {
inliers[cnt] = true;
inls++;
}
cnt++;
}
return inls;
}
int Quality::getInliers (const std::vector<float> &errors, std::vector<int> &inliers, double threshold) {
int cnt = 0, inls = 0;
for (const auto e : errors) {
if (e < threshold)
inliers[inls++] = cnt;
cnt++;
}
return inls;
}
class RansacQualityImpl : public RansacQuality {
private:
@ -41,14 +62,23 @@ public:
Score getScore (const Mat &model) const override {
error->setModelParameters(model);
int inlier_number = 0;
for (int point = 0; point < points_size; point++) {
const auto preemptive_thr = -points_size - best_score;
for (int point = 0; point < points_size; point++)
if (error->getError(point) < threshold)
inlier_number++;
if (inlier_number + (points_size - point) < -best_score)
break;
}
else if (inlier_number - point < preemptive_thr)
break;
// score is negative inlier number! If less then better
return Score(inlier_number, -static_cast<double>(inlier_number));
return {inlier_number, -static_cast<double>(inlier_number)};
}
Score getScore (const std::vector<float> &errors) const override {
int inlier_number = 0;
for (int point = 0; point < points_size; point++)
if (errors[point] < threshold)
inlier_number++;
// score is negative inlier number! If less then better
return {inlier_number, -static_cast<double>(inlier_number)};
}
void setBestScore(double best_score_) override {
@ -61,11 +91,9 @@ public:
{ return Quality::getInliers(error, model, inliers, thr); }
int getInliers (const Mat &model, std::vector<bool> &inliers_mask) const override
{ return Quality::getInliers(error, model, inliers_mask, threshold); }
double getThreshold () const override { return threshold; }
int getPointsSize () const override { return points_size; }
Ptr<Quality> clone () const override {
return makePtr<RansacQualityImpl>(points_size, threshold, error->clone());
}
Ptr<Error> getErrorFnc () const override { return error; }
};
Ptr<RansacQuality> RansacQuality::create(int points_size_, double threshold_,
@ -77,13 +105,13 @@ class MsacQualityImpl : public MsacQuality {
protected:
const Ptr<Error> error;
const int points_size;
const double threshold;
const double threshold, k_msac;
double best_score, norm_thr, one_over_thr;
public:
MsacQualityImpl (int points_size_, double threshold_, const Ptr<Error> &error_)
: error (error_), points_size (points_size_), threshold (threshold_) {
MsacQualityImpl (int points_size_, double threshold_, const Ptr<Error> &error_, double k_msac_)
: error (error_), points_size (points_size_), threshold (threshold_), k_msac(k_msac_) {
best_score = std::numeric_limits<double>::max();
norm_thr = threshold*9/4;
norm_thr = threshold*k_msac;
one_over_thr = 1 / norm_thr;
}
@ -91,17 +119,31 @@ public:
error->setModelParameters(model);
double err, sum_errors = 0;
int inlier_number = 0;
const auto preemptive_thr = points_size + best_score;
for (int point = 0; point < points_size; point++) {
err = error->getError(point);
if (err < norm_thr) {
sum_errors -= (1 - err * one_over_thr);
if (err < threshold)
inlier_number++;
}
if (sum_errors - points_size + point > best_score)
} else if (sum_errors + point > preemptive_thr)
break;
}
return Score(inlier_number, sum_errors);
return {inlier_number, sum_errors};
}
Score getScore (const std::vector<float> &errors) const override {
double sum_errors = 0;
int inlier_number = 0;
for (int point = 0; point < points_size; point++) {
const auto err = errors[point];
if (err < norm_thr) {
sum_errors -= (1 - err * one_over_thr);
if (err < threshold)
inlier_number++;
}
}
return {inlier_number, sum_errors};
}
void setBestScore(double best_score_) override {
@ -114,103 +156,61 @@ public:
{ return Quality::getInliers(error, model, inliers, thr); }
int getInliers (const Mat &model, std::vector<bool> &inliers_mask) const override
{ return Quality::getInliers(error, model, inliers_mask, threshold); }
double getThreshold () const override { return threshold; }
int getPointsSize () const override { return points_size; }
Ptr<Quality> clone () const override {
return makePtr<MsacQualityImpl>(points_size, threshold, error->clone());
}
Ptr<Error> getErrorFnc () const override { return error; }
};
Ptr<MsacQuality> MsacQuality::create(int points_size_, double threshold_,
const Ptr<Error> &error_) {
return makePtr<MsacQualityImpl>(points_size_, threshold_, error_);
const Ptr<Error> &error_, double k_msac) {
return makePtr<MsacQualityImpl>(points_size_, threshold_, error_, k_msac);
}
class MagsacQualityImpl : public MagsacQuality {
private:
const Ptr<Error> error;
const GammaValues& gamma_generator;
const Ptr<GammaValues> gamma_generator;
const int points_size;
// for example, maximum standard deviation of noise.
const double maximum_threshold_sqr, tentative_inlier_threshold;
// The degrees of freedom of the data from which the model is estimated.
// E.g., for models coming from point correspondences (x1,y1,x2,y2), it is 4.
const int degrees_of_freedom;
// A 0.99 quantile of the Chi^2-distribution to convert sigma values to residuals
const double k;
// Calculating k^2 / 2 which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double squared_k_per_2;
// Calculating (DoF - 1) / 2 which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double dof_minus_one_per_two;
// Calculating (DoF + 1) / 2 which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double dof_plus_one_per_two;
const double C;
// Calculating 2^(DoF - 1) which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double two_ad_dof_minus_one;
// Calculating 2^(DoF + 1) which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double two_ad_dof_plus_one;
// Calculate the gamma value of k
const double gamma_value_of_k;
// Calculate the lower incomplete gamma value of k
const double lower_gamma_value_of_k;
double previous_best_loss;
// Convert the maximum threshold to a sigma value
float maximum_sigma;
// Calculate the squared maximum sigma
float maximum_sigma_2;
// Calculate \sigma_{max}^2 / 2
float maximum_sigma_2_per_2;
// Calculate 2 * \sigma_{max}^2
float maximum_sigma_2_times_2;
// Calculating 2^(DoF + 1) / \sigma_{max} which will be used for the estimation and,
// due to being constant, it is better to calculate it a priori.
double two_ad_dof_plus_one_per_maximum_sigma;
double scale_of_stored_incomplete_gammas;
double max_loss;
double two_ad_dof_plus_one_per_maximum_sigma, rescale_err, norm_loss;
const std::vector<double> &stored_complete_gamma_values, &stored_lower_incomplete_gamma_values;
int stored_incomplete_gamma_number_min1;
unsigned int stored_incomplete_gamma_number_min1;
public:
MagsacQualityImpl (double maximum_thr, int points_size_, const Ptr<Error> &error_,
const Ptr<GammaValues> &gamma_generator_,
double tentative_inlier_threshold_, int DoF, double sigma_quantile,
double upper_incomplete_of_sigma_quantile,
double lower_incomplete_of_sigma_quantile, double C_)
: error (error_), gamma_generator(GammaValues::getSingleton()), points_size(points_size_),
double upper_incomplete_of_sigma_quantile)
: error (error_), gamma_generator(gamma_generator_), points_size(points_size_),
maximum_threshold_sqr(maximum_thr*maximum_thr),
tentative_inlier_threshold(tentative_inlier_threshold_), degrees_of_freedom(DoF),
k(sigma_quantile), C(C_), gamma_value_of_k (upper_incomplete_of_sigma_quantile),
lower_gamma_value_of_k (lower_incomplete_of_sigma_quantile),
stored_complete_gamma_values(gamma_generator.getCompleteGammaValues()),
stored_lower_incomplete_gamma_values(gamma_generator.getIncompleteGammaValues())
{
tentative_inlier_threshold(tentative_inlier_threshold_),
gamma_value_of_k (upper_incomplete_of_sigma_quantile),
stored_complete_gamma_values (gamma_generator->getCompleteGammaValues()),
stored_lower_incomplete_gamma_values (gamma_generator->getIncompleteGammaValues()) {
previous_best_loss = std::numeric_limits<double>::max();
squared_k_per_2 = k * k / 2.0;
dof_minus_one_per_two = (degrees_of_freedom - 1.0) / 2.0;
dof_plus_one_per_two = (degrees_of_freedom + 1.0) / 2.0;
two_ad_dof_minus_one = std::pow(2.0, dof_minus_one_per_two);
two_ad_dof_plus_one = std::pow(2.0, dof_plus_one_per_two);
maximum_sigma = (float)sqrt(maximum_threshold_sqr) / (float) k;
maximum_sigma_2 = maximum_sigma * maximum_sigma;
const auto maximum_sigma = (float)sqrt(maximum_threshold_sqr) / sigma_quantile;
const auto maximum_sigma_2 = (float) (maximum_sigma * maximum_sigma);
maximum_sigma_2_per_2 = maximum_sigma_2 / 2.f;
maximum_sigma_2_times_2 = maximum_sigma_2 * 2.f;
two_ad_dof_plus_one_per_maximum_sigma = two_ad_dof_plus_one / maximum_sigma;
scale_of_stored_incomplete_gammas = gamma_generator.getScaleOfGammaCompleteValues();
stored_incomplete_gamma_number_min1 = gamma_generator.getTableSize()-1;
max_loss = 1e-10;
// MAGSAC maximum / minimum loss does not have to be in extrumum residuals
// make 50 iterations to find maximum loss
const auto maximum_sigma_2_times_2 = maximum_sigma_2 * 2.f;
two_ad_dof_plus_one_per_maximum_sigma = pow(2.0, (DoF + 1.0)*.5)/maximum_sigma;
rescale_err = gamma_generator->getScaleOfGammaCompleteValues() / maximum_sigma_2_times_2;
stored_incomplete_gamma_number_min1 = static_cast<unsigned int>(gamma_generator->getTableSize()-1);
double max_loss = 1e-10;
// MAGSAC maximum / minimum loss does not have to be in extremum residuals
// make 30 iterations to find maximum loss
const double step = maximum_threshold_sqr / 30;
double sqr_res = 0;
while (sqr_res < maximum_threshold_sqr) {
int x=(int)round(scale_of_stored_incomplete_gammas * sqr_res
/ maximum_sigma_2_times_2);
if (x >= stored_incomplete_gamma_number_min1 || x < 0 /*overflow*/)
x = stored_incomplete_gamma_number_min1;
auto x= static_cast<unsigned int>(rescale_err * sqr_res);
if (x > stored_incomplete_gamma_number_min1)
x = stored_incomplete_gamma_number_min1;
const double loss = two_ad_dof_plus_one_per_maximum_sigma * (maximum_sigma_2_per_2 *
stored_lower_incomplete_gamma_values[x] + sqr_res * 0.25 *
(stored_complete_gamma_values[x] - gamma_value_of_k));
@ -218,6 +218,7 @@ public:
max_loss = loss;
sqr_res += step;
}
norm_loss = two_ad_dof_plus_one_per_maximum_sigma / max_loss;
}
// https://github.com/danini/magsac
@ -225,26 +226,25 @@ public:
error->setModelParameters(model);
double total_loss = 0.0;
int num_tentative_inliers = 0;
const auto preemptive_thr = points_size + previous_best_loss;
for (int point_idx = 0; point_idx < points_size; point_idx++) {
const float squared_residual = error->getError(point_idx);
if (squared_residual < tentative_inlier_threshold)
num_tentative_inliers++;
if (squared_residual < maximum_threshold_sqr) { // consider point as inlier
// Get the position of the gamma value in the lookup table
int x=(int)round(scale_of_stored_incomplete_gammas * squared_residual
/ maximum_sigma_2_times_2);
auto x = static_cast<unsigned int>(rescale_err * squared_residual);
// If the sought gamma value is not stored in the lookup, return the closest element
if (x >= stored_incomplete_gamma_number_min1 || x < 0 /*overflow*/)
x = stored_incomplete_gamma_number_min1;
if (x > stored_incomplete_gamma_number_min1)
x = stored_incomplete_gamma_number_min1;
// Calculate the loss implied by the current point
total_loss -= (1 - two_ad_dof_plus_one_per_maximum_sigma * (maximum_sigma_2_per_2 *
total_loss -= (1 - (maximum_sigma_2_per_2 *
stored_lower_incomplete_gamma_values[x] + squared_residual * 0.25 *
(stored_complete_gamma_values[x] - gamma_value_of_k)) / max_loss);
}
if (total_loss - (points_size - point_idx) > previous_best_loss)
(stored_complete_gamma_values[x] - gamma_value_of_k)) * norm_loss);
} else if (total_loss + point_idx > preemptive_thr)
break;
}
return Score(num_tentative_inliers, total_loss);
return {num_tentative_inliers, total_loss};
}
Score getScore (const std::vector<float> &errors) const override {
@ -255,18 +255,15 @@ public:
if (squared_residual < tentative_inlier_threshold)
num_tentative_inliers++;
if (squared_residual < maximum_threshold_sqr) {
int x=(int)round(scale_of_stored_incomplete_gammas * squared_residual
/ maximum_sigma_2_times_2);
if (x >= stored_incomplete_gamma_number_min1 || x < 0 /*overflow*/)
x = stored_incomplete_gamma_number_min1;
total_loss -= (1 - two_ad_dof_plus_one_per_maximum_sigma * (maximum_sigma_2_per_2 *
auto x = static_cast<unsigned int>(rescale_err * squared_residual);
if (x > stored_incomplete_gamma_number_min1)
x = stored_incomplete_gamma_number_min1;
total_loss -= (1 - (maximum_sigma_2_per_2 *
stored_lower_incomplete_gamma_values[x] + squared_residual * 0.25 *
(stored_complete_gamma_values[x] - gamma_value_of_k)) / max_loss);
(stored_complete_gamma_values[x] - gamma_value_of_k)) * norm_loss);
}
if (total_loss - (points_size - point_idx) > previous_best_loss)
break;
}
return Score(num_tentative_inliers, total_loss);
return {num_tentative_inliers, total_loss};
}
void setBestScore (double best_loss) override {
@ -279,20 +276,16 @@ public:
{ return Quality::getInliers(error, model, inliers, thr); }
int getInliers (const Mat &model, std::vector<bool> &inliers_mask) const override
{ return Quality::getInliers(error, model, inliers_mask, tentative_inlier_threshold); }
double getThreshold () const override { return tentative_inlier_threshold; }
int getPointsSize () const override { return points_size; }
Ptr<Quality> clone () const override {
return makePtr<MagsacQualityImpl>(maximum_sigma, points_size, error->clone(),
tentative_inlier_threshold, degrees_of_freedom,
k, gamma_value_of_k, lower_gamma_value_of_k, C);
}
Ptr<Error> getErrorFnc () const override { return error; }
};
Ptr<MagsacQuality> MagsacQuality::create(double maximum_thr, int points_size_, const Ptr<Error> &error_,
const Ptr<GammaValues> &gamma_generator,
double tentative_inlier_threshold_, int DoF, double sigma_quantile,
double upper_incomplete_of_sigma_quantile,
double lower_incomplete_of_sigma_quantile, double C_) {
return makePtr<MagsacQualityImpl>(maximum_thr, points_size_, error_,
tentative_inlier_threshold_, DoF, sigma_quantile, upper_incomplete_of_sigma_quantile,
lower_incomplete_of_sigma_quantile, C_);
double upper_incomplete_of_sigma_quantile) {
return makePtr<MagsacQualityImpl>(maximum_thr, points_size_, error_, gamma_generator,
tentative_inlier_threshold_, DoF, sigma_quantile, upper_incomplete_of_sigma_quantile);
}
class LMedsQualityImpl : public LMedsQuality {
@ -312,7 +305,16 @@ public:
if (errors[point] < threshold)
inlier_number++;
// score is median of errors
return Score(inlier_number, Utils::findMedian (errors));
return {inlier_number, Utils::findMedian (errors)};
}
Score getScore (const std::vector<float> &errors_) const override {
std::vector<float> errors = errors_;
int inlier_number = 0;
for (int point = 0; point < points_size; point++)
if (errors[point] < threshold)
inlier_number++;
// score is median of errors
return {inlier_number, Utils::findMedian (errors)};
}
void setBestScore (double /*best_score*/) override {}
@ -324,10 +326,8 @@ public:
{ return Quality::getInliers(error, model, inliers, thr); }
int getInliers (const Mat &model, std::vector<bool> &inliers_mask) const override
{ return Quality::getInliers(error, model, inliers_mask, threshold); }
Ptr<Quality> clone () const override {
return makePtr<LMedsQualityImpl>(points_size, threshold, error->clone());
}
double getThreshold () const override { return threshold; }
Ptr<Error> getErrorFnc () const override { return error; }
};
Ptr<LMedsQuality> LMedsQuality::create(int points_size_, double threshold_, const Ptr<Error> &error_) {
return makePtr<LMedsQualityImpl>(points_size_, threshold_, error_);
@ -335,47 +335,53 @@ Ptr<LMedsQuality> LMedsQuality::create(int points_size_, double threshold_, cons
class ModelVerifierImpl : public ModelVerifier {
private:
std::vector<float> errors;
Ptr<Quality> quality;
public:
inline bool isModelGood(const Mat &/*model*/) override { return true; }
inline bool getScore(Score &/*score*/) const override { return false; }
void update (int /*highest_inlier_number*/) override {}
const std::vector<float> &getErrors() const override { return errors; }
bool hasErrors () const override { return false; }
Ptr<ModelVerifier> clone (int /*state*/) const override { return makePtr<ModelVerifierImpl>();}
ModelVerifierImpl (const Ptr<Quality> &q) : quality(q) {}
inline bool isModelGood(const Mat &model, Score &score) override {
score = quality->getScore(model);
return true;
}
void update (const Score &/*score*/, int /*iteration*/) override {}
void reset() override {}
void updateSPRT (double , double , double , double , double , const Score &) override {}
};
Ptr<ModelVerifier> ModelVerifier::create() {
return makePtr<ModelVerifierImpl>();
Ptr<ModelVerifier> ModelVerifier::create(const Ptr<Quality> &quality) {
return makePtr<ModelVerifierImpl>(quality);
}
///////////////////////////////////// SPRT VERIFIER //////////////////////////////////////////
class SPRTImpl : public SPRT {
class AdaptiveSPRTImpl : public AdaptiveSPRT {
private:
RNG rng;
const Ptr<Error> err;
const Ptr<Quality> quality;
const int points_size;
int highest_inlier_number, current_sprt_idx; // i
int highest_inlier_number, last_iteration;
// time t_M needed to instantiate a model hypothesis given a sample
// Let m_S be the number of models that are verified per sample
const double inlier_threshold, norm_thr, one_over_thr, t_M, m_S;
const double inlier_threshold, norm_thr, one_over_thr;
double lowest_sum_errors, current_epsilon, current_delta, current_A,
delta_to_epsilon, complement_delta_to_complement_epsilon;
// alpha is false negative rate, alpha = 1 / A
double t_M, lowest_sum_errors, current_epsilon, current_delta, current_A,
delta_to_epsilon, complement_delta_to_complement_epsilon,
time_ver_corr_sprt = 0, time_ver_corr = 0,
one_over_complement_alpha, avg_num_checked_pts;
std::vector<SPRT_history> sprt_histories;
std::vector<SPRT_history> sprt_histories, empty;
std::vector<int> points_random_pool;
std::vector<float> errors;
Score score;
bool do_sprt, adapt, IS_ADAPTIVE;
const ScoreMethod score_type;
bool last_model_is_good, can_compute_score, has_errors;
double m_S;
public:
SPRTImpl (int state, const Ptr<Error> &err_, int points_size_,
double inlier_threshold_, double prob_pt_of_good_model, double prob_pt_of_bad_model,
double time_sample, double avg_num_models, ScoreMethod score_type_) : rng(state), err(err_),
points_size(points_size_), inlier_threshold (inlier_threshold_),
norm_thr(inlier_threshold_*9/4), one_over_thr (1/norm_thr), t_M (time_sample),
m_S (avg_num_models), score_type (score_type_) {
AdaptiveSPRTImpl (int state, const Ptr<Quality> &quality_, int points_size_,
double inlier_threshold_, double prob_pt_of_good_model, double prob_pt_of_bad_model,
double time_sample, double avg_num_models, ScoreMethod score_type_,
double k_mlesac_, bool is_adaptive) : rng(state), err(quality_->getErrorFnc()),
quality(quality_), points_size(points_size_), inlier_threshold (quality->getThreshold()),
norm_thr(inlier_threshold_*k_mlesac_), one_over_thr (1/norm_thr), t_M (time_sample),
score_type (score_type_), m_S (avg_num_models) {
// Generate array of random points for randomized evaluation
points_random_pool = std::vector<int> (points_size_);
@ -387,19 +393,23 @@ public:
// reserve (approximately) some space for sprt vector.
sprt_histories.reserve(20);
createTest(prob_pt_of_good_model, prob_pt_of_bad_model);
highest_inlier_number = 0;
highest_inlier_number = last_iteration = 0;
lowest_sum_errors = std::numeric_limits<double>::max();
last_model_is_good = false;
can_compute_score = score_type_ == ScoreMethod::SCORE_METHOD_MSAC
|| score_type_ == ScoreMethod::SCORE_METHOD_RANSAC
|| score_type_ == ScoreMethod::SCORE_METHOD_LMEDS;
// for MSAC and RANSAC errors not needed
if (score_type_ != ScoreMethod::SCORE_METHOD_MSAC && score_type_ != ScoreMethod::SCORE_METHOD_RANSAC)
errors = std::vector<float>(points_size_);
// however return errors only if we can't compute score
has_errors = !can_compute_score;
if (score_type_ != ScoreMethod::SCORE_METHOD_MSAC)
errors = std::vector<float>(points_size_);
IS_ADAPTIVE = is_adaptive;
delta_to_epsilon = one_over_complement_alpha = complement_delta_to_complement_epsilon = current_A = -1;
avg_num_checked_pts = points_size_;
adapt = IS_ADAPTIVE;
do_sprt = !IS_ADAPTIVE;
if (IS_ADAPTIVE) {
// all these variables will be initialized later
current_epsilon = prob_pt_of_good_model;
current_delta = prob_pt_of_bad_model;
} else {
current_epsilon = current_delta = 1e-5;
createTest(prob_pt_of_good_model, prob_pt_of_bad_model);
}
}
/*
@ -421,153 +431,143 @@ public:
* @current_hypothesis: current RANSAC iteration
* Return: true if model is good, false - otherwise.
*/
inline bool isModelGood(const Mat& model) override
{
if (model.empty())
return false;
inline bool isModelGood (const Mat &model, Score &out_score) override {
// update error object with current model
err->setModelParameters(model);
double lambda = 1, sum_errors = 0;
last_model_is_good = true;
int random_pool_idx = rng.uniform(0, points_size), tested_point, tested_inliers = 0;
for (tested_point = 0; tested_point < points_size; tested_point++) {
if (random_pool_idx >= points_size)
random_pool_idx = 0;
const double error = err->getError (points_random_pool[random_pool_idx++]);
if (error < inlier_threshold) {
tested_inliers++;
lambda *= delta_to_epsilon;
} else {
lambda *= complement_delta_to_complement_epsilon;
// since delta is always higher than epsilon, then lambda can increase only
// when point is not consistent with model
if (lambda > current_A)
break;
}
bool last_model_is_good = true;
double sum_errors = 0;
int tested_inliers = 0;
if (! do_sprt || adapt) { // if adapt or not sprt then compute model score directly
out_score = quality->getScore(model);
tested_inliers = out_score.inlier_number;
sum_errors = out_score.score;
} else { // do sprt and not adapt
err->setModelParameters(model);
double lambda = 1;
int random_pool_idx = rng.uniform(0, points_size), tested_point;
if (score_type == ScoreMethod::SCORE_METHOD_MSAC) {
if (error < norm_thr)
sum_errors -= (1 - error * one_over_thr);
if (sum_errors - points_size + tested_point > lowest_sum_errors)
break;
} else if (score_type == ScoreMethod::SCORE_METHOD_RANSAC) {
if (tested_inliers + points_size - tested_point < highest_inlier_number)
break;
} else errors[points_random_pool[random_pool_idx-1]] = (float)error;
const auto preemptive_thr = points_size + lowest_sum_errors;
for (tested_point = 0; tested_point < points_size; tested_point++) {
if (random_pool_idx == points_size)
random_pool_idx = 0;
const float error = err->getError (points_random_pool[random_pool_idx++]);
if (error < inlier_threshold) {
tested_inliers++;
lambda *= delta_to_epsilon;
} else {
lambda *= complement_delta_to_complement_epsilon;
// since delta is always higher than epsilon, then lambda can increase only
// when point is not consistent with model
if (lambda > current_A)
break;
}
if (error < norm_thr)
sum_errors -= (1 - error * one_over_thr);
else if (sum_errors + tested_point > preemptive_thr)
break;
}
} else { // save errors into array here
for (tested_point = 0; tested_point < points_size; tested_point++) {
if (random_pool_idx == points_size)
random_pool_idx = 0;
const int pt = points_random_pool[random_pool_idx++];
const float error = err->getError (pt);
if (error < inlier_threshold) {
tested_inliers++;
lambda *= delta_to_epsilon;
} else {
lambda *= complement_delta_to_complement_epsilon;
if (lambda > current_A)
break;
}
errors[pt] = error;
}
}
last_model_is_good = tested_point == points_size;
}
last_model_is_good = tested_point == points_size;
// increase number of samples processed by current test
sprt_histories[current_sprt_idx].tested_samples++;
if (last_model_is_good) {
score.inlier_number = tested_inliers;
if (score_type == ScoreMethod::SCORE_METHOD_MSAC) {
score.score = sum_errors;
if (lowest_sum_errors > sum_errors)
lowest_sum_errors = sum_errors;
} else if (score_type == ScoreMethod::SCORE_METHOD_RANSAC)
score.score = -static_cast<double>(tested_inliers);
else if (score_type == ScoreMethod::SCORE_METHOD_LMEDS)
score.score = Utils::findMedian(errors);
const double new_epsilon = static_cast<double>(tested_inliers) / points_size;
if (new_epsilon > current_epsilon) {
highest_inlier_number = tested_inliers; // update max inlier number
/*
* Model accepted and the largest support so far:
* design (i+1)-th test (εi + 1= εˆ, δi+1 = δ, i := i + 1).
* Store the current model parameters θ
*/
createTest(new_epsilon, current_delta);
}
} else {
/*
* Since almost all tested models are bad, the probability
* δ can be estimated as the average fraction of consistent data points
* in rejected models.
*/
// add 1 to tested_point, because loop over tested_point starts from 0
const double delta_estimated = static_cast<double> (tested_inliers) / (tested_point+1);
if (delta_estimated > 0 && fabs(current_delta - delta_estimated)
/ current_delta > 0.05)
/*
* Model rejected: re-estimate δ. If the estimate δ_ differs
* from δi by more than 5% design (i+1)-th test (εi+1 = εi,
* δi+1 = δˆ, i := i + 1)
*/
createTest(current_epsilon, delta_estimated);
if (last_model_is_good && do_sprt) {
out_score.inlier_number = tested_inliers;
if (score_type == ScoreMethod::SCORE_METHOD_MSAC)
out_score.score = sum_errors;
else if (score_type == ScoreMethod::SCORE_METHOD_RANSAC)
out_score.score = -static_cast<double>(tested_inliers);
else out_score = quality->getScore(errors);
}
return last_model_is_good;
}
inline bool getScore (Score &score_) const override {
if (!last_model_is_good || !can_compute_score)
return false;
score_ = score;
return true;
}
bool hasErrors () const override { return has_errors; }
const std::vector<float> &getErrors () const override { return errors; }
const std::vector<SPRT_history> &getSPRTvector () const override { return sprt_histories; }
void update (int highest_inlier_number_) override {
const double new_epsilon = static_cast<double>(highest_inlier_number_) / points_size;
if (new_epsilon > current_epsilon) {
highest_inlier_number = highest_inlier_number_;
if (sprt_histories[current_sprt_idx].tested_samples == 0)
sprt_histories[current_sprt_idx].tested_samples = 1;
// save sprt test and create new one
createTest(new_epsilon, current_delta);
// update SPRT parameters = called only once inside usac
void updateSPRT (double time_model_est, double time_corr_ver, double new_avg_models, double new_delta, double new_epsilon, const Score &best_score) override {
if (adapt) {
adapt = false;
m_S = new_avg_models;
t_M = time_model_est / time_corr_ver;
time_ver_corr = time_corr_ver;
time_ver_corr_sprt = time_corr_ver * 1.05;
createTest(new_epsilon, new_delta);
highest_inlier_number = best_score.inlier_number;
lowest_sum_errors = best_score.score;
}
}
Ptr<ModelVerifier> clone (int state) const override {
return makePtr<SPRTImpl>(state, err->clone(), points_size, inlier_threshold,
sprt_histories[current_sprt_idx].epsilon,
sprt_histories[current_sprt_idx].delta, t_M, m_S, score_type);
const std::vector<SPRT_history> &getSPRTvector () const override { return adapt ? empty : sprt_histories; }
void update (const Score &score, int iteration) override {
if (adapt || highest_inlier_number > score.inlier_number)
return;
if (sprt_histories.size() == 1 && sprt_histories.back().tested_samples == 0)
sprt_histories.back().tested_samples = iteration;
else if (! sprt_histories.empty())
sprt_histories.back().tested_samples += iteration - last_iteration;
SPRT_history new_sprt_history;
new_sprt_history.epsilon = (double)score.inlier_number / points_size;
highest_inlier_number = score.inlier_number;
lowest_sum_errors = score.score;
createTest(static_cast<double>(highest_inlier_number) / points_size, current_delta);
new_sprt_history.delta = current_delta;
new_sprt_history.A = current_A;
sprt_histories.emplace_back(new_sprt_history);
last_iteration = iteration;
}
int avgNumCheckedPts () const override { return do_sprt ? (int)avg_num_checked_pts + 1 : points_size; }
void reset() override {
adapt = true;
do_sprt = false;
highest_inlier_number = last_iteration = 0;
lowest_sum_errors = DBL_MAX;
sprt_histories.clear();
}
private:
// Saves sprt test to sprt history and update current epsilon, delta and threshold.
void createTest (double epsilon, double delta) {
// Update current epsilon, delta and threshold (A).
bool createTest (double epsilon, double delta) {
if (fabs(current_epsilon - epsilon) < FLT_EPSILON && fabs(current_delta - delta) < FLT_EPSILON)
return false;
// if epsilon is closed to 1 then set them to 0.99 to avoid numerical problems
if (epsilon > 0.999999) epsilon = 0.999;
// delta can't be higher than epsilon, because ratio delta / epsilon will be greater than 1
if (epsilon < delta) delta = epsilon-0.0001;
if (epsilon < delta) delta = epsilon-0.001;
// avoid delta going too high as it is very unlikely
// e.g., 30% of points are consistent with bad model is not very real
if (delta > 0.3) delta = 0.3;
SPRT_history new_sprt_history;
new_sprt_history.epsilon = epsilon;
new_sprt_history.delta = delta;
new_sprt_history.A = estimateThresholdA (epsilon, delta);
sprt_histories.emplace_back(new_sprt_history);
current_A = new_sprt_history.A;
const auto AC = estimateThresholdA (epsilon, delta);
current_A = AC.first;
const auto C = AC.second;
current_delta = delta;
current_epsilon = epsilon;
one_over_complement_alpha = 1 / (1 - 1 / current_A);
delta_to_epsilon = delta / epsilon;
complement_delta_to_complement_epsilon = (1 - delta) / (1 - epsilon);
current_sprt_idx = static_cast<int>(sprt_histories.size()) - 1;
}
/*
* A(0) = K1/K2 + 1
* A(n+1) = K1/K2 + 1 + log (A(n))
* K1 = t_M / P_g
* K2 = m_S/(P_g*C)
* t_M is time needed to instantiate a model hypotheses given a sample
* P_g = epsilon ^ m, m is the number of data point in the Ransac sample.
* m_S is the number of models that are verified per sample.
* p (0|Hb) p (1|Hb)
* C = p(0|Hb) log (---------) + p(1|Hb) log (---------)
* p (0|Hg) p (1|Hg)
*/
double estimateThresholdA (double epsilon, double delta) {
const double C = (1 - delta) * log ((1 - delta) / (1 - epsilon)) +
delta * (log(delta / epsilon));
if (IS_ADAPTIVE) {
avg_num_checked_pts = std::min((log(current_A) / C) * one_over_complement_alpha, (double)points_size);
do_sprt = time_ver_corr_sprt * avg_num_checked_pts < time_ver_corr * points_size;
}
return true;
}
std::pair<double,double> estimateThresholdA (double epsilon, double delta) {
const double C = (1 - delta) * log ((1 - delta) / (1 - epsilon)) + delta * log (delta / epsilon);
// K = K1/K2 + 1 = (t_M / P_g) / (m_S / (C * P_g)) + 1 = (t_M * C)/m_S + 1
const double K = t_M * C / m_S + 1;
double An, An_1 = K;
@ -579,13 +579,13 @@ private:
break;
An_1 = An;
}
return An;
return std::make_pair(An, C);
}
};
Ptr<SPRT> SPRT::create (int state, const Ptr<Error> &err_, int points_size_,
double inlier_threshold_, double prob_pt_of_good_model, double prob_pt_of_bad_model,
double time_sample, double avg_num_models, ScoreMethod score_type_) {
return makePtr<SPRTImpl>(state, err_, points_size_, inlier_threshold_,
prob_pt_of_good_model, prob_pt_of_bad_model, time_sample, avg_num_models, score_type_);
Ptr<AdaptiveSPRT> AdaptiveSPRT::create (int state, const Ptr<Quality> &quality, int points_size_,
double inlier_threshold_, double prob_pt_of_good_model, double prob_pt_of_bad_model,
double time_sample, double avg_num_models, ScoreMethod score_type_, double k_mlesac, bool is_adaptive) {
return makePtr<AdaptiveSPRTImpl>(state, quality, points_size_, inlier_threshold_,
prob_pt_of_good_model, prob_pt_of_bad_model, time_sample, avg_num_models, score_type_, k_mlesac, is_adaptive);
}
}}

File diff suppressed because it is too large Load Diff

View File

@ -40,9 +40,6 @@ public:
points_random_pool[--random_pool_size]);
}
}
Ptr<Sampler> clone (int state) const override {
return makePtr<UniformSamplerImpl>(state, sample_size, points_size);
}
private:
void setPointsSize (int points_size_) {
CV_Assert (sample_size <= points_size_);
@ -143,10 +140,6 @@ public:
points_size = points_size_;
initialize ();
}
Ptr<Sampler> clone (int state) const override {
return makePtr<ProsacSimpleSamplerImpl>(state, points_size, sample_size,
max_prosac_samples_count);
}
private:
void initialize () {
largest_sample_size = points_size; // termination length, n*
@ -266,15 +259,19 @@ public:
// Choice of the hypothesis generation set
// if (t = T'_n) & (n < n*) then n = n + 1 (eqn. 4)
if (kth_sample_number == growth_function[subset_size-1] && subset_size < termination_length)
if (kth_sample_number >= growth_function[subset_size-1] && subset_size < termination_length)
subset_size++;
// Semi-random sample M_t of size m
// if T'n < t then
if (growth_function[subset_size-1] < kth_sample_number) {
// The sample contains m-1 points selected from U_(n-1) at random and u_n
random_gen->generateUniqueRandomSet(sample, sample_size-1, subset_size-1);
sample[sample_size-1] = subset_size-1;
if (subset_size >= termination_length) {
random_gen->generateUniqueRandomSet(sample, sample_size, subset_size);
} else {
// The sample contains m-1 points selected from U_(n-1) at random and u_n
random_gen->generateUniqueRandomSet(sample, sample_size-1, subset_size-1);
sample[sample_size-1] = subset_size-1;
}
} else {
// Select m points from U_n at random.
random_gen->generateUniqueRandomSet(sample, sample_size, subset_size);
@ -306,10 +303,6 @@ public:
CV_Error(cv::Error::StsError, "Changing points size in PROSAC requires to change also "
"termination criteria! Use PROSAC simpler version");
}
Ptr<Sampler> clone (int state) const override {
return makePtr<ProsacSamplerImpl>(state, points_size, sample_size,
growth_max_samples);
}
};
Ptr<ProsacSampler> ProsacSampler::create(int state, int points_size_, int sample_size_,
@ -465,10 +458,6 @@ public:
CV_Error(cv::Error::StsError, "Changing points size requires changing neighborhood graph! "
"You must reinitialize P-NAPSAC!");
}
Ptr<Sampler> clone (int state) const override {
return makePtr<ProgressiveNapsacImpl>(state, points_size, sample_size, *layers,
sampler_length);
}
};
Ptr<ProgressiveNapsac> ProgressiveNapsac::create(int state, int points_size_, int sample_size_,
const std::vector<Ptr<NeighborhoodGraph>> &layers, int sampler_length_) {
@ -537,9 +526,6 @@ public:
CV_Error(cv::Error::StsError, "Changing points size requires changing neighborhood graph!"
" You must reinitialize NAPSAC!");
}
Ptr<Sampler> clone (int state) const override {
return makePtr<NapsacSamplerImpl>(state, points_size, sample_size, neighborhood_graph);
}
};
Ptr<NapsacSampler> NapsacSampler::create(int state, int points_size_, int sample_size_,
const Ptr<NeighborhoodGraph> &neighborhood_graph_) {

View File

@ -28,7 +28,8 @@ public:
* (1 - w^n) is probability that at least one point of N is outlier.
* 1 - p = (1-w^n)^k is probability that in K steps of getting at least one outlier is 1% (5%).
*/
int update (const Mat &/*model*/, int inlier_number) override {
int update (const Mat &model, int inlier_number) const override {
CV_UNUSED(model);
const double predicted_iters = log_confidence / log(1 - std::pow
(static_cast<double>(inlier_number) / points_size, sample_size));
@ -40,9 +41,11 @@ public:
return MAX_ITERATIONS;
}
Ptr<TerminationCriteria> clone () const override {
return makePtr<StandardTerminationCriteriaImpl>(1-exp(log_confidence), points_size,
sample_size, MAX_ITERATIONS);
static int getMaxIterations (int inlier_number, int sample_size, int points_size, double conf) {
const double pred_iters = log(1 - conf) / log(1 - pow(static_cast<double>(inlier_number)/points_size, sample_size));
if (std::isinf(pred_iters))
return INT_MAX;
return (int) pred_iters + 1;
}
};
Ptr<StandardTerminationCriteria> StandardTerminationCriteria::create(double confidence,
@ -54,13 +57,13 @@ Ptr<StandardTerminationCriteria> StandardTerminationCriteria::create(double conf
/////////////////////////////////////// SPRT TERMINATION //////////////////////////////////////////
class SPRTTerminationImpl : public SPRTTermination {
private:
const std::vector<SPRT_history> &sprt_histories;
const Ptr<AdaptiveSPRT> sprt;
const double log_eta_0;
const int points_size, sample_size, MAX_ITERATIONS;
public:
SPRTTerminationImpl (const std::vector<SPRT_history> &sprt_histories_, double confidence,
SPRTTerminationImpl (const Ptr<AdaptiveSPRT> &sprt_, double confidence,
int points_size_, int sample_size_, int max_iterations_)
: sprt_histories (sprt_histories_), log_eta_0(log(1-confidence)),
: sprt (sprt_), log_eta_0(log(1-confidence)),
points_size (points_size_), sample_size (sample_size_),MAX_ITERATIONS(max_iterations_){}
/*
@ -80,9 +83,11 @@ public:
* this equation does not have to be evaluated before nR < n0
* nR = (1 - P_g)^k
*/
int update (const Mat &/*model*/, int inlier_size) override {
if (sprt_histories.empty())
return std::min(MAX_ITERATIONS, getStandardUpperBound(inlier_size));
int update (const Mat &model, int inlier_size) const override {
CV_UNUSED(model);
const auto &sprt_histories = sprt->getSPRTvector();
if (sprt_histories.size() <= 1)
return getStandardUpperBound(inlier_size);
const double epsilon = static_cast<double>(inlier_size) / points_size; // inlier probability
const double P_g = pow (epsilon, sample_size); // probability of good sample
@ -90,23 +95,21 @@ public:
double log_eta_lmin1 = 0;
int total_number_of_tested_samples = 0;
const int sprts_size_min1 = static_cast<int>(sprt_histories.size())-1;
if (sprts_size_min1 < 0) return getStandardUpperBound(inlier_size);
// compute log n(l-1), l is number of tests
for (int test = 0; test < sprts_size_min1; test++) {
log_eta_lmin1 += log (1 - P_g * (1 - pow (sprt_histories[test].A,
-computeExponentH(sprt_histories[test].epsilon, epsilon,sprt_histories[test].delta))))
* sprt_histories[test].tested_samples;
total_number_of_tested_samples += sprt_histories[test].tested_samples;
for (const auto &test : sprt_histories) {
if (test.tested_samples == 0) continue;
log_eta_lmin1 += log (1 - P_g * (1 - pow (test.A,
-computeExponentH(test.epsilon, epsilon,test.delta)))) * test.tested_samples;
total_number_of_tested_samples += test.tested_samples;
}
// Implementation note: since η > ηR the equation (9) does not have to be evaluated
// before ηR < η0 is satisfied.
if (std::pow(1 - P_g, total_number_of_tested_samples) < log_eta_0)
return std::min(MAX_ITERATIONS, getStandardUpperBound(inlier_size));
return getStandardUpperBound(inlier_size);
// use decision threshold A for last test (l-th)
const double predicted_iters_sprt = (log_eta_0 - log_eta_lmin1) /
log (1 - P_g * (1 - 1 / sprt_histories[sprts_size_min1].A)); // last A
const double predicted_iters_sprt = total_number_of_tested_samples + (log_eta_0 - log_eta_lmin1) /
log (1 - P_g * (1 - 1 / sprt_histories.back().A)); // last A
if (std::isnan(predicted_iters_sprt) || std::isinf(predicted_iters_sprt))
return getStandardUpperBound(inlier_size);
@ -117,11 +120,6 @@ public:
getStandardUpperBound(inlier_size));
return getStandardUpperBound(inlier_size);
}
Ptr<TerminationCriteria> clone () const override {
return makePtr<SPRTTerminationImpl>(sprt_histories, 1-exp(log_eta_0), points_size,
sample_size, MAX_ITERATIONS);
}
private:
inline int getStandardUpperBound(int inlier_size) const {
const double predicted_iters = log_eta_0 / log(1 - std::pow
@ -157,9 +155,9 @@ private:
return h;
}
};
Ptr<SPRTTermination> SPRTTermination::create(const std::vector<SPRT_history> &sprt_histories_,
Ptr<SPRTTermination> SPRTTermination::create(const Ptr<AdaptiveSPRT> &sprt_,
double confidence, int points_size_, int sample_size_, int max_iterations_) {
return makePtr<SPRTTerminationImpl>(sprt_histories_, confidence, points_size_, sample_size_,
return makePtr<SPRTTerminationImpl>(sprt_, confidence, points_size_, sample_size_,
max_iterations_);
}
@ -167,21 +165,20 @@ Ptr<SPRTTermination> SPRTTermination::create(const std::vector<SPRT_history> &sp
class SPRTPNapsacTerminationImpl : public SPRTPNapsacTermination {
private:
SPRTTerminationImpl sprt_termination;
const std::vector<SPRT_history> &sprt_histories;
const double relax_coef, log_confidence;
const int points_size, sample_size, MAX_ITERS;
public:
SPRTPNapsacTerminationImpl (const std::vector<SPRT_history> &sprt_histories_,
SPRTPNapsacTerminationImpl (const Ptr<AdaptiveSPRT> &sprt,
double confidence, int points_size_, int sample_size_,
int max_iterations_, double relax_coef_)
: sprt_termination (sprt_histories_, confidence, points_size_, sample_size_,
max_iterations_), sprt_histories (sprt_histories_),
: sprt_termination (sprt, confidence, points_size_, sample_size_,
max_iterations_),
relax_coef (relax_coef_), log_confidence(log(1-confidence)),
points_size (points_size_), sample_size (sample_size_),
MAX_ITERS (max_iterations_) {}
int update (const Mat &model, int inlier_number) override {
int update (const Mat &model, int inlier_number) const override {
int predicted_iterations = sprt_termination.update(model, inlier_number);
const double inlier_prob = static_cast<double>(inlier_number) / points_size + relax_coef;
@ -192,52 +189,41 @@ public:
if (! std::isinf(predicted_iters) && predicted_iters < predicted_iterations)
return static_cast<int>(predicted_iters);
return predicted_iterations;
}
Ptr<TerminationCriteria> clone () const override {
return makePtr<SPRTPNapsacTerminationImpl>(sprt_histories, 1-exp(log_confidence),
points_size, sample_size, MAX_ITERS, relax_coef);
return std::min(MAX_ITERS, predicted_iterations);
}
};
Ptr<SPRTPNapsacTermination> SPRTPNapsacTermination::create(const std::vector<SPRT_history>&
sprt_histories_, double confidence, int points_size_, int sample_size_,
Ptr<SPRTPNapsacTermination> SPRTPNapsacTermination::create(const Ptr<AdaptiveSPRT> &
sprt, double confidence, int points_size_, int sample_size_,
int max_iterations_, double relax_coef_) {
return makePtr<SPRTPNapsacTerminationImpl>(sprt_histories_, confidence, points_size_,
return makePtr<SPRTPNapsacTerminationImpl>(sprt, confidence, points_size_,
sample_size_, max_iterations_, relax_coef_);
}
////////////////////////////////////// PROSAC TERMINATION /////////////////////////////////////////
////////////////////////////////////// PROSAC TERMINATION /////////////////////////////////////////
class ProsacTerminationCriteriaImpl : public ProsacTerminationCriteria {
private:
const double log_confidence, beta, non_randomness_phi, inlier_threshold;
const double log_conf, beta, non_randomness_phi, inlier_threshold;
const int MAX_ITERATIONS, points_size, min_termination_length, sample_size;
const Ptr<ProsacSampler> sampler;
std::vector<int> non_random_inliers;
const Ptr<Error> error;
public:
ProsacTerminationCriteriaImpl (const Ptr<Error> &error_, int points_size_,int sample_size_,
double confidence, int max_iterations, int min_termination_length_, double beta_,
double non_randomness_phi_, double inlier_threshold_) : log_confidence
(log(1-confidence)), beta(beta_), non_randomness_phi(non_randomness_phi_),
inlier_threshold(inlier_threshold_), MAX_ITERATIONS(max_iterations),
points_size (points_size_), min_termination_length (min_termination_length_),
sample_size(sample_size_), error (error_) { init(); }
ProsacTerminationCriteriaImpl (const Ptr<ProsacSampler> &sampler_,const Ptr<Error> &error_,
int points_size_, int sample_size_, double confidence, int max_iterations,
int min_termination_length_, double beta_, double non_randomness_phi_,
double inlier_threshold_) : log_confidence(log(1-confidence)), beta(beta_),
double inlier_threshold_, const std::vector<int> &non_rand_inliers) : log_conf(log(1-confidence)), beta(beta_),
non_randomness_phi(non_randomness_phi_), inlier_threshold(inlier_threshold_),
MAX_ITERATIONS(max_iterations), points_size (points_size_),
min_termination_length (min_termination_length_), sample_size(sample_size_),
sampler(sampler_), error (error_) { init(); }
sampler(sampler_), error (error_) {
CV_Assert(min_termination_length_ <= points_size_ && min_termination_length_ >= 0);
if (non_rand_inliers.empty())
init();
else non_random_inliers = non_rand_inliers;
}
void init () {
// m is sample_size
// N is points_size
// m is sample_size, N is points_size
// non-randomness constraint
// The non-randomness requirement prevents PROSAC
// from selecting a solution supported by outliers that are
@ -245,20 +231,15 @@ public:
// checked ex-post in standard approaches [1]. The distribution
// of the cardinalities of sets of random inliers is binomial
// i-th entry - inlier counts for termination up to i-th point (term length = i+1)
// ------------------------------------------------------------------------
// initialize the data structures that determine stopping
// see probabilities description below.
non_random_inliers = std::vector<int>(points_size, 0);
std::vector<double> pn_i_arr(points_size);
std::vector<double> pn_i_arr(points_size, 0);
const double beta2compl_beta = beta / (1-beta);
const int step_n = 50, max_n = std::min(points_size, 1200);
for (int n = sample_size; n <= points_size; n+=step_n) {
if (n > max_n) {
// skip expensive calculation
break;
}
for (int n = sample_size; n < points_size; n+=step_n) {
if (n > max_n) break; // skip expensive calculation
// P^R_n(i) = β^(im) (1β)^(ni+m) (nm im). (7) i = m,...,N
// initial value for i = m = sample_size
@ -288,7 +269,7 @@ public:
non_random_inliers[n-1] = i_min;
}
// approximate values of binomial distribution
// approximate values of binomial distribution using linear interpolation
for (int n = sample_size; n <= points_size; n+=step_n) {
if (n-1+step_n >= max_n) {
// copy rest of the values
@ -301,19 +282,24 @@ public:
non_random_inliers[n+i] = (int)(non_rand_n + (i+1)*step);
}
}
const std::vector<int> &getNonRandomInliers () const override { return non_random_inliers; }
/*
* The PROSAC algorithm terminates if the number of inliers I_n*
* within the set U_n* satisfies the following conditions:
*
* non-randomness the probability that I_n* out of n* (termination_length)
* non-randomness the probability that I_n* out of n* (termination_length)
* data points are by chance inliers to an arbitrary incorrect model
* is smaller than Ψ (typically set to 5%)
* is smaller than Sigma (typically set to 5%)
*
* maximality the probability that a solution with more than
* maximality the probability that a solution with more than
* In* inliers in U_n* exists and was not found after k
* samples is smaller than η0 (typically set to 5%).
* samples is smaller than eta_0 (typically set to 5%).
*/
int update (const Mat &model, int inliers_size) override {
int update (const Mat &model, int inliers_size) const override {
int t; return updateTerminationLength(model, inliers_size, t);
}
int updateTerminationLength (const Mat &model, int inliers_size, int &found_termination_length) const override {
found_termination_length = points_size;
int predicted_iterations = MAX_ITERATIONS;
/*
* The termination length n* is chosen to minimize k_n*(η0) subject to I_n* I_min n*;
@ -327,23 +313,22 @@ public:
for (int pt = 0; pt < min_termination_length; pt++)
if (errors[pt] < inlier_threshold)
num_inliers_under_termination_len++;
for (int termination_len = min_termination_length; termination_len < points_size;termination_len++){
if (errors[termination_len /* = point*/] < inlier_threshold) {
num_inliers_under_termination_len++;
// non-random constraint must satisfy I_n* ≥ I_min n*.
if (num_inliers_under_termination_len < non_random_inliers[termination_len])
if (num_inliers_under_termination_len < non_random_inliers[termination_len] || (double) num_inliers_under_termination_len/(points_size) < 0.2)
continue;
// add 1 to termination length since num_inliers_under_termination_len is updated
const double new_max_samples = log_confidence / log(1 -
std::pow(static_cast<double>(num_inliers_under_termination_len)
const double new_max_samples = log_conf/log(1-pow(static_cast<double>(num_inliers_under_termination_len)
/ (termination_len+1), sample_size));
if (! std::isinf(new_max_samples) && predicted_iterations > new_max_samples) {
predicted_iterations = static_cast<int>(new_max_samples);
if (predicted_iterations == 0) break;
found_termination_length = termination_len;
if (sampler != nullptr)
sampler->setTerminationLength(termination_len);
}
@ -352,27 +337,22 @@ public:
// compare also when termination length = points_size,
// so inliers under termination length is total number of inliers:
const double predicted_iters = log_confidence / log(1 - std::pow
const double predicted_iters = log_conf / log(1 - std::pow
(static_cast<double>(inliers_size) / points_size, sample_size));
if (! std::isinf(predicted_iters) && predicted_iters < predicted_iterations)
return static_cast<int>(predicted_iters);
return predicted_iterations;
}
Ptr<TerminationCriteria> clone () const override {
return makePtr<ProsacTerminationCriteriaImpl>(error->clone(),
points_size, sample_size, 1-exp(log_confidence), MAX_ITERATIONS,
min_termination_length, beta, non_randomness_phi, inlier_threshold);
}
};
Ptr<ProsacTerminationCriteria>
ProsacTerminationCriteria::create(const Ptr<ProsacSampler> &sampler, const Ptr<Error> &error,
int points_size_, int sample_size_, double confidence, int max_iterations,
int min_termination_length_, double beta, double non_randomness_phi, double inlier_thresh) {
int min_termination_length_, double beta, double non_randomness_phi, double inlier_thresh,
const std::vector<int> &non_rand_inliers) {
return makePtr<ProsacTerminationCriteriaImpl> (sampler, error, points_size_, sample_size_,
confidence, max_iterations, min_termination_length_,
beta, non_randomness_phi, inlier_thresh);
beta, non_randomness_phi, inlier_thresh, non_rand_inliers);
}
}}

View File

@ -8,9 +8,234 @@
#include <map>
namespace cv { namespace usac {
double Utils::getCalibratedThreshold (double threshold, const Matx33d &K1, const Matx33d &K2) {
return threshold / ((K1(0, 0) + K1(1, 1) +
K2(0, 0) + K2(1, 1)) / 4.0);
/*
SolvePoly is used to find only real roots of N-degree polynomial using Sturm sequence.
It recursively finds interval where a root lies, and the actual root is found using Regula-Falsi method.
*/
class SolvePoly : public SolverPoly {
private:
static int sgn(double val) {
return (double(0) < val) - (val < double(0));
}
class Poly {
public:
Poly () = default;
Poly (const std::vector<double> &coef_) {
coef = coef_;
checkDegree();
}
Poly (const Poly &p) { coef = p.coef; }
// a_n x^n + a_n-1 x^(n-1) + ... + a_1 x + a_0
// coef[i] = a_i
std::vector<double> coef = {0};
inline int degree() const { return (int)coef.size()-1; }
void multiplyScalar (double s) {
// multiplies polynom by scalar
if (fabs(s) < DBL_EPSILON) { // check if scalar is 0
coef = {0};
return;
}
for (double &c : coef) c *= s;
}
void checkDegree() {
int deg = degree(); // approximate degree
// check if coefficients of the highest power is non-zero
while (fabs(coef[deg]) < DBL_EPSILON) {
coef.pop_back(); // remove last zero element
if (--deg == 0)
break;
}
}
double eval (double x) const {
// Horner method a0 + x (a1 + x (a2 + x (a3 + ... + x (an-1 + x an))))
const int d = degree();
double y = coef[d];
for (int i = d; i >= 1; i--)
y = coef[i-1] + x * y;
return y;
}
// at +inf and -inf
std::pair<int,int> signsAtInf () const {
// lim x->+-inf p(x) = lim x->+-inf a_n x^n
const int d = degree();
const int s = sgn(coef[d]); // sign of the highest coefficient
// compare even and odd degree
return std::make_pair(s, d % 2 == 0 ? s : -s);
}
Poly derivative () const {
Poly deriv;
if (degree() == 0)
return deriv;
// derive.degree = poly.degree-1;
deriv.coef = std::vector<double>(coef.size()-1);
for (int i = degree(); i > 0; i--)
// (a_n * x^n)' = n * a_n * x^(n-1)
deriv.coef[i-1] = i * coef[i];
return deriv;
}
void copyFrom (const Poly &p) { coef = p.coef; }
};
// return remainder
static void dividePoly (const Poly &p1, const Poly &p2, /*Poly &quotient,*/ Poly &remainder) {
remainder.copyFrom(p1);
int p2_degree = p2.degree(), remainder_degree = remainder.degree();
if (p1.degree() < p2_degree)
return;
if (p2_degree == 0) { // special case for dividing polynomial by constant
remainder.multiplyScalar(1/p2.coef[0]);
// quotient.coef[0] = p2.coef[0];
return;
}
// quotient.coef = std::vector<double>(p1.degree() - p2_degree + 1, 0);
const double p2_term = 1/p2.coef[p2_degree];
while (remainder_degree >= p2_degree) {
const double temp = remainder.coef[remainder_degree] * p2_term;
// quotient.coef[remainder_degree-p2_degree] = temp;
// polynoms now have the same degree, but p2 is shorter than remainder
for (int i = p2_degree, j = remainder_degree; i >= 0; i--, j--)
remainder.coef[j] -= temp * p2.coef[i];
remainder.checkDegree();
remainder_degree = remainder.degree();
}
}
constexpr static int REGULA_FALSI_MAX_ITERS = 500, MAX_POWER = 10, MAX_LEVEL = 200;
constexpr static double TOLERANCE = 1e-10, DIFF_TOLERANCE = 1e-7;
static bool findRootRegulaFalsi (const Poly &poly, double min, double max, double &root) {
double f_min = poly.eval(min), f_max = poly.eval(max);
if (f_min * f_max > 0 || min > max) {// conditions are not fulfilled
return false;
}
int sign = 0, iter = 0;
for (; iter < REGULA_FALSI_MAX_ITERS; iter++) {
root = (f_min * max - f_max * min) / (f_min - f_max);
const double f_root = poly.eval(root);
if (fabs(f_root) < TOLERANCE || fabs(min - max) < DIFF_TOLERANCE) {
return true; // root is found
}
if (f_root * f_max > 0) {
max = root; f_max = f_root;
if (sign == -1)
f_min *= 0.5;
sign = -1;
} else if (f_min * f_root > 0) {
min = root; f_min = f_root;
if (sign == 1)
f_max *= 0.5;
sign = 1;
}
}
return false;
}
static int numberOfSignChanges (const std::vector<Poly> &sturm, double x) {
int prev_sign = 0, sign_changes = 0;
for (const auto &poly : sturm) {
const int s = sgn(poly.eval(x));
if (s != 0 && prev_sign != 0 && s != prev_sign)
sign_changes++;
prev_sign = s;
}
return sign_changes;
}
static void findRootsRecursive (const Poly &poly, const std::vector<Poly> &sturm, double min, double max,
int sign_changes_at_min, int sign_changes_at_max, std::vector<double> &roots, int level) {
const int num_roots = sign_changes_at_min - sign_changes_at_max;
if (level == MAX_LEVEL) {
// roots are too close
const double mid = (min + max) * 0.5;
if (fabs(poly.eval(mid)) < DBL_EPSILON) {
roots.emplace_back(mid);
}
} else if (num_roots == 1) {
double root;
if (findRootRegulaFalsi(poly, min, max, root)) {
roots.emplace_back(root);
}
} else if (num_roots > 1) { // at least 2 roots
const double mid = (min + max) * 0.5;
const int sign_changes_at_mid = numberOfSignChanges(sturm, mid);
// try to split interval equally for the roots
if (sign_changes_at_min - sign_changes_at_mid > 0)
findRootsRecursive(poly, sturm, min, mid, sign_changes_at_min, sign_changes_at_mid, roots, level+1);
if (sign_changes_at_mid - sign_changes_at_max > 0)
findRootsRecursive(poly, sturm, mid, max, sign_changes_at_mid, sign_changes_at_max, roots, level+1);
}
}
public:
int getRealRoots (const std::vector<double> &coeffs, std::vector<double> &real_roots) override {
if (coeffs.empty())
return 0;
Poly input(coeffs);
if (input.degree() < 1)
return 0;
// derivative of input polynomial
const Poly input_der = input.derivative();
/////////// build Sturm sequence //////////
Poly p (input), q (input_der), remainder;
std::vector<std::pair<int,int>> signs_at_inf; signs_at_inf.reserve(p.degree()); // +inf, -inf pair
signs_at_inf.emplace_back(p.signsAtInf());
signs_at_inf.emplace_back(q.signsAtInf());
std::vector<Poly> sturm_sequence; sturm_sequence.reserve(input.degree());
sturm_sequence.emplace_back(input);
sturm_sequence.emplace_back(input_der);
while (q.degree() > 0) {
dividePoly(p, q, remainder);
remainder.multiplyScalar(-1);
p.copyFrom(q);
q.copyFrom(remainder);
sturm_sequence.emplace_back(remainder);
signs_at_inf.emplace_back(remainder.signsAtInf());
}
////////// find changes in signs of Sturm sequence /////////
int num_sign_changes_at_pos_inf = 0, num_sign_changes_at_neg_inf = 0;
int prev_sign_pos_inf = signs_at_inf[0].first, prev_sign_neg_inf = signs_at_inf[0].second;
for (int i = 1; i < (int)signs_at_inf.size(); i++) {
const auto s_pos_inf = signs_at_inf[i].first, s_neg_inf = signs_at_inf[i].second;
// zeros must be ignored
if (s_pos_inf != 0) {
if (prev_sign_pos_inf != 0 && prev_sign_pos_inf != s_pos_inf)
num_sign_changes_at_pos_inf++;
prev_sign_pos_inf = s_pos_inf;
}
if (s_neg_inf != 0) {
if (prev_sign_neg_inf != 0 && prev_sign_neg_inf != s_neg_inf)
num_sign_changes_at_neg_inf++;
prev_sign_neg_inf = s_neg_inf;
}
}
////////// find roots' bounds for numerical method for roots finding /////////
double root_neg_bound = -0.01, root_pos_bound = 0.01;
int num_sign_changes_min_x = -1, num_sign_changes_pos_x = -1; // -1 = unknown, trigger next if condition
for (int i = 0; i < MAX_POWER; i++) {
if (num_sign_changes_min_x != num_sign_changes_at_neg_inf) {
root_neg_bound *= 10;
num_sign_changes_min_x = numberOfSignChanges(sturm_sequence, root_neg_bound);
}
if (num_sign_changes_pos_x != num_sign_changes_at_pos_inf) {
root_pos_bound *= 10;
num_sign_changes_pos_x = numberOfSignChanges(sturm_sequence, root_pos_bound);
}
}
/////////// get real roots //////////
real_roots.clear();
findRootsRecursive(input, sturm_sequence, root_neg_bound, root_pos_bound, num_sign_changes_min_x, num_sign_changes_pos_x, real_roots, 0 /*level*/);
///////////////////////////////
if ((int)real_roots.size() > input.degree())
real_roots.resize(input.degree()); // must not happen, unless some roots repeat
return (int) real_roots.size();
}
};
Ptr<SolverPoly> SolverPoly::create() {
return makePtr<SolvePoly>();
}
double Utils::getCalibratedThreshold (double threshold, const Mat &K1, const Mat &K2) {
const auto * const k1 = (double *) K1.data, * const k2 = (double *) K2.data;
return threshold / ((k1[0] + k1[4] + k2[0] + k2[4]) / 4.0);
}
/*
@ -20,20 +245,22 @@ double Utils::getCalibratedThreshold (double threshold, const Matx33d &K1, const
* 0 k22 k23
* 0 0 1]
*/
void Utils::calibratePoints (const Matx33d &K1, const Matx33d &K2, const Mat &points, Mat &calib_points) {
const auto inv1_k11 = float(1 / K1(0, 0)); // 1 / k11
const auto inv1_k12 = float(-K1(0, 1) / (K1(0, 0)*K1(1, 1))); // -k12 / (k11*k22)
// (-k13*k22 + k12*k23) / (k11*k22)
const auto inv1_k13 = float((-K1(0, 2)*K1(1, 1) + K1(0, 1)*K1(1, 2)) / (K1(0, 0)*K1(1, 1)));
const auto inv1_k22 = float(1 / K1(1, 1)); // 1 / k22
const auto inv1_k23 = float(-K1(1, 2) / K1(1, 1)); // -k23 / k22
const auto inv2_k11 = float(1 / K2(0, 0));
const auto inv2_k12 = float(-K2(0, 1) / (K2(0, 0)*K2(1, 1)));
const auto inv2_k13 = float((-K2(0, 2)*K2(1, 1) + K2(0, 1)*K2(1, 2)) / (K2(0, 0)*K2(1, 1)));
const auto inv2_k22 = float(1 / K2(1, 1));
const auto inv2_k23 = float(-K2(1, 2) / K2(1, 1));
void Utils::calibratePoints (const Mat &K1, const Mat &K2, const Mat &points, Mat &calib_points) {
const auto * const points_ = (float *) points.data;
const auto * const k1 = (double *) K1.data;
const auto inv1_k11 = float(1 / k1[0]); // 1 / k11
const auto inv1_k12 = float(-k1[1] / (k1[0]*k1[4])); // -k12 / (k11*k22)
// (-k13*k22 + k12*k23) / (k11*k22)
const auto inv1_k13 = float((-k1[2]*k1[4] + k1[1]*k1[5]) / (k1[0]*k1[4]));
const auto inv1_k22 = float(1 / k1[4]); // 1 / k22
const auto inv1_k23 = float(-k1[5] / k1[4]); // -k23 / k22
const auto * const k2 = (double *) K2.data;
const auto inv2_k11 = float(1 / k2[0]);
const auto inv2_k12 = float(-k2[1] / (k2[0]*k2[4]));
const auto inv2_k13 = float((-k2[2]*k2[4] + k2[1]*k2[5]) / (k2[0]*k2[4]));
const auto inv2_k22 = float(1 / k2[4]);
const auto inv2_k23 = float(-k2[5] / k2[4]);
calib_points = Mat ( points.rows, 4, points.type());
auto * calib_points_ = (float *) calib_points.data;
@ -52,13 +279,15 @@ void Utils::calibratePoints (const Matx33d &K1, const Matx33d &K2, const Mat &po
* points is matrix of size |N| x 5, first two columns are image points [u_i, v_i]
* calib_norm_pts are K^-1 [u v 1]^T / ||K^-1 [u v 1]^T||
*/
void Utils::calibrateAndNormalizePointsPnP (const Matx33d &K, const Mat &pts, Mat &calib_norm_pts) {
void Utils::calibrateAndNormalizePointsPnP (const Mat &K, const Mat &pts, Mat &calib_norm_pts) {
const auto * const points = (float *) pts.data;
const auto inv_k11 = float(1 / K(0, 0));
const auto inv_k12 = float(-K(0, 1) / (K(0, 0)*K(1, 1)));
const auto inv_k13 = float((-K(0, 2)*K(1, 1) + K(0, 1)*K(1, 2)) / (K(0, 0)*K(1, 1)));
const auto inv_k22 = float(1 / K(1, 1));
const auto inv_k23 = float(-K(1, 2) / K(1, 1));
const auto * const k = (double *) K.data;
const auto inv_k11 = float(1 / k[0]);
const auto inv_k12 = float(-k[1] / (k[0]*k[4]));
const auto inv_k13 = float((-k[2]*k[4] + k[1]*k[5]) / (k[0]*k[4]));
const auto inv_k22 = float(1 / k[4]);
const auto inv_k23 = float(-k[5] / k[4]);
calib_norm_pts = Mat (pts.rows, 3, pts.type());
auto * calib_norm_pts_ = (float *) calib_norm_pts.data;
@ -73,9 +302,10 @@ void Utils::calibrateAndNormalizePointsPnP (const Matx33d &K, const Mat &pts, Ma
}
}
void Utils::normalizeAndDecalibPointsPnP (const Matx33d &K_, Mat &pts, Mat &calib_norm_pts) {
const auto k11 = (float)K_(0, 0), k12 = (float)K_(0, 1), k13 = (float)K_(0, 2),
k22 = (float)K_(1, 1), k23 = (float)K_(1, 2);
void Utils::normalizeAndDecalibPointsPnP (const Mat &K_, Mat &pts, Mat &calib_norm_pts) {
const auto * const K = (double *) K_.data;
const auto k11 = (float)K[0], k12 = (float)K[1], k13 = (float)K[2],
k22 = (float)K[4], k23 = (float)K[5];
calib_norm_pts = Mat (pts.rows, 3, pts.type());
auto * points = (float *) pts.data;
auto * calib_norm_pts_ = (float *) calib_norm_pts.data;
@ -98,25 +328,95 @@ void Utils::normalizeAndDecalibPointsPnP (const Matx33d &K_, Mat &pts, Mat &cali
* 0 fy ty
* 0 0 1]
*/
void Utils::decomposeProjection (const Mat &P, Matx33d &K_, Mat &R, Mat &t, bool same_focal) {
const Mat M = P.colRange(0,3);
void Utils::decomposeProjection (const Mat &P, Matx33d &K, Matx33d &R, Vec3d &t, bool same_focal) {
const Matx33d M = P.colRange(0,3);
double scale = norm(M.row(2)); scale *= scale;
K_ = Matx33d::eye();
K_(1,2) = M.row(1).dot(M.row(2)) / scale;
K_(0,2) = M.row(0).dot(M.row(2)) / scale;
K_(1,1) = sqrt(M.row(1).dot(M.row(1)) / scale - K_(1,2)*K_(1,2));
K_(0,0) = sqrt(M.row(0).dot(M.row(0)) / scale - K_(0,2)*K_(0,2));
K = Matx33d::eye();
K(1,2) = M.row(1).dot(M.row(2)) / scale;
K(0,2) = M.row(0).dot(M.row(2)) / scale;
K(1,1) = sqrt(M.row(1).dot(M.row(1)) / scale - K(1,2)*K(1,2));
K(0,0) = sqrt(M.row(0).dot(M.row(0)) / scale - K(0,2)*K(0,2));
if (same_focal)
K_(0,0) = K_(1,1) = (K_(0,0) + K_(1,1)) / 2;
R = K_.inv() * M / sqrt(scale);
K(0,0) = K(1,1) = (K(0,0) + K(1,1)) / 2;
R = K.inv() * M / sqrt(scale);
if (determinant(M) < 0) R *= -1;
t = R * M.inv() * P.col(3);
t = R * M.inv() * Vec3d(P.col(3));
}
double Utils::getPoissonCDF (double lambda, int inliers) {
double exp_lamda = exp(-lambda), cdf = exp_lamda, lambda_i_div_fact_i = 1;
for (int i = 1; i <= inliers; i++) {
lambda_i_div_fact_i *= (lambda / i);
cdf += exp_lamda * lambda_i_div_fact_i;
if (fabs(cdf - 1) < DBL_EPSILON) // cdf is almost 1
break;
}
return cdf;
}
// since F(E) has rank 2 we use cross product to compute epipole,
// since the third column / row is linearly dependent on two first
// this is faster than SVD
// It is recommended to normalize F, such that ||F|| = 1
Vec3d Utils::getLeftEpipole (const Mat &F/*E*/) {
Vec3d _e = F.col(0).cross(F.col(2)); // F^T e' = 0; e'^T F = 0
const auto * const e = _e.val;
if (e[0] <= DBL_EPSILON && e[0] > -DBL_EPSILON &&
e[1] <= DBL_EPSILON && e[1] > -DBL_EPSILON &&
e[2] <= DBL_EPSILON && e[2] > -DBL_EPSILON)
_e = Vec3d(Mat(F.col(1))).cross(F.col(2)); // if e' is zero
return _e; // e'
}
Vec3d Utils::getRightEpipole (const Mat &F/*E*/) {
Vec3d _e = F.row(0).cross(F.row(2)); // Fe = 0
const auto * const e = _e.val;
if (e[0] <= DBL_EPSILON && e[0] > -DBL_EPSILON &&
e[1] <= DBL_EPSILON && e[1] > -DBL_EPSILON &&
e[2] <= DBL_EPSILON && e[2] > -DBL_EPSILON)
_e = F.row(1).cross(F.row(2)); // if e is zero
return _e;
}
void Utils::densitySort (const Mat &points, int knn, Mat &sorted_points, std::vector<int> &sorted_mask) {
// mask of sorted points (array of indexes)
const int points_size = points.rows, dim = points.cols;
sorted_mask = std::vector<int >(points_size);
for (int i = 0; i < points_size; i++)
sorted_mask[i] = i;
// get neighbors
FlannNeighborhoodGraph &graph = *FlannNeighborhoodGraph::create(points, points_size, knn,
true /*get distances */, 6, 1);
std::vector<double> sum_knn_distances (points_size, 0);
for (int p = 0; p < points_size; p++) {
const std::vector<double> &dists = graph.getNeighborsDistances(p);
for (int k = 0; k < knn; k++)
sum_knn_distances[p] += dists[k];
}
// compare by sum of distances to k nearest neighbors.
std::sort(sorted_mask.begin(), sorted_mask.end(), [&](int a, int b) {
return sum_knn_distances[a] < sum_knn_distances[b];
});
// copy array of points to array with sorted points
// using @sorted_idx mask of sorted points indexes
sorted_points = Mat(points_size, dim, points.type());
const auto * const points_ptr = (float *) points.data;
auto * spoints_ptr = (float *) sorted_points.data;
for (int i = 0; i < points_size; i++) {
const int pt2 = sorted_mask[i] * dim;
for (int j = 0; j < dim; j++)
(*spoints_ptr++) = points_ptr[pt2+j];
}
}
Matx33d Math::getSkewSymmetric(const Vec3d &v) {
return Matx33d(0, -v[2], v[1],
v[2], 0, -v[0],
-v[1], v[0], 0);
return {0, -v[2], v[1],
v[2], 0, -v[0],
-v[1], v[0], 0};
}
Matx33d Math::rotVec2RotMat (const Vec3d &v) {
@ -124,9 +424,9 @@ Matx33d Math::rotVec2RotMat (const Vec3d &v) {
const double x = v[0] / phi, y = v[1] / phi, z = v[2] / phi;
const double a = sin(phi), b = cos(phi);
// R = I + sin(phi) * skew(v) + (1 - cos(phi) * skew(v)^2
return Matx33d((b - 1)*y*y + (b - 1)*z*z + 1, -a*z - x*y*(b - 1), a*y - x*z*(b - 1),
return {(b - 1)*y*y + (b - 1)*z*z + 1, -a*z - x*y*(b - 1), a*y - x*z*(b - 1),
a*z - x*y*(b - 1), (b - 1)*x*x + (b - 1)*z*z + 1, -a*x - y*z*(b - 1),
-a*y - x*z*(b - 1), a*x - y*z*(b - 1), (b - 1)*x*x + (b - 1)*y*y + 1);
-a*y - x*z*(b - 1), a*x - y*z*(b - 1), (b - 1)*x*x + (b - 1)*y*y + 1};
}
Vec3d Math::rotMat2RotVec (const Matx33d &R) {
@ -176,7 +476,7 @@ bool Math::eliminateUpperTriangular (std::vector<double> &a, int m, int n) {
// if pivot value is 0 continue
if (fabs(pivot) < DBL_EPSILON)
return false; // matrix is not full rank -> terminate
continue;
// swap row with maximum pivot value with current row
for (int c = r; c < n; c++)
@ -194,6 +494,18 @@ bool Math::eliminateUpperTriangular (std::vector<double> &a, int m, int n) {
return true;
}
double Utils::intersectionOverUnion (const std::vector<bool> &a, const std::vector<bool> &b) {
int intersects = 0, unions = 0;
for (int i = 0; i < (int)a.size(); i++)
if (a[i] || b[i]) {
unions++; // one value is true
if (a[i] && b[i])
intersects++; // a[i] == b[i] and if they both true
}
if (unions == 0) return 0.0;
return (double) intersects / unions;
}
//////////////////////////////////////// RANDOM GENERATOR /////////////////////////////
class UniformRandomGeneratorImpl : public UniformRandomGenerator {
private:
@ -209,15 +521,12 @@ public:
max_range = max_range_;
subset = std::vector<int>(subset_size_);
}
int getRandomNumber () override {
return rng.uniform(0, max_range);
}
int getRandomNumber (int max_rng) override {
return rng.uniform(0, max_rng);
}
// closed range
void resetGenerator (int max_range_) override {
CV_CheckGE(0, max_range_, "max range must be greater than 0");
@ -243,7 +552,6 @@ public:
// interval is <0; max_range)
void generateUniqueRandomSet (std::vector<int>& sample, int max_range_) override {
/*
* necessary condition:
* if subset size is bigger than range then array cannot be unique,
* so function has infinite loop.
*/
@ -282,14 +590,12 @@ public:
}
return subset;
}
void setSubsetSize (int subset_size_) override {
if (subset_size < subset_size_)
subset.resize(subset_size_);
subset_size = subset_size_;
}
int getSubsetSize () const override { return subset_size; }
Ptr<RandomGenerator> clone (int state) const override {
return makePtr<UniformRandomGeneratorImpl>(state, max_range, subset_size);
}
};
Ptr<UniformRandomGenerator> UniformRandomGenerator::create (int state) {
@ -338,19 +644,17 @@ float Utils::findMedian (std::vector<float> &array) {
} else {
// even: return average
return (quicksort_median(array, length/2 , 0, length-1) +
quicksort_median(array, length/2+1, 0, length-1))/2;
quicksort_median(array, length/2+1, 0, length-1))*.5f;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// Radius Search Graph /////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
class RadiusSearchNeighborhoodGraphImpl : public RadiusSearchNeighborhoodGraph {
private:
std::vector<std::vector<int>> graph;
public:
RadiusSearchNeighborhoodGraphImpl (const Mat &container_, int points_size,
double radius, int flann_search_params, int num_kd_trees) {
double radius, int flann_search_params, int num_kd_trees) {
// Radius search OpenCV works only with float data
CV_Assert(container_.type() == CV_32F);
@ -363,6 +667,8 @@ public:
int pt = 0;
for (const auto &n : neighbours) {
if (n.size() <= 1)
continue;
auto &graph_row = graph[pt];
graph_row = std::vector<int>(n.size()-1);
int j = 0;
@ -373,7 +679,7 @@ public:
pt++;
}
}
const std::vector<std::vector<int>> &getGraph () const override { return graph; }
inline const std::vector<int> &getNeighbors(int point_idx) const override {
return graph[point_idx];
}
@ -384,9 +690,7 @@ Ptr<RadiusSearchNeighborhoodGraph> RadiusSearchNeighborhoodGraph::create (const
flann_search_params, num_kd_trees);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// FLANN Graph /////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
class FlannNeighborhoodGraphImpl : public FlannNeighborhoodGraph {
private:
std::vector<std::vector<int>> graph;
@ -425,6 +729,7 @@ public:
const std::vector<double>& getNeighborsDistances (int idx) const override {
return distances[idx];
}
const std::vector<std::vector<int>> &getGraph () const override { return graph; }
inline const std::vector<int> &getNeighbors(int point_idx) const override {
// CV_Assert(point_idx_ < num_vertices);
return graph[point_idx];
@ -438,9 +743,7 @@ Ptr<FlannNeighborhoodGraph> FlannNeighborhoodGraph::create(const Mat &points,
k_nearest_neighbors_, get_distances, flann_search_params_, num_kd_trees);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// Grid Neighborhood Graph /////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
class GridNeighborhoodGraphImpl : public GridNeighborhoodGraph {
private:
// This struct is used for the nearest neighbors search by griding two images.
@ -460,13 +763,13 @@ private:
}
};
std::map<CellCoord, std::vector<int >> neighbors_map;
std::vector<std::vector<int>> graph;
public:
GridNeighborhoodGraphImpl (const Mat &container_, int points_size,
int cell_size_x_img1, int cell_size_y_img1, int cell_size_x_img2, int cell_size_y_img2,
int max_neighbors) {
std::map<CellCoord, std::vector<int >> neighbors_map;
const auto * const container = (float *) container_.data;
// <int, int, int, int> -> {neighbors set}
// Key is cell position. The value is indexes of neighbors.
@ -490,7 +793,6 @@ public:
// store neighbors cells into graph (2D vector)
for (const auto &cell : neighbors_map) {
const int neighbors_in_cell = static_cast<int>(cell.second.size());
// only one point in cell -> no neighbors
if (neighbors_in_cell < 2) continue;
@ -510,7 +812,7 @@ public:
}
}
}
const std::vector<std::vector<int>> &getGraph () const override { return graph; }
inline const std::vector<int> &getNeighbors(int point_idx) const override {
// Note, neighbors vector also includes point_idx!
// return neighbors_map[vertices_to_cells[point_idx]];
@ -524,4 +826,94 @@ Ptr<GridNeighborhoodGraph> GridNeighborhoodGraph::create(const Mat &points,
return makePtr<GridNeighborhoodGraphImpl>(points, points_size,
cell_size_x_img1_, cell_size_y_img1_, cell_size_x_img2_, cell_size_y_img2_, max_neighbors);
}
class GridNeighborhoodGraph2ImagesImpl : public GridNeighborhoodGraph2Images {
private:
// This struct is used for the nearest neighbors search by griding two images.
struct CellCoord {
int c1x, c1y;
CellCoord (int c1x_, int c1y_) {
c1x = c1x_; c1y = c1y_;
}
bool operator==(const CellCoord &o) const {
return c1x == o.c1x && c1y == o.c1y;
}
bool operator<(const CellCoord &o) const {
if (c1x < o.c1x) return true;
return c1x == o.c1x && c1y < o.c1y;
}
};
std::vector<std::vector<int>> graph;
public:
GridNeighborhoodGraph2ImagesImpl (const Mat &container_, int points_size,
float cell_size_x_img1, float cell_size_y_img1, float cell_size_x_img2, float cell_size_y_img2) {
std::map<CellCoord, std::vector<int >> neighbors_map1, neighbors_map2;
const auto * const container = (float *) container_.data;
// Key is cell position. The value is indexes of neighbors.
const auto cell_sz_x1 = 1.f / cell_size_x_img1,
cell_sz_y1 = 1.f / cell_size_y_img1,
cell_sz_x2 = 1.f / cell_size_x_img2,
cell_sz_y2 = 1.f / cell_size_y_img2;
const int dimension = container_.cols;
for (int i = 0; i < points_size; i++) {
const int idx = dimension * i;
neighbors_map1[CellCoord((int)(container[idx ] * cell_sz_x1),
(int)(container[idx+1] * cell_sz_y1))].emplace_back(i);
neighbors_map2[CellCoord((int)(container[idx+2] * cell_sz_x2),
(int)(container[idx+3] * cell_sz_y2))].emplace_back(i);
}
//--------- create a graph ----------
graph = std::vector<std::vector<int>>(points_size);
// store neighbors cells into graph (2D vector)
for (const auto &cell : neighbors_map1) {
const int neighbors_in_cell = static_cast<int>(cell.second.size());
// only one point in cell -> no neighbors
if (neighbors_in_cell < 2) continue;
const std::vector<int> &neighbors = cell.second;
// ---------- fill graph -----
// for speed-up we make no symmetric graph, eg, x has a neighbor y, but y does not have x
const int v_in_cell = neighbors[0];
// there is always at least one neighbor
auto &graph_row = graph[v_in_cell];
graph_row.reserve(neighbors_in_cell);
for (int n : neighbors)
if (n != v_in_cell)
graph_row.emplace_back(n);
}
// fill neighbors of a second image
for (const auto &cell : neighbors_map2) {
if (cell.second.size() < 2) continue;
const std::vector<int> &neighbors = cell.second;
const int v_in_cell = neighbors[0];
auto &graph_row = graph[v_in_cell];
for (const int &n : neighbors)
if (n != v_in_cell) {
bool has = false;
for (const int &nn : graph_row)
if (n == nn) {
has = true; break;
}
if (!has) graph_row.emplace_back(n);
}
}
}
const std::vector<std::vector<int>> &getGraph () const override { return graph; }
inline const std::vector<int> &getNeighbors(int point_idx) const override {
// Note, neighbors vector also includes point_idx!
return graph[point_idx];
}
};
Ptr<GridNeighborhoodGraph2Images> GridNeighborhoodGraph2Images::create(const Mat &points,
int points_size, float cell_size_x_img1_, float cell_size_y_img1_, float cell_size_x_img2_, float cell_size_y_img2_) {
return makePtr<GridNeighborhoodGraph2ImagesImpl>(points, points_size,
cell_size_x_img1_, cell_size_y_img1_, cell_size_x_img2_, cell_size_y_img2_);
}
}}

View File

@ -181,12 +181,9 @@ static double getError (TestSolver test_case, int pt_idx, const cv::Mat &pts1, c
if (test_case == TestSolver::Fundam || test_case == TestSolver::Essen) {
cv::Mat l2 = model * pt1;
cv::Mat l1 = model.t() * pt2;
if (test_case == TestSolver::Fundam) // sampson error
return fabs(pt2.dot(l2)) / sqrt(pow(l1.at<double>(0), 2) + pow(l1.at<double>(1), 2) +
pow(l2.at<double>(0), 2) + pow(l2.at<double>(1), 2));
else // symmetric geometric distance
return sqrt(pow(pt1.dot(l1),2) / (pow(l1.at<double>(0),2) + pow(l1.at<double>(1),2)) +
pow(pt2.dot(l2),2) / (pow(l2.at<double>(0),2) + pow(l2.at<double>(1),2)));
// Sampson error
return fabs(pt2.dot(l2)) / sqrt(pow(l1.at<double>(0), 2) + pow(l1.at<double>(1), 2) +
pow(l2.at<double>(0), 2) + pow(l2.at<double>(1), 2));
} else
if (test_case == TestSolver::PnP) { // PnP, reprojection error
cv::Mat img_pt = model * pt2; img_pt /= img_pt.at<double>(2);
@ -340,7 +337,7 @@ TEST_P(usac_Essential, maxiters) {
cv::Mat K1 = cv::Mat(cv::Matx33d(1, 0, 0,
0, 1, 0,
0, 0, 1.));
const double conf = 0.99, thr = 0.5;
const double conf = 0.99, thr = 0.25;
int roll_results_sum = 0;
for (int iters = 0; iters < 10; iters++) {
@ -361,8 +358,8 @@ TEST_P(usac_Essential, maxiters) {
FAIL() << "Essential matrix estimation failed!\n";
else continue;
}
EXPECT_NE(roll_results_sum, 0);
}
EXPECT_NE(roll_results_sum, 0);
}
INSTANTIATE_TEST_CASE_P(Calib3d, usac_Essential, UsacMethod::all());