// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. #include "../precomp.hpp" #include "../usac.hpp" namespace cv { namespace usac { ////////////////////////////////// STANDARD TERMINATION /////////////////////////////////////////// class StandardTerminationCriteriaImpl : public StandardTerminationCriteria { private: const double log_confidence; const int points_size, sample_size, MAX_ITERATIONS; public: StandardTerminationCriteriaImpl (double confidence, int points_size_, int sample_size_, int max_iterations_) : log_confidence(log(1 - confidence)), points_size (points_size_), sample_size (sample_size_), MAX_ITERATIONS(max_iterations_) {} /* * Get upper bound iterations for any sample number * n is points size, w is inlier ratio, p is desired probability, k is expceted number of iterations. * 1 - p = (1 - w^n)^k, * k = log_(1-w^n) (1-p) * k = ln (1-p) / ln (1-w^n) * * w^n is probability that all N points are inliers. * (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 { const double predicted_iters = log_confidence / log(1 - std::pow (static_cast(inlier_number) / points_size, sample_size)); // if inlier_prob == 1 then log(0) = -inf, predicted_iters == -0 // if inlier_prob == 0 then log(1) = 0 , predicted_iters == (+-) inf if (! std::isinf(predicted_iters) && predicted_iters < MAX_ITERATIONS) return static_cast(predicted_iters); return MAX_ITERATIONS; } Ptr clone () const override { return makePtr(1-exp(log_confidence), points_size, sample_size, MAX_ITERATIONS); } }; Ptr StandardTerminationCriteria::create(double confidence, int points_size_, int sample_size_, int max_iterations_) { return makePtr(confidence, points_size_, sample_size_, max_iterations_); } /////////////////////////////////////// SPRT TERMINATION ////////////////////////////////////////// class SPRTTerminationImpl : public SPRTTermination { private: const std::vector &sprt_histories; const double log_eta_0; const int points_size, sample_size, MAX_ITERATIONS; public: SPRTTerminationImpl (const std::vector &sprt_histories_, double confidence, int points_size_, int sample_size_, int max_iterations_) : sprt_histories (sprt_histories_), log_eta_0(log(1-confidence)), points_size (points_size_), sample_size (sample_size_),MAX_ITERATIONS(max_iterations_){} /* * Termination criterion: * l is number of tests * n(l) = Product from i = 0 to l ( 1 - P_g (1 - A(i)^(-h(i)))^k(i) ) * log n(l) = sum from i = 0 to l k(i) * ( 1 - P_g (1 - A(i)^(-h(i))) ) * * log (n0) - log (n(l-1)) * k(l) = ----------------------- (9) * log (1 - P_g*A(l)^-1) * * A is decision threshold * P_g is probability of good model. * k(i) is number of samples verified by i-th sprt. * n0 is typically set to 0.05 * 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)); const double epsilon = static_cast(inlier_size) / points_size; // inlier probability const double P_g = pow (epsilon, sample_size); // probability of good sample double log_eta_lmin1 = 0; int total_number_of_tested_samples = 0; const int sprts_size_min1 = static_cast(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; } // 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)); // 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 if (std::isnan(predicted_iters_sprt) || std::isinf(predicted_iters_sprt)) return getStandardUpperBound(inlier_size); if (predicted_iters_sprt < 0) return 0; // compare with standard upper bound if (predicted_iters_sprt < MAX_ITERATIONS) return std::min(static_cast(predicted_iters_sprt), getStandardUpperBound(inlier_size)); return getStandardUpperBound(inlier_size); } Ptr clone () const override { return makePtr(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 (static_cast(inlier_size) / points_size, sample_size)); return (! std::isinf(predicted_iters) && predicted_iters < MAX_ITERATIONS) ? static_cast(predicted_iters) : MAX_ITERATIONS; } /* * h(i) must hold * * δ(i) 1 - δ(i) * ε (-----)^h(i) + (1 - ε) (--------)^h(i) = 1 * ε(i) 1 - ε(i) * * ε * a^h + (1 - ε) * b^h = 1 * Has numerical solution. */ static double computeExponentH (double epsilon, double epsilon_new, double delta) { const double a = log (delta / epsilon); // log likelihood ratio const double b = log ((1 - delta) / (1 - epsilon)); const double x0 = log (1 / (1 - epsilon_new)) / b; const double v0 = epsilon_new * exp (x0 * a); const double x1 = log ((1 - 2*v0) / (1 - epsilon_new)) / b; const double v1 = epsilon_new * exp (x1 * a) + (1 - epsilon_new) * exp(x1 * b); const double h = x0 - (x0 - x1) / (1 + v0 - v1) * v0; if (std::isnan(h)) // The equation always has solution for h = 0 // ε * a^0 + (1 - ε) * b^0 = 1 // ε + 1 - ε = 1 -> 1 = 1 return 0; return h; } }; Ptr SPRTTermination::create(const std::vector &sprt_histories_, double confidence, int points_size_, int sample_size_, int max_iterations_) { return makePtr(sprt_histories_, confidence, points_size_, sample_size_, max_iterations_); } ///////////////////////////// PROGRESSIVE-NAPSAC-SPRT TERMINATION ///////////////////////////////// class SPRTPNapsacTerminationImpl : public SPRTPNapsacTermination { private: SPRTTerminationImpl sprt_termination; const std::vector &sprt_histories; const double relax_coef, log_confidence; const int points_size, sample_size, MAX_ITERS; public: SPRTPNapsacTerminationImpl (const std::vector &sprt_histories_, 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_), 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 predicted_iterations = sprt_termination.update(model, inlier_number); const double inlier_prob = static_cast(inlier_number) / points_size + relax_coef; if (inlier_prob >= 1) return 0; const double predicted_iters = log_confidence / log(1 - std::pow(inlier_prob, sample_size)); if (! std::isinf(predicted_iters) && predicted_iters < predicted_iterations) return static_cast(predicted_iters); return predicted_iterations; } Ptr clone () const override { return makePtr(sprt_histories, 1-exp(log_confidence), points_size, sample_size, MAX_ITERS, relax_coef); } }; Ptr SPRTPNapsacTermination::create(const std::vector& sprt_histories_, double confidence, int points_size_, int sample_size_, int max_iterations_, double relax_coef_) { return makePtr(sprt_histories_, confidence, points_size_, sample_size_, max_iterations_, relax_coef_); } ////////////////////////////////////// PROSAC TERMINATION ///////////////////////////////////////// class ProsacTerminationCriteriaImpl : public ProsacTerminationCriteria { private: const double log_confidence, beta, non_randomness_phi, inlier_threshold; const int MAX_ITERATIONS, points_size, min_termination_length, sample_size; const Ptr sampler; std::vector non_random_inliers; const Ptr error; public: ProsacTerminationCriteriaImpl (const Ptr &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 &sampler_,const Ptr &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_), sampler(sampler_), error (error_) { init(); } void init () { // 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 // by chance consistent with it. The constraint is typically // 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(points_size, 0); std::vector pn_i_arr(points_size); 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; } // P^R_n(i) = β^(i−m) (1−β)^(n−i+m) (n−m i−m). (7) i = m,...,N // initial value for i = m = sample_size // P^R_n(i=m) = β^(0) (1−β)^(n) (n-m 0) = (1-β)^(n) // P^R_n(i=m+1) = β^(1) (1−β)^(n−1) (n−m 1) = P^R_n(i=m) * β / (1-β) * (n-m) / 1 // P^R_n(i=m+2) = β^(2) (1−β)^(n−2) (n−m 2) = P^R_n(i=m) * β^2 / (1-β)^2 * (n-m-1)(n-m) / 2 // So, for each i=m+1.., P^R_n(i+1) must be calculated as P^R_n(i) * β / (1-β) * (n-i+1) / (i-m) pn_i_arr[sample_size-1] = std::pow(1-beta, n); double pn_i = pn_i_arr[sample_size-1]; // prob of random inlier set of size i for subset size n for (int i = sample_size+1; i <= n; i++) { // use recurrent relation to fulfill remaining values pn_i *= beta2compl_beta * static_cast(n-i+1) / (i-sample_size); // update pn_i_arr[i-1] = pn_i; } // find minimum number of inliers satisfying the non-randomness constraint // Imin n = min{j : n∑i=j P^R_n(i) < Ψ }. (8) double acc = 0; int i_min = sample_size; // there is always sample_size inliers for (int i = n; i >= sample_size; i--) { acc += pn_i_arr[i-1]; if (acc < non_randomness_phi) i_min = i; else break; } non_random_inliers[n-1] = i_min; } // approximate values of binomial distribution for (int n = sample_size; n <= points_size; n+=step_n) { if (n-1+step_n >= max_n) { // copy rest of the values std::fill(&non_random_inliers[0]+n-1, &non_random_inliers[0]+points_size, non_random_inliers[n-1]); break; } const int non_rand_n = non_random_inliers[n-1]; const double step = (double)(non_random_inliers[n-1+step_n] - non_rand_n) / (double)step_n; for (int i = 0; i < step_n-1; i++) non_random_inliers[n+i] = (int)(non_rand_n + (i+1)*step); } } /* * 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) * data points are by chance inliers to an arbitrary incorrect model * is smaller than Ψ (typically set to 5%) * * • 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%). */ int update (const Mat &model, int inliers_size) override { int predicted_iterations = MAX_ITERATIONS; /* * The termination length n* is chosen to minimize k_n*(η0) subject to I_n* ≥ I_min n*; * k_n*(η0) >= log(η0) / log(1 - (I_n* / n*)^m) * g(k) <= n, I_n is number of inliers under termination length n. */ const auto &errors = error->getErrors(model); // find number of inliers under g(k) int num_inliers_under_termination_len = 0; 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]) 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(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(new_max_samples); if (predicted_iterations == 0) break; if (sampler != nullptr) sampler->setTerminationLength(termination_len); } } } // 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 (static_cast(inliers_size) / points_size, sample_size)); if (! std::isinf(predicted_iters) && predicted_iters < predicted_iterations) return static_cast(predicted_iters); return predicted_iterations; } Ptr clone () const override { return makePtr(error->clone(), points_size, sample_size, 1-exp(log_confidence), MAX_ITERATIONS, min_termination_length, beta, non_randomness_phi, inlier_threshold); } }; Ptr ProsacTerminationCriteria::create(const Ptr &sampler, const Ptr &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) { return makePtr (sampler, error, points_size_, sample_size_, confidence, max_iterations, min_termination_length_, beta, non_randomness_phi, inlier_thresh); } }}