NASA Astrobee Robot Software  Astrobee Version:
Flight software for the Astrobee robots operating inside the International Space Station.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ransac.h
Go to the documentation of this file.
1 /* Copyright (c) 2017, United States Government, as represented by the
2  * Administrator of the National Aeronautics and Space Administration.
3  *
4  * All rights reserved.
5  *
6  * The Astrobee platform is licensed under the Apache License, Version 2.0
7  * (the "License"); you may not use this file except in compliance with the
8  * License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15  * License for the specific language governing permissions and limitations
16  * under the License.
17  */
18 
19 #ifndef SPARSE_MAPPING_RANSAC_H_
20 #define SPARSE_MAPPING_RANSAC_H_
21 
22 #include <glog/logging.h>
23 
24 #include <ctime>
25 #include <climits>
26 
27 #include <string>
28 #include <vector>
29 #include <random>
30 #include <limits>
31 
32 
33 namespace sparse_mapping {
34 
35  void get_n_unique_integers(int min_val, int max_val, int num,
36  std::mt19937 * generator, std::vector<int> * values);
37 
39 template <class FittingFuncT, class ErrorFuncT>
41  const FittingFuncT& m_fitting_func;
42  const ErrorFuncT & m_error_func;
43  int m_num_iterations;
44  double m_inlier_threshold;
45  int m_min_num_output_inliers;
46  bool m_reduce_min_num_output_inliers_if_no_fit;
47  bool m_increase_threshold_if_no_fit;
48  std::mt19937 m_generator;
49 
50  public:
51  // Returns the list of inliers.
52  template <class ContainerT1, class ContainerT2>
53  void inliers(typename FittingFuncT::result_type const& H,
54  std::vector<ContainerT1> const& p1,
55  std::vector<ContainerT2> const& p2,
56  std::vector<ContainerT1> & inliers1,
57  std::vector<ContainerT2> & inliers2) const {
58  inliers1.clear();
59  inliers2.clear();
60 
61  for (size_t i = 0; i < p1.size(); i++) {
62  if (m_error_func(H, p1[i], p2[i]) < m_inlier_threshold) {
63  inliers1.push_back(p1[i]);
64  inliers2.push_back(p2[i]);
65  }
66  }
67  }
68 
69  // Returns the list of inlier indices.
70  template <class ContainerT1, class ContainerT2>
71  std::vector<size_t> inlier_indices(typename FittingFuncT::result_type const& H,
72  std::vector<ContainerT1> const& p1,
73  std::vector<ContainerT2> const& p2) const {
74  std::vector<size_t> result;
75  for (size_t i = 0; i < p1.size(); i++)
76  if (m_error_func(H, p1[i], p2[i]) < m_inlier_threshold)
77  result.push_back(i);
78 
79  LOG(INFO) << "RANSAC inliers / total = " << result.size() << " / " << p1.size() << ".\n";
80 
81  return result;
82  }
83 
85  m_min_num_output_inliers = static_cast<int>(m_min_num_output_inliers/1.5);
86  }
87 
89  RandomSampleConsensus(FittingFuncT const& fitting_func,
90  ErrorFuncT const& error_func,
91  int num_iterations,
92  double inlier_threshold,
93  int min_num_output_inliers,
94  bool reduce_min_num_output_inliers_if_no_fit,
95  bool increase_threshold_if_no_fit):
96  m_fitting_func(fitting_func), m_error_func(error_func),
97  m_num_iterations(num_iterations),
98  m_inlier_threshold(inlier_threshold),
99  m_min_num_output_inliers(min_num_output_inliers),
100  m_reduce_min_num_output_inliers_if_no_fit(reduce_min_num_output_inliers_if_no_fit),
101  m_increase_threshold_if_no_fit(increase_threshold_if_no_fit),
102  m_generator(std::mt19937(std::time(0))) {}
103 
105  template <class ContainerT1, class ContainerT2>
106  typename FittingFuncT::result_type operator()(std::vector<ContainerT1> const& p1,
107  std::vector<ContainerT2> const& p2) {
108  // Try to fit using RANSAC. Perform repeated fits with smaller
109  // m_min_num_output_inliers if the fit fails and
110  // m_reduce_min_num_output_inliers_if_no_fit is true.
111 
112  typename FittingFuncT::result_type H;
113  bool success = false;
114 
115  int orig_num_inliers = m_min_num_output_inliers;
116 
117  // Try various attempts while getting ever more generous with the
118  // threshold and the number of liners. for each of these, there
119  // will be m_num_iterations attempts.
120  for (int attempt_thresh = 0; attempt_thresh < 10; attempt_thresh++) {
121  for (int attempt_inlier = 0; attempt_inlier < 10; attempt_inlier++) {
122  try {
123  H = attempt_ransac(p1, p2);
124  success = true;
125  break;
126  } catch (const std::exception& e) {
127  LOG(INFO) << e.what() << "\n";
128  if (!m_reduce_min_num_output_inliers_if_no_fit)
129  break;
131  // A similarity transform needs at least 3 samples
132  if (m_min_num_output_inliers < 3)
133  break;
134  LOG(INFO) << "Attempting RANSAC with " << m_min_num_output_inliers
135  << " output inliers.\n";
136  }
137  }
138 
139  if (success)
140  break;
141  if (!m_increase_threshold_if_no_fit)
142  break;
143 
144  m_min_num_output_inliers = orig_num_inliers; // restore this
145  m_inlier_threshold *= 1.5;
146  LOG(INFO) << "Increasing the inlier threshold to: " << m_inlier_threshold << ".\n";
147  }
148 
149  if (!success)
150  LOG(FATAL) << "RANSAC was unable to find a fit that matched the supplied data.";
151 
152  return H;
153  }
154 
156  template <class ContainerT1, class ContainerT2>
157  typename FittingFuncT::result_type attempt_ransac(std::vector<ContainerT1> const& p1,
158  std::vector<ContainerT2> const& p2) {
159  if (p1.empty())
160  LOG(FATAL) << "RANSAC error. Insufficient data.\n";
161  if (p1.size() != p2.size())
162  LOG(FATAL) << "RANSAC error. Data vectors are not the same size.\n";
163 
164  int min_elems_for_fit = m_fitting_func.min_elements_needed_for_fit();
165 
166  if (static_cast<int>(p1.size()) < min_elems_for_fit)
167  LOG(FATAL) << "RANSAC error. Not enough potential matches for this fitting functor.\n";
168 
169  // This one we want to catch.
170  if (m_min_num_output_inliers < min_elems_for_fit)
171  throw std::runtime_error("RANSAC error. Number of requested inliers is less than "
172  "min number of elements needed for fit.\n");
173 
174  typename FittingFuncT::result_type best_H;
175 
176  std::vector<ContainerT1> try1;
177  std::vector<ContainerT2> try2;
178  std::vector<int> random_indices(min_elems_for_fit);
179 
180  int num_inliers = 0;
181  double min_err = std::numeric_limits<double>::max();
182  for (int iteration = 0; iteration < m_num_iterations; iteration++) {
183  // Get min_elems_for_fit points at random, taking care not to
184  // select the same point twice.
185  int num = p1.size();
186  get_n_unique_integers(0, num - 1, min_elems_for_fit, &m_generator, &random_indices);
187 
188  // Resizing below is essential, as by now their size may have changed
189  try1.resize(min_elems_for_fit);
190  try2.resize(min_elems_for_fit);
191  for (int i = 0; i < min_elems_for_fit; i++) {
192  try1[i] = p1[random_indices[i]];
193  try2[i] = p2[random_indices[i]];
194  }
195 
196  // 1. Compute the fit using these samples.
197  typename FittingFuncT::result_type H = m_fitting_func(try1, try2);
198 
199  // 2. Find all the inliers for this fit.
200  inliers(H, p1, p2, try1, try2);
201 
202  // 3. Skip this model if too few inliers.
203  if (static_cast<int>(try1.size()) < m_min_num_output_inliers)
204  continue;
205 
206  // 4. Re-estimate the model using the inliers.
207  H = m_fitting_func(try1, try2);
208 
209  // 5. Find the mean error for the inliers.
210  double err_val = 0.0;
211  for (size_t i = 0; i < try1.size(); i++)
212  err_val += m_error_func(H, try1[i], try2[i]);
213  err_val /= try1.size();
214 
215  // 6. Save this model if its error is lowest so far.
216  if (err_val < min_err) {
217  min_err = err_val;
218  best_H = H;
219  num_inliers = try1.size();
220  }
221  }
222 
223  if (num_inliers < m_min_num_output_inliers) {
224  // Here throw, to be able to catch it in the caller.
225  std::ostringstream os;
226  os << "RANSAC was unable to find a fit with " << m_min_num_output_inliers << " inliers.";
227  throw std::runtime_error(os.str());
228  }
229  return best_H;
230  }
231 }; // End of RandomSampleConsensus class definition
232 
233  // Helper function to instantiate a RANSAC class object and immediately call it
234  template <class ContainerT1, class ContainerT2, class FittingFuncT, class ErrorFuncT>
235  typename FittingFuncT::result_type ransac(std::vector<ContainerT1> const& p1,
236  std::vector<ContainerT2> const& p2,
237  FittingFuncT const& fitting_func,
238  ErrorFuncT const& error_func,
239  int num_iterations,
240  double inlier_threshold,
241  int min_num_output_inliers,
242  bool reduce_min_num_output_inliers_if_no_fit,
243  bool increase_threshold_if_no_fit) {
245  ransac_instance(fitting_func, error_func,
246  num_iterations, inlier_threshold,
247  min_num_output_inliers,
248  reduce_min_num_output_inliers_if_no_fit,
249  increase_threshold_if_no_fit);
250  return ransac_instance(p1, p2);
251  }
252 
253 } // namespace sparse_mapping
254 
255 #endif // SPARSE_MAPPING_RANSAC_H_
sparse_mapping
Definition: localization_parameters.h:22
sparse_mapping::RandomSampleConsensus::reduce_min_num_output_inliers
void reduce_min_num_output_inliers()
Definition: ransac.h:84
sparse_mapping::get_n_unique_integers
void get_n_unique_integers(int min_val, int max_val, int num, std::mt19937 *generator, std::vector< int > *values)
Definition: ransac.cc:33
sparse_mapping::RandomSampleConsensus::inliers
void inliers(typename FittingFuncT::result_type const &H, std::vector< ContainerT1 > const &p1, std::vector< ContainerT2 > const &p2, std::vector< ContainerT1 > &inliers1, std::vector< ContainerT2 > &inliers2) const
Definition: ransac.h:53
sparse_mapping::RandomSampleConsensus::RandomSampleConsensus
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, bool increase_threshold_if_no_fit)
Constructor - Stores all the inputs in member variables.
Definition: ransac.h:89
sparse_mapping::RandomSampleConsensus::operator()
FittingFuncT::result_type operator()(std::vector< ContainerT1 > const &p1, std::vector< ContainerT2 > const &p2)
As attempt_ransac but keep trying with smaller numbers of required inliers.
Definition: ransac.h:106
sparse_mapping::RandomSampleConsensus::attempt_ransac
FittingFuncT::result_type attempt_ransac(std::vector< ContainerT1 > const &p1, std::vector< ContainerT2 > const &p2)
Run RANSAC on two input data lists using the current parameters.
Definition: ransac.h:157
sparse_mapping::RandomSampleConsensus
RANSAC Driver class.
Definition: ransac.h:40
std
Definition: tensor.h:39
sparse_mapping::RandomSampleConsensus::inlier_indices
std::vector< size_t > inlier_indices(typename FittingFuncT::result_type const &H, std::vector< ContainerT1 > const &p1, std::vector< ContainerT2 > const &p2) const
Definition: ransac.h:71
sparse_mapping::ransac
FittingFuncT::result_type ransac(std::vector< ContainerT1 > const &p1, std::vector< ContainerT2 > const &p2, 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, bool increase_threshold_if_no_fit)
Definition: ransac.h:235