1 // This file is part of OpenMVG, an Open Multiple View Geometry C++ library.
2
3 // Copyright (c) 2013 Pierre MOULON.
4
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9 #ifndef OPENMVG_ROBUST_ESTIMATION_GUIDED_MATCHING_HPP
10 #define OPENMVG_ROBUST_ESTIMATION_GUIDED_MATCHING_HPP
11
12 #include <algorithm>
13 #include <limits>
14 #include <vector>
15
16 #include "openMVG/cameras/Camera_Intrinsics.hpp"
17 #include "openMVG/features/regions.hpp"
18 #include "openMVG/matching/indMatch.hpp"
19 #include "openMVG/numeric/numeric.h"
20
21 namespace openMVG{
22 namespace geometry_aware{
23
24 /// Guided Matching (features only):
25 /// Use a model to find valid correspondences:
26 /// Keep the best corresponding points for the given model under the
27 /// user specified distance.
28 template<
29 typename ModelArg, // The used model type
30 typename ErrorArg> // The metric to compute distance to the model
GuidedMatching(const ModelArg & mod,const Mat & xLeft,const Mat & xRight,double errorTh,matching::IndMatches & vec_corresponding_index)31 void GuidedMatching(
32 const ModelArg & mod, // The model
33 const Mat & xLeft, // The left data points
34 const Mat & xRight, // The right data points
35 double errorTh, // Maximal authorized error threshold
36 matching::IndMatches & vec_corresponding_index) // Ouput corresponding index
37 {
38 assert(xLeft.rows() == xRight.rows());
39
40 // Looking for the corresponding points that have
41 // the smallest distance (smaller than the provided Threshold)
42
43 #pragma omp parallel for
44 for (int i = 0; i < xLeft.cols(); ++i) {
45
46 double min = std::numeric_limits<double>::max();
47 matching::IndMatch match;
48 for (size_t j = 0; j < xRight.cols(); ++j) {
49 // Compute the geometric error: error to the model
50 const double err = ErrorArg::Error(
51 mod, // The model
52 xLeft.col(i), xRight.col(j)); // The corresponding points
53 // if smaller error update corresponding index
54 if (err < errorTh && err < min) {
55 min = err;
56 match = matching::IndMatch(i,j);
57 }
58 }
59 if (min < errorTh) {
60 // save the best corresponding index
61 #pragma omp critical
62 vec_corresponding_index.push_back(match);
63 }
64 }
65
66 // Remove duplicates (when multiple points at same position exist)
67 matching::IndMatch::getDeduplicated(vec_corresponding_index);
68 }
69
70 // Struct to help filtering of correspondence according update of
71 // two smallest distance.
72 // -> useful for descriptor distance ratio filtering
73 template <typename DistT>
74 struct distanceRatio
75 {
76 DistT bd, sbd; // best and second best distance
77 size_t idx; // best corresponding index
78
distanceRatioopenMVG::geometry_aware::distanceRatio79 distanceRatio():
80 bd(std::numeric_limits<DistT>::max()),
81 sbd(std::numeric_limits<DistT>::max()),
82 idx(0)
83 { }
84
85 // Update match according the provided distance
updateopenMVG::geometry_aware::distanceRatio86 inline bool update(size_t index, DistT dist)
87 {
88 if (dist < bd) // best than any previous
89 {
90 idx = index;
91 // update and swap
92 sbd = dist;
93 std::swap(bd, sbd);
94 return true;
95 }
96 else if (dist < sbd)
97 {
98 sbd = dist;
99 return true;
100 }
101 return false;
102 }
103
104 // Return if the ratio of distance is ok or not
isValidopenMVG::geometry_aware::distanceRatio105 inline bool isValid(const double distRatio) const{
106 // check:
107 // - that two best distance have been found
108 // - the distance ratio
109 return
110 (sbd != std::numeric_limits<DistT>::max()
111 && bd < distRatio * sbd);
112 }
113 };
114
115 /// Guided Matching (features + descriptors with distance ratio):
116 /// Use a model to find valid correspondences:
117 /// Keep the best corresponding points for the given model under the
118 /// user specified distance ratio.
119 template<
120 typename ModelArg, // The used model type
121 typename ErrorArg, // The metric to compute distance to the model
122 typename DescriptorT, // The descriptor type
123 typename MetricT > // The metric to compare two descriptors
GuidedMatching(const ModelArg & mod,const Mat & xLeft,const std::vector<DescriptorT> & lDescriptors,const Mat & xRight,const std::vector<DescriptorT> & rDescriptors,double errorTh,double distRatio,matching::IndMatches & vec_corresponding_index)124 void GuidedMatching(
125 const ModelArg & mod, // The model
126 const Mat & xLeft, // The left data points
127 const std::vector<DescriptorT > & lDescriptors,
128 const Mat & xRight, // The right data points
129 const std::vector<DescriptorT > & rDescriptors,
130 double errorTh, // Maximal authorized error threshold
131 double distRatio, // Maximal authorized distance ratio
132 matching::IndMatches & vec_corresponding_index) // Ouput corresponding index
133 {
134 assert(xLeft.rows() == xRight.rows());
135 assert(xLeft.cols() == lDescriptors.size());
136 assert(xRight.cols() == rDescriptors.size());
137
138 MetricT metric;
139
140 // Looking for the corresponding points that have to satisfy:
141 // 1. a geometric distance below the provided Threshold
142 // 2. a distance ratio between descriptors of valid geometric correspondencess
143
144 for (size_t i = 0; i < xLeft.cols(); ++i) {
145
146 distanceRatio<typename MetricT::ResultType > dR;
147 for (size_t j = 0; j < xRight.cols(); ++j) {
148 // Compute the geometric error: error to the model
149 const double geomErr = ErrorArg::Error(
150 mod, // The model
151 xLeft.col(i), xRight.col(j)); // The corresponding points
152 if (geomErr < errorTh) {
153 const typename MetricT::ResultType descDist =
154 metric( lDescriptors[i].getData(), rDescriptors[j].getData(), DescriptorT::static_size );
155 // Update the corresponding points & distance (if required)
156 dR.update(j, descDist);
157 }
158 }
159 // Add correspondence only iff the distance ratio is valid
160 if (dR.isValid(distRatio)) {
161 // save the best corresponding index
162 vec_corresponding_index.push_back(matching::IndMatch(i,dR.idx));
163 }
164 }
165
166 // Remove duplicates (when multiple points at same position exist)
167 matching::IndMatch::getDeduplicated(vec_corresponding_index);
168 }
169
170 /// Guided Matching (features + descriptors with distance ratio):
171 /// Use a model to find valid correspondences:
172 /// Keep the best corresponding points for the given model under the
173 /// user specified distance ratio.
174 template<
175 typename ModelArg, // The used model type
176 typename ErrorArg // The metric to compute distance to the model
177 >
GuidedMatching(const ModelArg & mod,const cameras::IntrinsicBase * camL,const features::Regions & lRegions,const cameras::IntrinsicBase * camR,const features::Regions & rRegions,double errorTh,double distRatio,matching::IndMatches & vec_corresponding_index)178 void GuidedMatching(
179 const ModelArg & mod, // The model
180 const cameras::IntrinsicBase * camL, // Optional camera (in order to undistord on the fly feature positions, can be nullptr)
181 const features::Regions & lRegions, // regions (point features & corresponding descriptors)
182 const cameras::IntrinsicBase * camR, // Optional camera (in order to undistord on the fly feature positions, can be nullptr)
183 const features::Regions & rRegions, // regions (point features & corresponding descriptors)
184 double errorTh, // Maximal authorized error threshold
185 double distRatio, // Maximal authorized distance ratio
186 matching::IndMatches & vec_corresponding_index) // Ouput corresponding index
187 {
188 // Looking for the corresponding points that have to satisfy:
189 // 1. a geometric distance below the provided Threshold
190 // 2. a distance ratio between descriptors of valid geometric correspondencess
191
192 // Build region positions arrays (in order to un-distord on-demand point position once)
193 std::vector<Vec2>
194 lRegionsPos(lRegions.RegionCount()),
195 rRegionsPos(rRegions.RegionCount());
196 for (size_t i = 0; i < lRegions.RegionCount(); ++i) {
197 lRegionsPos[i] = camL ? camL->get_ud_pixel(lRegions.GetRegionPosition(i)) : lRegions.GetRegionPosition(i);
198 }
199 for (size_t i = 0; i < rRegions.RegionCount(); ++i) {
200 rRegionsPos[i] = camR ? camR->get_ud_pixel(rRegions.GetRegionPosition(i)) : rRegions.GetRegionPosition(i);
201 }
202
203 for (size_t i = 0; i < lRegions.RegionCount(); ++i) {
204
205 distanceRatio<double> dR;
206 for (size_t j = 0; j < rRegions.RegionCount(); ++j) {
207 // Compute the geometric error: error to the model
208 const double geomErr = ErrorArg::Error(
209 mod, // The model
210 // The corresponding points
211 lRegionsPos[i],
212 rRegionsPos[j]);
213 if (geomErr < errorTh) {
214 // Update the corresponding points & distance (if required)
215 dR.update(j, lRegions.SquaredDescriptorDistance(i, &rRegions, j));
216 }
217 }
218 // Add correspondence only iff the distance ratio is valid
219 if (dR.isValid(distRatio)) {
220 // save the best corresponding index
221 vec_corresponding_index.push_back(matching::IndMatch(i,dR.idx));
222 }
223 }
224
225 // Remove duplicates (when multiple points at same position exist)
226 matching::IndMatch::getDeduplicated(vec_corresponding_index);
227 }
228
229 /// Compute a bucket index from an epipolar point
230 /// (the one that is closer to image border intersection)
pix_to_bucket(const Vec2i & x,int W,int H)231 static inline unsigned int pix_to_bucket(const Vec2i &x, int W, int H)
232 {
233 if (x(1) == 0) return x(0); // Top border
234 if (x(0) == W-1) return W-1 + x(1); // Right border
235 if (x(1) == H-1) return 2*W + H-3 - x(0); // Bottom border
236 return 2*(W+H-2) - x(1); // Left border
237 }
238
239 /// Compute intersection of the epipolar line with the image border
line_to_endPoints(const Vec3 & line,int W,int H,Vec2 & x0,Vec2 & x1)240 static inline bool line_to_endPoints(const Vec3 & line, int W, int H, Vec2 & x0, Vec2 & x1)
241 {
242 const double a = line(0), b = line(1), c = line(2);
243
244 float r1, r2;
245 // Intersection with Y axis (0 or W-1)
246 if (b!=0)
247 {
248 double x = (b<0) ? 0 : W-1;
249 double y = -(a*x+c)/b;
250 if (y < 0) y = 0.;
251 else if (y >= H) y = H-1;
252 r1 = std::abs(a*x + b*y + c);
253 x0 << x,y;
254 }
255 else {
256 return false;
257 }
258
259 // Intersection with X axis (0 or H-1)
260 if (a!=0)
261 {
262 double y = (a<0) ? H-1 : 0;
263 double x = -(b*y+c)/a;
264 if (x < 0) x = 0.;
265 else if (x >= W) x = W-1;
266 r2 = std::abs(a*x + b*y + c);
267 x1 << x,y;
268 }
269 else {
270 return false;
271 }
272
273 // Choose x0 to be as close as the intersection axis
274 if (r1>r2)
275 std::swap(x0,x1);
276
277 return true;
278 }
279
280 /// Guided Matching (features + descriptors with distance ratio):
281 /// Cluster correspondences per epipolar line (faster than exhaustive search).
282 /// Keep the best corresponding points for the given model under the
283 /// user specified distance ratio.
284 /// Can be seen as a variant of geometry_aware method [1].
285 /// Note that implementation done here use a pixel grid limited to image border.
286 ///
287 /// [1] Rajvi Shah, Vanshika Shrivastava, and P J Narayanan
288 /// Geometry-aware Feature Matching for Structure from Motion Applications.
289 /// WACV 2015.
290 template<
291 typename ErrorArg> // The used model type
GuidedMatching_Fundamental_Fast(const Mat3 & FMat,const Vec3 & epipole2,const cameras::IntrinsicBase * camL,const features::Regions & lRegions,const cameras::IntrinsicBase * camR,const features::Regions & rRegions,const int widthR,const int heightR,double errorTh,double distRatio,matching::IndMatches & vec_corresponding_index)292 void GuidedMatching_Fundamental_Fast(
293 const Mat3 & FMat, // The fundamental matrix
294 const Vec3 & epipole2,// Epipole2 (camera center1 in image plane2; must not be normalized)
295 const cameras::IntrinsicBase * camL, // Optional camera (in order to undistord on the fly feature positions, can be nullptr)
296 const features::Regions & lRegions, // regions (point features & corresponding descriptors)
297 const cameras::IntrinsicBase * camR, // Optional camera (in order to undistord on the fly feature positions, can be nullptr)
298 const features::Regions & rRegions, // regions (point features & corresponding descriptors)
299 const int widthR, const int heightR,
300 double errorTh, // Maximal authorized error threshold (consider it's a square threshold)
301 double distRatio, // Maximal authorized distance ratio
302 matching::IndMatches & vec_corresponding_index) // Ouput corresponding index
303 {
304 // Looking for the corresponding points that have to satisfy:
305 // 1. a geometric distance below the provided Threshold
306 // 2. a distance ratio between descriptors of valid geometric correspondencess
307 //
308 // - Cluster left point according their epipolar line border intersection.
309 // - For each right point, compute threshold limited bandwidth and compare only
310 // points that belong to this range (limited buckets).
311
312 // Normalize F and epipole for (ep2->p2) line adequation
313 Mat3 F = FMat;
314 Vec3 ep2 = epipole2;
315 if (ep2(2) > 0.0) {
316 F = -F;
317 ep2 = -ep2;
318 }
319 ep2 = ep2 / ep2(2);
320
321 //--
322 //-- Store point in the corresponding epipolar line bucket
323 //--
324 using Bucket_vec = std::vector<IndexT>;
325 using Buckets_vec = std::vector<Bucket_vec>;
326 const int nb_buckets = 2*(widthR + heightR-2);
327
328 Buckets_vec buckets(nb_buckets);
329 for (size_t i = 0; i < lRegions.RegionCount(); ++i) {
330
331 // Compute epipolar line
332 const Vec2 l_pt = camL ? camL->get_ud_pixel(lRegions.GetRegionPosition(i)) : lRegions.GetRegionPosition(i);
333 const Vec3 line = F * Vec3(l_pt(0), l_pt(1), 1.);
334 // If the epipolar line exists in Right image
335 Vec2 x0, x1;
336 if (line_to_endPoints(line, widthR, heightR, x0, x1))
337 {
338 // Find in which cluster the point belongs
339 const int bucket = pix_to_bucket(x0.cast<int>(), widthR, heightR);
340 buckets[bucket].push_back(i);
341 }
342 }
343
344 // For each point in right image, find if there is good candidates.
345 std::vector<distanceRatio<double >> dR(lRegions.RegionCount());
346 for (size_t j = 0; j < rRegions.RegionCount(); ++j)
347 {
348 // According the point:
349 // - Compute the epipolar line from the epipole
350 // - Compute the range of possible bucket by computing
351 // the epipolar line gauge limitation introduced by the tolerated pixel error
352
353 const Vec2 xR = camR ? camR->get_ud_pixel(rRegions.GetRegionPosition(j)) : rRegions.GetRegionPosition(j);
354 const Vec3 l2 = ep2.cross(xR.homogeneous());
355 const Vec2 n = l2.head<2>() * (sqrt(errorTh) / l2.head<2>().norm());
356
357 const Vec3 l2min = ep2.cross(Vec3(xR(0) - n(0), xR(1) - n(1), 1.));
358 const Vec3 l2max = ep2.cross(Vec3(xR(0) + n(0), xR(1) + n(1), 1.));
359
360 // Compute corresponding buckets
361 Vec2 x0, x1;
362 if (!line_to_endPoints(l2min, widthR, heightR, x0, x1))
363 continue;
364 const int bucket_start = pix_to_bucket(x0.cast<int>(), widthR, heightR);
365
366 if (!line_to_endPoints(l2max, widthR, heightR, x0, x1))
367 continue;
368 const int bucket_stop = pix_to_bucket(x0.cast<int>(), widthR, heightR);
369
370 if (bucket_stop - bucket_start > 0) // test candidate buckets
371 for (Buckets_vec::const_iterator itBs = buckets.begin() + bucket_start;
372 itBs != buckets.begin() + bucket_stop; ++itBs)
373 {
374 const Bucket_vec & bucket = *itBs;
375 for (const auto & i : bucket )
376 {
377 // Compute descriptor distance
378 const double descDist = lRegions.SquaredDescriptorDistance(i, &rRegions, j);
379 // Update the corresponding points & distance (if required)
380 dR[i].update(j, descDist);
381 }
382 }
383 }
384 // Check distance ratio validity
385 for (size_t i=0; i < dR.size(); ++i)
386 {
387 if (dR[i].isValid(distRatio))
388 {
389 // save the best corresponding index
390 vec_corresponding_index.push_back(matching::IndMatch(i, dR[i].idx));
391 }
392 }
393 }
394
395 } // namespace geometry_aware
396 } // namespace openMVG
397
398 #endif // OPENMVG_ROBUST_ESTIMATION_GUIDED_MATCHING_HPP
399