NASA Astrobee Robot Software  0.19.1
Flight software for the Astrobee robots operating inside the International Space Station.
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