Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
ransac.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <concepts>
6#include <cstddef>
7#include <iostream>
8#include <limits>
9#include <numeric>
10#include <optional>
11#include <random>
12#include <span>
13#include <stdexcept>
14#include <type_traits>
15#include <unordered_map>
16#include <vector>
17
18#include "calib/io/serialization.h" // to_json/from_json
19
20namespace calib {
21
22struct RansacOptions final {
23 int max_iters = 1000;
24 double thresh = 2.0;
25 int min_inliers = 12;
26 double confidence = 0.99;
27 uint64_t seed = 1234567;
28 bool refit_on_inliers = true;
29};
30
31template <class ModelT>
32struct RansacResult final {
33 bool success{false};
34 ModelT model{};
35 std::vector<int> inliers;
36 double inlier_rms{std::numeric_limits<double>::infinity()};
37 int iters{0};
38};
39
40namespace detail {
41
42template <typename Estimator>
43concept HasRefit =
44 requires(const std::vector<typename Estimator::Datum>& data, std::span<const int> idxs) {
45 { Estimator::refit(data, idxs) } -> std::same_as<std::optional<typename Estimator::Model>>;
46 };
47
48template <typename Estimator>
50 requires(const std::vector<typename Estimator::Datum>& data, std::span<const int> idxs) {
51 { Estimator::is_degenerate(data, idxs) } -> std::same_as<bool>;
52 };
53
54inline auto rms(const std::vector<double>& vals) -> double {
55 if (vals.empty()) {
56 std::cout << "Warning: RMS of empty set\n";
57 return std::numeric_limits<double>::infinity();
58 }
59 double ss = std::accumulate(vals.begin(), vals.end(), 0.0,
60 [](double acc, double v) { return acc + v * v; });
61 return std::sqrt(ss / static_cast<double>(vals.size()));
62}
63
64inline int calculate_iterations(double confidence, double inlier_ratio, int min_samples,
65 int iters_so_far, int max_iters) {
66 if (confidence <= 0.0 || inlier_ratio <= 0.0) {
67 return max_iters;
68 }
69 double p = confidence;
70 double w = inlier_ratio;
71 auto m = static_cast<double>(min_samples);
72 double denom = std::log(std::max(1e-12, 1.0 - std::pow(w, m)));
73 if (denom >= 0.0) {
74 return max_iters;
75 }
76 const int niter = static_cast<int>(std::ceil(std::log(1.0 - p) / denom));
77 return std::clamp(niter, iters_so_far, max_iters);
78}
79
80template <typename Estimator, typename Model>
81inline void find_inliers(const std::vector<typename Estimator::Datum>& data, const Model& model,
82 double threshold, std::vector<int>& inliers,
83 std::vector<double>& inlier_residuals) {
84 inliers.clear();
85 inlier_residuals.clear();
86 inliers.reserve(data.size());
87 inlier_residuals.reserve(data.size());
88 for (int i = 0; i < static_cast<int>(data.size()); ++i) {
89 double r = Estimator::residual(model, data[i]);
90 if (r <= threshold) {
91 inliers.push_back(i);
92 inlier_residuals.push_back(r);
93 }
94 }
95}
96
97template <typename Estimator, typename Model>
98inline auto refit_model(const std::vector<typename Estimator::Datum>& data, const Model& model,
99 const std::vector<int>& inliers, double threshold,
100 std::vector<int>& updated_inliers, std::vector<double>& updated_residuals)
101 -> Model {
102 Model refined_model = model;
103 if constexpr (HasRefit<Estimator>) {
104 if (auto m2 = Estimator::refit(data, std::span<const int>(inliers))) {
105 refined_model = *m2;
106 find_inliers<Estimator>(data, refined_model, threshold, updated_inliers,
107 updated_residuals);
108 }
109 }
110 return refined_model;
111}
112
113inline auto is_better_model(bool has_current_best, size_t new_inlier_count, double new_inlier_rms,
114 size_t best_inlier_count, double best_inlier_rms) -> bool {
115 return !has_current_best || (new_inlier_count > best_inlier_count) ||
116 (new_inlier_count == best_inlier_count && new_inlier_rms < best_inlier_rms);
117}
118
119} // namespace detail
120
121template <class Estimator>
122auto ransac(const std::vector<typename Estimator::Datum>& data, const RansacOptions& opts = {})
123 -> RansacResult<typename Estimator::Model> {
124 using Model = typename Estimator::Model;
125
126 RansacResult<Model> best;
127 if (data.size() < Estimator::k_min_samples) {
128 return best;
129 }
130
131 std::vector<int> all_indices(data.size());
132 std::vector<int> idxs(Estimator::k_min_samples);
133 std::iota(all_indices.begin(), all_indices.end(), 0);
134
135 std::mt19937_64 rng(opts.seed);
136
137 int dynamic_max_iters = opts.max_iters;
138 std::vector<int> inliers;
139 std::vector<double> inlier_residuals;
140 std::vector<int> refined_inliers;
141 std::vector<double> refined_residuals;
142
143 for (int it = 0; it < dynamic_max_iters; ++it) {
144 std::sample(all_indices.begin(), all_indices.end(), idxs.begin(), Estimator::k_min_samples,
145 rng);
146
147 if constexpr (detail::HasDegeneracyCheck<Estimator>) {
148 if (Estimator::is_degenerate(data, std::span<const int>(idxs))) {
149 continue;
150 }
151 }
152
153 auto model_opt = Estimator::fit(data, std::span<const int>(idxs));
154 if (!model_opt) {
155 continue;
156 }
157 const Model& model = *model_opt;
158
159 detail::find_inliers<Estimator>(data, model, opts.thresh, inliers, inlier_residuals);
160
161 if (static_cast<int>(inliers.size()) < opts.min_inliers) {
162 continue;
163 }
164
165 Model model_refit = model;
166 if (opts.refit_on_inliers) {
167 refined_inliers = inliers;
168 refined_residuals = inlier_residuals;
169 model_refit = detail::refit_model<Estimator>(data, model, inliers, opts.thresh,
170 refined_inliers, refined_residuals);
171 }
172
173 const auto& final_inliers = opts.refit_on_inliers ? refined_inliers : inliers;
174 const auto& final_residuals = opts.refit_on_inliers ? refined_residuals : inlier_residuals;
175 double final_rms = detail::rms(final_residuals);
176
177 if (detail::is_better_model(best.success, final_inliers.size(), final_rms,
178 best.inliers.size(), best.inlier_rms)) {
179 best.success = true;
180 best.model = model_refit;
181 best.inliers = final_inliers;
182 best.inlier_rms = final_rms;
183 best.iters = it + 1;
184 }
185
186 const double inlier_ratio =
187 static_cast<double>(final_inliers.size()) / static_cast<double>(data.size());
188 dynamic_max_iters = detail::calculate_iterations(opts.confidence, inlier_ratio,
189 static_cast<int>(Estimator::k_min_samples),
190 it + 1, opts.max_iters);
191 }
192
193 return best;
194}
195
196} // namespace calib
auto refit_model(const std::vector< typename Estimator::Datum > &data, const Model &model, const std::vector< int > &inliers, double threshold, std::vector< int > &updated_inliers, std::vector< double > &updated_residuals) -> Model
Definition ransac.h:98
auto is_better_model(bool has_current_best, size_t new_inlier_count, double new_inlier_rms, size_t best_inlier_count, double best_inlier_rms) -> bool
Definition ransac.h:113
int calculate_iterations(double confidence, double inlier_ratio, int min_samples, int iters_so_far, int max_iters)
Definition ransac.h:64
void find_inliers(const std::vector< typename Estimator::Datum > &data, const Model &model, double threshold, std::vector< int > &inliers, std::vector< double > &inlier_residuals)
Definition ransac.h:81
auto rms(const std::vector< double > &vals) -> double
Definition ransac.h:54
Linear multi-camera extrinsics initialisation (DLT)
HomographyEstimator::Model Model
auto ransac(const std::vector< typename Estimator::Datum > &data, const RansacOptions &opts={}) -> RansacResult< typename Estimator::Model >
Definition ransac.h:122
double inlier_rms
Definition ransac.h:36
std::vector< int > inliers
Definition ransac.h:35