vpgl_em_compute_5_point.hxx
Go to the documentation of this file.
1 // This is core/vpgl/algo/vpgl_em_compute_5_point.hxx
2 #ifndef vpgl_em_compute_5_point_hxx_
3 #define vpgl_em_compute_5_point_hxx_
4 //:
5 // \file
6 #include <iostream>
7 #include <cstdlib>
9 
10 #include <cassert>
11 #ifdef _MSC_VER
12 # include <vcl_msvc_warnings.h>
13 #endif
14 
15 #include <vnl/vnl_matrix_fixed.h>
16 #include <vnl/vnl_inverse.h>
18 #include <vnl/algo/vnl_svd.h>
19 
20 /*-----------------------------------------------------------------------*/
21 template <class T>
23  const std::vector<vgl_point_2d<T> > &right_points,
24  const vpgl_calibration_matrix<T> &k_right,
25  const std::vector<vgl_point_2d<T> > &left_points,
26  const vpgl_calibration_matrix<T> &k_left,
27  std::vector<vpgl_essential_matrix<T> > &ems) const
28 {
29  std::vector<vgl_point_2d<T> > normed_right_points, normed_left_points;
30 
31  normalize(right_points, k_right, normed_right_points);
32  normalize(left_points, k_left, normed_left_points);
33 
34  return compute(normed_right_points, normed_left_points, ems);
35 }
36 
37 
38 template <class T>
40  const std::vector<vgl_point_2d<T> > &normed_right_points,
41  const std::vector<vgl_point_2d<T> > &normed_left_points,
42  std::vector<vpgl_essential_matrix<T> > &ems) const
43 {
44  // Check that we have the right number of points
45  if (normed_right_points.size() != 5 || normed_left_points.size() != 5) {
46  if (verbose) {
47  std::cerr<<"Wrong number of input points!\n" <<
48  "right_points has "<<normed_right_points.size() <<
49  " and left_points has "<<normed_left_points.size() << '\n';
50  }
51  return false;
52  }
53 
54  // Compute the null space basis of the epipolar constraint matrix
55  std::vector<vnl_vector_fixed<T,9> > basis;
56  compute_nullspace_basis(normed_right_points, normed_left_points, basis);
57 
58  // Using this basis, we now can compute the polynomial constraints
59  // on the E matrix.
60  std::vector<vnl_real_npolynomial> constraints;
61  compute_constraint_polynomials(basis, constraints);
62 
63  // Find the groebner basis
64  vnl_matrix<double> groebner_basis(10, 10);
65  compute_groebner_basis(constraints, groebner_basis);
66 
67  // Action matrix
68  vnl_matrix<double> action_matrix(10, 10);
69  compute_action_matrix(groebner_basis, action_matrix);
70 
71  // Finally, use the action matrix to compute the essential matrices,
72  // one possibility for each real eigenvalue of the action matrix
73  compute_e_matrices(basis, action_matrix, ems);
74 
75  return true;
76 }
77 
78 
79 //-------------------------------------------------------------------------
80 //:
81 //Normalization is the process of left-multiplying by the inverse of the
82 // calibration matrix.
83 template <class T>
85  const std::vector<vgl_point_2d<T> > &points,
87  std::vector<vgl_point_2d<T> > &normed_points) const
88 {
89  for (unsigned int i = 0; i < points.size(); ++i)
90  {
91  vgl_point_2d<T> p = k.map_to_focal_plane(points[i]);
92  normed_points.push_back(vgl_point_2d<T>(p.x(), p.y()));
93  }
94 }
95 
96 
97 //:
98 // Constructs the 5x9 epipolar constraint matrix based off the constraint
99 // that q1' * E * q2 = 0, and fills the vectors x, y, z and
100 // w with the nullspace basis for this matrix
101 template <class T>
103  const std::vector<vgl_point_2d<T> > &right_points,
104  const std::vector<vgl_point_2d<T> > &left_points,
105  std::vector<vnl_vector_fixed<T, 9> > &basis) const
106 {
107  // Create the 5x9 epipolar constraint matrix
108  vnl_matrix<T> A(5, 9);
109 
110  for (int i = 0; i < 5; ++i) {
111  A.put(i, 0, right_points[i].x() * left_points[i].x());
112  A.put(i, 1, right_points[i].y() * left_points[i].x());
113  A.put(i, 2, left_points[i].x());
114 
115  A.put(i, 3, right_points[i].x() * left_points[i].y());
116  A.put(i, 4, right_points[i].y() * left_points[i].y());
117  A.put(i, 5, left_points[i].y());
118 
119  A.put(i, 6, right_points[i].x());
120  A.put(i, 7, right_points[i].y());
121  A.put(i, 8, 1.0);
122  }
123 
124  // Find four vectors that span the right nullspace of the matrix.
125  // Do this using SVD.
126  vnl_svd<T> svd(A, tolerance);
127  vnl_matrix<T> V = svd.V();
128 
129  // The null space is spanned by the last four columns of V.
130  for (int i = 5; i < 9; ++i) {
131  vnl_vector_fixed<T,9> basis_vector;
132 
133  for (int j = 0; j < 9; ++j) {
134  basis_vector[j] = V.get(j, i);
135  }
136 
137  basis.push_back(basis_vector);
138  }
139 }
140 
141 
142 //:
143 // Finds 10 constraint polynomials, based on the following criteria:
144 // if X, Y, Z and W are the basis vectors, then
145 // E = xX + yY + zZ + wW for some scalars x, y, z, w. Since these are
146 // unique up to a scale, we say w = 1;
147 //
148 // Furthermore, det(E) = 0, and E*E'*E - .5 * trace(E*E') * E = 0.
149 // Substituting the original equation into all 10 of the equations
150 // generated by these two constraints gives us the constraint polynomials.
151 template <class T>
153  const std::vector<vnl_vector_fixed<T,9> > &basis,
154  std::vector<vnl_real_npolynomial> &constraints) const
155 {
156  // Create a polynomial for each entry of E.
157  //
158  // E = [e11 e12 e13] = x * [ X11 ... ...] + ...
159  // [e21 e22 e23] [... ... ...]
160  // [e31 e32 e33] [... ... ...]
161  //
162  // This means e11 = x * X11 + y * Y11 + z * Z11 + W11.
163  // Form these polynomials. They will be used in the other constraints.
164  std::vector<vnl_real_npolynomial> entry_polynomials(9);
165  vnl_vector<double> coeffs(4);
166 
167  vnl_matrix<unsigned> exps(4, 4);
168  exps.set_identity();
169  exps.put(3, 3, 0);
170 
171  for (int i = 0; i < 9; ++i) {
172  coeffs[0] = basis[0][i];
173  coeffs[1] = basis[1][i];
174  coeffs[2] = basis[2][i];
175  coeffs[3] = basis[3][i];
176 
177  entry_polynomials[i].set(coeffs, exps);
178  }
179 
180  // Create polynomials for the singular value constraint.
181  // These nine equations are from the constraint
182  // E*E'*E - .5 * trace(E*E') * E = 0. If you want to see these in
183  // their full glory, type the following snippet into matlab
184  // (not octave, won't work).
185  //
186  // syms a b c d e f g h i;
187  // E = [a b c; d e f; g h i];
188  // pretty(2*E*E'*E - trace(E*E')*E)
189  //
190  // The first polynomial is this:
191  // a(2*a*a+ 2*b*b+ 2*c*c)+ d(2*a*d+ 2*b*e+ 2*c*f)+ g(2*a*g+ 2*b*h+ 2*c*i)
192  // - a(a*a+b*b+c*c+d*d+e*e+f*f+g*g+h*h+i*i)
193  // The other polynomials have similar forms.
194 
195  // Define a*a + b*b + c*c + d*d + e*e + f*f + g*g + h*h + i*i, a
196  // term in all other constraint polynomials
197  vnl_real_npolynomial sum_of_squares =
198  entry_polynomials[0] * entry_polynomials[0];
199 
200  for (int i = 1; i < 9; ++i) {
201  sum_of_squares = sum_of_squares +
202  entry_polynomials[i] * entry_polynomials[i];
203  }
204 
205  // Create the first two terms in each polynomial and add it to
206  // constraints
207  for (int i = 0; i < 9; ++i) {
208  constraints.push_back(
209  entry_polynomials[i%3] *
210  (entry_polynomials[0] * entry_polynomials[3*(i/3) + 0]*2 +
211  entry_polynomials[1] * entry_polynomials[3*(i/3) + 1]*2 +
212  entry_polynomials[2] * entry_polynomials[3*(i/3) + 2]*2)
213 
214  - entry_polynomials[i] * sum_of_squares);
215  }
216 
217  // Now add the next term (there are four terms total)
218  for (int i = 0; i < 9; ++i) {
219  constraints[i] +=
220  entry_polynomials[(i%3) + 3] *
221  (entry_polynomials[3] * entry_polynomials[3*(i/3) + 0]*2 +
222  entry_polynomials[4] * entry_polynomials[3*(i/3) + 1]*2 +
223  entry_polynomials[5] * entry_polynomials[3*(i/3) + 2]*2);
224  }
225 
226  // Last term
227  for (int i = 0; i < 9; ++i) {
228  constraints[i] = (constraints[i] +
229  entry_polynomials[(i%3) + 6] *
230  (entry_polynomials[6] * entry_polynomials[3*(i/3) + 0]*2 +
231  entry_polynomials[7] * entry_polynomials[3*(i/3) + 1]*2 +
232  entry_polynomials[8] * entry_polynomials[3*(i/3) + 2]*2)) * .5;
233  }
234 
235  // Now we are going to create a polynomial from the constraint det(E)= 0.
236  // if E = [a b c; d e f; g h i], (E = [0 1 2; 3 4 5; 6 7 8]) then
237  // det(E) = (ai - gc) * e + (bg - ah) * f + (ch - bi) * d.
238  // We have a through i in terms of the basis vectors from above, so
239  // use those to construct a constraint polynomial, and put it into
240  // constraints.
241 
242  // (bf - ec) * g = (1*5 - 4*2) * 4
243  vnl_real_npolynomial det_term_1 = entry_polynomials[6] *
244  (entry_polynomials[1] * entry_polynomials[5] -
245  entry_polynomials[2] * entry_polynomials[4]);
246 
247  // (cd - fa) * h
248  vnl_real_npolynomial det_term_2 = entry_polynomials[7] *
249  (entry_polynomials[2] * entry_polynomials[3] -
250  entry_polynomials[0] * entry_polynomials[5]);
251 
252  // (ae - db) * i
253  vnl_real_npolynomial det_term_3 = entry_polynomials[8] *
254  (entry_polynomials[0] * entry_polynomials[4] -
255  entry_polynomials[1] * entry_polynomials[3]);
256 
257  constraints.push_back(det_term_1 + det_term_2 + det_term_3);
258 }
259 
260 
261 /*-----------------------------------------------------------------------*/
262 //:
263 // Returns the coefficient of a term of a vnl_real_npolynomial in three
264 // variables with an x power of x_p, a y power of y_p and a z power of z_p
265 template <class T>
267  const vnl_real_npolynomial &p,
268  unsigned int x_p,
269  unsigned int y_p,
270  unsigned int z_p) const
271 {
272  for (unsigned int i = 0; i < p.polyn().rows(); ++i) {
273  if (x_p == p.polyn().get(i, 0) &&
274  y_p == p.polyn().get(i, 1) &&
275  z_p == p.polyn().get(i, 2)) {
276  return p.coefficients()[i];
277  }
278  }
279  return -1.0;
280 }
281 
282 template <class T>
284  const std::vector<vnl_real_npolynomial> &constraints,
285  vnl_matrix<double> &groebner_basis) const
286 {
287  assert(groebner_basis.rows() == 10);
288  assert(groebner_basis.cols() == 10);
289 
290  vnl_matrix<double> A(10, 20);
291 
292  for (int i = 0; i < 10; ++i) {
293  // x3 x2y xy2 y3 x2z xyz y2z xz2 yz2 z3 x2 xy y2 xz yz z2 x y z 1
294  A.put(i, 0, get_coeff(constraints[i], 3, 0, 0));
295  A.put(i, 1, get_coeff(constraints[i], 2, 1, 0));
296  A.put(i, 2, get_coeff(constraints[i], 1, 2, 0));
297  A.put(i, 3, get_coeff(constraints[i], 0, 3, 0));
298  A.put(i, 4, get_coeff(constraints[i], 2, 0, 1));
299  A.put(i, 5, get_coeff(constraints[i], 1, 1, 1));
300  A.put(i, 6, get_coeff(constraints[i], 0, 2, 1));
301  A.put(i, 7, get_coeff(constraints[i], 1, 0, 2));
302  A.put(i, 8, get_coeff(constraints[i], 0, 1, 2));
303  A.put(i, 9, get_coeff(constraints[i], 0, 0, 3));
304  A.put(i, 10, get_coeff(constraints[i], 2, 0, 0));
305  A.put(i, 11, get_coeff(constraints[i], 1, 1, 0));
306  A.put(i, 12, get_coeff(constraints[i], 0, 2, 0));
307  A.put(i, 13, get_coeff(constraints[i], 1, 0, 1));
308  A.put(i, 14, get_coeff(constraints[i], 0, 1, 1));
309  A.put(i, 15, get_coeff(constraints[i], 0, 0, 2));
310  A.put(i, 16, get_coeff(constraints[i], 1, 0, 0));
311  A.put(i, 17, get_coeff(constraints[i], 0, 1, 0));
312  A.put(i, 18, get_coeff(constraints[i], 0, 0, 1));
313  A.put(i, 19, get_coeff(constraints[i], 0, 0, 0));
314  }
315 
316  // Do a full Gaussian elimination
317  for (int i = 0; i < 10; ++i)
318  {
319  // Make the leading coefficient of row i = 1
320  double leading = A.get(i, i);
321  A.scale_row(i, 1/leading);
322 
323  // Subtract from other rows
324  for (int j = i+1; j < 10; ++j) {
325  double leading2 = A.get(j, i);
326  vnl_vector<double> new_row =
327  A.get_row(j) - A.get_row(i) * leading2;
328 
329  A.set_row(j, new_row);
330  }
331  }
332 
333  // Now, do the back substitution
334  for (int i = 9; i >= 0; --i) {
335  for (int j = 0; j < i; ++j) {
336  double scale = A.get(j, i);
337 
338  vnl_vector<double> new_row =
339  A.get_row(j) - A.get_row(i) * scale;
340 
341  A.set_row(j, new_row);
342  }
343  }
344 
345  // Copy out results. Since the first 10*10 block of A is the
346  // identity (due to the row_reduce), we are interested in the
347  // second 10*10 block.
348  for (int i = 0; i < 10; ++i) {
349  for (int j = 0; j < 10; ++j) {
350  groebner_basis.put(i, j, A.get(i, j+10));
351  }
352  }
353 }
354 
355 
356 /*-----------------------------------------------------------------------*/
357 template <class T>
359  const vnl_matrix<double> &groebner_basis,
360  vnl_matrix<double> &action_matrix) const
361 {
362  action_matrix.fill(0.0);
363 
364  action_matrix.set_row(0, groebner_basis.get_row(0));
365  action_matrix.set_row(1, groebner_basis.get_row(1));
366  action_matrix.set_row(2, groebner_basis.get_row(2));
367  action_matrix.set_row(3, groebner_basis.get_row(4));
368  action_matrix.set_row(4, groebner_basis.get_row(5));
369  action_matrix.set_row(5, groebner_basis.get_row(7));
370  action_matrix *= -1;
371 
372  action_matrix.put(6, 0, 1.0);
373  action_matrix.put(7, 1, 1.0);
374  action_matrix.put(8, 3, 1.0);
375  action_matrix.put(9, 6, 1.0);
376 }
377 
378 
379 /*------------------------------------------------------------------------*/
380 template <class T>
382  const std::vector<vnl_vector_fixed<T, 9> > &basis,
383  const vnl_matrix<double> &action_matrix,
384  std::vector<vpgl_essential_matrix<T> > &ems) const
385 {
386  vnl_matrix<double> zeros(action_matrix.rows(), action_matrix.cols(), 0);
387  vnl_complex_eigensystem eigs(action_matrix, zeros);
388 
389  for (unsigned int i = 0; i < eigs.W.size(); ++i) {
390  if (std::abs(eigs.W[i].imag()) <= tolerance)
391  {
392  vnl_vector_fixed<T, 9> linear_e;
393 
394  double w_inv = 1.0 / eigs.R.get(i, 9).real();
395  double x = eigs.R.get(i, 6).real() * w_inv;
396  double y = eigs.R.get(i, 7).real() * w_inv;
397  double z = eigs.R.get(i, 8).real() * w_inv;
398 
399  linear_e =
400  x * basis[0] + y * basis[1] + z * basis[2] + basis[3];
401  linear_e /= linear_e[8];
402  // ignore solutions that are non-finite
403  if (!linear_e.is_finite())
404  continue;
405 
406  ems.push_back(vpgl_essential_matrix<T>(
408  }
409  }
410 }
411 
412 
413 //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
414 static inline void get_distinct_indices(
415  int n, int *idxs, int number_entries)
416 {
417  for (int i = 0; i < n; ++i) {
418  bool found = false;
419  int idx=0;
420 
421  while (!found) {
422  found = true;
423  idx = std::rand() % number_entries;
424 
425  for (int j = 0; j < i; ++j) {
426  if (idxs[j] == idx) {
427  found = false;
428  break;
429  }
430  }
431  }
432 
433  idxs[i] = idx;
434  }
435 }
436 
437 
438 template <class T>
440  std::vector<vgl_point_2d<T> > const& right_points,
441  vpgl_calibration_matrix<T> const& right_k,
442 
443  std::vector<vgl_point_2d<T> > const& left_points,
444  vpgl_calibration_matrix<T> const& left_k,
445 
446  vpgl_essential_matrix<T> &best_em) const
447 {
448  // ----- Test the input
449  if ( right_points.size() != left_points.size()) {
450  if (verbose) {
451  std::cerr
452  << "The two vectors of points must be the same size!\n"
453  << "right_points is size " << right_points.size()
454  << " while left_points is size " << left_points.size()
455  << ".\n";
456  }
457  return false;
458  }
459  else if (right_points.size() < 5) {
460  if (verbose) {
461  std::cerr
462  << "There need to be at least 5 points to run the "
463  << "five-point algorithm!\n"
464  << "Input only contained " << right_points.size()
465  << " points.\n";
466  }
467  return false;
468  }
469 
470  // ----- Good input! Do the ransac
471  const size_t num_points = right_points.size();
472 
473  unsigned best_inlier_count = 0u;
474 
475  vpgl_em_compute_5_point<T> five_point;
476 
477  int match_idxs[5];
478  for (unsigned int r = 0; r < num_rounds; ++r)
479  {
480  // Choose 5 random points, and use the 5-point algorithm on
481  // these points to find the relative pose.
482  std::vector<vgl_point_2d<T> > right_points_to_use;
483  std::vector<vgl_point_2d<T> > left_points_to_use;
484 
485  get_distinct_indices(5, match_idxs, (int) num_points);
486 
487  for (int & match_idx : match_idxs) {
488  right_points_to_use.push_back(right_points[match_idx]);
489  left_points_to_use.push_back(left_points[match_idx]);
490  }
491  std::vector<vpgl_essential_matrix<T> > ems;
492  five_point.compute(
493  right_points_to_use, right_k,
494  left_points_to_use, left_k,
495  ems);
496 
497  // Now test all the essential matrices we've found, using them as
498  // RANSAC hypotheses.
499  typename std::vector<vpgl_essential_matrix<T> >::const_iterator i;
500  for (i = ems.begin(); i != ems.end(); ++i) {
501  vpgl_fundamental_matrix<T> f(right_k, left_k, *i);
502 
503  vnl_double_3x1 point_r, point_l;
504 
505  // Count the number of inliers
506  unsigned inlier_count = 0;
507  for (unsigned j = 0; j < num_points; ++j) {
508  point_r.put(0, 0, right_points[j].x());
509  point_r.put(1, 0, right_points[j].y());
510  point_r.put(2, 0, 1.0);
511 
512  point_l.put(0, 0, left_points[j].x());
513  point_l.put(1, 0, left_points[j].y());
514  point_l.put(2, 0, 1.0);
515 
516  vnl_double_3x1 f_r = f.get_matrix() * point_r;
517  vnl_double_3x1 f_l = f.get_matrix().transpose() * point_l;
518 
519  // compute normalized distance to line
520  double p = (point_r.transpose() * f_l).get(0,0);
521  double error = (1.0 / (f_l.get(0,0) * f_l.get(0,0) + f_l.get(1,0) * f_l.get(1,0)) +
522  1.0 / (f_r.get(0,0) * f_r.get(0,0) + f_r.get(1,0) * f_r.get(1,0))) * (p * p);
523 
524  if ( error <= inlier_threshold) {
525  ++inlier_count;
526  }
527  }
528 
529  if (best_inlier_count < inlier_count) {
530  best_em = *i;
531  best_inlier_count = inlier_count;
532  }
533  }
534  }
535 
536  return true;
537 }
538 
539 
540 #define VPGL_EM_COMPUTE_5_POINT_INSTANTIATE(T) \
541 template class vpgl_em_compute_5_point<T >; \
542 template class vpgl_em_compute_5_point_ransac<T >
543 
544 #endif // vpgl_em_compute_5_point_hxx_
unsigned int cols() const
void compute_nullspace_basis(const std::vector< vgl_point_2d< T > > &right_points, const std::vector< vgl_point_2d< T > > &left_points, std::vector< vnl_vector_fixed< T, 9 > > &basis) const
Constructs the 5x9 epipolar constraint matrix based off the constraint that q1' * E * q2 = 0,...
The 5 point algorithm as described by David Nister for computing an essential matrix from point corre...
bool is_finite() const
void compute_groebner_basis(const std::vector< vnl_real_npolynomial > &constraints, vnl_matrix< double > &groebner_basis) const
const vnl_vector< double > & coefficients() const
A class representing the "K" matrix of a perspective camera matrix as described in.
void put(unsigned r, unsigned c, T const &v)
T get(unsigned r, unsigned c) const
double get_coeff(const vnl_real_npolynomial &p, unsigned int x_p, unsigned int y_p, unsigned int z_p) const
Returns the coefficient of a term of a vnl_real_npolynomial in three variables with an x power of x_p...
T get(unsigned r, unsigned c) const
bool compute(std::vector< vgl_point_2d< T > > const &right_points, vpgl_calibration_matrix< T > const &right_k, std::vector< vgl_point_2d< T > > const &left_points, vpgl_calibration_matrix< T > const &left_k, vpgl_essential_matrix< T > &best_em) const
vnl_matrix_fixed< T, num_cols, num_rows > transpose() const
vnl_matrix & fill(double const &)
T const * data_block() const
vnl_vector< double > get_row(unsigned r) const
void compute_constraint_polynomials(const std::vector< vnl_vector_fixed< T, 9 > > &basis, std::vector< vnl_real_npolynomial > &constraints) const
Finds 10 constraint polynomials, based on the following criteria: if X, Y, Z and W are the basis vect...
vnl_matrix & set_row(unsigned i, double const *v)
void normalize(const std::vector< vgl_point_2d< T > > &points, const vpgl_calibration_matrix< T > &k, std::vector< vgl_point_2d< T > > &normed_points) const
Normalization is the process of left-multiplying by the inverse of the calibration matrix.
void compute_action_matrix(const vnl_matrix< double > &groebner_basis, vnl_matrix< double > &action_matrix) const
unsigned int size() const
void compute_e_matrices(const std::vector< vnl_vector_fixed< T, 9 > > &basis, const vnl_matrix< double > &action_matrix, std::vector< vpgl_essential_matrix< T > > &ems) const
unsigned int rows() const
vnl_matrix< T > & V()
const vnl_matrix< unsigned int > & polyn() const
vnl_vector & set(double const *ptr)
bool compute(const std::vector< vgl_point_2d< T > > &normed_right_points, const std::vector< vgl_point_2d< T > > &normed_left_points, std::vector< vpgl_essential_matrix< T > > &ems) const
Compute from two sets of corresponding points.
vgl_point_2d< T > map_to_focal_plane(vgl_point_2d< T > const &p_image) const
Maps to and from the focal plane.
v & normalize(v &a)
double get(size_t i) const
vnl_matrix & set_identity()
const vnl_matrix_fixed< T, 3, 3 > & get_matrix() const
Get a copy of the FM in vnl form.
void put(unsigned r, unsigned c, T const &)