contrib/oxl/mvl/FManifoldProject.cxx
Go to the documentation of this file.
00001 // This is oxl/mvl/FManifoldProject.cxx
00002 #ifdef VCL_NEEDS_PRAGMA_INTERFACE
00003 #pragma implementation
00004 #endif
00005 //:
00006 //  \file
00007 
00008 #include "FManifoldProject.h"
00009 
00010 #include <vnl/vnl_double_2x2.h>
00011 #include <vnl/vnl_double_4.h>
00012 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00013 #include <vnl/algo/vnl_rpoly_roots.h>
00014 #include <vnl/vnl_real_polynomial.h>
00015 #include <vnl/algo/vnl_svd.h>
00016 #include <vnl/vnl_diag_matrix.h>
00017 #include <vnl/vnl_math.h>
00018 #include <vgl/vgl_homg_point_2d.h>
00019 
00020 #include <mvl/HomgPoint2D.h>
00021 #include <mvl/FMatrix.h>
00022 #include <mvl/HomgOperator2D.h>
00023 
00024 #include <vcl_iostream.h>
00025 
00026 //: Construct an FManifoldProject object which will use the given F to correct point pairs.
00027 FManifoldProject::FManifoldProject(const FMatrix& Fobj)
00028 {
00029   set_F(Fobj);
00030 }
00031 
00032 //: Construct an FManifoldProject object with the intention of later setting its F.
00033 FManifoldProject::FManifoldProject() {}
00034 
00035 //: Use the given F to correct point pairs.
00036 void FManifoldProject::set_F(const FMatrix& Fobj)
00037 {
00038   F_ = Fobj.get_matrix();
00039 
00040   F_.assert_finite();
00041 
00042   // Top left corner of F
00043   vnl_double_2x2 f22 = F_.extract(2,2);
00044 
00045   // A := 0.5*[O f22'; f22 O];
00046   A_.fill(0.0);
00047   A_.update(0.5*f22.transpose().as_ref(), 0, 2);
00048   A_.update(0.5*f22.as_ref(), 2, 0);
00049 
00050   vnl_double_4 b(F_(2,0), F_(2,1), F_(0,2), F_(1,2));
00051 
00052   double c = F_(2,2);
00053 
00054   // Compute eig(A) to translate and rotate the quadric
00055   vnl_symmetric_eigensystem<double>  eig(A_.as_ref()); // size 4x4
00056 
00057 #ifdef DEBUG
00058   vcl_cerr << vnl_svd<double>(F_);
00059   MATLABPRINT(F_);
00060   MATLABPRINT(eig.D);
00061 #endif // DEBUG
00062 
00063   // If all eigs are 0, had an affine F
00064   affine_F_ = eig.D(3,3) < 1e-6;
00065   if (affine_F_) {
00066 #ifdef DEBUG
00067     vcl_cerr << "FManifoldProject: Affine F = " << F_ << vcl_endl;
00068 #endif // DEBUG
00069     double s = 1.0 / b.magnitude();
00070     t_ = b * s;
00071     d_[0] = c * s;
00072   }
00073   else {
00074     // Translate Quadric so that b = 0. (Translates to the epipoles)
00075     t_ = -0.5 * eig.solve(b.as_ref()); // length 4
00076 
00077     vnl_double_4 At = A_*t_;
00078     vnl_double_4 Bprime = 2.0*At + b;
00079     double tAt = dot_product(t_, At);
00080     double Cprime = tAt + dot_product(t_, b) + c;
00081 
00082     // Now C is zero cos F is rank 2
00083     if (vnl_math_abs(Cprime) > 1e-6) {
00084       vcl_cerr << "FManifoldProject: ** HartleySturm: F = " << F_ << vcl_endl
00085                << "FManifoldProject: ** HartleySturm: B = " << Bprime << vcl_endl
00086                << "FManifoldProject: ** HartleySturm: Cerror = " << Cprime << vcl_endl
00087                << "FManifoldProject: ** HartleySturm: F not rank 2 ?\n"
00088                << "FManifoldProject: singular values are "  << vnl_svd<double>(F_.as_ref()).W() << vcl_endl;
00089     }
00090     // **** Now have quadric x'*A*x = 0 ****
00091 
00092     // Rotate A
00093 
00094     // Group the sign-conjugates
00095     // Re-sort the eigensystem so that it is -a a -b b
00096     {
00097       int I[] = { 0, 3, 1, 2 };
00098       for (int col = 0; col < 4; ++col) {
00099         int from_col = I[col];
00100         d_[col] = eig.D(from_col,from_col);
00101         for (int r=0;r<4;++r)
00102           V_(r,col) = eig.V(r, from_col);
00103       }
00104     }
00105   }
00106 }
00107 
00108 //: Find the points out1, out2 which minimize d(out1,p1) + d(out2,p2) subject to out1'*F*out2 = 0.
00109 //  Returns the minimum distance squared: ||x[1..4] - p[1..4]||^2.
00110 double FManifoldProject::correct(vgl_homg_point_2d<double> const& p1,
00111                                  vgl_homg_point_2d<double> const& p2,
00112                                  vgl_homg_point_2d<double>& out1,
00113                                  vgl_homg_point_2d<double>& out2) const
00114 {
00115   if (p1.w()==0 || p2.w()==0) {
00116     vcl_cerr << "FManifoldProject: p1 or p2 at infinity\n";
00117     out1 = p1; out2 = p2; return 1e33;
00118   }
00119 
00120   double p_out[4];
00121   double d = correct(p1.x()/p1.w(), p1.y()/p1.w(), p2.x()/p2.w(), p2.y()/p2.w(),
00122                      p_out, p_out+1, p_out+2, p_out+3);
00123 
00124   out1.set(p_out[0], p_out[1], 1.0);
00125   out2.set(p_out[2], p_out[3], 1.0);
00126   return d;
00127 }
00128 
00129 //: Find the points out1, out2 which minimize d(out1,p1) + d(out2,p2) subject to out1'*F*out2 = 0.
00130 //  Returns the minimum distance squared: ||x[1..4] - p[1..4]||^2.
00131 double FManifoldProject::correct(const HomgPoint2D& p1, const HomgPoint2D& p2, HomgPoint2D* out1, HomgPoint2D* out2) const
00132 {
00133   double p[4];
00134   if (!p1.get_nonhomogeneous(p[0], p[1]) ||
00135       !p2.get_nonhomogeneous(p[2], p[3])) {
00136     vcl_cerr << "FManifoldProject: p1 or p2 at infinity\n";
00137     *out1 = p1;
00138     *out2 = p2;
00139     return 1e30;
00140   }
00141 
00142   double p_out[4];
00143   double d = correct(p[0], p[1], p[2], p[3], &p_out[0], &p_out[1], &p_out[2], &p_out[3]);
00144 
00145   out1->set(p_out[0], p_out[1], 1.0);
00146   out2->set(p_out[2], p_out[3], 1.0);
00147   return d;
00148 }
00149 
00150 //: Find the points out1, out2 which minimize d(out1,p1) + d(out2,p2) subject to out1'*F*out2 = 0.
00151 //  Returns the minimum distance squared: ||x[1..4] - p[1..4]||^2.
00152 double FManifoldProject::correct(double   x1, double   y1, double   x2, double   y2,
00153                                  double *ox1, double *oy1, double *ox2, double *oy2) const
00154 {
00155   // Make the query point
00156   vnl_double_4 p;
00157   p[0] = x1;
00158   p[1] = y1;
00159   p[2] = x2;
00160   p[3] = y2;
00161 
00162   if (affine_F_) {
00163     // Easy case for affine F, F is a plane.
00164     // t_ = n;
00165     // d_[0] = d;
00166     // pout = x - n (n p + d)
00167     const vnl_double_4& n = t_;
00168     double d = d_[0];
00169 
00170     double distance = (dot_product(n, p) + d);
00171     *ox1 = p[0];
00172     *oy1 = p[1];
00173     *ox2 = p[2];
00174     *oy2 = p[3];
00175 
00176     vnl_double_3 l = F_ * vnl_double_3(p[2], p[3], 1.0);
00177     double EPIDIST = (l[0] * p[0] + l[1] * p[1] + l[2])/vcl_sqrt(l[0]*l[0]+l[1]*l[1]);
00178     if (EPIDIST > 1e-4) {
00179       vcl_cerr << "FManifoldProject: Affine F: EPIDIST = " << EPIDIST << vcl_endl
00180                << "FManifoldProject: Affine F: p = " << (dot_product(p,n) + d) << vcl_endl;
00181 #if 0
00182       double EPI1 = dot_product(out2->get_vector(), F_*out1->get_vector());
00183       double EPI2 = dot_product(p, n) + d;
00184       vcl_cerr << "t = " << n << ' ' << d << vcl_endl
00185                << "F_ = " << F_ << vcl_endl
00186                << "FManifoldProject: Affine F: E = " << (EPI1 - EPI2) << vcl_endl;
00187       vcl_abort();
00188 #endif // 0
00189     }
00190 
00191     return distance * distance;
00192   }
00193 
00194   // Transform the query point
00195   vnl_double_4 pprime = V_.transpose() * (p - t_);
00196 
00197   // Solve p' (I - lambda D)^-1 D (I - lambda D)^-1 p = 0
00198   double b1 = 1./d_[0]; double a1 = vnl_math_sqr(pprime[0])*b1;
00199   double b2 = 1./d_[1]; double a2 = vnl_math_sqr(pprime[1])*b2;
00200   double b3 = 1./d_[2]; double a3 = vnl_math_sqr(pprime[2])*b3;
00201   double b4 = 1./d_[3]; double a4 = vnl_math_sqr(pprime[3])*b4;
00202 
00203   if (vnl_math_max(vnl_math_abs(b1 + b2), vnl_math_abs(b3 + b4)) > 1e-7) {
00204     vcl_cerr << "FManifoldProject: B = [" <<b1<< ' ' <<b2<< ' ' <<b3<< ' ' <<b4<< "];\n"
00205              << "FManifoldProject: b1 != -b2 or b3 != -b4\n";
00206   }
00207 
00208   // a11 ../ (b1 - x).^2 + a12 ../ (b1 + x).^2 + a21 ../ (b2 - x).^2 + a22 ../ (b2 + x).^2
00209   // a11 = p1^2*b1
00210   //                 2         2              2         2              2         2          2              2         2         2
00211   //     (a3*(x - b1)  (x - b2)  + a2*(x - b1)  (x - b3)  + a1*(x - b2)  (x - b3) ) (x - b4)  + a4*(x - b1)  (x - b2)  (x - b3)
00212   // Coeffs from mma, assuming /. { b4 -> -b3, b2 -> -b1 }
00213   static vnl_vector<double> coeffs_(7);
00214   double b12 = b1*b1;
00215   double b32 = b3*b3;
00216   double b14 = b12*b12;
00217   double b34 = b32*b32;
00218 
00219   coeffs_[6] = a3*b14*b32 + a4*b14*b32 + a1*b12*b34 + a2*b12*b34;
00220   coeffs_[5] = (2*a3*b14*b3 - 2*a4*b14*b3 + 2*a1*b1*b34 - 2*a2*b1*b34);
00221   coeffs_[4] = (a3*b14 + a4*b14 - 2*(a1 +a2 + a3 + a4)*b12*b32 + a1*b34 + a2*b34);
00222   coeffs_[3] = (-4*a3*b12*b3 + 4*a4*b12*b3 - 4*a1*b1*b32 + 4*a2*b1*b32);
00223   coeffs_[2] = (a1*b12 + a2*b12 - 2*a3*b12 - 2*a4*b12 - 2*a1*b32 - 2*a2*b32 + a3*b32 + a4*b32);
00224   coeffs_[1] = 2*(b3*(a3 - a4) + b1*(a1 - a2));
00225   coeffs_[0] = (a1 + a2 + a3 + a4);
00226 
00227   // Don't try this: c = c ./ [1e0 1e2 1e4 1e6 1e8 1e10 1e12]
00228   coeffs_ /= coeffs_.magnitude();
00229 
00230   vnl_real_polynomial poly(coeffs_);
00231   vnl_rpoly_roots roots(coeffs_);
00232   double dmin = 1e30;
00233   vnl_double_4 Xmin;
00234   vnl_vector<double> realroots = roots.realroots(1e-8);
00235   int errs = 0;
00236   bool got_one = false;
00237   for (unsigned i = 0; i < realroots.size(); ++i) {
00238     double lambda = realroots[i];
00239 
00240     // Some roots to the multiplied out poly are not roots to the rational polynomial.
00241     double RATPOLY_RESIDUAL = (a1/vnl_math_sqr(b1 - lambda) +
00242                                a2/vnl_math_sqr(b2 - lambda) +
00243                                a3/vnl_math_sqr(b3 - lambda) +
00244                                a4/vnl_math_sqr(b4 - lambda));
00245 
00246     if (vcl_fabs(RATPOLY_RESIDUAL) > 1e-8)
00247       continue;
00248 
00249     vnl_diag_matrix<double> Dinv((1.0 - lambda * d_).as_ref()); // length 4
00250     Dinv.invert_in_place();
00251     vnl_double_4 Xp = Dinv * pprime.as_ref();
00252     vnl_double_4 X = V_ * Xp + t_;
00253 
00254     // Paranoia check
00255     {
00256       HomgPoint2D X1(X[0], X[1]);
00257       HomgPoint2D X2(X[2], X[3]);
00258       double EPIDIST = HomgOperator2D::perp_dist_squared(X2, HomgLine2D(F_*X1.get_vector()));
00259       if (0 && EPIDIST > 1e-12) {
00260         // This can happen in reasonable circumstances -- notably when one
00261         // epipole is at infinity.
00262         vcl_cerr << "FManifoldProject: A root has epidist = " << vcl_sqrt(EPIDIST) << vcl_endl
00263                  << "  coeffs: " << coeffs_ << vcl_endl
00264                  << "  root = " << lambda << vcl_endl
00265                  << "  poly residual = " << poly.evaluate(lambda) << vcl_endl
00266                  << "  rational poly residual = " << RATPOLY_RESIDUAL << vcl_endl;
00267         ++ errs;
00268         break;
00269       }
00270     }
00271 
00272     double dist = (X - p).squared_magnitude();
00273     if (!got_one || dist < dmin) {
00274       dmin = dist;
00275       Xmin = X;
00276       got_one = true;
00277     }
00278   }
00279 
00280   if (!got_one) {
00281     vcl_cerr << "FManifoldProject: AROOGAH. Final epipolar distance =  " << dmin << ", errs = " << errs << vcl_endl;
00282     *ox1 = x1;
00283     *oy1 = y1;
00284     *ox2 = x2;
00285     *oy2 = y2;
00286   }
00287   else {
00288     *ox1 = Xmin[0];
00289     *oy1 = Xmin[1];
00290     *ox2 = Xmin[2];
00291     *oy2 = Xmin[3];
00292   }
00293 
00294   return dmin;
00295 }