// 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 { /* * Uniform Sampler: * Choose uniformly m (sample size) points from N (points size). * Uses Fisher-Yates shuffle. */ class UniformSamplerImpl : public UniformSampler { private: std::vector points_random_pool; int sample_size, points_size = 0; RNG rng; public: UniformSamplerImpl (int state, int sample_size_, int points_size_) : rng(state) { sample_size = sample_size_; setPointsSize (points_size_); } void setNewPointsSize (int points_size_) override { setPointsSize(points_size_); } void generateSample (std::vector &sample) override { int random_pool_size = points_size; // random points of entire range for (int i = 0; i < sample_size; i++) { // get random point index const int array_random_index = rng.uniform(0, random_pool_size); // get point by random index // store sample sample[i] = points_random_pool[array_random_index]; // swap random point with the end of random pool std::swap(points_random_pool[array_random_index], points_random_pool[--random_pool_size]); } } Ptr clone (int state) const override { return makePtr(state, sample_size, points_size); } private: void setPointsSize (int points_size_) { CV_Assert (sample_size <= points_size_); if (points_size_ > points_size) points_random_pool = std::vector(points_size_); if (points_size != points_size_) { points_size = points_size_; for (int i = 0; i < points_size; i++) points_random_pool[i] = i; } } }; Ptr UniformSampler::create(int state, int sample_size_, int points_size_) { return makePtr(state, sample_size_, points_size_); } /////////////////////////////////// PROSAC (SIMPLE) SAMPLER /////////////////////////////////////// /* * PROSAC (simple) sampler does not use array of precalculated T_n (n is subset size) samples, but computes T_n for * specific n directy in generateSample() function. * Also, the stopping length (or maximum subset size n*) by default is set to points_size (N) and does not updating * during computation. */ class ProsacSimpleSamplerImpl : public ProsacSimpleSampler { protected: int points_size, subset_size, t_n_prime, kth_sample_number, max_prosac_samples_count, largest_sample_size, sample_size; double t_n; Ptr random_gen; public: ProsacSimpleSamplerImpl (int state, int points_size_, int sample_size_, int max_prosac_samples_count_) : random_gen(UniformRandomGenerator::create(state)) { CV_Assert(sample_size_ <= points_size_); sample_size = sample_size_; points_size = points_size_; max_prosac_samples_count = max_prosac_samples_count_; initialize (); } void generateSample (std::vector &sample) override { if (kth_sample_number > max_prosac_samples_count) { // do uniform sampling, if prosac has not found solution random_gen->generateUniqueRandomSet(sample, sample_size, points_size); return; } kth_sample_number++; // t := t + 1 // Choice of the hypothesis generation set if (kth_sample_number >= t_n_prime && subset_size < largest_sample_size) { // do not use array of growth sample, calculate it directly double t_n_plus1 = (subset_size + 1) * t_n / (subset_size + 1 - sample_size); t_n_prime += static_cast(ceil(t_n_plus1 - t_n)); t_n = t_n_plus1; subset_size++; } // Semi-random sample Mt of size m if (t_n_prime < kth_sample_number) { random_gen->generateUniqueRandomSet(sample, sample_size, subset_size); } else { random_gen->generateUniqueRandomSet(sample, sample_size-1, subset_size-1); sample[sample_size-1] = subset_size-1; // Make the last point from the nth position. } } // Set the sample such that you are sampling the kth prosac sample (Eq. 6). void setSampleNumber (int k) { kth_sample_number = k; // If the method should act exactly like RANSAC if (kth_sample_number > max_prosac_samples_count) return; else { // Increment the size of the sampling pool while required t_n = max_prosac_samples_count; t_n_prime = 1; // reset growth function subset_size = sample_size; // reset subset size as from the beginning for (int i = 0; i < sample_size; i++) t_n *= static_cast(subset_size - i) / (points_size - i); while (kth_sample_number > t_n_prime) { // t_n_prime == growth_function double t_n_plus1 = static_cast(subset_size + 1) * t_n / (subset_size + 1 - sample_size); t_n_prime += static_cast(ceil(t_n_plus1 - t_n)); t_n = t_n_plus1; subset_size++; } if (subset_size > points_size) subset_size = points_size; } } void setNewPointsSize (int points_size_) override { CV_Assert(sample_size <= points_size_); points_size = points_size_; initialize (); } Ptr clone (int state) const override { return makePtr(state, points_size, sample_size, max_prosac_samples_count); } private: void initialize () { largest_sample_size = points_size; // termination length, n* subset_size = sample_size; // n t_n = max_prosac_samples_count; t_n_prime = 1; // From Equations leading up to Eq 3 in Chum et al. // t_n samples containing only data points from U_n and // t_n+1 samples containing only data points from U_n+1 for (int i = 0; i < sample_size; i++) t_n *= static_cast(subset_size - i) / (points_size - i); kth_sample_number = 0; } }; Ptr ProsacSimpleSampler::create(int state, int points_size_, int sample_size_, int max_prosac_samples_count_) { return makePtr(state, points_size_, sample_size_, max_prosac_samples_count_); } ////////////////////////////////////// PROSAC SAMPLER //////////////////////////////////////////// class ProsacSamplerImpl : public ProsacSampler { protected: std::vector growth_function; // subset_size = size of sampling range (subset of good sorted points) // termination_length = n*, maximum sampling range (largest subset size) int points_size, sample_size, subset_size, termination_length; // it is T_N // Imagine standard RANSAC drawing T_N samples of size m out of N data points // In our experiments, the parameter was set to T_N = 200000 int growth_max_samples; // how many time PROSAC generateSample() was called int kth_sample_number; Ptr random_gen; public: void setTerminationLength (int termination_length_) override { termination_length = termination_length_; } // return constant reference to prosac termination criteria int getKthSample () const override { return kth_sample_number; } // return constant points of growth function to prosac termination criteria const std::vector & getGrowthFunction () const override { return growth_function; } ProsacSamplerImpl (int state, int points_size_, int sample_size_, int growth_max_samples_) : random_gen(UniformRandomGenerator::create(state)) { CV_Assert(sample_size_ <= points_size_); sample_size = sample_size_; points_size = points_size_; growth_max_samples = growth_max_samples_; growth_function = std::vector(points_size); kth_sample_number = 0; // The data points in U_N are sorted in descending order w.r.t. the quality function q. // Let {Mi}i = 1...T_N denote the sequence of samples Mi c U_N that are uniformly drawn by Ransac. // Let T_n be an average number of samples from {Mi}i=1...T_N that contain data points from U_n only. // compute initial value for T_n // n - i // T_n = T_N * Product i = 0...m-1 -------, n >= sample size, N = points size // N - i double T_n = growth_max_samples; for (int i = 0; i < sample_size; i++) T_n *= static_cast (sample_size-i) / (points_size-i); int T_n_prime = 1; // fill growth function with T'_n until sample_size for (int n = 0; n < sample_size; n++) growth_function[n] = T_n_prime; // compute values using recurrent relation // n + 1 // T(n+1) = --------- T(n), m is sample size. // n + 1 - m // growth function is defined as // g(t) = min {n, T'_(n) >= t} // T'_(n+1) = T'_(n) + (T_(n+1) - T_(n)) // T'_m = 1 for (int n = sample_size; n < points_size; n++) { double Tn_plus1 = static_cast(n + 1) * T_n / (n + 1 - sample_size); growth_function[n] = T_n_prime + (int) ceil(Tn_plus1 - T_n); // T'_{n+1} // update T_n = Tn_plus1; T_n_prime = growth_function[n]; // T'_{n+1} } // other initializations termination_length = points_size; // n* = N, largest set sampled in PROSAC (termination length) subset_size = sample_size; // n, size of the current sampling pool kth_sample_number = 0; // t (iteration) } void generateSample (std::vector &sample) override { if (kth_sample_number > growth_max_samples) { // if PROSAC has not converged to solution then do uniform sampling. random_gen->generateUniqueRandomSet(sample, sample_size, points_size); return; } kth_sample_number++; // t := t + 1 // 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) 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; } else { // Select m points from U_n at random. random_gen->generateUniqueRandomSet(sample, sample_size, subset_size); } } // Set the sample such that you are sampling the kth prosac sample (Eq. 6). void setSampleNumber (int k) { kth_sample_number = k; // If the method should act exactly like RANSAC if (kth_sample_number > growth_max_samples) return; else { // Increment the size of the sampling pool while required subset_size = sample_size; // reset subset size as from the beginning while (kth_sample_number > growth_function[subset_size-1]) { subset_size++; if (subset_size >= points_size){ subset_size = points_size; break; } } if (termination_length < subset_size) termination_length = subset_size; } } void setNewPointsSize (int /*points_size_*/) override { CV_Error(cv::Error::StsError, "Changing points size in PROSAC requires to change also " "termination criteria! Use PROSAC simpler version"); } Ptr clone (int state) const override { return makePtr(state, points_size, sample_size, growth_max_samples); } }; Ptr ProsacSampler::create(int state, int points_size_, int sample_size_, int growth_max_samples_) { return makePtr(state, points_size_, sample_size_, growth_max_samples_); } ////////////////////////////////////// P-NAPSAC SAMPLER //////////////////////////////////////////// class ProgressiveNapsacImpl : public ProgressiveNapsac { private: int max_progressive_napsac_iterations, points_size; // how many times generateSample() was called. int kth_sample_number, grid_layers_number, sample_size, sampler_length; const Ptr random_generator; ProsacSamplerImpl one_point_prosac, prosac_sampler; // The overlapping neighborhood layers const std::vector> * layers; std::vector growth_function; std::vector hits_per_point; // number of iterations, t std::vector subset_size_per_point; // k std::vector current_layer_per_point; // layer of grid neighborhood graph public: // points must be sorted ProgressiveNapsacImpl (int state,int points_size_, int sample_size_, const std::vector> &layers_, int sampler_length_) : // initialize one-point prosac sampler and global prosac sampler random_generator (UniformRandomGenerator::create(state)), one_point_prosac (random_generator->getRandomNumber(INT_MAX), points_size_, 1 /* sample_size*/,points_size_), prosac_sampler (random_generator->getRandomNumber(INT_MAX), points_size_, sample_size_, 200000), layers(&layers_) { CV_Assert(sample_size_ <= points_size_); sample_size = sample_size_; points_size = points_size_; sampler_length = sampler_length_; grid_layers_number = static_cast(layers_.size()); // Create growth function for P-NAPSAC growth_function = std::vector(points_size); // 20 is sampler_length = The length of fully blending to global sampling max_progressive_napsac_iterations = sampler_length * points_size; const int local_sample_size = sample_size - 1; // not including initial point double T_n = max_progressive_napsac_iterations; for (int i = 0; i < local_sample_size; i++) T_n *= static_cast (local_sample_size - i) / (points_size - i); // calculate growth function by recurrent relation (see PROSAC) int T_n_prime = 1; for (int n = 0; n < points_size; n++) { if (n + 1 <= local_sample_size) { growth_function[n] = T_n_prime; continue; } double Tn_plus1 = (n+1) * T_n / (n + 1 - local_sample_size); growth_function[n] = T_n_prime + static_cast(ceil(Tn_plus1 - T_n)); T_n = Tn_plus1; T_n_prime = growth_function[n]; } subset_size_per_point = std::vector(points_size, sample_size); // subset size hits_per_point = std::vector(points_size, 0); // 0 hits current_layer_per_point = std::vector(points_size, 0); // 0-th layer kth_sample_number = 0; // iteration } void generateSample (std::vector &sample) override { // Do completely global sampling (PROSAC is used now), instead of Progressive NAPSAC, // if the maximum iterations has been done without finding the sought model. if (kth_sample_number > max_progressive_napsac_iterations) { prosac_sampler.generateSample(sample); return; } kth_sample_number++; // get PROSAC one-point sample (initial point) one_point_prosac.generateSample(sample); const int initial_point = sample[0]; // get hits number and subset size (i.e., the size of the neighborhood sphere) // of initial point (note, get by reference) int &iters_of_init_pt = ++hits_per_point[initial_point]; // t := t + 1, increase iteration int &subset_size_of_init_pt = subset_size_per_point[initial_point]; while (iters_of_init_pt > growth_function[subset_size_of_init_pt - 1] && subset_size_of_init_pt < points_size) subset_size_of_init_pt++; // Get layer of initial point (note, get by reference) int ¤t_layer = current_layer_per_point[initial_point]; bool is_last_layer = false; do {// Try to find the grid which contains enough points // In the case when the grid with a single cell is used, // apply PROSAC. if (current_layer >= grid_layers_number) { is_last_layer = true; break; } // If there are not enough points in the cell, start using a // less fine grid. if ((int)layers->at(current_layer)->getNeighbors(initial_point).size() < subset_size_of_init_pt) { ++current_layer; // Jump to the next layer with bigger cells. continue; } // If the procedure got to this point, there is no reason to choose a different layer of grids // since the current one has enough points. break; } while (true); // If not the last layer has been chosen, sample from the neighbors of the initially selected point. if (!is_last_layer) { // The indices of the points which are in the same cell as the // initially selected one. const std::vector &neighbors = layers->at(current_layer)->getNeighbors(initial_point); // Put the selected point to the end of the sample array to avoid // being overwritten when sampling the remaining points. sample[sample_size - 1] = initial_point; // The next point should be the farthest one from the initial point. Note that the points in the grid cell are // not ordered w.r.t. to their distances from the initial point. However, they are ordered as in PROSAC. sample[sample_size - 2] = neighbors[subset_size_of_init_pt - 1]; // Select n - 2 points randomly random_generator->generateUniqueRandomSet(sample, sample_size - 2, subset_size_of_init_pt - 1); for (int i = 0; i < sample_size - 2; i++) { sample[i] = neighbors[sample[i]]; // Replace the neighbor index by the index of the point ++hits_per_point[sample[i]]; // Increase the hit number of each selected point } ++hits_per_point[sample[sample_size - 2]]; // Increase the hit number of each selected point } // If the last layer (i.e., the layer with a single cell) has been chosen, do global sampling // by PROSAC sampler. else { // last layer, all points are neighbors // If local sampling prosac_sampler.setSampleNumber(kth_sample_number); prosac_sampler.generateSample (sample); sample[sample_size - 1] = initial_point; } } void setNewPointsSize (int /*points_size_*/) override { CV_Error(cv::Error::StsError, "Changing points size requires changing neighborhood graph! " "You must reinitialize P-NAPSAC!"); } Ptr clone (int state) const override { return makePtr(state, points_size, sample_size, *layers, sampler_length); } }; Ptr ProgressiveNapsac::create(int state, int points_size_, int sample_size_, const std::vector> &layers, int sampler_length_) { return makePtr(state, points_size_, sample_size_, layers, sampler_length_); } ////////////////////// N adjacent points sample consensus (NAPSAC) SAMPLER //////////////////////// class NapsacSamplerImpl : public NapsacSampler { private: const Ptr neighborhood_graph; const Ptr random_generator; bool do_uniform = false; std::vector points_large_neighborhood; int points_large_neighborhood_size, points_size, sample_size; public: NapsacSamplerImpl (int state, int points_size_, int sample_size_, const Ptr &neighborhood_graph_) : neighborhood_graph (neighborhood_graph_), random_generator(UniformRandomGenerator::create(state, points_size_, sample_size_)) { CV_Assert(points_size_ >= sample_size_); points_size = points_size_; sample_size = sample_size_; points_large_neighborhood = std::vector(points_size); points_large_neighborhood_size = 0; // find indicies of points that have sufficient neighborhood (at least sample_size-1) for (int pt_idx = 0; pt_idx < points_size; pt_idx++) if ((int)neighborhood_graph->getNeighbors(pt_idx).size() >= sample_size-1) points_large_neighborhood[points_large_neighborhood_size++] = pt_idx; // if no points with sufficient neighborhood then do only uniform sampling if (points_large_neighborhood_size == 0) do_uniform = true; // set random generator to generate random points of sample_size-1 random_generator->setSubsetSize(sample_size-1); } void generateSample (std::vector &sample) override { if (do_uniform) // uniform sampling random_generator->generateUniqueRandomSet(sample, points_size); else { // Take uniformly one initial point from points with sufficient neighborhood int initial_point = points_large_neighborhood [random_generator->getRandomNumber(points_large_neighborhood_size)]; const std::vector &neighbors = neighborhood_graph->getNeighbors(initial_point); // select random neighbors of initial point random_generator->generateUniqueRandomSet(sample, (int)neighbors.size()); for (int i = 0; i < sample_size-1; i++) sample[i] = neighbors[sample[i]]; // sample includes initial point too. sample[sample_size-1] = initial_point; } } void setNewPointsSize (int /*points_size_*/) override { CV_Error(cv::Error::StsError, "Changing points size requires changing neighborhood graph!" " You must reinitialize NAPSAC!"); } Ptr clone (int state) const override { return makePtr(state, points_size, sample_size, neighborhood_graph); } }; Ptr NapsacSampler::create(int state, int points_size_, int sample_size_, const Ptr &neighborhood_graph_) { return makePtr(state, points_size_, sample_size_, neighborhood_graph_); } }}