Visual Servoing Platform  version 3.0.0
vpHomographyRansac.cpp
1 /****************************************************************************
2  *
3  * This file is part of the ViSP software.
4  * Copyright (C) 2005 - 2015 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  unsigned int ind2;
164  for(unsigned int i = 0; i < 4 ;i++)
165  {
166  ind2 = 2 * ind[i];
167  pb[i][0] = x[ind2];
168  pb[i][1] = x[ind2+1];
169  pb[i][2] = 1;
170 
171  pa[i][0] = x[n2+ind2] ;
172  pa[i][1] = x[n2+ind2+1] ;
173  pa[i][2] = 1;
174  }
175  return ( iscolinear(pa[0],pa[1],pa[2]) ||
176  iscolinear(pa[0],pa[1],pa[3]) ||
177  iscolinear(pa[0],pa[2],pa[3]) ||
178  iscolinear(pa[1],pa[2],pa[3]) ||
179  iscolinear(pb[0],pb[1],pb[2]) ||
180  iscolinear(pb[0],pb[1],pb[3]) ||
181  iscolinear(pb[0],pb[2],pb[3]) ||
182  iscolinear(pb[1],pb[2],pb[3]));
183 }
184 bool
185 vpHomography::degenerateConfiguration(const std::vector<double> &xb, const std::vector<double> &yb,
186  const std::vector<double> &xa, const std::vector<double> &ya)
187 {
188  unsigned int n = (unsigned int)xb.size();
189  if (n < 4)
190  throw(vpException(vpException::fatalError, "There must be at least 4 matched points"));
191 
192  std::vector<vpColVector> pa(n), pb(n);
193  for (unsigned i=0; i<n;i++) {
194  pa[i].resize(3);
195  pa[i][0] = xa[i];
196  pa[i][1] = ya[i];
197  pa[i][2] = 1;
198  pb[i].resize(3);
199  pb[i][0] = xb[i];
200  pb[i][1] = yb[i];
201  pb[i][2] = 1;
202  }
203 
204  for (unsigned int i = 0; i < n-2; i++) {
205  for (unsigned int j = i+1; j < n-1; j++) {
206  for (unsigned int k = j+1; k < n ; k++)
207  {
208  if (isColinear(pa[i], pa[j], pa[k])) {
209  return true;
210  }
211  if (isColinear(pb[i], pb[j], pb[k])){
212  return true;
213  }
214  }
215  }
216  }
217  return false;
218 }
219 // Fit model to this random selection of data points.
220 void
221 vpHomography::computeTransformation(vpColVector &x, unsigned int *ind, vpColVector &M)
222 {
223  unsigned int i ;
224  unsigned int n = x.getRows()/4 ;
225  std::vector<double> xa(4), xb(4);
226  std::vector<double> ya(4), yb(4);
227  unsigned int n2 = n * 2;
228  unsigned int ind2;
229  for(i=0 ; i < 4 ; i++)
230  {
231  ind2 = 2 * ind[i];
232  xb[i] = x[ind2] ;
233  yb[i] = x[ind2+1] ;
234 
235  xa[i] = x[n2+ind2] ;
236  ya[i] = x[n2+ind2+1] ;
237  }
238 
239  vpHomography aHb ;
240  try {
241  vpHomography::HLM(xb, yb, xa, ya, true, aHb);
242  }
243  catch(...)
244  {
245  aHb.setIdentity();
246  }
247 
248  M.resize(9);
249  for (i=0 ; i <9 ; i++)
250  {
251  M[i] = aHb.data[i] ;
252  }
253  aHb /= aHb[2][2] ;
254 }
255 
256 
257 // Evaluate distances between points and model.
258 double
259 vpHomography::computeResidual(vpColVector &x, vpColVector &M, vpColVector &d)
260 {
261  unsigned int i ;
262  unsigned int n = x.getRows()/4 ;
263  unsigned int n2 = n * 2;
264  unsigned int i2;
265  vpColVector *pa;
266  vpColVector *pb;
267 
268  pa = new vpColVector [n];
269  pb = new vpColVector [n];
270 
271  for( i=0 ; i < n ; i++)
272  {
273  i2 = 2 * i;
274  pb[i].resize(3) ;
275  pb[i][0] = x[i2] ;
276  pb[i][1] = x[i2+1] ;
277  pb[i][2] = 1;
278 
279  pa[i].resize(3) ;
280  pa[i][0] = x[n2+i2] ;
281  pa[i][1] = x[n2+i2+1] ;
282  pa[i][2] = 1;
283  }
284 
285  vpMatrix aHb(3,3) ;
286 
287  for (i=0 ; i <9 ; i++)
288  {
289  aHb.data[i] = M[i];
290  }
291 
292  aHb /= aHb[2][2];
293 
294  d.resize(n);
295 
296  vpColVector Hpb ;
297  for (i=0 ; i <n ; i++)
298  {
299  Hpb = aHb*pb[i] ;
300  Hpb /= Hpb[2] ;
301  d[i] = sqrt((pa[i] - Hpb ).sumSquare()) ;
302  }
303 
304  delete [] pa;
305  delete [] pb;
306 
307  return 0 ;
308 }
309 #endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
310 
311 
312 void
313 vpHomography::initRansac(unsigned int n,
314  double *xb, double *yb,
315  double *xa, double *ya,
316  vpColVector &x)
317 {
318  x.resize(4*n) ;
319  unsigned int n2 = n * 2;
320  unsigned int i2;
321  for (unsigned int i=0 ; i < n ; i++)
322  {
323  i2 = 2 * i;
324  x[i2] = xb[i] ;
325  x[i2+1] = yb[i] ;
326  x[n2+i2] = xa[i] ;
327  x[n2+i2+1] = ya[i] ;
328  }
329 }
330 
357 bool vpHomography::ransac(const std::vector<double> &xb, const std::vector<double> &yb,
358  const std::vector<double> &xa, const std::vector<double> &ya,
359  vpHomography &aHb,
360  std::vector<bool> &inliers,
361  double &residual,
362  unsigned int nbInliersConsensus,
363  double threshold,
364  bool normalization)
365 {
366  unsigned int n = (unsigned int)xb.size();
367  if (yb.size() != n || xa.size() != n || ya.size() != n)
369  "Bad dimension for robust homography estimation"));
370 
371  // 4 point are required
372  if(n<4)
373  throw(vpException(vpException::fatalError, "There must be at least 4 matched points"));
374 
375  vpUniRand random((const long)time(NULL)) ;
376 
377  std::vector<unsigned int> best_consensus;
378  std::vector<unsigned int> cur_consensus;
379  std::vector<unsigned int> cur_outliers;
380  std::vector<unsigned int> cur_randoms;
381 
382  std::vector<unsigned int> rand_ind;
383 
384  unsigned int nbMinRandom = 4 ;
385  unsigned int ransacMaxTrials = 1000;
386  unsigned int maxDegenerateIter = 1000;
387 
388  unsigned int nbTrials = 0;
389  unsigned int nbDegenerateIter = 0;
390  unsigned int nbInliers = 0;
391 
392  bool foundSolution = false;
393 
394  std::vector<double> xa_rand(nbMinRandom);
395  std::vector<double> ya_rand(nbMinRandom);
396  std::vector<double> xb_rand(nbMinRandom);
397  std::vector<double> yb_rand(nbMinRandom);
398 
399  if (inliers.size() != n)
400  inliers.resize(n);
401 
402  while (nbTrials < ransacMaxTrials && nbInliers < nbInliersConsensus)
403  {
404  cur_outliers.clear();
405  cur_randoms.clear();
406 
407  bool degenerate = true;
408  while(degenerate == true){
409  std::vector<bool> usedPt(n, false);
410 
411  rand_ind.clear();
412  for(unsigned int i = 0; i < nbMinRandom; i++)
413  {
414  // Generate random indicies in the range 0..n
415  unsigned int r = (unsigned int)ceil(random()*n) -1;
416  while(usedPt[r]) {
417  r = (unsigned int)ceil(random()*n) -1;
418  }
419  usedPt[r] = true;
420  rand_ind.push_back(r);
421 
422  xa_rand[i] = xa[r];
423  ya_rand[i] = ya[r];
424  xb_rand[i] = xb[r];
425  yb_rand[i] = yb[r];
426  }
427 
428  try{
429  if (! vpHomography::degenerateConfiguration(xb_rand, yb_rand, xa_rand, ya_rand)) {
430  vpHomography::DLT(xb_rand, yb_rand, xa_rand, ya_rand, aHb, normalization);
431  degenerate = false;
432  }
433  }
434  catch(...){
435  degenerate = true;
436  }
437 
438  nbDegenerateIter ++;
439 
440  if (nbDegenerateIter > maxDegenerateIter){
441  vpERROR_TRACE("Unable to select a nondegenerate data set");
442  throw(vpException(vpException::fatalError, "Unable to select a nondegenerate data set"));
443  }
444  }
445 
446  aHb /= aHb[2][2] ;
447 
448  // Computing Residual
449  double r = 0;
450  vpColVector a(3), b(3), c(3) ;
451  for (unsigned int i=0 ; i < nbMinRandom ; i++) {
452  a[0] = xa_rand[i] ; a[1] = ya_rand[i] ; a[2] = 1 ;
453  b[0] = xb_rand[i] ; b[1] = yb_rand[i] ; b[2] = 1 ;
454 
455  c = aHb*b; c /= c[2] ;
456  r += (a-c).sumSquare() ;
457  //cout << "point " <<i << " " << (a-c).sumSquare() <<endl ;;
458  }
459 
460  // Finding inliers & ouliers
461  r = sqrt(r/nbMinRandom);
462  //std::cout << "Candidate residual: " << r << std::endl;
463  if (r < threshold)
464  {
465  unsigned int nbInliersCur = 0;
466  for (unsigned int i = 0; i < n ; i++)
467  {
468  a[0] = xa[i] ; a[1] = ya[i] ; a[2] = 1 ;
469  b[0] = xb[i] ; b[1] = yb[i] ; b[2] = 1 ;
470 
471  c = aHb*b ; c /= c[2] ;
472  double error = sqrt((a-c).sumSquare()) ;
473  if(error <= threshold){
474  nbInliersCur++;
475  cur_consensus.push_back(i);
476  inliers[i] = true;
477  }
478  else{
479  cur_outliers.push_back(i);
480  inliers[i] = false;
481  }
482  }
483  //std::cout << "nb inliers that matches: " << nbInliersCur << std::endl;
484  if(nbInliersCur > nbInliers)
485  {
486  foundSolution = true;
487  best_consensus = cur_consensus;
488  nbInliers = nbInliersCur;
489  }
490 
491  cur_consensus.clear();
492  }
493 
494  nbTrials++;
495  if(nbTrials >= ransacMaxTrials){
496  vpERROR_TRACE("Ransac reached the maximum number of trials");
497  foundSolution = true;
498  }
499  }
500 
501  if(foundSolution){
502  if(nbInliers >= nbInliersConsensus)
503  {
504  std::vector<double> xa_best(best_consensus.size());
505  std::vector<double> ya_best(best_consensus.size());
506  std::vector<double> xb_best(best_consensus.size());
507  std::vector<double> yb_best(best_consensus.size());
508 
509  for(unsigned i = 0 ; i < best_consensus.size(); i++)
510  {
511  xa_best[i] = xa[best_consensus[i]];
512  ya_best[i] = ya[best_consensus[i]];
513  xb_best[i] = xb[best_consensus[i]];
514  yb_best[i] = yb[best_consensus[i]];
515  }
516 
517  vpHomography::DLT(xb_best, yb_best, xa_best, ya_best, aHb, normalization) ;
518  aHb /= aHb[2][2];
519 
520  residual = 0 ;
521  vpColVector a(3), b(3), c(3);
522  for (unsigned int i=0 ; i < best_consensus.size() ; i++) {
523  a[0] = xa_best[i] ; a[1] = ya_best[i] ; a[2] = 1 ;
524  b[0] = xb_best[i] ; b[1] = yb_best[i] ; b[2] = 1 ;
525 
526  c = aHb*b ; c /= c[2] ;
527  residual += (a-c).sumSquare() ;
528  }
529 
530  residual = sqrt(residual/best_consensus.size());
531  return true;
532  }
533  else {
534  return false;
535  }
536  }
537  else {
538  return false;
539  }
540 }
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:92
static vpColVector cross(const vpColVector &a, const vpColVector &b)
Definition: vpColVector.h:257
#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 setIdentity()
void resize(const unsigned int i, const bool flagNullify=true)
Definition: vpColVector.h:217