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