ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
vote_and_verify.cc
Go to the documentation of this file.
1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 // its contributors may be used to endorse or promote products derived
16 // from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31 
33 
34 #include <array>
35 #include <unordered_map>
36 
38 #include "optim/ransac.h"
39 #include "util/logging.h"
40 #include "util/math.h"
41 
42 namespace colmap {
43 namespace retrieval {
44 namespace {
45 
46 // Affine transformation from left to right and from left to right image.
47 struct TwoWayTransform {
48  TwoWayTransform()
49  : A12(Eigen::Matrix2f::Zero()),
50  t12(Eigen::Vector2f::Zero()),
51  A21(Eigen::Matrix2f::Zero()),
52  t21(Eigen::Vector2f::Zero()) {}
53  TwoWayTransform(const FeatureGeometryTransform& tform) {
54  const float sin_angle = std::sin(tform.angle);
55  const float cos_angle = std::cos(tform.angle);
56 
57  Eigen::Matrix2f R;
58  R << cos_angle, -sin_angle, sin_angle, cos_angle;
59 
60  A12 = tform.scale * R;
61  t12 << tform.tx, tform.ty;
62  A21 = R.transpose() / tform.scale;
63  t21 = -A21 * t12;
64  }
65 
66  Eigen::Matrix2f A12;
67  Eigen::Vector2f t12;
68  Eigen::Matrix2f A21;
69  Eigen::Vector2f t21;
70 };
71 
72 // Class representing a single bin in the voting space of a similarity
73 // transformation. Keeps track of the mean transformation described by the bin.
74 class VotingBin {
75  public:
76  void Vote(const FeatureGeometryTransform& tform) {
77  num_votes_ += 1;
78  sum_tform_.scale += tform.scale;
79  sum_tform_.angle += tform.angle;
80  sum_tform_.tx += tform.tx;
81  sum_tform_.ty += tform.ty;
82  }
83 
84  // Get the number of votes.
85  size_t GetNumVotes() const { return num_votes_; }
86 
87  // Compute the mean transformation of the voting bin.
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;
95  return tform;
96  }
97 
98  private:
99  size_t num_votes_ = 0;
100  FeatureGeometryTransform sum_tform_;
101 };
102 
103 // Compute the difference in scale between the two features when aligning them
104 // with the given transformation.
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;
112  } else {
113  return area_measured / area_transformed;
114  }
115 }
116 
117 // Compute the two-way transfer error between two features.
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;
126 }
127 
128 // Compute inlier matches that satisfy the transfer, scale thresholds.
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);
135 
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) <=
142  max_scale_error &&
143  ComputeTransferError(match.geometry1, geometry2, tform) <=
144  max_transfer_error) {
145  inlier_idxs->emplace_back(i, j);
146  }
147  }
148  }
149 }
150 
151 // Compute effective inlier count that satisfy the transfer, scale thresholds.
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);
160 
161  std::vector<std::pair<float, float>> inlier_coords;
162  inlier_coords.reserve(matches.size());
163 
164  float min_x = std::numeric_limits<float>::max();
165  float min_y = std::numeric_limits<float>::max();
166  float max_x = 0;
167  float max_y = 0;
168 
169  for (const auto& match : matches) {
170  for (const auto& geometry2 : match.geometries2) {
171  if (ComputeScaleError(match.geometry1, geometry2, tform) <=
172  max_scale_error &&
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);
180  break;
181  }
182  }
183  }
184 
185  if (inlier_coords.empty()) {
186  return 0;
187  }
188 
189  const float scale_x = num_bins / (max_x - min_x);
190  const float scale_y = num_bins / (max_y - min_y);
191 
192  Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic> counter(num_bins,
193  num_bins);
194  counter.setZero();
195 
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;
201  }
202 
203  return counter.sum();
204 }
205 
206 } // namespace
207 
209  const std::vector<FeatureGeometryMatch>& matches) {
210  CHECK_GT(options.num_transformations, 0);
211  CHECK_GT(options.num_trans_bins, 0);
212  CHECK_EQ(options.num_trans_bins % 2, 0);
213  CHECK_GT(options.num_scale_bins, 0);
214  CHECK_EQ(options.num_scale_bins % 2, 0);
215  CHECK_GT(options.num_angle_bins, 0);
216  CHECK_EQ(options.num_angle_bins % 2, 0);
217  CHECK_GT(options.max_image_size, 0);
218  CHECK_GT(options.min_num_votes, 0);
219  CHECK_GE(options.confidence, 0);
220  CHECK_LE(options.confidence, 1);
221 
222  if (matches.size() < AffineTransformEstimator::kMinNumSamples) {
223  return 0;
224  }
225 
226  const float max_trans = options.max_image_size;
227  const float kMaxScale = 10.0f;
228  const float max_log_scale = std::log2(kMaxScale);
229 
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);
233 
235  // Fill the multi-resolution voting histogram.
237 
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;
241 
242  for (const auto& match : matches) {
243  for (const auto& geometry2 : match.geometries2) {
244  const auto T =
245  FeatureGeometry::TransformFromMatch(match.geometry1, geometry2);
246 
247  if (std::abs(T.tx) > max_trans || std::abs(T.ty) > max_trans) {
248  continue;
249  }
250 
251  const float log_scale = std::log2(T.scale);
252  if (std::abs(log_scale) > max_log_scale) {
253  continue;
254  }
255 
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;
260 
261  int n_x = std::min(static_cast<int>(x * options.num_trans_bins),
262  static_cast<int>(options.num_trans_bins - 1));
263  int n_y = std::min(static_cast<int>(y * options.num_trans_bins),
264  static_cast<int>(options.num_trans_bins - 1));
265  int n_s = std::min(static_cast<int>(s * options.num_scale_bins),
266  static_cast<int>(options.num_scale_bins - 1));
267  int n_a = std::min(static_cast<int>(a * options.num_angle_bins),
268  static_cast<int>(options.num_angle_bins - 1));
269 
270  for (int level = 0; level < kNumLevels; ++level) {
271  const uint64_t index =
272  n_a + options.num_angle_bins *
273  (n_s + options.num_scale_bins *
274  (n_x + options.num_trans_bins * n_y));
275 
276  if (level == 0) {
277  coords[index] = Eigen::Vector4i(n_a, n_s, n_x, n_y);
278  }
279 
280  bins[level][index].Vote(T);
281 
282  n_x >>= 1;
283  n_y >>= 1;
284  n_s >>= 1;
285  n_a >>= 1;
286  }
287  }
288  }
289 
291  // Compute the multi-resolution scores for all occupied bins.
293 
294  std::vector<std::pair<int, float>> bin_scores;
295  for (const auto& bin : bins[0]) {
296  if (bin.second.GetNumVotes() >=
297  static_cast<size_t>(options.min_num_votes)) {
298  const auto coord = coords.at(bin.first);
299  int n_a = coord(0);
300  int n_s = coord(1);
301  int n_x = coord(2);
302  int n_y = coord(3);
303  float score = bin.second.GetNumVotes();
304  float level_weight = 0.5f;
305  for (int level = 1; level < kNumLevels; ++level) {
306  n_x >>= 1;
307  n_y >>= 1;
308  n_s >>= 1;
309  n_a >>= 1;
310  const uint64_t index =
311  n_a + options.num_angle_bins *
312  (n_s + options.num_scale_bins *
313  (n_x + options.num_trans_bins * n_y));
314  score += bins[level][index].GetNumVotes() * level_weight;
315  level_weight *= 0.5f;
316  }
317  bin_scores.emplace_back(bin.first, score);
318  }
319  }
320 
322  // Extract the top transformations.
324 
325  const size_t num_transformations = std::min(
326  static_cast<size_t>(options.num_transformations), bin_scores.size());
327 
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;
333  });
334 
336  // Verify the top transformations.
338 
339  size_t max_num_trials = std::numeric_limits<size_t>::max();
340  size_t best_num_inliers = 0;
341  TwoWayTransform best_tform;
342 
343  std::vector<std::pair<int, int>> inlier_idxs;
344  std::vector<Eigen::Vector2d> inlier_points1;
345  std::vector<Eigen::Vector2d> inlier_points2;
346 
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());
350  ComputeInliers(tform, matches, options.max_transfer_error,
351  options.max_scale_error, &inlier_idxs);
352 
353  if (inlier_idxs.size() < best_num_inliers ||
354  inlier_idxs.size() < AffineTransformEstimator::kMinNumSamples) {
355  continue;
356  }
357 
358  best_num_inliers = inlier_idxs.size();
359  best_tform = tform;
360 
361  if (best_num_inliers == matches.size()) {
362  break;
363  }
364 
365  // Collect matching inlier points.
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);
375  }
376 
377  // Local optimization on matching inlier points.
378  const Eigen::Matrix<double, 2, 3> A =
379  AffineTransformEstimator::Estimate(inlier_points1, inlier_points2)[0];
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>();
384 
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>();
390 
391  ComputeInliers(local_tform, matches, options.max_transfer_error,
392  options.max_scale_error, &inlier_idxs);
393 
394  if (inlier_idxs.size() > best_num_inliers) {
395  best_num_inliers = inlier_idxs.size();
396  best_tform = local_tform;
397 
398  if (best_num_inliers == matches.size()) {
399  break;
400  }
401  }
402 
404  best_num_inliers, matches.size(), options.confidence,
405  /* num_trials_multiplier = */ 3.0);
406  }
407 
408  if (best_num_inliers == 0) {
409  return 0;
410  }
411 
412  const size_t kNumBins = 64;
413  return ComputeEffectiveInlierCount(best_tform, matches,
414  options.max_transfer_error,
415  options.max_scale_error, kNumBins);
416 }
417 
418 } // namespace retrieval
419 } // namespace colmap
constexpr double M_PI
Pi.
Definition: CVConst.h:19
static std::vector< M_t > Estimate(const std::vector< X_t > &points1, const std::vector< Y_t > &points2)
static size_t ComputeNumTrials(const size_t num_inliers, const size_t num_samples, const double confidence, const double num_trials_multiplier)
Definition: ransac.h:136
a[190]
normal_z y
normal_z x
Definition: Eigen.h:103
int VoteAndVerify(const VoteAndVerifyOptions &options, const std::vector< FeatureGeometryMatch > &matches)
Eigen::Matrix< Index, 4, 1 > Vector4i
Definition: knncpp.h:31
static FeatureGeometryTransform TransformFromMatch(const FeatureGeometry &feature1, const FeatureGeometry &feature2)
Definition: geometry.cc:37
Eigen::Vector2f t21
Eigen::Matrix2f A12
Eigen::Matrix2f A21
Eigen::Vector2f t12