#ifndef __SEACAVE_AUTO_ESTIMATOR_H__ #define __SEACAVE_AUTO_ESTIMATOR_H__ //------------------- // Generic implementation of ACRANSAC //------------------- // The A contrario parametrization have been first explained in [1] and // later extended to generic model estimation in [2] (with a demonstration for // the homography) and extended and use at large scale for Structure from // Motion in [3]. // //-- // [1] Lionel Moisan, Berenger Stival, // A probalistic criterion to detect rigid point matches between // two images and estimate the fundamental matrix. // IJCV 04. //-- // [2] Lionel Moisan, Pierre Moulon, Pascal Monasse. // Automatic Homographic Registration of a Pair of Images, // with A Contrario Elimination of Outliers // Image Processing On Line (IPOL), 2012. // http://dx.doi.org/10.5201/ipol.2012.mmm-oh //-- // [3] Pierre Moulon, Pascal Monasse and Renaud Marlet. // Adaptive Structure from Motion with a contrario mode estimation. // In 11th Asian Conference on Computer Vision (ACCV 2012) //-- // [4] cDc@seacave - rewrite it // // I N C L U D E S ///////////////////////////////////////////////// // D E F I N E S /////////////////////////////////////////////////// // uncomment it to enable std::vector implementation // (slower) //#define ACRANSAC_STD_VECTOR // uncomment it to enable sampling from inliers // (sometimes does not find the right solution) //#define ACRANSAC_SAMPLE_INLIERS // P R O T O T Y P E S ///////////////////////////////////////////// namespace SEACAVE { /// logarithm (base 10) of binomial coefficient static inline double logcombi(size_t k, size_t n) { if (k>=n) return(0.0); if (n-k static inline void makelogcombi_n(size_t n, std::vector& l) { l.resize(n+1); for (size_t k = 0; k <= n; ++k) l[k] = static_cast(logcombi(k,n)); } /// tabulate logcombi(k,.) template static inline void makelogcombi_k(size_t nmax, std::vector& l) { l.resize(nmax+1); for (size_t n = 0; n <= nmax; ++n) l[n] = static_cast(logcombi(k,n)); } /// Distance and associated index struct ErrorIndex { double first; size_t second; inline ErrorIndex() {} inline ErrorIndex(double _first, size_t _second) : first(_first), second(_second) {} inline bool operator==(const ErrorIndex& r) const { return (first == r.first); } inline bool operator <(const ErrorIndex& r) const { return (first < r.first); } }; #ifdef ACRANSAC_STD_VECTOR typedef std::vector ErrorIndexArr; #else typedef CLISTDEF0(ErrorIndex) ErrorIndexArr; #endif /// Find best NFA and its index wrt square error threshold in e. template static ErrorIndex bestNFA( double logalpha0, const ErrorIndexArr& e, double loge0, double maxThresholdSq, const std::vector& logc_n, const std::vector& logc_k, double multError = 1.0) { ErrorIndex bestIndex(DBL_MAX, NSAMPLES); const size_t n = e.size(); for (size_t k=NSAMPLES+1; k<=n && e[k-1].first<=maxThresholdSq; ++k) { const double logalpha = logalpha0 + multError * log10(e[k-1].first+FLT_EPSILON); const double NFA = loge0 + logalpha * (double)(k-NSAMPLES) + logc_n[k] + logc_k[k]; if (NFA < bestIndex.first) bestIndex = ErrorIndex(NFA, k); } return bestIndex; } /// Pick a random subset of the integers [0, total), in random order. /// Note that this can behave badly if num_samples is close to total; runtime /// could be unlimited! /// /// This uses a quadratic rejection strategy and should only be used for small /// num_samples. /// /// \param NSAMPLES The number of samples to produce. /// \param n The number of samples available. /// \param samples num_samples of numbers in [0, total_samples) is placed /// here on return. struct UniformSampler { template inline void Sample( size_t n, size_t* samples) { ASSERT(NSAMPLES > 0); size_t* const samplesBegin = samples; size_t* const samplesEnd = samples + NSAMPLES; *samples = size_t(RAND() % n); while (++samples < samplesEnd) { do { *samples = size_t(RAND() % n); } while (std::find(samplesBegin, samples, *samples) != samples); } } }; /// Same as above, but ensure that the first point is always included struct UniformSamplerLockFirst { template inline void Sample( size_t n, size_t* samples) { ASSERT(NSAMPLES > 1); size_t* const samplesBegin = samples; size_t* const samplesEnd = samples + NSAMPLES; *samples++ = 0; do { do { *samples = size_t(RAND() % n); } while (std::find(samplesBegin, samples, *samples) != samples); } while (++samples < samplesEnd); } }; /// Pick a random sample: /// get a (sorted) random sample of size NSAMPLES in [0:n-1] /// \param NSAMPLES The number of samples to produce. /// \param vec_index The possible data indices. /// \param n The number of samples available. /// \param samples The random sample of NSAMPLES indices (output). struct UniformSamplerSorted { template inline void Sample( #ifdef ACRANSAC_SAMPLE_INLIERS const std::vector& vec_index, #else size_t n, #endif size_t* samples) { #ifdef ACRANSAC_SAMPLE_INLIERS const size_t n = vec_index.size(); #endif for (size_t i=0; i>3)%(n-i), j; for (j=0; j=samples[j]; ++j) ++r; size_t j0 = j; for (j=i; j>j0; --j) samples[j] = samples[j-1]; samples[j0] = r; } #ifdef ACRANSAC_SAMPLE_INLIERS for (size_t i=0; i inline void Sample( size_t n, size_t* samples) { ASSERT(NSAMPLES > 1); samples[0] = 0; for (size_t i=1; i>3)%(n-i), j; for (j=0; j=samples[j]; ++j) ++r; size_t j0 = j; for (j=i; j>j0; --j) samples[j] = samples[j-1]; samples[j0] = r; } } }; /// Returns the requested matrix rows template TMat ExtractRows(const TMat &A, const TRows &rows) { TMat compressed((int)rows.size(), A.cols); for (size_t i=0; i(rows.size()); ++i) A.row(rows[i]).copyTo(compressed.row((int)i)); return compressed; } /// ACRANSAC routine (ErrorThreshold, NFA) template std::pair ACRANSAC( Kernel& kernel, Sampler& sampler, std::vector& vec_inliers, typename Kernel::Model& model, double maxThreshold = DBL_MAX, #ifndef ACRANSAC_SAMPLE_INLIERS double confidence = 0.9999, #endif size_t nIter = 0) { const size_t nData = kernel.NumSamples(); if (nData < Kernel::MINIMUM_SAMPLES) { vec_inliers.clear(); return std::make_pair(0.0,0.0); } const double maxThresholdSq = SQUARE(maxThreshold); std::vector vec_sample(Kernel::MINIMUM_SAMPLES); // sample indices typename Kernel::Models vec_models; // up to max_models solutions if (nData == Kernel::MINIMUM_SAMPLES) { vec_inliers.resize(Kernel::MINIMUM_SAMPLES); std::iota(vec_inliers.begin(), vec_inliers.end(), 0); if (!kernel.Fit(vec_inliers, vec_models) || vec_models.size() != 1) { vec_inliers.clear(); return std::make_pair(0.0,0.0); } model = vec_models[0]; return std::make_pair(maxThresholdSq,0.0); } #ifdef ACRANSAC_SAMPLE_INLIERS // Possible sampling indices (could change in the optimization phase) std::vector vec_index(nData); for (size_t i = 0; i < nData; ++i) vec_index[i] = i; #endif // Precompute log combi const double loge0 = log10((double)Kernel::MAX_MODELS * (nData-Kernel::MINIMUM_SAMPLES)); std::vector vec_logc_n, vec_logc_k; makelogcombi_n(nData, vec_logc_n); makelogcombi_k(nData, vec_logc_k); // Output parameters double minNFA = DBL_MAX; double errorSqMax = DBL_MAX; // Reserve 10% of iterations for focused sampling if (nIter == 0) nIter = MINF((size_t)4000, MAXF((size_t)300, nData*3/2)); const size_t nMinIter = nIter*8/100; // Main estimation loop ErrorIndexArr vec_residuals(nData); // [residual,index] for (size_t iter=0; iter( #ifdef ACRANSAC_SAMPLE_INLIERS vec_index, #else nData, #endif &vec_sample[0] ); if (!kernel.Fit(vec_sample, vec_models)) continue; // Evaluate models #ifdef ACRANSAC_SAMPLE_INLIERS bool better = false; #else const size_t nTrialsRound = nIter; #endif for (typename Kernel::Models::const_iterator itModel=vec_models.cbegin(); itModel!=vec_models.cend(); ++itModel) { // Residuals computation and ordering kernel.EvaluateModel(*itModel); for (size_t i = 0; i < nData; ++i) vec_residuals[i] = ErrorIndex(kernel.Error(i), i); std::sort(vec_residuals.begin(), vec_residuals.end()); // Most meaningful discrimination inliers/outliers ErrorIndex best = bestNFA( kernel.logalpha0(), vec_residuals, loge0, maxThresholdSq, vec_logc_n, vec_logc_k, kernel.multError()); if (best.first < minNFA /*&& vec_residuals[best.second-1].first < errorSqMax*/) { // A better model was found minNFA = best.first; vec_inliers.resize(best.second); for (size_t i=0; inData/3) { // Optimization: draw samples among best set of inliers so far vec_index = vec_inliers; } #endif } if (minNFA >= 0) vec_inliers.clear(); return std::make_pair(errorSqMax, minNFA); } /*----------------------------------------------------------------*/ /// RANSAC routine (standard robust fitter, based on a known threshold) template void RANSAC( Kernel& kernel, Sampler& sampler, std::vector& vec_inliers, typename Kernel::Model& model, double threshold, #ifndef ACRANSAC_SAMPLE_INLIERS double confidence = 0.9999, #endif size_t nIter = 0) { vec_inliers.clear(); const size_t nData = kernel.NumSamples(); if (nData < Kernel::MINIMUM_SAMPLES) return; const double thresholdSq = SQUARE(threshold); std::vector vec_sample(Kernel::MINIMUM_SAMPLES); // sample indices typename Kernel::Models vec_models; // up to max_models solutions #ifdef ACRANSAC_SAMPLE_INLIERS // Possible sampling indices (could change in the optimization phase) std::vector vec_index(nData); for (size_t i = 0; i < nData; ++i) vec_index[i] = i; #endif // Reserve 10% of iterations for focused sampling if (nIter == 0) nIter = MINF((size_t)4000, MAXF((size_t)300, nData*3/2)); const size_t nMinIter = nIter*8/100; // Main estimation loop #ifdef _USE_MSAC double best_score = thresholdSq*nData; #endif std::vector vec_inliers_tmp; vec_inliers_tmp.reserve(nData); vec_inliers.reserve(nData); for (size_t iter=0; iter( #ifdef ACRANSAC_SAMPLE_INLIERS vec_index, #else nData, #endif &vec_sample[0] ); if (!kernel.Fit(vec_sample, vec_models)) continue; // Evaluate models #ifdef ACRANSAC_SAMPLE_INLIERS bool better = false; #else const size_t nTrialsRound = nIter; #endif for (typename Kernel::Models::const_iterator itModel=vec_models.cbegin(); itModel!=vec_models.cend(); ++itModel) { // Residuals computation and ordering #ifdef _USE_MSAC double score = 0; #endif vec_inliers_tmp.clear(); kernel.EvaluateModel(*itModel); for (size_t i = 0; i < nData; ++i) { const double errSq = kernel.Error(i); if (errSq <= thresholdSq) { #ifdef _USE_MSAC score += errSq; #endif vec_inliers_tmp.push_back(i); } else { #ifdef _USE_MSAC score += thresholdSq; #endif } #ifdef _USE_MSAC if (score >= best_score) break; #endif } // Store best model #ifdef _USE_MSAC if (score < best_score) { #else if (vec_inliers_tmp.size() > vec_inliers.size()) { #endif // A better model was found model = *itModel; vec_inliers.swap(vec_inliers_tmp); if (vec_inliers.size() == nData) { nIter = 0; // force loop exit break; } #ifdef _USE_MSAC best_score = score; DEBUG_LEVEL(4, "\titer=%3u inliers=%u score=%g", iter, vec_inliers.size(), best_score); #else DEBUG_LEVEL(4, "\titer=%3u inliers=%u", iter, vec_inliers.size()); #endif #ifdef ACRANSAC_SAMPLE_INLIERS better = true; #else #ifdef _USE_MSAC nIter = cvRANSACUpdateNumIters(confidence, (double)score/((double)nData*thresholdSq), unsigned(Kernel::MINIMUM_SAMPLES), (unsigned)nTrialsRound); #else nIter = cvRANSACUpdateNumIters(confidence, (double)(nData-vec_inliers.size())/nData, unsigned(Kernel::MINIMUM_SAMPLES), (unsigned)nTrialsRound); #endif #endif } } vec_models.clear(); #ifdef ACRANSAC_SAMPLE_INLIERS if (better && vec_inliers.size()>nData/3) { // Optimization: draw samples among best set of inliers so far vec_index = vec_inliers; } #endif } if (vec_inliers.size() < Kernel::MINIMUM_SAMPLES) vec_inliers.clear(); } /*----------------------------------------------------------------*/ /// Two view Kernel adaptator for the A contrario model estimator /// Handle data normalization and compute the corresponding logalpha 0 /// that depends of the error model (point to line, or point to point) /// This kernel adaptor is working for affine, homography, fundamental matrix /// estimation. template class ACKernelAdaptor { public: typedef SOLVER Solver; typedef typename SOLVER::Model Model; typedef typename SOLVER::Models Models; enum { MINIMUM_SAMPLES = Solver::MINIMUM_SAMPLES }; enum { MAX_MODELS = Solver::MAX_MODELS }; ACKernelAdaptor( const DMatrix32F &x1, float w1, float h1, const DMatrix32F &x2, float w2, float h2, bool bPointToLine = true) : x1_(x1), x2_(x2), bPointToLine_(bPointToLine) { ASSERT(2 == x1_.cols); ASSERT(x1_.rows == x2_.rows); ASSERT(x1_.cols == x2_.cols); ASSERT(NumSamples() >= MINIMUM_SAMPLES); // LogAlpha0 is used to make error data scale invariant const float s2 = 1.0f; if (bPointToLine) { // Ratio of containing diagonal image rectangle over image area const float D = sqrt(w2*w2 + h2*h2); // diameter const float A = w2*h2; // area logalpha0_ = log10(2.0f*D/A/s2); } else { // ratio of area : unit circle over image area logalpha0_ = log10(M_PI/(w2*h2)/(s2*s2)); } } inline bool Fit(const std::vector& samples, Models& models) const { const DMatrix32F x1(ExtractRows(x1_, samples)); if (CheckCollinearity(x1.ptr(), x1.rows)) return false; const DMatrix32F x2(ExtractRows(x2_, samples)); if (CheckCollinearity(x2.ptr(), x2.rows)) return false; Solver::Solve(x1, x2, models); return true; } inline void EvaluateModel(const Model& model) { model2evaluate = model; } inline double Error(size_t sample) const { return Solver::Error(model2evaluate, *x1_.ptr((int)sample), *x2_.ptr((int)sample)); } inline size_t NumSamples() const { return static_cast(x1_.rows); } inline double logalpha0() const { return logalpha0_; } inline double multError() const { return (bPointToLine_ ? 0.5 : 1.0); } protected: DMatrix32F x1_, x2_; // Normalized input data double logalpha0_; // Alpha0 is used to make the error adaptive to the image size bool bPointToLine_;// Store if error model is pointToLine or point to point Model model2evaluate; // current model to be evaluated }; /// Pose/Resection Kernel adaptator for the A contrario model estimator template class ACKernelAdaptorResection { public: typedef SOLVER Solver; typedef typename SOLVER::Model Model; typedef typename SOLVER::Models Models; enum { MINIMUM_SAMPLES = Solver::MINIMUM_SAMPLES }; enum { MAX_MODELS = Solver::MAX_MODELS }; ACKernelAdaptorResection(const DMatrix32F &x2d, const DMatrix &x3D) : x2d_(x2d), x3D_(x3D), logalpha0_(log10(M_PI)) { ASSERT(2 == x2d_.cols); ASSERT(3 == x3D_.cols); ASSERT(x2d_.rows == x3D_.rows); ASSERT(NumSamples() >= MINIMUM_SAMPLES); } inline bool Fit(const std::vector& samples, Models& models) const { const DMatrix32F x1(ExtractRows(x2d_, samples)); if (CheckCollinearity(x1.ptr(), x1.rows)) return false; const DMatrix x2(ExtractRows(x3D_, samples)); Solver::Solve(x1, x2, models); return true; } inline void EvaluateModel(const Model &model) { model2evaluate = model; } inline double Error(size_t sample) const { return Solver::Error(model2evaluate, *x2d_.ptr((int)sample), *x3D_.ptr((int)sample)); } inline size_t NumSamples() const { return x2d_.rows; } inline double logalpha0() const { return logalpha0_; } inline double multError() const { return 1.0; } // point to point error protected: DMatrix32F x2d_; DMatrix x3D_; double logalpha0_; // Alpha0 is used to make the error adaptive to the image size Model model2evaluate; // current model to be evaluated }; /// Essential matrix Kernel adaptator for the A contrario model estimator template class ACKernelAdaptorEssential { public: typedef SOLVER Solver; typedef typename SOLVER::Model Model; typedef typename SOLVER::Models Models; enum { MINIMUM_SAMPLES = Solver::MINIMUM_SAMPLES }; enum { MAX_MODELS = Solver::MAX_MODELS }; ACKernelAdaptorEssential( const DMatrix32F &x1, float w1, float h1, const Matrix3x3f& invK1, const DMatrix32F &x2, float w2, float h2, const Matrix3x3f& invK2) : solver(invK1, invK2), x1_(x1), x2_(x2) { ASSERT(2 == x1_.cols); ASSERT(x1_.rows == x2_.rows); ASSERT(x1_.cols == x2_.cols); ASSERT(NumSamples() >= MINIMUM_SAMPLES); //Point to line probability (line is the epipolar line) const float D = sqrt(w2*w2 + h2*h2); // diameter const float A = w2*h2+1.f; // area logalpha0_ = log10(2.0f*D/A*0.5f); } inline bool Fit(const std::vector& samples, Models& models) const { const DMatrix32F x1(ExtractRows(x1_, samples)); if (CheckCollinearity(x1.ptr(), x1.rows)) return false; const DMatrix32F x2(ExtractRows(x2_, samples)); if (CheckCollinearity(x2.ptr(), x2.rows)) return false; solver.Solve(x1, x2, models); return true; } inline void EvaluateModel(const Model &model) { solver.EvaluateModel(model); } inline double Error(size_t sample) const { return solver.Error(*x1_.ptr((int)sample), *x2_.ptr((int)sample)); } inline size_t NumSamples() const { return x1_.rows; } inline double logalpha0() const { return logalpha0_; } inline double multError() const { return 0.5; } // point to line error protected: Solver solver; DMatrix32F x1_, x2_; // image point and camera plane point. double logalpha0_; // Alpha0 is used to make the error adaptive to the image size }; /*----------------------------------------------------------------*/ } // namespace SEACAVE #endif // __SEACAVE_AUTO_ESTIMATOR_H__