35 #include <unordered_map>
47 struct TwoWayTransform {
53 TwoWayTransform(
const FeatureGeometryTransform& tform) {
54 const float sin_angle = std::sin(tform.angle);
55 const float cos_angle = std::cos(tform.angle);
58 R << cos_angle, -sin_angle, sin_angle, cos_angle;
60 A12 = tform.scale * R;
61 t12 << tform.tx, tform.ty;
62 A21 = R.transpose() / tform.scale;
76 void Vote(
const FeatureGeometryTransform& tform) {
78 sum_tform_.scale += tform.scale;
79 sum_tform_.angle += tform.angle;
80 sum_tform_.tx += tform.tx;
81 sum_tform_.ty += tform.ty;
85 size_t GetNumVotes()
const {
return num_votes_; }
88 FeatureGeometryTransform GetTransformation()
const {
89 const float inv_num_votes = 1.0f /
static_cast<float>(num_votes_);
90 FeatureGeometryTransform tform = sum_tform_;
91 tform.scale *= inv_num_votes;
92 tform.angle *= inv_num_votes;
93 tform.tx *= inv_num_votes;
94 tform.ty *= inv_num_votes;
99 size_t num_votes_ = 0;
100 FeatureGeometryTransform sum_tform_;
105 float ComputeScaleError(
const FeatureGeometry& feature1,
106 const FeatureGeometry& feature2,
107 const TwoWayTransform& tform) {
108 const float area_transformed = feature1.GetAreaUnderTransform(tform.A21);
109 const float area_measured = feature2.GetArea();
110 if (area_transformed > area_measured) {
111 return area_transformed / area_measured;
113 return area_measured / area_transformed;
118 float ComputeTransferError(
const FeatureGeometry& feature1,
119 const FeatureGeometry& feature2,
120 const TwoWayTransform& tform) {
121 const Eigen::Vector2f xy1(feature1.x, feature1.y);
122 const Eigen::Vector2f xy2(feature2.x, feature2.y);
123 const float error1 = (xy2 - tform.A12 * xy1 - tform.t12).squaredNorm();
124 const float error2 = (xy1 - tform.A21 * xy2 - tform.t21).squaredNorm();
125 return error1 + error2;
129 void ComputeInliers(
const TwoWayTransform& tform,
130 const std::vector<FeatureGeometryMatch>& matches,
131 const float max_transfer_error,
const float max_scale_error,
132 std::vector<std::pair<int, int>>* inlier_idxs) {
133 CHECK_GT(max_transfer_error, 0);
134 CHECK_GT(max_scale_error, 0);
136 inlier_idxs->clear();
137 for (
size_t i = 0; i < matches.size(); ++i) {
138 const auto& match = matches[i];
139 for (
size_t j = 0; j < match.geometries2.size(); ++j) {
140 const auto& geometry2 = match.geometries2[j];
141 if (ComputeScaleError(match.geometry1, geometry2, tform) <=
143 ComputeTransferError(match.geometry1, geometry2, tform) <=
144 max_transfer_error) {
145 inlier_idxs->emplace_back(i, j);
152 size_t ComputeEffectiveInlierCount(
153 const TwoWayTransform& tform,
154 const std::vector<FeatureGeometryMatch>& matches,
155 const float max_transfer_error,
const float max_scale_error,
156 const int num_bins) {
157 CHECK_GT(max_transfer_error, 0);
158 CHECK_GT(max_scale_error, 0);
159 CHECK_GT(num_bins, 0);
161 std::vector<std::pair<float, float>> inlier_coords;
162 inlier_coords.reserve(matches.size());
164 float min_x = std::numeric_limits<float>::max();
165 float min_y = std::numeric_limits<float>::max();
169 for (
const auto& match : matches) {
170 for (
const auto& geometry2 : match.geometries2) {
171 if (ComputeScaleError(match.geometry1, geometry2, tform) <=
173 ComputeTransferError(match.geometry1, geometry2, tform) <=
174 max_transfer_error) {
175 inlier_coords.emplace_back(match.geometry1.x, match.geometry1.y);
176 min_x = std::min(min_x, match.geometry1.x);
177 min_y = std::min(min_y, match.geometry1.y);
178 max_x = std::max(max_x, match.geometry1.x);
179 max_y = std::max(max_y, match.geometry1.y);
185 if (inlier_coords.empty()) {
189 const float scale_x = num_bins / (max_x - min_x);
190 const float scale_y = num_bins / (max_y - min_y);
192 Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic> counter(num_bins,
196 for (
const auto& coord : inlier_coords) {
197 const int c_x = (coord.first - min_x) * scale_x;
198 const int c_y = (coord.second - min_y) * scale_y;
199 counter(std::max(0, std::min(num_bins - 1, c_x)),
200 std::max(0, std::min(num_bins - 1, c_y))) = 1;
203 return counter.sum();
209 const std::vector<FeatureGeometryMatch>& matches) {
227 const float kMaxScale = 10.0f;
228 const float max_log_scale = std::log2(kMaxScale);
230 const float trans_norm = 1.0f / (2.0f * max_trans);
231 const float scale_norm = 1.0f / (2.0f * max_log_scale);
232 const float angle_norm = 1.0f / (2.0f *
M_PI);
238 const int kNumLevels = 6;
239 std::array<std::unordered_map<size_t, VotingBin>, kNumLevels> bins;
240 std::unordered_map<size_t, Eigen::Vector4i> coords;
242 for (
const auto& match : matches) {
243 for (
const auto& geometry2 : match.geometries2) {
247 if (std::abs(T.tx) > max_trans || std::abs(T.ty) > max_trans) {
251 const float log_scale = std::log2(T.scale);
252 if (std::abs(log_scale) > max_log_scale) {
256 const float x = (T.tx + max_trans) * trans_norm;
257 const float y = (T.ty + max_trans) * trans_norm;
258 const float s = (log_scale + max_log_scale) * scale_norm;
259 const float a = (T.angle +
M_PI) * angle_norm;
270 for (
int level = 0; level < kNumLevels; ++level) {
271 const uint64_t index =
280 bins[level][index].Vote(T);
294 std::vector<std::pair<int, float>> bin_scores;
295 for (
const auto& bin : bins[0]) {
296 if (bin.second.GetNumVotes() >=
298 const auto coord = coords.at(bin.first);
303 float score = bin.second.GetNumVotes();
304 float level_weight = 0.5f;
305 for (
int level = 1; level < kNumLevels; ++level) {
310 const uint64_t index =
314 score += bins[level][index].GetNumVotes() * level_weight;
315 level_weight *= 0.5f;
317 bin_scores.emplace_back(bin.first, score);
325 const size_t num_transformations = std::min(
328 std::partial_sort(bin_scores.begin(),
329 bin_scores.begin() + num_transformations, bin_scores.end(),
330 [](
const std::pair<int, float>& score1,
331 const std::pair<int, float>& score2) {
332 return score1.second > score2.second;
339 size_t max_num_trials = std::numeric_limits<size_t>::max();
340 size_t best_num_inliers = 0;
341 TwoWayTransform best_tform;
343 std::vector<std::pair<int, int>> inlier_idxs;
344 std::vector<Eigen::Vector2d> inlier_points1;
345 std::vector<Eigen::Vector2d> inlier_points2;
347 for (
size_t i = 0; i < num_transformations && i < max_num_trials; ++i) {
348 const auto& bin = bins[0].at(bin_scores.at(i).first);
349 const auto tform = TwoWayTransform(bin.GetTransformation());
353 if (inlier_idxs.size() < best_num_inliers ||
358 best_num_inliers = inlier_idxs.size();
361 if (best_num_inliers == matches.size()) {
366 inlier_points1.resize(inlier_idxs.size());
367 inlier_points2.resize(inlier_idxs.size());
368 for (
size_t j = 0; j < inlier_idxs.size(); ++j) {
369 const auto& inlier_idx = inlier_idxs[j];
370 const auto& match = matches.at(inlier_idx.first);
371 const auto& geometry1 = match.geometry1;
372 const auto& geometry2 = match.geometries2.at(inlier_idx.second);
373 inlier_points1[j] = Eigen::Vector2d(geometry1.x, geometry1.y);
374 inlier_points2[j] = Eigen::Vector2d(geometry2.x, geometry2.y);
378 const Eigen::Matrix<double, 2, 3> A =
380 Eigen::Matrix3d A_homogeneous = Eigen::Matrix3d::Identity();
381 A_homogeneous.topRows<2>() = A;
382 const Eigen::Matrix<double, 2, 3> inv_A =
383 A_homogeneous.inverse().topRows<2>();
385 TwoWayTransform local_tform;
386 local_tform.A12 = A.leftCols<2>().cast<float>();
387 local_tform.t12 = A.rightCols<1>().cast<float>();
388 local_tform.A21 = inv_A.leftCols<2>().cast<float>();
389 local_tform.t21 = inv_A.rightCols<1>().cast<float>();
394 if (inlier_idxs.size() > best_num_inliers) {
395 best_num_inliers = inlier_idxs.size();
396 best_tform = local_tform;
398 if (best_num_inliers == matches.size()) {
404 best_num_inliers, matches.size(), options.
confidence,
408 if (best_num_inliers == 0) {
412 const size_t kNumBins = 64;
413 return ComputeEffectiveInlierCount(best_tform, matches,
static size_t ComputeNumTrials(const size_t num_inliers, const size_t num_samples, const double confidence, const double num_trials_multiplier)
int VoteAndVerify(const VoteAndVerifyOptions &options, const std::vector< FeatureGeometryMatch > &matches)
Eigen::Matrix< Index, 4, 1 > Vector4i
static FeatureGeometryTransform TransformFromMatch(const FeatureGeometry &feature1, const FeatureGeometry &feature2)
double max_transfer_error