Visual Servoing Platform  version 3.0.1
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
vpHomographyRansac.cpp
1 /****************************************************************************
2  *
3  * This file is part of the ViSP software.
4  * Copyright (C) 2005 - 2017 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * ("GPL") version 2 as published by the Free Software Foundation.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See http://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * Homography estimation.
32  *
33  * Authors:
34  * Eric Marchand
35  *
36  *****************************************************************************/
37 
38 #include <visp3/vision/vpHomography.h>
39 #include <visp3/core/vpColVector.h>
40 #include <visp3/core/vpRansac.h>
41 
42 #include <visp3/core/vpImage.h>
43 #include <visp3/core/vpDisplay.h>
44 #include <visp3/core/vpMeterPixelConversion.h>
45 
46 #define vpEps 1e-6
47 
53 #ifndef DOXYGEN_SHOULD_SKIP_THIS
54 
55 bool iscolinear(double *x1, double *x2, double *x3);
56 bool isColinear(vpColVector &p1, vpColVector &p2, vpColVector &p3);
57 
58 bool
59 iscolinear(double *x1, double *x2, double *x3)
60 {
61  vpColVector p1(3), p2(3), p3(3);
62  p1 << x1 ;
63  p2 << x2 ;
64  p3 << x3 ;
65  //vpColVector v;
66  //vpColVector::cross(p2-p1, p3-p1, v);
67  //return (v.sumSquare() < vpEps);
68  // Assume inhomogeneous coords, or homogeneous coords with equal
69  // scale.
70  return ((vpColVector::cross(p2-p1, p3-p1).sumSquare()) < vpEps);
71 }
72 bool
73 isColinear(vpColVector &p1, vpColVector &p2, vpColVector &p3)
74 {
75  return ((vpColVector::cross(p2-p1, p3-p1).sumSquare()) < vpEps);
76 }
77 
78 
79 bool
80 vpHomography::degenerateConfiguration(vpColVector &x, unsigned int *ind,
81  double threshold_area)
82 {
83 
84  unsigned int i, j, k;
85 
86  for (i=1 ; i < 4 ; i++)
87  for (j=0 ; j<i ; j++)
88  if (ind[i]==ind[j]) return true ;
89 
90  unsigned int n = x.getRows()/4 ;
91  double pa[4][3] ;
92  double pb[4][3] ;
93 
94 
95 
96  for(i = 0 ; i < 4 ; i++)
97  {
98  pb[i][0] = x[2*ind[i]] ;
99  pb[i][1] = x[2*ind[i]+1] ;
100  pb[i][2] = 1;
101 
102  pa[i][0] = x[2*n+2*ind[i]] ;
103  pa[i][1] = x[2*n+2*ind[i]+1] ;
104  pa[i][2] = 1;
105  }
106 
107  i = 0, j = 1, k = 2;
108 
109  double area012 = (-pa[j][0]*pa[i][1] + pa[k][0]*pa[i][1] +
110  pa[i][0]*pa[j][1] - pa[k][0]*pa[j][1] +
111  -pa[i][0]*pa[k][1] + pa[1][j]*pa[k][1]);
112 
113  i = 0; j = 1, k = 3;
114  double area013 = (-pa[j][0]*pa[i][1] + pa[k][0]*pa[i][1] +
115  pa[i][0]*pa[j][1] - pa[k][0]*pa[j][1] +
116  -pa[i][0]*pa[k][1] + pa[1][j]*pa[k][1]);
117 
118  i = 0; j = 2, k = 3;
119  double area023 = (-pa[j][0]*pa[i][1] + pa[k][0]*pa[i][1] +
120  pa[i][0]*pa[j][1] - pa[k][0]*pa[j][1] +
121  -pa[i][0]*pa[k][1] + pa[1][j]*pa[k][1]);
122 
123  i = 1; j = 2, k = 3;
124  double area123 = (-pa[j][0]*pa[i][1] + pa[k][0]*pa[i][1] +
125  pa[i][0]*pa[j][1] - pa[k][0]*pa[j][1] +
126  -pa[i][0]*pa[k][1] + pa[1][j]*pa[k][1]);
127 
128  double sum_area = area012 + area013 + area023 + area123;
129 
130  return ((sum_area < threshold_area) ||
131  (iscolinear(pa[0],pa[1],pa[2]) ||
132  iscolinear(pa[0],pa[1],pa[3]) ||
133  iscolinear(pa[0],pa[2],pa[3]) ||
134  iscolinear(pa[1],pa[2],pa[3]) ||
135  iscolinear(pb[0],pb[1],pb[2]) ||
136  iscolinear(pb[0],pb[1],pb[3]) ||
137  iscolinear(pb[0],pb[2],pb[3]) ||
138  iscolinear(pb[1],pb[2],pb[3])));
139 }
140  /*
141 \brief
142 Function to determine if a set of 4 pairs of matched points give rise
143 to a degeneracy in the calculation of a homography as needed by RANSAC.
144 This involves testing whether any 3 of the 4 points in each set is
145 colinear.
146 
147 point are coded this way
148 x1b,y1b, x2b, y2b, ... xnb, ynb
149 x1a,y1a, x2a, y2a, ... xna, yna
150 leading to 2*2*n
151 */
152 bool
153 vpHomography::degenerateConfiguration(vpColVector &x, unsigned int *ind)
154 {
155  for (unsigned int i = 1; i < 4 ; i++)
156  for (unsigned int j = 0 ;j < i ; j++)
157  if (ind[i] == ind[j]) return true ;
158 
159  unsigned int n = x.getRows()/4;
160  double pa[4][3];
161  double pb[4][3];
162  unsigned int n2 = 2 * n;
163  for(unsigned int i = 0; i < 4 ;i++)
164  {
165  unsigned int ind2 = 2 * ind[i];
166  pb[i][0] = x[ind2];
167  pb[i][1] = x[ind2+1];
168  pb[i][2] = 1;
169 
170  pa[i][0] = x[n2+ind2] ;
171  pa[i][1] = x[n2+ind2+1] ;
172  pa[i][2] = 1;
173  }
174  return ( iscolinear(pa[0],pa[1],pa[2]) ||
175  iscolinear(pa[0],pa[1],pa[3]) ||
176  iscolinear(pa[0],pa[2],pa[3]) ||
177  iscolinear(pa[1],pa[2],pa[3]) ||
178  iscolinear(pb[0],pb[1],pb[2]) ||
179  iscolinear(pb[0],pb[1],pb[3]) ||
180  iscolinear(pb[0],pb[2],pb[3]) ||
181  iscolinear(pb[1],pb[2],pb[3]));
182 }
183 bool
184 vpHomography::degenerateConfiguration(const std::vector<double> &xb, const std::vector<double> &yb,
185  const std::vector<double> &xa, const std::vector<double> &ya)
186 {
187  unsigned int n = (unsigned int)xb.size();
188  if (n < 4)
189  throw(vpException(vpException::fatalError, "There must be at least 4 matched points"));
190 
191  std::vector<vpColVector> pa(n), pb(n);
192  for (unsigned i=0; i<n;i++) {
193  pa[i].resize(3);
194  pa[i][0] = xa[i];
195  pa[i][1] = ya[i];
196  pa[i][2] = 1;
197  pb[i].resize(3);
198  pb[i][0] = xb[i];
199  pb[i][1] = yb[i];
200  pb[i][2] = 1;
201  }
202 
203  for (unsigned int i = 0; i < n-2; i++) {
204  for (unsigned int j = i+1; j < n-1; j++) {
205  for (unsigned int k = j+1; k < n ; k++)
206  {
207  if (isColinear(pa[i], pa[j], pa[k])) {
208  return true;
209  }
210  if (isColinear(pb[i], pb[j], pb[k])){
211  return true;
212  }
213  }
214  }
215  }
216  return false;
217 }
218 // Fit model to this random selection of data points.
219 void
220 vpHomography::computeTransformation(vpColVector &x, unsigned int *ind, vpColVector &M)
221 {
222  unsigned int n = x.getRows()/4 ;
223  std::vector<double> xa(4), xb(4);
224  std::vector<double> ya(4), yb(4);
225  unsigned int n2 = n * 2;
226  for(unsigned int i=0 ; i < 4 ; i++)
227  {
228  unsigned int ind2 = 2 * ind[i];
229  xb[i] = x[ind2] ;
230  yb[i] = x[ind2+1] ;
231 
232  xa[i] = x[n2+ind2] ;
233  ya[i] = x[n2+ind2+1] ;
234  }
235 
236  vpHomography aHb ;
237  try {
238  vpHomography::HLM(xb, yb, xa, ya, true, aHb);
239  }
240  catch(...)
241  {
242  aHb.eye();
243  }
244 
245  M.resize(9);
246  for (unsigned int i=0 ; i <9 ; i++)
247  {
248  M[i] = aHb.data[i] ;
249  }
250  aHb /= aHb[2][2] ;
251 }
252 
253 
254 // Evaluate distances between points and model.
255 double
256 vpHomography::computeResidual(vpColVector &x, vpColVector &M, vpColVector &d)
257 {
258  unsigned int n = x.getRows()/4 ;
259  unsigned int n2 = n * 2;
260  vpColVector *pa;
261  vpColVector *pb;
262 
263  pa = new vpColVector [n];
264  pb = new vpColVector [n];
265 
266  for(unsigned int i=0 ; i < n ; i++)
267  {
268  unsigned int i2 = 2 * i;
269  pb[i].resize(3) ;
270  pb[i][0] = x[i2] ;
271  pb[i][1] = x[i2+1] ;
272  pb[i][2] = 1;
273 
274  pa[i].resize(3) ;
275  pa[i][0] = x[n2+i2] ;
276  pa[i][1] = x[n2+i2+1] ;
277  pa[i][2] = 1;
278  }
279 
280  vpMatrix aHb(3,3) ;
281 
282  for (unsigned int i=0 ; i <9 ; i++)
283  {
284  aHb.data[i] = M[i];
285  }
286 
287  aHb /= aHb[2][2];
288 
289  d.resize(n);
290 
291  vpColVector Hpb ;
292  for (unsigned int i=0 ; i <n ; i++)
293  {
294  Hpb = aHb*pb[i] ;
295  Hpb /= Hpb[2] ;
296  d[i] = sqrt((pa[i] - Hpb ).sumSquare()) ;
297  }
298 
299  delete [] pa;
300  delete [] pb;
301 
302  return 0 ;
303 }
304 #endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
305 
306 
307 void
308 vpHomography::initRansac(unsigned int n,
309  double *xb, double *yb,
310  double *xa, double *ya,
311  vpColVector &x)
312 {
313  x.resize(4*n) ;
314  unsigned int n2 = n * 2;
315  for (unsigned int i=0 ; i < n ; i++)
316  {
317  unsigned int i2 = 2 * i;
318  x[i2] = xb[i] ;
319  x[i2+1] = yb[i] ;
320  x[n2+i2] = xa[i] ;
321  x[n2+i2+1] = ya[i] ;
322  }
323 }
324 
351 bool vpHomography::ransac(const std::vector<double> &xb, const std::vector<double> &yb,
352  const std::vector<double> &xa, const std::vector<double> &ya,
353  vpHomography &aHb,
354  std::vector<bool> &inliers,
355  double &residual,
356  unsigned int nbInliersConsensus,
357  double threshold,
358  bool normalization)
359 {
360  unsigned int n = (unsigned int)xb.size();
361  if (yb.size() != n || xa.size() != n || ya.size() != n)
363  "Bad dimension for robust homography estimation"));
364 
365  // 4 point are required
366  if(n<4)
367  throw(vpException(vpException::fatalError, "There must be at least 4 matched points"));
368 
369  vpUniRand random((const long)time(NULL)) ;
370 
371  std::vector<unsigned int> best_consensus;
372  std::vector<unsigned int> cur_consensus;
373  std::vector<unsigned int> cur_outliers;
374  std::vector<unsigned int> cur_randoms;
375 
376  std::vector<unsigned int> rand_ind;
377 
378  unsigned int nbMinRandom = 4 ;
379  unsigned int ransacMaxTrials = 1000;
380  unsigned int maxDegenerateIter = 1000;
381 
382  unsigned int nbTrials = 0;
383  unsigned int nbDegenerateIter = 0;
384  unsigned int nbInliers = 0;
385 
386  bool foundSolution = false;
387 
388  std::vector<double> xa_rand(nbMinRandom);
389  std::vector<double> ya_rand(nbMinRandom);
390  std::vector<double> xb_rand(nbMinRandom);
391  std::vector<double> yb_rand(nbMinRandom);
392 
393  if (inliers.size() != n)
394  inliers.resize(n);
395 
396  while (nbTrials < ransacMaxTrials && nbInliers < nbInliersConsensus)
397  {
398  cur_outliers.clear();
399  cur_randoms.clear();
400 
401  bool degenerate = true;
402  while(degenerate == true){
403  std::vector<bool> usedPt(n, false);
404 
405  rand_ind.clear();
406  for(unsigned int i = 0; i < nbMinRandom; i++)
407  {
408  // Generate random indicies in the range 0..n
409  unsigned int r = (unsigned int)ceil(random()*n) -1;
410  while(usedPt[r]) {
411  r = (unsigned int)ceil(random()*n) -1;
412  }
413  usedPt[r] = true;
414  rand_ind.push_back(r);
415 
416  xa_rand[i] = xa[r];
417  ya_rand[i] = ya[r];
418  xb_rand[i] = xb[r];
419  yb_rand[i] = yb[r];
420  }
421 
422  try{
423  if (! vpHomography::degenerateConfiguration(xb_rand, yb_rand, xa_rand, ya_rand)) {
424  vpHomography::DLT(xb_rand, yb_rand, xa_rand, ya_rand, aHb, normalization);
425  degenerate = false;
426  }
427  }
428  catch(...){
429  degenerate = true;
430  }
431 
432  nbDegenerateIter ++;
433 
434  if (nbDegenerateIter > maxDegenerateIter){
435  vpERROR_TRACE("Unable to select a nondegenerate data set");
436  throw(vpException(vpException::fatalError, "Unable to select a nondegenerate data set"));
437  }
438  }
439 
440  aHb /= aHb[2][2] ;
441 
442  // Computing Residual
443  double r = 0;
444  vpColVector a(3), b(3), c(3) ;
445  for (unsigned int i=0 ; i < nbMinRandom ; i++) {
446  a[0] = xa_rand[i] ; a[1] = ya_rand[i] ; a[2] = 1 ;
447  b[0] = xb_rand[i] ; b[1] = yb_rand[i] ; b[2] = 1 ;
448 
449  c = aHb*b; c /= c[2] ;
450  r += (a-c).sumSquare() ;
451  //cout << "point " <<i << " " << (a-c).sumSquare() <<endl ;;
452  }
453 
454  // Finding inliers & ouliers
455  r = sqrt(r/nbMinRandom);
456  //std::cout << "Candidate residual: " << r << std::endl;
457  if (r < threshold)
458  {
459  unsigned int nbInliersCur = 0;
460  for (unsigned int i = 0; i < n ; i++)
461  {
462  a[0] = xa[i] ; a[1] = ya[i] ; a[2] = 1 ;
463  b[0] = xb[i] ; b[1] = yb[i] ; b[2] = 1 ;
464 
465  c = aHb*b ; c /= c[2] ;
466  double error = sqrt((a-c).sumSquare()) ;
467  if(error <= threshold){
468  nbInliersCur++;
469  cur_consensus.push_back(i);
470  inliers[i] = true;
471  }
472  else{
473  cur_outliers.push_back(i);
474  inliers[i] = false;
475  }
476  }
477  //std::cout << "nb inliers that matches: " << nbInliersCur << std::endl;
478  if(nbInliersCur > nbInliers)
479  {
480  foundSolution = true;
481  best_consensus = cur_consensus;
482  nbInliers = nbInliersCur;
483  }
484 
485  cur_consensus.clear();
486  }
487 
488  nbTrials++;
489  if(nbTrials >= ransacMaxTrials){
490  vpERROR_TRACE("Ransac reached the maximum number of trials");
491  foundSolution = true;
492  }
493  }
494 
495  if(foundSolution){
496  if(nbInliers >= nbInliersConsensus)
497  {
498  std::vector<double> xa_best(best_consensus.size());
499  std::vector<double> ya_best(best_consensus.size());
500  std::vector<double> xb_best(best_consensus.size());
501  std::vector<double> yb_best(best_consensus.size());
502 
503  for(unsigned i = 0 ; i < best_consensus.size(); i++)
504  {
505  xa_best[i] = xa[best_consensus[i]];
506  ya_best[i] = ya[best_consensus[i]];
507  xb_best[i] = xb[best_consensus[i]];
508  yb_best[i] = yb[best_consensus[i]];
509  }
510 
511  vpHomography::DLT(xb_best, yb_best, xa_best, ya_best, aHb, normalization) ;
512  aHb /= aHb[2][2];
513 
514  residual = 0 ;
515  vpColVector a(3), b(3), c(3);
516  for (unsigned int i=0 ; i < best_consensus.size() ; i++) {
517  a[0] = xa_best[i] ; a[1] = ya_best[i] ; a[2] = 1 ;
518  b[0] = xb_best[i] ; b[1] = yb_best[i] ; b[2] = 1 ;
519 
520  c = aHb*b ; c /= c[2] ;
521  residual += (a-c).sumSquare() ;
522  }
523 
524  residual = sqrt(residual/best_consensus.size());
525  return true;
526  }
527  else {
528  return false;
529  }
530  }
531  else {
532  return false;
533  }
534 }
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:97
static vpColVector cross(const vpColVector &a, const vpColVector &b)
Definition: vpColVector.h:266
#define vpERROR_TRACE
Definition: vpDebug.h:391
error that can be emited by ViSP classes.
Definition: vpException.h:73
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:84
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:156
static void HLM(const std::vector< double > &xb, const std::vector< double > &yb, const std::vector< double > &xa, const std::vector< double > &ya, bool isplanar, vpHomography &aHb)
Implementation of an homography and operations on homographies.
Definition: vpHomography.h:179
static bool ransac(const std::vector< double > &xb, const std::vector< double > &yb, const std::vector< double > &xa, const std::vector< double > &ya, vpHomography &aHb, std::vector< bool > &inliers, double &residual, unsigned int nbInliersConsensus, double threshold, bool normalization=true)
unsigned int getRows() const
Return the number of rows of the 2D array.
Definition: vpArray2D.h:152
static void DLT(const std::vector< double > &xb, const std::vector< double > &yb, const std::vector< double > &xa, const std::vector< double > &ya, vpHomography &aHb, bool normalization=true)
Implementation of column vector and the associated operations.
Definition: vpColVector.h:72
Class for generating random numbers with uniform probability density.
Definition: vpUniRand.h:64
void resize(const unsigned int i, const bool flagNullify=true)
Definition: vpColVector.h:225