1 // This is oxl/mvl/FMatrixComputeNonLinear.cxx
2 //:
3 // \file
4 
5 #include <iostream>
6 #include "FMatrixComputeNonLinear.h"
7 
8 #ifdef _MSC_VER
9 #  include "vcl_msvc_warnings.h"
10 #endif
11 
12 #include "vnl/vnl_matrix.h"
13 #include <vnl/algo/vnl_levenberg_marquardt.h>
14 #include "vnl/vnl_double_2.h"
15 #include "vnl/vnl_double_3.h"
16 #include "vgl/vgl_homg_line_2d.h"
17 #include <mvl/HomgNorm2D.h>
18 #include <mvl/HomgPoint2D.h>
19 #include <mvl/HomgLine2D.h>
20 #include <mvl/HomgInterestPointSet.h>
21 #include <mvl/FMatrix.h>
22 #include <mvl/FManifoldProject.h>
23 #include <mvl/FMatrixCompute7Point.h>
24 
25 
26 // Seven parameters for a minimal parametrization of the F matrix
27 constexpr int FMatrixComputeNonLinear_nparams = 7;
28 
29 //-----------------------------------------------------------------------------
30 //: Constructor
31 //
FMatrixComputeNonLinear(PairMatchSetCorner * matches)32 FMatrixComputeNonLinear::FMatrixComputeNonLinear(PairMatchSetCorner* matches)
33  : vnl_least_squares_function(FMatrixComputeNonLinear_nparams, matches->compute_match_count(), no_gradient),
34    data_size_(matches->compute_match_count()),
35    matches_(*matches),
36    one_(true)
37 {
38   // Copy matching points from matchset.
39   // Set up some initial variables
40   HomgInterestPointSet const* points1 = matches_.get_corners1();
41   HomgInterestPointSet const* points2 = matches_.get_corners2();
42   std::vector<HomgPoint2D> dead1, dead2;
43   std::vector<int> point1_int, point2_int;
44   matches_.extract_matches(dead1, point1_int, dead2, point2_int);
45   data_size_ = matches_.count();
46   points1_.resize(points1->size());
47   points2_.resize(points2->size());
48 
49   // Get the actual image points
50   for (int a = 0; a < data_size_; a++) {
51     vnl_double_2 temp1;
52     temp1 = points1->get_2d(point1_int[a]);
53     points1_[a] = vgl_homg_point_2d<double>(temp1[0], temp1[1], 1.0);
54   }
55 
56   for (int a = 0; a < data_size_; a++) {
57     vnl_double_2 temp2;
58     temp2 = points2->get_2d(point2_int[a]);
59     points2_[a] = vgl_homg_point_2d<double>(temp2[0], temp2[1], 1.0);
60   }
61 }
62 
63 //-----------------------------------------------------------------------------
64 //: Compute the F Matrix by augmenting a 7 point basis
65 
compute_basis(FMatrix * F,std::vector<int> basis)66 bool FMatrixComputeNonLinear::compute_basis(FMatrix* F, std::vector<int> basis)
67 {
68   one_ = false;
69   std::vector<vgl_homg_point_2d<double> > basis1(7), basis2(7);
70   for (int i = 0; i < 7; i++) {
71     int other = matches_.get_match_12(basis[i]);
72     if (other == -1)
73       std::cerr << "The basis index doesn't include a match for " << i << ".\n";
74     else {
75       vnl_double_2 p1 = matches_.get_corners1()->get_2d(basis[i]);
76       vnl_double_2 p2 = matches_.get_corners2()->get_2d(other);
77         basis1[i] = vgl_homg_point_2d<double>(p1[0], p1[1], 1.0);
78         basis2[i] = vgl_homg_point_2d<double>(p2[0], p2[1], 1.0);
79     }
80   }
81   basis1_ = basis1;
82   basis2_ = basis2;
83   return compute(F);
84 }
85 
86 
87 // General compute method, will alternate between either of the
88 // selected options
compute(FMatrix * F)89 bool FMatrixComputeNonLinear::compute(FMatrix* F)
90 {
91   FMatrix F_final;
92   // fm_fmatrix_nagmin
93   std::cerr << "FMatrixComputeNonLinear: matches = "<< data_size_ <<", using "<< FMatrixComputeNonLinear_nparams <<" parameters\n";
94   double so_far = 1e+8;
95   FMatrix norm_F = *F;
96   if (one_)
97   {
98     for (p_ = 0; p_ < 3; p_++)
99     {
100       for (q_ = 0; q_ < 3; q_++)
101       {
102         for (r_ = 0; r_ < 4; r_++)
103         {
104           FMatrix norm_F = *F;
105           int r1 = 0, c1 = 0, r2 = 0, c2 = 0;
106           get_plan(r1, c1, r2, c2);
107           vnl_matrix<double> mat;
108           norm_F.get(&mat);
109 
110           switch (r_) {
111             case 0 :
112               mat /= mat.get(r1, c1);
113               break;
114             case 1 :
115               mat /= mat.get(r1, c2);
116               break;
117             case 2 :
118               mat /= mat.get(r2, c1);
119               break;
120             case 3 :
121               mat /= mat.get(r2, c2);
122               break;
123             default: // this cannot happen
124               break;
125           }
126 
127           vnl_vector<double> f_params(FMatrixComputeNonLinear_nparams);
128           fmatrix_to_params(FMatrix(mat), f_params);
129           //FMatrix res = params_to_fmatrix(f_params);
130           vnl_levenberg_marquardt lm(*this);
131 
132           // Adjusting these parameters will alter the
133           // convergence properties of the minimisation
134           lm.set_max_function_evals(200);
135           lm.set_f_tolerance(1e-6);
136           lm.set_x_tolerance(1e-6);
137 //        lm.set_epsilon_function(1e-6);
138           lm.minimize(f_params);
139 
140           if (lm.get_end_error() < so_far) {
141             so_far = lm.get_end_error();
142             std::cerr << "so_far : " << so_far << std::endl;
143             norm_F = params_to_fmatrix(f_params);
144             F_final = norm_F;
145             vgl_homg_point_2d<double> e1, e2;
146             F_final.get_epipoles(e1, e2);
147             std::cerr << "Epipole locations 1 : " << e1 << " 2 : " << e2 << '\n';
148           }
149         }
150       }
151     }
152   }
153   else
154   {
155     F_orig_ = norm_F;
156     vnl_vector<double> f_params(FMatrixComputeNonLinear_nparams, 0.0);
157     FMatrix res = params_to_fmatrix(f_params);
158     vnl_double_3x3 mat1 = norm_F.get_matrix();
159     vnl_double_3x3 mat2 = res.get_matrix();
160     mat1 /= mat1(2, 2);
161     mat2 /= mat2(2, 2);
162 
163     vnl_levenberg_marquardt lm(*this);
164 
165     // Adjusting these parameters will alter the
166     // convergence properties of the minimisation
167     lm.set_max_function_evals(100);
168     lm.set_f_tolerance(1e-4);
169     lm.set_x_tolerance(1e-3);
170     lm.set_epsilon_function(1e-4);
171     lm.minimize(f_params);
172 
173     if (lm.get_end_error() < so_far) {
174       so_far = lm.get_end_error();
175       std::cerr << "so_far : " << so_far << std::endl;
176       for (int l = 0; l < 7; l++)
177         std::cerr << f_params(l) << std::endl;
178       norm_F = params_to_fmatrix(f_params);
179       F_final = norm_F;
180       lm.diagnose_outcome();
181       vgl_homg_point_2d<double> e1, e2;
182       F_final.get_epipoles(e1, e2);
183       std::cerr << "Epipole locations 1 : " << e1 << " 2 : " << e2 << '\n';
184     }
185   }
186   *F = F_final;
187   return true;
188 }
189 
190 //-----------------------------------------------------------------------------
191 //: The virtual function from vnl_levenberg_marquardt which returns the RMS epipolar error and a vector of residuals.
f(const vnl_vector<double> & f_params,vnl_vector<double> & fx)192 void FMatrixComputeNonLinear::f(const vnl_vector<double>& f_params, vnl_vector<double>& fx)
193 {
194   FMatrix F = params_to_fmatrix(f_params);
195 
196   fx = calculate_residuals(&F);
197 }
198 
199 // Convert an F Matrix to the parameters to be minimised
fmatrix_to_params(const FMatrix & F,vnl_vector<double> & params)200 void FMatrixComputeNonLinear::fmatrix_to_params(const FMatrix& F, vnl_vector<double>& params)
201 {
202   // Leaving d as 1.0 on all the rotations
203   int c1= 0, r1 = 0, c2 = 0, r2 = 0;
204   get_plan(r1, c1, r2, c2);
205   double b = 0.0, a = 0.0, c = 0.0;
206   switch (r_) {
207     case 0 :
208       a = F.get(r1, c2);
209       b = -F.get(r2, c1);
210       c = -F.get(r2, c2);
211       break;
212     case 1 :
213       a = F.get(r1, c1);
214       b = -F.get(r2 ,c1);
215       c = -F.get(r2, c2);
216       break;
217     case 2 :
218       a = F.get(r1, c1);
219       b = F.get(r1, c2);
220       c = -F.get(r2, c2);
221       break;
222     case 3 :
223       a = F.get(r1, c1);
224       b = F.get(r1, c2);
225       c = -F.get(r2, c1);
226       break;
227     default: // this cannot happen
228       break;
229   }
230   vgl_homg_point_2d<double> one, two;
231   vnl_double_2 e1, e2;
232   F.get_epipoles(one, two);
233   vnl_double_3 e1h(one.x(), one.y(), one.w());
234   vnl_double_3 e2h(two.x(), two.y(), two.w());
235   e1h = e1h.normalize();
236   e2h = e2h.normalize();
237 
238   switch (p_) {
239     case 0 :
240       e1 = vnl_double_2(e1h[1]/e1h[0], e1h[2]/e1h[0]);
241       break;
242     case 1 :
243       e1 = vnl_double_2(e1h[0]/e1h[1], e1h[2]/e1h[1]);
244       break;
245     case 2 :
246       e1 = vnl_double_2(e1h[0]/e1h[2], e1h[1]/e1h[2]);
247       break;
248     default: // this cannot happen
249       break;
250   }
251   switch (q_) {
252     case 0 :
253       e2 = vnl_double_2(e2h[1]/e2h[0], e2h[2]/e2h[0]);
254       break;
255     case 1 :
256       e2 = vnl_double_2(e2h[0]/e2h[1], e2h[2]/e2h[1]);
257       break;
258     case 2 :
259       e2 = vnl_double_2(e2h[0]/e2h[2], e2h[1]/e2h[2]);
260       break;
261     default: // this cannot happen
262       break;
263   }
264   params.put(0, a);
265   params.put(1, b);
266   params.put(2, c);
267   params.put(3, e1[0]);
268   params.put(4, e1[1]);
269   params.put(5, e2[0]);
270   params.put(6, e2[1]);
271 }
272 
273 
274 // Convert the parameters to an F Matrix
params_to_fmatrix(const vnl_vector<double> & params)275 FMatrix FMatrixComputeNonLinear::params_to_fmatrix(const vnl_vector<double>& params)
276 {
277   FMatrix ret;
278 
279   if (one_)
280   {
281     vnl_double_3x3 ref = ret.get_matrix();
282     // Again the d is moved about through the different parametrizations
283     int c1= 0, r1 = 0, c2 = 0, r2 = 0;
284     get_plan(r1, c1, r2, c2);
285     double a = params.get(0);
286     double b = params.get(1);
287     double c = params.get(2);
288     double x = -params.get(3);
289     double y = -params.get(4);
290     double xd = -params.get(5);
291     double yd = -params.get(6);
292     double d;
293     switch (r_)
294     {
295       case 0 :
296         d = 1.0;
297         ref.put(r1, c2, a);
298         ref.put(r2, c1, -b);
299         ref.put(r2, c2, -c);
300         ref.put(r1, c1, d);
301         ref.put(r1, p_, d*x + a*y);
302         ref.put(r2, p_, -b*x - c*y);
303         ref.put(q_, c1, d*xd - b*yd);
304         ref.put(q_, c2, a*xd - c*yd);
305         ref.put(q_, p_, xd*(d*x + a*y) - yd*(b*x + c*y));
306         break;
307       case 1 :
308         d = 1.0;
309         ref.put(r1, c1, a);
310         ref.put(r2, c1, -b);
311         ref.put(r2, c2, -c);
312         ref.put(r1, c2, d);
313         ref.put(r1, p_, a*x + d*y);
314         ref.put(r2, p_, -b*x - c*y);
315         ref.put(q_, c1, a*xd - b*yd);
316         ref.put(q_, c2, d*xd - c*yd);
317         ref.put(q_, p_, xd*(a*x + d*y) - yd*(b*x + c*y));
318         break;
319       case 2 :
320         d = -1.0;
321         ref.put(r1, c1, a);
322         ref.put(r1, c2, b);
323         ref.put(r2, c2, -c);
324         ref.put(r2, c1, -d);
325         ref.put(r1, p_, a*x + b*y);
326         ref.put(r2, p_, -d*x - c*y);
327         ref.put(q_, c1, a*xd - d*yd);
328         ref.put(q_, c2, b*xd - c*yd);
329         ref.put(q_, p_, xd*(a*x + b*y) - yd*(d*x + c*y));
330         break;
331       case 3 :
332         d = -1.0;
333         ref.put(r1, c1, a);
334         ref.put(r1, c2, b);
335         ref.put(r2, c1, -c);
336         ref.put(r2, c2, -d);
337         ref.put(r1, p_, a*x + b*y);
338         ref.put(r2, p_, -c*x - d*y);
339         ref.put(q_, c1, a*xd - c*yd);
340         ref.put(q_, c2, b*xd - d*yd);
341         ref.put(q_, p_, xd*(a*x + b*y) - yd*(c*x + d*y));
342         break;
343       default: // this cannot happen
344         break;
345     }
346     ret.set(ref);
347     return ret;
348   }
349   else
350   {
351     std::vector<vgl_homg_point_2d<double> > new_points1(7);
352     vgl_homg_point_2d<double> e1, e2;
353     F_orig_.get_epipoles(e1, e2);
354     double e1nx = e1.x()/e1.w(), e1ny = e1.y()/e1.w();
355 //  double e2nx = e2.x()/e2.w(), e2ny = e2.y()/e2.w();
356     for (int i = 0; i < 7; i++) {
357       double t1x = basis1_[i].x()/basis1_[i].w(), t1y = basis1_[i].y()/basis1_[i].w();
358 //    double t2x = basis2_[i].x()/basis2_[i].w(), t2y = basis2_[i].y()/basis2_[i].w();
359       double grads1 = -(e1ny - t1y)/(e1nx - t1x);
360 //    double grads2 = -(e2ny - t2y)/(e2nx - t2x);
361       new_points1[i] = vgl_homg_point_2d<double>(params[i]/grads1 + t1x, params[i]*grads1 + t1y, 1.0);
362 //    new_points2[i] = vgl_homg_point_2d<double>(params[i]/grads2 + t2x, params[i]*grads2 + t2y, 1.0);
363     }
364     FMatrixCompute7Point computor(true, true);
365     std::vector<FMatrix*> ref;
366     if (!computor.compute(new_points1, basis2_, ref))
367       std::cerr << "FMatrixCompute7Point Failure\n";
368     double final = 0.0;
369     unsigned int num = 0;
370     for (unsigned int l = 0; l < ref.size(); l++) {
371       vnl_vector<double> res = calculate_residuals(ref[l]);
372       double so_far = 0.0;
373       for (double re : res)
374         so_far += re;
375 //      std::cerr << "so_far : " << so_far << std::endl;
376       if (so_far < final) {
377         final = so_far;
378         num = l;
379       }
380     }
381     ret = *ref[num];
382 
383     for (auto & l : ref)
384       delete l;
385 
386     return ret;
387   }
388 }
389 
390 // Forms a map of the different rank-2 structures for the F Matrix
get_plan(int & r1,int & c1,int & r2,int & c2) const391 void FMatrixComputeNonLinear::get_plan(int &r1, int &c1, int &r2, int &c2) const
392 {
393   switch (p_) {
394     case 0 :
395       c1 = 1;
396       c2 = 2;
397       break;
398     case 1 :
399       c1 = 0;
400       c2 = 2;
401       break;
402     case 2 :
403       c1 = 0;
404       c2 = 1;
405       break;
406     default: // this cannot happen
407       break;
408   }
409   switch (q_) {
410     case 0 :
411       r1 = 1;
412       r2 = 2;
413       break;
414     case 1 :
415       r1 = 0;
416       r2 = 2;
417       break;
418     case 2 :
419       r1 = 0;
420       r2 = 1;
421       break;
422     default: // this cannot happen
423       break;
424   }
425 }
426 
perp_dist_squared(vgl_homg_point_2d<double> const & p,vgl_homg_line_2d<double> const & l)427 static double perp_dist_squared(vgl_homg_point_2d<double> const& p,
428                                 vgl_homg_line_2d <double> const& l)
429 {
430   double r = l.a()*p.x() + l.b()*p.y() + l.c()*p.w();
431   if (r == 0) return 0.0;
432   r /= p.w();
433   return r * r / (l.a()*l.a() + l.b()*l.b());
434 }
435 
calculate_residuals(FMatrix * F)436 vnl_vector<double> FMatrixComputeNonLinear::calculate_residuals(FMatrix* F)
437 {
438   vnl_vector<double> fx(data_size_);
439   vnl_matrix<double> f(3, 3, 0.0);
440   F->get(&f);
441   f /= f.rms();
442 #if 0
443   vnl_matrix<double> ft = f.transpose();
444   vnl_matrix<double> z(3, 3, 0.0);
445   z.put(0, 0, 1.0);z.put(1, 1, 1.0);
446   vnl_matrix<double> pre1 = ft*z*f;
447   vnl_matrix<double> pre2 = f*z*ft;
448 #endif
449 
450   for (int i = 0; i < data_size_; i++)
451   {
452     vgl_homg_point_2d<double> one = points1_[i], two = points2_[i];
453     double t = 0.0;
454     vgl_homg_line_2d<double> l1 = F->image2_epipolar_line(one);
455     vgl_homg_line_2d<double> l2 = F->image1_epipolar_line(two);
456     t += perp_dist_squared(two, l1);
457     t += perp_dist_squared(one, l2);
458 #if 0
459     vnl_double_3 oi(1.0); oi[0]=one.x()/one.w(); oi[1]=one.y()/one.w();
460     vnl_double_3 ti(1.0); ti[0]=two.x()/two.w(); ti[1]=two.x()/two.w();
461     vnl_double_3 p1 = ti*pre1;
462     vnl_double_3 p2 = oi*pre2;
463     double p11 = p1[0]*oi[0] + p1[1]*oi[1] + p1[2]*oi[2];
464     double p21 = p2[0]*ti[0] + p2[1]*ti[1] + p2[2]*ti[2];
465     double factor = 1.0/ p11 + 1.0/ p21;
466 #endif
467     fx[i] = t;//*factor;
468   }
469   return fx;
470 }
471