Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

RANSAC: Fix incorrect algorithm

  • Loading branch information...
commit 9f742ab249813de3d718ac10d04bd599091d4863 1 parent 4843d86
Oleg Alexandrov oleg-alexandrov authored
4 src/vw/Camera/PinholeModelCalibrate.h
View
@@ -422,8 +422,8 @@ std::vector<size_t> pinholemodel_calibrate_ransac(PinholeModel& m, const std::ve
PinholeModelRansacFitting<Ex1T, Ex2T, Ex3T, Ex4T> fitting(m, lm_iter);
PinholeModelRansacError error;
- math::RandomSampleConsensus<PinholeModelRansacFitting<Ex1T, Ex2T, Ex3T, Ex4T>, PinholeModelRansacError> ransac(fitting, error, inlier_threshold);
- m = ransac(pixels, points, ransac_iter);
+ math::RandomSampleConsensus<PinholeModelRansacFitting<Ex1T, Ex2T, Ex3T, Ex4T>, PinholeModelRansacError> ransac(fitting, error, ransac_iter, inlier_threshold, pixels.size()/2, true);
+ m = ransac(pixels, points);
std::vector<size_t> inlier_indices(ransac.inlier_indices(m, pixels, points));
vw_out(DebugMessage, "Camera") << "RANSAC classified as inliers: " << inlier_indices.size() << '/' << pixels.size() << std::endl;
2  src/vw/Camera/tests/TestCameraGeometry.cxx
View
@@ -115,7 +115,7 @@ TEST_F( CameraGeometryTest, DISABLED_RansacSolve ) {
world_m[j][2] = rand()%200;
}
- math::RandomSampleConsensus<CameraMatrixFittingFunctor,CameraMatrixErrorMetric> ransac( CameraMatrixFittingFunctor(), CameraMatrixErrorMetric(), 2 );
+ math::RandomSampleConsensus<CameraMatrixFittingFunctor,CameraMatrixErrorMetric> ransac( CameraMatrixFittingFunctor(), CameraMatrixErrorMetric(), 100, 2, world_m.size()/2, true);
Matrix<double> P(ransac(world_m,image_m));
ASSERT_EQ( P.rows(), 3u );
ASSERT_EQ( P.cols(), 4u );
203 src/vw/Math/RANSAC.h
View
@@ -111,32 +111,31 @@ namespace math {
class RandomSampleConsensus {
const FittingFuncT& m_fitting_func;
const ErrorFuncT& m_error_func;
+ int m_num_iterations;
double m_inlier_threshold;
-
- // Returns the number of inliers for a given threshold.
- template <class ContainerT1, class ContainerT2>
- unsigned num_inliers(typename FittingFuncT::result_type const& H,
- std::vector<ContainerT1> const& p1,
- std::vector<ContainerT2> const& p2) const {
- unsigned result = 0;
- for (unsigned i=0; i<p1.size(); i++) {
- if (m_error_func(H,p1[i],p2[i]) < m_inlier_threshold)
- ++result;
- }
- return result;
- }
-
+ int m_min_num_output_inliers;
+ bool m_reduce_min_num_output_inliers_if_no_fit;
+
/// \cond INTERNAL
// Utility Function: Pick N UNIQUE, random integers in the range [0, size]
- inline void get_n_unique_integers(size_t size, size_t n, int* samples) const {
+ inline void get_n_unique_integers(int size, std::vector<int> & samples) const {
+
+ // Note: We do not modify the initial random seed. As such, if
+ // a program uses RANSAC, repeatedly running this program will
+ // always return the same results. However, if that program
+ // calls RANSAC twice while within the same instance of the
+ // program, the second time the result of RANSAC will be
+ // different, since we keep on pulling new random numbers.
+
+ int n = samples.size();
VW_ASSERT(size >= n, ArgumentErr() << "Not enough samples (" << n << " / " << size << ")\n");
- for (size_t i=0; i<n; ++i) {
+ for (int i = 0; i < n; ++i) {
bool done = false;
while (!done) {
samples[i] = int( (double(std::rand()) / double(RAND_MAX)) * size );
done = true;
- for (size_t j = 0; j < i; j++)
+ for (int j = 0; j < i; j++)
if (samples[i] == samples[j])
done = false;
}
@@ -174,108 +173,146 @@ namespace math {
return result;
}
- RandomSampleConsensus(FittingFuncT const& fitting_func, ErrorFuncT const& error_func, double inlier_threshold)
- : m_fitting_func(fitting_func), m_error_func(error_func), m_inlier_threshold(inlier_threshold) {}
+ void reduce_min_num_output_inliers(){
+ m_min_num_output_inliers = int(m_min_num_output_inliers/1.5);
+ }
+
+ RandomSampleConsensus(FittingFuncT const& fitting_func, ErrorFuncT const& error_func,
+ int num_iterations,
+ double inlier_threshold,
+ int min_num_output_inliers,
+ bool reduce_min_num_output_inliers_if_no_fit = false
+ ):
+ m_fitting_func(fitting_func), m_error_func(error_func),
+ m_num_iterations(num_iterations),
+ m_inlier_threshold(inlier_threshold),
+ m_min_num_output_inliers(min_num_output_inliers),
+ m_reduce_min_num_output_inliers_if_no_fit(reduce_min_num_output_inliers_if_no_fit){}
template <class ContainerT1, class ContainerT2>
typename FittingFuncT::result_type operator()(std::vector<ContainerT1> const& p1,
- std::vector<ContainerT2> const& p2,
- int32 ransac_iterations = 0) const {
- // check consistency
- VW_ASSERT( p1.size() == p2.size(),
- RANSACErr() << "RANSAC Error. data vectors are not the same size." );
+ std::vector<ContainerT2> const& p2) {
+
+ // Try to fit using RANSAC. Perform repeated fits with smaller
+ // m_min_num_output_inliers if the fit fails and
+ // m_reduce_min_num_output_inliers_if_no_fit is true.
+
+ typename FittingFuncT::result_type H;
+ bool success = false;
+
+ for (int attempt = 0; attempt < 10; attempt++){
+ try{
+ H = attempt_ransac(p1, p2);
+ success = true;
+ break;
+ } catch ( const std::exception& e ) {
+ vw_out() << e.what() << "\n";
+ if (!m_reduce_min_num_output_inliers_if_no_fit) break;
+ vw_out() << "Attempting RANSAC with " << m_min_num_output_inliers << " number of output inliers.\n";
+ reduce_min_num_output_inliers();
+ }
+ }
+
+ if (!success) vw_throw( RANSACErr() << "RANSAC was unable to find a fit that matched the supplied data." );
+
+ return H;
+ }
+
+ template <class ContainerT1, class ContainerT2>
+ typename FittingFuncT::result_type attempt_ransac(std::vector<ContainerT1> const& p1,
+ std::vector<ContainerT2> const& p2) const {
+
VW_ASSERT( !p1.empty(),
RANSACErr() << "RANSAC Error. Insufficient data.\n");
- VW_ASSERT( p1.size() >= m_fitting_func.min_elements_needed_for_fit(p1[0]),
- RANSACErr() << "RANSAC Error. Not enough potential matches for this fitting functor. ("<<p1.size() << "/" << m_fitting_func.min_elements_needed_for_fit(p1[0]) << ")\n");
+ VW_ASSERT( p1.size() == p2.size(),
+ RANSACErr() << "RANSAC Error. Data vectors are not the same size." );
- uint32 inliers_max = 0;
- typename FittingFuncT::result_type H;
- typename FittingFuncT::result_type H_max;
+ int min_elems_for_fit = m_fitting_func.min_elements_needed_for_fit(p1[0]);
- /////////////////////////////////////////
- // First part:
- // 1. choose N points at random
- // 2. find a fit for those N points
- // 3. check for consensus
- // 4. keep fit with best consensus so far
- /////////////////////////////////////////
+ VW_ASSERT( (int)p1.size() >= min_elems_for_fit,
+ RANSACErr() << "RANSAC Error. Not enough potential matches for this fitting functor. (" << p1.size() << "/" << min_elems_for_fit << ")\n");
- // Note: We do not modify the initial random seed. As such, if
- // a program uses RANSAC, repeatedly running this program will
- // always return the same results. However, if that program
- // calls RANSAC twice while within the same instance of the
- // program, the second time the result of RANSAC will be
- // different, since we keep on pulling new random numbers.
-
- // This is a rough value, but it seems to produce reasonably good results.
- if (ransac_iterations == 0)
- ransac_iterations = p1.size() * 2;
+ VW_ASSERT( m_min_num_output_inliers >= min_elems_for_fit,
+ RANSACErr() << "RANSAC Error. Number of requested inliers is less than min number of elements needed for fit. (" << m_min_num_output_inliers << "/" << min_elems_for_fit << ")\n");
+
+ typename FittingFuncT::result_type best_H;
- size_t n = m_fitting_func.min_elements_needed_for_fit(p1[0]);
- std::vector<ContainerT1> try1(n);
- std::vector<ContainerT2> try2(n);
- boost::scoped_array<int32> random_indices(new int32[n]);
+ std::vector<ContainerT1> try1;
+ std::vector<ContainerT2> try2;
+ std::vector<int> random_indices(min_elems_for_fit);
- for (int32 iteration=0; iteration < ransac_iterations; ++iteration) {
- // Get n points at random, taking care not
- // to select the same point twice.
- get_n_unique_integers(p1.size(), n, random_indices.get());
+ int num_inliers = 0;
+ double min_err = std::numeric_limits<double>::max();
+ for (int iteration = 0; iteration < m_num_iterations; ++iteration) {
- for (size_t i=0; i < n; ++i) {
+ // 0. Get min_elems_for_fit points at random, taking care not
+ // to select the same point twice.
+ get_n_unique_integers(p1.size(), random_indices);
+ // Resizing below is essential, as by now their size may have changed
+ try1.resize(min_elems_for_fit);
+ try2.resize(min_elems_for_fit);
+ for (int i = 0; i < min_elems_for_fit; ++i) {
try1[i] = p1[random_indices[i]];
try2[i] = p2[random_indices[i]];
}
- // Compute the fit using these samples
- H = m_fitting_func(try1, try2);
+ // 1. Compute the fit using these samples.
+ typename FittingFuncT::result_type H = m_fitting_func(try1, try2);
- // Compute consensuss
- unsigned n_inliers = num_inliers(H, p1, p2);
+ // 2. Find all the inliers for this fit.
+ inliers(H, p1, p2, try1, try2);
- // Keep best consensus
- if (n_inliers > inliers_max) {
- inliers_max = n_inliers;
- H_max = H;
+ // 3. Skip this model if too few inliers.
+ if ((int)try1.size() < m_min_num_output_inliers) continue;
+
+ // 4. Re-estimate the model using the inliers.
+ H = m_fitting_func(try1, try2, H);
+
+ // 5. Find the mean error for the inliers.
+ double err_val = 0.0;
+ for (size_t i = 0; i < try1.size(); i++) err_val += m_error_func(H, try1[i], try2[i]);
+ err_val /= try1.size();
+
+ // 6. Save this model if its error is lowest so far.
+ if (err_val < min_err){
+ min_err = err_val;
+ best_H = H;
+ num_inliers = try1.size();
}
+
}
- if (inliers_max < m_fitting_func.min_elements_needed_for_fit(p1[0])) {
+ if (num_inliers < m_min_num_output_inliers) {
vw_throw( RANSACErr() << "RANSAC was unable to find a fit that matched the supplied data." );
}
- ////////////////////////////////////
- // Second part:
- // 1. find all inliers the best fit
- // 2. re-estimate the fit using all inliers
- // 3. repeat until # of inliers stabilizes
- ///////////////////////////////////
- unsigned int num_old = 0;
- inliers(H_max, p1, p2, try1, try2 );
- while( try1.size() > num_old ){
- num_old = try1.size();
- H = m_fitting_func(try1, try2, H_max); // Seeding with best solution
- inliers( H, p1, p2, try1, try2 );
- }
// For debugging
VW_OUT(InfoMessage, "interest_point") << "\nRANSAC Summary:" << std::endl;
- VW_OUT(InfoMessage, "interest_point") << "\tFit = " << H << std::endl;
- VW_OUT(InfoMessage, "interest_point") << "\tInliers / Total = " << try1.size() << " / " << p1.size() << "\n\n";
- return H;
+ VW_OUT(InfoMessage, "interest_point") << "\tFit = " << best_H << std::endl;
+ VW_OUT(InfoMessage, "interest_point") << "\tInliers / Total = " << num_inliers << " / " << p1.size() << "\n\n";
+
+ return best_H;
}
};
- // Free function
template <class ContainerT1, class ContainerT2, class FittingFuncT, class ErrorFuncT>
typename FittingFuncT::result_type ransac(std::vector<ContainerT1> const& p1,
std::vector<ContainerT2> const& p2,
FittingFuncT const& fitting_func,
ErrorFuncT const& error_func,
- double inlier_threshold = 50) {
+ int num_iterations,
+ double inlier_threshold,
+ int min_num_output_inliers,
+ bool reduce_min_num_output_inliers_if_no_fit = false
+ ) {
RandomSampleConsensus<FittingFuncT, ErrorFuncT> ransac_instance(fitting_func,
error_func,
- inlier_threshold);
+ num_iterations,
+ inlier_threshold,
+ min_num_output_inliers,
+ reduce_min_num_output_inliers_if_no_fit
+ );
return ransac_instance(p1,p2);
}
2  src/vw/tools/correlate.cc
View
@@ -105,7 +105,7 @@ int main( int argc, char *argv[] ) {
matched_ip1, matched_ip2 );
std::vector<Vector3> ransac_ip1 = ip::iplist_to_vectorlist(matched_ip1);
std::vector<Vector3> ransac_ip2 = ip::iplist_to_vectorlist(matched_ip2);
- vw::math::RandomSampleConsensus<vw::math::HomographyFittingFunctor, vw::math::InterestPointErrorMetric> ransac( vw::math::HomographyFittingFunctor(), vw::math::InterestPointErrorMetric(), 30 );
+ vw::math::RandomSampleConsensus<vw::math::HomographyFittingFunctor, vw::math::InterestPointErrorMetric> ransac( vw::math::HomographyFittingFunctor(), vw::math::InterestPointErrorMetric(), 100, 30, ransac_ip1.size()/2, true );
alignment = ransac( ransac_ip2, ransac_ip1 );
DiskImageView<PixelGray<float> > right_disk_image( right_file_name );
6 src/vw/tools/ipalign.cc
View
@@ -47,6 +47,7 @@ struct Options {
std::string output_prefix, interest_operator, descriptor_generator;
float matcher_threshold, detect_gain, tile_size;
float inlier_threshold;
+ int ransac_iterations;
bool single_scale, homography, debug_images;
bool save_intermediate;
};
@@ -203,11 +204,11 @@ void align_images( Options & opt ) {
std::vector<size_t> indices;
if ( opt.homography ) {
- math::RandomSampleConsensus<math::HomographyFittingFunctor, math::InterestPointErrorMetric> ransac(math::HomographyFittingFunctor(), math::InterestPointErrorMetric(), opt.inlier_threshold);
+ math::RandomSampleConsensus<math::HomographyFittingFunctor, math::InterestPointErrorMetric> ransac(math::HomographyFittingFunctor(), math::InterestPointErrorMetric(), opt.ransac_iterations, opt.inlier_threshold, ransac_ip1.size()/2, true);
align_matrix = ransac(ransac_ip2,ransac_ip1);
indices = ransac.inlier_indices(align_matrix,ransac_ip2,ransac_ip1);
} else {
- math::RandomSampleConsensus<math::AffineFittingFunctor, math::InterestPointErrorMetric> ransac(math::AffineFittingFunctor(), math::InterestPointErrorMetric(), opt.inlier_threshold);
+ math::RandomSampleConsensus<math::AffineFittingFunctor, math::InterestPointErrorMetric> ransac(math::AffineFittingFunctor(), math::InterestPointErrorMetric(), opt.ransac_iterations, opt.inlier_threshold, ransac_ip1.size()/2, true);
align_matrix = ransac(ransac_ip2,ransac_ip1);
indices = ransac.inlier_indices(align_matrix,ransac_ip2,ransac_ip1);
}
@@ -263,6 +264,7 @@ void handle_arguments( int argc, char* argv[], Options& opt ) {
("matcher-threshold,t", po::value(&opt.matcher_threshold)->default_value(0.5),
"Rejects points during matching if best > matcher_threshold * second_best")
("inlier-threshold,i", po::value(&opt.inlier_threshold)->default_value(10), "RANSAC inlier threshold.")
+ ("ransac-iterations", po::value(&opt.ransac_iterations)->default_value(100), "Number of RANSAC iterations.")
("homography", "Align images using a full projective transform (homography). By default, aligment uses a more restricted Similarity transform.");
po::options_description positional("");
15 src/vw/tools/ipmatch.cc
View
@@ -109,6 +109,7 @@ int main(int argc, char** argv) {
double matcher_threshold;
std::string ransac_constraint;
float inlier_threshold;
+ int ransac_iterations;
po::options_description general_options("Options");
general_options.add_options()
@@ -117,6 +118,7 @@ int main(int argc, char** argv) {
("non-kdtree", "Use an implementation of the interest matcher that is not reliant on a KDTree algorithm")
("ransac-constraint,r", po::value(&ransac_constraint)->default_value("similarity"), "RANSAC constraint type. Choose one of: [similarity, homography, fundamental, or none].")
("inlier-threshold,i", po::value(&inlier_threshold)->default_value(10), "RANSAC inlier threshold.")
+ ("ransac-iterations", po::value(&ransac_iterations)->default_value(100), "Number of RANSAC iterations.")
("debug-image,d", "Write out debug images.");
po::options_description hidden_options("");
@@ -192,19 +194,26 @@ int main(int argc, char** argv) {
if (ransac_constraint == "similarity") {
math::RandomSampleConsensus<math::SimilarityFittingFunctor, math::InterestPointErrorMetric> ransac( math::SimilarityFittingFunctor(),
math::InterestPointErrorMetric(),
- inlier_threshold ); // inlier_threshold
+ ransac_iterations,
+ inlier_threshold,
+ ransac_ip1.size()/2,
+ true);
Matrix<double> H(ransac(ransac_ip1,ransac_ip2));
std::cout << "\t--> Similarity: " << H << "\n";
indices = ransac.inlier_indices(H,ransac_ip1,ransac_ip2);
} else if (ransac_constraint == "homography") {
math::RandomSampleConsensus<math::HomographyFittingFunctor, math::InterestPointErrorMetric> ransac( math::HomographyFittingFunctor(),
math::InterestPointErrorMetric(),
- inlier_threshold ); // inlier_threshold
+ ransac_iterations,
+ inlier_threshold,
+ ransac_ip1.size()/2,
+ true
+ );
Matrix<double> H(ransac(ransac_ip1,ransac_ip2));
std::cout << "\t--> Homography: " << H << "\n";
indices = ransac.inlier_indices(H,ransac_ip1,ransac_ip2);
} else if (ransac_constraint == "fundamental") {
- math::RandomSampleConsensus<camera::FundamentalMatrix8PFittingFunctor, camera::FundamentalMatrixDistanceErrorMetric> ransac( camera::FundamentalMatrix8PFittingFunctor(), camera::FundamentalMatrixDistanceErrorMetric(), inlier_threshold );
+ math::RandomSampleConsensus<camera::FundamentalMatrix8PFittingFunctor, camera::FundamentalMatrixDistanceErrorMetric> ransac( camera::FundamentalMatrix8PFittingFunctor(), camera::FundamentalMatrixDistanceErrorMetric(), ransac_iterations, inlier_threshold, ransac_ip1.size()/2, true );
Matrix<double> F(ransac(ransac_ip1,ransac_ip2));
std::cout << "\t--> Fundamental: " << F << "\n";
indices = ransac.inlier_indices(F,ransac_ip1,ransac_ip2);
Please sign in to comment.
Something went wrong with that request. Please try again.