SLAMflex SE  0.1.0
SLAMflex provides detection and tracking of dominant planes for smartphone devices. This plane can then be used to show AR content relative to the plane orientation. The detection of plane is performed in the field of view of the smartphone camera. In subsequent frames it is tracked. The interface returns the plane position and orientation.
HomographyInit.cpp
Go to the documentation of this file.
1 // Copyright 2008 Isis Innovation Limited
2 #include "HomographyInit.h"
3 #include "SmallMatrixOpts.h"
4 #include <utility>
5 #include "se3.h"
6 #include "SVD.h"
7 #include "SymEigen.h"
8 #include "wls.h"
9 #include "MEstimator.h"
10 
11 using namespace std;
13 {
14  Vector<2> v2Projected = project(m3Homography * unproject(match.v2CamPlaneFirst));
15  Vector<2> v2Error = match.v2CamPlaneSecond - v2Projected;
16  Vector<2> v2PixelError = match.m2PixelProjectionJac * v2Error;
17  double dSquaredError = v2PixelError * v2PixelError;
18  return (dSquaredError < mdMaxPixelErrorSquared);
19 }
20 
22 {
23  Vector<2> v2Projected = project(m3Homography * unproject(match.v2CamPlaneFirst));
24  Vector<2> v2Error = match.v2CamPlaneSecond - v2Projected;
25  Vector<2> v2PixelError = match.m2PixelProjectionJac * v2Error;
26  double dSquaredError = v2PixelError * v2PixelError;
27  if(dSquaredError > mdMaxPixelErrorSquared)
28  return mdMaxPixelErrorSquared;
29  else
30  return dSquaredError;
31 }
32 
33 bool HomographyInit::Compute(vector<HomographyMatch> vMatches, double dMaxPixelError, SE3<> &se3SecondFromFirst)
34 {
35  mdMaxPixelErrorSquared = dMaxPixelError * dMaxPixelError;
36  mvMatches = vMatches;
37 
38  // Find best homography from minimal sets of image matches
39  BestHomographyFromMatches_MLESAC();
40 
41  // Generate the inlier set, and refine the best estimate using this
42  mvHomographyInliers.clear();
43  for(unsigned int i=0; i<mvMatches.size(); i++)
44  if(IsHomographyInlier(mm3BestHomography, mvMatches[i]))
45  mvHomographyInliers.push_back(mvMatches[i]);
46  for(int iteration = 0; iteration < 5; iteration++)
47  RefineHomographyWithInliers();
48 
49  // Decompose the best homography into a set of possible decompositions
50  DecomposeHomography();
51 
52  // At this stage should have eight decomposition options, if all went according to plan
53  if(mvDecompositions.size() != 8)
54  return false;
55 
56  // And choose the best one based on visibility constraints
57  ChooseBestDecomposition();
58 
59  se3SecondFromFirst = mvDecompositions[0].se3SecondFromFirst;
60  return true;
61 }
62 
63 Matrix<3> HomographyInit::HomographyFromMatches(vector<HomographyMatch> vMatches)
64 {
65  unsigned int nPoints = vMatches.size();
66  assert(nPoints >= 4);
67  int nRows = 2*nPoints;
68  if(nRows < 9)
69  nRows = 9;
70  Matrix<> m2Nx9(nRows, 9);
71  for(unsigned int n=0; n<nPoints; n++)
72  {
73  double u = vMatches[n].v2CamPlaneSecond[0];
74  double v = vMatches[n].v2CamPlaneSecond[1];
75 
76  double x = vMatches[n].v2CamPlaneFirst[0];
77  double y = vMatches[n].v2CamPlaneFirst[1];
78  //NSLog(@"%f,%f <=> %f,%f",u,v,x,y);
79 
80  // [u v]T = H [x y]T
81  m2Nx9[n*2+0][0] = x;
82  m2Nx9[n*2+0][1] = y;
83  m2Nx9[n*2+0][2] = 1;
84  m2Nx9[n*2+0][3] = 0;
85  m2Nx9[n*2+0][4] = 0;
86  m2Nx9[n*2+0][5] = 0;
87  m2Nx9[n*2+0][6] = -x*u;
88  m2Nx9[n*2+0][7] = -y*u;
89  m2Nx9[n*2+0][8] = -u;
90 
91  m2Nx9[n*2+1][0] = 0;
92  m2Nx9[n*2+1][1] = 0;
93  m2Nx9[n*2+1][2] = 0;
94  m2Nx9[n*2+1][3] = x;
95  m2Nx9[n*2+1][4] = y;
96  m2Nx9[n*2+1][5] = 1;
97  m2Nx9[n*2+1][6] = -x*v;
98  m2Nx9[n*2+1][7] = -y*v;
99  m2Nx9[n*2+1][8] = -v;
100  }
101 
102  if(nRows == 9)
103  for(int i=0; i<9; i++) // Zero the last row of the matrix,
104  m2Nx9[8][i] = 0.0; // TooN SVD leaves out the null-space otherwise
105 
106  // The right null-space of the matrix gives the homography...
107  SVD<> svdHomography(m2Nx9);
108  Vector<9> vH = svdHomography.get_VT()[8];
109  Matrix<3> m3Homography;
110  m3Homography[0] = vH.slice<0,3>();
111  m3Homography[1] = vH.slice<3,3>();
112  m3Homography[2] = vH.slice<6,3>();
113  return m3Homography;
114 };
115 
116 // Throughout the whole thing,
117 // SecondView = Homography * FirstView
118 
120 {
121  WLS<9> wls;
122  wls.add_prior(1.0);
123 
124  vector<Matrix<2,9> > vmJacobians;
125  vector<Vector<2> > vvErrors;
126  vector<double> vdErrorSquared;
127 
128 // for(unsigned int i=0; i<5; i++)
129  for(unsigned int i=0; i<mvHomographyInliers.size(); i++)
130  {
131  // First, find error.
132  Vector<2> v2First = mvHomographyInliers[i].v2CamPlaneFirst;
133  Vector<3> v3Second = mm3BestHomography * unproject(v2First);
134  Vector<2> v2Second = project(v3Second);
135  Vector<2> v2Second_real = mvHomographyInliers[i].v2CamPlaneSecond;
136  Vector<2> v2Error = mvHomographyInliers[i].m2PixelProjectionJac * (v2Second_real - v2Second);
137 
138  vdErrorSquared.push_back(v2Error* v2Error);
139  vvErrors.push_back(v2Error);
140 
141  Matrix<2,9> m29Jacobian;
142  double dDenominator = v3Second[2];
143 
144  // Jacobians wrt to the elements of the homography:
145  // For x:
146  m29Jacobian[0].slice<0,3>() = unproject(v2First) / dDenominator;
147  m29Jacobian[0].slice<3,3>() = Zeros;
148  double dNumerator = v3Second[0];
149  m29Jacobian[0].slice<6,3>() = -unproject(v2First) * dNumerator / (dDenominator * dDenominator);
150  // For y:
151  m29Jacobian[1].slice<0,3>() = Zeros;
152  m29Jacobian[1].slice<3,3>() = unproject(v2First) / dDenominator;;
153  dNumerator = v3Second[1];
154  m29Jacobian[1].slice<6,3>() = -unproject(v2First) * dNumerator / (dDenominator * dDenominator);
155 
156  vmJacobians.push_back(mvHomographyInliers[i].m2PixelProjectionJac * m29Jacobian);
157  }
158 
159  // Calculate robust sigma:
160  vector<double> vdd = vdErrorSquared;
161  double dSigmaSquared = Tukey::FindSigmaSquared(vdd);
162 
163  // Add re-weighted measurements to WLS:
164  for(unsigned int i=0; i<mvHomographyInliers.size(); i++)
165  {
166  double dWeight = Tukey::Weight(vdErrorSquared[i], dSigmaSquared);
167  wls.add_mJ(vvErrors[i][0], vmJacobians[i][0], dWeight);
168  wls.add_mJ(vvErrors[i][1], vmJacobians[i][1], dWeight);
169  }
170  wls.compute();
171  Vector<9> v9Update = wls.get_mu();
172  Matrix<3> m3Update;
173  m3Update[0] = v9Update.slice<0,3>();
174  m3Update[1] = v9Update.slice<3,3>();
175  m3Update[2] = v9Update.slice<6,3>();
176  mm3BestHomography += m3Update;
177 }
178 
180 {
181  // Not many matches? Don't do ransac, throw them all in a pot and see what comes out.
182  if(mvMatches.size() < 10)
183  {
184  mm3BestHomography = HomographyFromMatches(mvMatches);
185  return;
186  }
187 
188  // Enough matches? Run MLESAC.
189  int anIndices[4];
190 
191  mm3BestHomography = Identity;
192  double dBestError = 999999999999999999.9;
193 
194  // Do 300 MLESAC trials.
195  for(int nR = 0; nR < 300 ; nR++)
196  {
197  // Find set of four unique matches
198  for(int i=0; i<4; i++)
199  {
200  bool isUnique = false;
201  int n;
202  while(!isUnique)
203  {
204  n = rand() % mvMatches.size();
205  isUnique =true;
206  for(int j=0; j<i && isUnique; j++)
207  if(anIndices[j] == n)
208  isUnique = false;
209  };
210  anIndices[i] = n;
211  }
212  vector<HomographyMatch> vMinimalMatches;
213  for(int i=0; i<4; i++)
214  vMinimalMatches.push_back(mvMatches[anIndices[i]]);
215 
216  // Find a homography from the minimal set..
217  Matrix<3> m3Homography = HomographyFromMatches(vMinimalMatches);
218 
219  //..and sum resulting MLESAC score
220  double dError = 0.0;
221  for(unsigned int i=0; i<mvMatches.size(); i++)
222  dError += MLESACScore(m3Homography, mvMatches[i]);
223 
224  if(dError < dBestError)
225  {
226  mm3BestHomography = m3Homography;
227  dBestError = dError;
228  }
229  };
230 }
231 
233 {
234  mvDecompositions.clear();
235  SVD<3> svd(mm3BestHomography);
236  Vector<3> v3Diag = svd.get_diagonal();
237  double d1 = fabs(v3Diag[0]); // The paper suggests the square of these (e.g. the evalues of AAT)
238  double d2 = fabs(v3Diag[1]); // should be used, but this is wrong. c.f. Faugeras' book.
239  double d3 = fabs(v3Diag[2]);
240 
241  Matrix<3> U = svd.get_U();
242  Matrix<3> V = svd.get_VT().T();
243 
244  double s = M3Det(U) * M3Det(V);
245 
246  double dPrime_PM = d2;
247 
248  int nCase;
249  if(d1 != d2 && d2 != d3)
250  nCase = 1;
251  else if( d1 == d2 && d2 == d3)
252  nCase = 3;
253  else
254  nCase = 2;
255 
256  if(nCase != 1)
257  {
258  cout << " Homographyinit: This motion case is not implemented or is degenerate. Try again. " << endl;
259  return;
260  }
261 
262  double x1_PM;
263  double x2;
264  double x3_PM;
265 
266  // All below deals with the case = 1 case.
267  // Case 1 implies (d1 != d3)
268  { // Eq. 12
269  x1_PM = sqrt((d1*d1 - d2*d2) / (d1*d1 - d3*d3));
270  x2 = 0;
271  x3_PM = sqrt((d2*d2 - d3*d3) / (d1*d1 - d3*d3));
272  };
273 
274  double e1[4] = {1.0,-1.0,1.0,-1.0};
275  double e3[4] = {1.0, 1.0, -1.0,-1.0};
276 
277  Vector<3> v3np;
278  HomographyDecomposition decomposition;
279 
280  // Case 1, d' > 0:
281  decomposition.d = s * dPrime_PM;
282  for(int signs=0; signs<4; signs++)
283  {
284  // Eq 13
285  decomposition.m3Rp = Identity;
286  double dSinTheta = (d1 - d3) * x1_PM * x3_PM * e1[signs] * e3[signs] / d2;
287  double dCosTheta = (d1 * x3_PM * x3_PM + d3 * x1_PM * x1_PM) / d2;
288  decomposition.m3Rp[0][0] = dCosTheta;
289  decomposition.m3Rp[0][2] = -dSinTheta;
290  decomposition.m3Rp[2][0] = dSinTheta;
291  decomposition.m3Rp[2][2] = dCosTheta;
292 
293  // Eq 14
294  decomposition.v3Tp[0] = (d1 - d3) * x1_PM * e1[signs];
295  decomposition.v3Tp[1] = 0.0;
296  decomposition.v3Tp[2] = (d1 - d3) * -x3_PM * e3[signs];
297 
298  v3np[0] = x1_PM * e1[signs];
299  v3np[1] = x2;
300  v3np[2] = x3_PM * e3[signs];
301  decomposition.v3n = V * v3np;
302 
303  mvDecompositions.push_back(decomposition);
304  }
305  // Case 1, d' < 0:
306  decomposition.d = s * -dPrime_PM;
307  for(int signs=0; signs<4; signs++)
308  {
309  // Eq 15
310  decomposition.m3Rp = -1 * Identity;
311  double dSinPhi = (d1 + d3) * x1_PM * x3_PM * e1[signs] * e3[signs] / d2;
312  double dCosPhi = (d3 * x1_PM * x1_PM - d1 * x3_PM * x3_PM) / d2;
313  decomposition.m3Rp[0][0] = dCosPhi;
314  decomposition.m3Rp[0][2] = dSinPhi;
315  decomposition.m3Rp[2][0] = dSinPhi;
316  decomposition.m3Rp[2][2] = -dCosPhi;
317 
318  // Eq 16
319  decomposition.v3Tp[0] = (d1 + d3) * x1_PM * e1[signs];
320  decomposition.v3Tp[1] = 0.0;
321  decomposition.v3Tp[2] = (d1 + d3) * x3_PM * e3[signs];
322 
323  v3np[0] = x1_PM * e1[signs];
324  v3np[1] = x2;
325  v3np[2] = x3_PM * e3[signs];
326  decomposition.v3n = V * v3np;
327 
328  mvDecompositions.push_back(decomposition);
329  }
330 
331  // While we have the SVD results calculated here, store the decomposition R and t results as well..
332  for(unsigned int i=0; i<mvDecompositions.size(); i++)
333  {
334  mvDecompositions[i].se3SecondFromFirst.get_rotation() =
335  s * U * mvDecompositions[i].m3Rp * V.T();
336  mvDecompositions[i].se3SecondFromFirst.get_translation() =
337  U * mvDecompositions[i].v3Tp;
338  }
339 }
340 
342 {
343  return lhs.nScore < rhs.nScore;
344 }
345 
346 static double SampsonusError(Vector<2> &v2Dash, const Matrix<3> &m3Essential, Vector<2> &v2)
347 {
348  Vector<3> v3Dash = unproject(v2Dash);
349  Vector<3> v3 = unproject(v2);
350 
351  double dError = v3Dash * m3Essential * v3;
352 
353  Vector<3> fv3 = m3Essential * v3;
354  Vector<3> fTv3Dash = m3Essential.T() * v3Dash;
355 
356  Vector<2> fv3Slice = fv3.slice<0,2>();
357  Vector<2> fTv3DashSlice = fTv3Dash.slice<0,2>();
358 
359  return (dError * dError / (fv3Slice * fv3Slice + fTv3DashSlice * fTv3DashSlice));
360 }
361 
362 
364 {
365  assert(mvDecompositions.size() == 8);
366  for(unsigned int i=0; i<mvDecompositions.size(); i++)
367  {
368  HomographyDecomposition &decom = mvDecompositions[i];
369  int nPositive = 0;
370  for(unsigned int m=0; m<mvHomographyInliers.size(); m++)
371  {
372  Vector<2> &v2 = mvHomographyInliers[m].v2CamPlaneFirst;
373  double dVisibilityTest = (mm3BestHomography[2][0] * v2[0] + mm3BestHomography[2][1] * v2[1] + mm3BestHomography[2][2]) / decom.d;
374  if(dVisibilityTest > 0.0)
375  nPositive++;
376  };
377  decom.nScore = -nPositive;
378  }
379 
380  sort(mvDecompositions.begin(), mvDecompositions.end());
381  mvDecompositions.resize(4);
382 
383  for(unsigned int i=0; i<mvDecompositions.size(); i++)
384  {
385  HomographyDecomposition &decom = mvDecompositions[i];
386  int nPositive = 0;
387  for(unsigned int m=0; m<mvHomographyInliers.size(); m++)
388  {
389  Vector<3> v3 = unproject(mvHomographyInliers[m].v2CamPlaneFirst);
390  double dVisibilityTest = v3 * decom.v3n / decom.d;
391  if(dVisibilityTest > 0.0)
392  nPositive++;
393  };
394  decom.nScore = -nPositive;
395  }
396 
397  sort(mvDecompositions.begin(), mvDecompositions.end());
398  mvDecompositions.resize(2);
399 
400  // According to Faugeras and Lustman, ambiguity exists if the two scores are equal
401  // but in practive, better to look at the ratio!
402  double dRatio = (double) mvDecompositions[1].nScore / (double) mvDecompositions[0].nScore;
403 
404  if(dRatio < 0.9) // no ambiguity!
405  mvDecompositions.erase(mvDecompositions.begin() + 1);
406  else // two-way ambiguity. Resolve by sampsonus score of all points.
407  {
408  double dErrorSquaredLimit = mdMaxPixelErrorSquared * 4;
409  double adSampsonusScores[2];
410  for(int i=0; i<2; i++)
411  {
412  SE3<> se3 = mvDecompositions[i].se3SecondFromFirst;
413  Matrix<3> m3Essential;
414  for(int j=0; j<3; j++)
415  m3Essential.T()[j] = se3.get_translation() ^ se3.get_rotation().get_matrix().T()[j];
416 
417  double dSumError = 0;
418  for(unsigned int m=0; m < mvMatches.size(); m++ )
419  {
420  double d = SampsonusError(mvMatches[m].v2CamPlaneSecond, m3Essential, mvMatches[m].v2CamPlaneFirst);
421  if(d > dErrorSquaredLimit)
422  d = dErrorSquaredLimit;
423  dSumError += d;
424  }
425 
426  adSampsonusScores[i] = dSumError;
427  }
428 
429  if(adSampsonusScores[0] <= adSampsonusScores[1])
430  mvDecompositions.erase(mvDecompositions.begin() + 1);
431  else
432  mvDecompositions.erase(mvDecompositions.begin());
433  }
434 
435 }
436 
437 
void RefineHomographyWithInliers()
bool Compute(std::vector< HomographyMatch > vMatches, double dMaxPixelError, SE3<> &se3SecondCameraPose)
SO3< Precision > & get_rotation()
Returns the rotation part of the transformation as a SO3.
Definition: se3.h:64
static Operator< Internal::Identity< Internal::One > > Identity
Definition: objects.h:748
Matrix< Rows, Min_Dim, Precision, Reference::RowMajor > get_U()
Definition: SVD.h:238
static double FindSigmaSquared(std::vector< double > &vdErrorSquared)
Definition: MEstimator.h:77
Vector<(Size==Dynamic?Dynamic:Size+1), Precision > unproject(const Vector< Size, Precision, Base > &v)
Definition: helpers.h:166
bool IsHomographyInlier(Matrix< 3 > m3Homography, HomographyMatch match)
void add_mJ(Precision m, const Vector< Size, Precision, B2 > &J, Precision weight=1)
Definition: wls.h:100
Matrix< 3 > HomographyFromMatches(std::vector< HomographyMatch > vMatches)
double M3Det(Matrix< 3 > m)
static double Weight(double dErrorSquared, double dSigmaSquared)
Definition: MEstimator.h:52
Vector< Size, Precision > & get_mu()
Returns the update. With no prior, this is the result of .
Definition: wls.h:202
Vector< Min_Dim, Precision > & get_diagonal()
Return the singular values as a vector.
Definition: SVD.h:249
Definition: SVD.h:89
void BestHomographyFromMatches_MLESAC()
Definition: se3.h:50
Matrix< 2 > m2PixelProjectionJac
double MLESACScore(Matrix< 3 > m3Homography, HomographyMatch match)
void DecomposeHomography()
Vector< 3, Precision > & get_translation()
Returns the translation part of the transformation as a Vector.
Definition: se3.h:69
Matrix< Min_Dim, Cols, Precision, Reference::RowMajor > get_VT()
Definition: SVD.h:254
Vector< 2 > v2CamPlaneFirst
void compute()
Definition: wls.h:180
void ChooseBestDecomposition()
static double SampsonusError(Vector< 2 > &v2Dash, const Matrix< 3 > &m3Essential, Vector< 2 > &v2)
void add_prior(Precision val)
Definition: wls.h:70
static Operator< Internal::Zero > Zeros
Definition: objects.h:727
Vector<(Size==Dynamic?Dynamic:Size-1), Precision > project(const Vector< Size, Precision, Base > &v)
Definition: helpers.h:157
bool operator<(const HomographyDecomposition lhs, const HomographyDecomposition rhs)
Vector< 2 > v2CamPlaneSecond
#define cout
Definition: Bundle.cpp:16
Definition: wls.h:48