core/vpgl/algo/vpgl_camera_compute.cxx
Go to the documentation of this file.
00001 // This is core/vpgl/algo/vpgl_camera_compute.cxx
00002 #ifndef vpgl_camera_compute_cxx_
00003 #define vpgl_camera_compute_cxx_
00004 
00005 #include "vpgl_camera_compute.h"
00006 //:
00007 // \file
00008 #include <vcl_iostream.h>
00009 #include <vcl_cassert.h>
00010 #include <vcl_cstdlib.h> // for rand()
00011 #include <vcl_cmath.h>
00012 #include <vnl/vnl_numeric_traits.h>
00013 #include <vnl/vnl_det.h>
00014 #include <vnl/vnl_inverse.h>
00015 #include <vnl/vnl_vector_fixed.h>
00016 #include <vnl/vnl_double_3.h>
00017 #include <vnl/vnl_matrix_fixed.h>
00018 #include <vnl/algo/vnl_svd.h>
00019 #include <vnl/algo/vnl_qr.h>
00020 #include <vgl/algo/vgl_rotation_3d.h>
00021 #include <vgl/algo/vgl_h_matrix_2d_compute_linear.h>
00022 #include <vgl/algo/vgl_h_matrix_3d.h>
00023 #include <vgl/vgl_homg_point_3d.h>
00024 #include <vpgl/algo/vpgl_ortho_procrustes.h>
00025 #include <vpgl/algo/vpgl_optimize_camera.h>
00026 #include <vgl/vgl_point_2d.h>
00027 #include <vgl/vgl_point_3d.h>
00028 #include <vgl/vgl_homg_point_2d.h>
00029 #include <vpgl/vpgl_lvcs.h>
00030 #include <vpgl/algo/vpgl_backproject.h>
00031 
00032 //#define CAMERA_DEBUG
00033 //------------------------------------------
00034 bool
00035 vpgl_proj_camera_compute::compute(
00036                                   const vcl_vector< vgl_point_2d<double> >& image_pts,
00037                                   const vcl_vector< vgl_point_3d<double> >& world_pts,
00038                                   vpgl_proj_camera<double>& camera )
00039 {
00040   vcl_vector< vgl_homg_point_2d<double> > image_pts2;
00041   vcl_vector< vgl_homg_point_3d<double> > world_pts2;
00042   for (unsigned int i = 0; i < image_pts.size(); ++i)
00043     image_pts2.push_back( vgl_homg_point_2d<double>( image_pts[i] ) );
00044   for (unsigned int i = 0; i < world_pts.size(); ++i)
00045     world_pts2.push_back( vgl_homg_point_3d<double>( world_pts[i] ) );
00046   return compute( image_pts2, world_pts2, camera );
00047 }
00048 
00049 
00050 //------------------------------------------
00051 bool
00052 vpgl_proj_camera_compute::compute(
00053                                   const vcl_vector< vgl_homg_point_2d<double> >& image_pts,
00054                                   const vcl_vector< vgl_homg_point_3d<double> >& world_pts,
00055                                   vpgl_proj_camera<double>& camera )
00056 {
00057   unsigned int num_correspondences = image_pts.size();
00058   if ( world_pts.size() < num_correspondences ) num_correspondences = world_pts.size();
00059   assert( num_correspondences >= 6 );
00060 
00061   // Form the solution matrix.
00062   vnl_matrix<double> S( 2*num_correspondences, 12, 0);
00063   for ( unsigned i = 0; i < num_correspondences; ++i ) {
00064     S(2*i,0) = -image_pts[i].w()*world_pts[i].x();
00065     S(2*i,1) = -image_pts[i].w()*world_pts[i].y();
00066     S(2*i,2) = -image_pts[i].w()*world_pts[i].z();
00067     S(2*i,3) = -image_pts[i].w()*world_pts[i].w();
00068     S(2*i,8) = image_pts[i].x()*world_pts[i].x();
00069     S(2*i,9) = image_pts[i].x()*world_pts[i].y();
00070     S(2*i,10) = image_pts[i].x()*world_pts[i].z();
00071     S(2*i,11) = image_pts[i].x()*world_pts[i].w();
00072     S(2*i+1,4) = -image_pts[i].w()*world_pts[i].x();
00073     S(2*i+1,5) = -image_pts[i].w()*world_pts[i].y();
00074     S(2*i+1,6) = -image_pts[i].w()*world_pts[i].z();
00075 
00076     S(2*i+1,7) = -image_pts[i].w()*world_pts[i].w();
00077     S(2*i+1,8) = image_pts[i].y()*world_pts[i].x();
00078     S(2*i+1,9) = image_pts[i].y()*world_pts[i].y();
00079     S(2*i+1,10) = image_pts[i].y()*world_pts[i].z();
00080     S(2*i+1,11) = image_pts[i].y()*world_pts[i].w();
00081   }
00082   vnl_svd<double> svd( S );
00083   vnl_vector<double> c = svd.nullvector();
00084   vnl_matrix_fixed<double,3,4> cm;
00085   cm(0,0)=c(0); cm(0,1)=c(1); cm(0,2)=c(2); cm(0,3)=c(3);
00086   cm(1,0)=c(4); cm(1,1)=c(5); cm(1,2)=c(6); cm(1,3)=c(7);
00087   cm(2,0)=c(8); cm(2,1)=c(9); cm(2,2)=c(10); cm(2,3)=c(11);
00088   camera = vpgl_proj_camera<double>( cm );
00089   return true;
00090 }
00091 
00092 
00093 //------------------------------------------
00094 bool
00095 vpgl_affine_camera_compute::compute(
00096                                     const vcl_vector< vgl_point_2d<double> >& image_pts,
00097                                     const vcl_vector< vgl_point_3d<double> >& world_pts,
00098                                     vpgl_affine_camera<double>& camera )
00099 {
00100   assert( image_pts.size() == world_pts.size() );
00101   assert( image_pts.size() > 3 );
00102 
00103   // Form the solution matrix.
00104   vnl_matrix<double> A( world_pts.size(), 4, 1 );
00105   for (unsigned int i = 0; i < world_pts.size(); ++i) {
00106     A(i,0) = world_pts[i].x(); A(i,1) = world_pts[i].y(); A(i,2) = world_pts[i].z();
00107   }
00108   vnl_vector<double> b1( image_pts.size() );
00109   vnl_vector<double> b2( image_pts.size() );
00110   for (unsigned int i = 0; i < image_pts.size(); ++i) {
00111     b1(i) = image_pts[i].x(); b2(i) = image_pts[i].y();
00112   }
00113   vnl_matrix<double> AtA = A.transpose()*A;
00114   vnl_svd<double> svd(AtA);
00115   if ( svd.rank() < 4 ) {
00116     vcl_cerr << "vpgl_affine_camera_compute:compute() cannot compute,\n"
00117              << "    input data has insufficient rank.\n";
00118     return false;
00119   }
00120   vnl_matrix<double> S = svd.inverse()*A.transpose();
00121   vnl_vector_fixed<double,4> x1, x2;
00122   x1 = S*b1;
00123   x2 = S*b2;
00124 
00125   // Fill in the camera.
00126   camera.set_rows( x1, x2 );
00127   return true;
00128 }
00129 
00130 
00131 //Compute the rotation matrix and translation vector for a
00132 //perspective camera given world to image correspondences and
00133 //the calibration matrix
00134 bool vpgl_perspective_camera_compute::
00135 compute( const vcl_vector< vgl_point_2d<double> >& image_pts,
00136          const vcl_vector< vgl_point_3d<double> >& world_pts,
00137          const vpgl_calibration_matrix<double>& K,
00138          vpgl_perspective_camera<double>& camera )
00139 {
00140   unsigned N = world_pts.size();
00141   if (image_pts.size()!=N)
00142   {
00143     vcl_cout << "Unequal points sets in"
00144              << " vpgl_perspective_camera_compute::compute()\n";
00145     return false;
00146   }
00147   if (N<6)
00148   {
00149     vcl_cout << "Need at least 6 points for"
00150              << " vpgl_perspective_camera_compute::compute()\n";
00151     return false;
00152   }
00153 
00154   //get the inverse calibration map
00155   vnl_matrix_fixed<double, 3, 3> km = K.get_matrix();
00156   vnl_matrix_fixed<double, 3, 3> k_inv = vnl_inverse<double>(km);
00157 
00158   //Form the world point matrix
00159 
00160   //Solve for the unknown point depths (projective scale factors)
00161   vnl_matrix<double> wp(4, N);
00162   for (unsigned c = 0; c<N; ++c)
00163   {
00164     vgl_point_3d<double> p = world_pts[c];
00165     wp[0][c] = p.x(); wp[1][c] = p.y(); wp[2][c] = p.z();
00166     wp[3][c] = 1.0;
00167   }
00168 #ifdef CAMERA_DEBUG
00169   vcl_cout << "World Points\n" << wp << '\n';
00170 #endif
00171   vnl_svd<double> svd(wp);
00172   unsigned rank = svd.rank();
00173   if (rank != 4)
00174   {
00175     vcl_cout << "Insufficient rank for world point"
00176              << " matrix in vpgl_perspective_camera_compute::compute()\n";
00177     return false;
00178   }
00179   //extract the last N-4 columns of V as the null space of wp
00180   vnl_matrix<double> V = svd.V();
00181   unsigned nr = V.rows(), nc = V.columns();
00182   vnl_matrix<double> null_space(nr, nc-4);
00183   for (unsigned c = 4; c<nc; ++c)
00184     for (unsigned r = 0; r<nr; ++r)
00185       null_space[r][c-4] = V[r][c];
00186 #ifdef CAMERA_DEBUG
00187   vcl_cout << "Null Space\n" << null_space << '\n';
00188 #endif
00189   //form Kronecker product of the null space (transpose) with K inverse
00190   unsigned nrk = 3*(nc-4), nck = 3*nr;
00191   vnl_matrix<double> v2k(nrk, nck);
00192   for (unsigned r = 0; r<(nc-4); ++r)
00193     for (unsigned c = 0; c<nr; ++c)
00194       for (unsigned rk = 0; rk<3; ++rk)
00195         for (unsigned ck = 0; ck<3; ++ck)
00196           v2k[rk+3*r][ck+3*c] = k_inv[rk][ck]*null_space[c][r];
00197 #ifdef CAMERA_DEBUG
00198   vcl_cout << "V2K\n" << v2k << '\n';
00199 #endif
00200   //Stack the image points in homogeneous form in a diagonal matrix
00201   vnl_matrix<double> D(3*N, N);
00202   D.fill(0);
00203   for (unsigned c = 0; c<N; ++c)
00204   {
00205     vgl_point_2d<double> p = image_pts[c];
00206     D[3*c][c] = p.x();  D[3*c+1][c] = p.y(); D[3*c+2][c] = 1.0;
00207   }
00208 #ifdef CAMERA_DEBUG
00209   vcl_cout << "D\n" << D << '\n';
00210 #endif
00211   //form the singular matrix
00212   vnl_matrix<double> M = v2k*D;
00213   vnl_svd<double> svdm(M);
00214 
00215   //The point depth solution
00216   vnl_vector<double> depth = svdm.nullvector();
00217 
00218 #ifdef CAMERA_DEBUG
00219   vcl_cout << "depths\n" << depth << '\n';
00220 #endif
00221 
00222   //Check if depths are all approximately the same (near affine projection)
00223   double average_depth = 0;
00224   unsigned nd = depth.size();
00225   for (unsigned i = 0; i<nd; ++i)
00226     average_depth += depth[i];
00227   average_depth /= nd;
00228   double max_dev = 0;
00229   for (unsigned i = 0; i<nd; ++i)
00230   {
00231     double dev = vcl_fabs(depth[i]-average_depth);
00232     if (dev>max_dev)
00233       max_dev = dev;
00234   }
00235   double norm_max_dev = max_dev/average_depth;
00236   //if depths are nearly the same make them exactly equal
00237   //since variations are not meaningful
00238   if (norm_max_dev < 0.01)
00239     for (unsigned i = 0; i<nd; ++i)
00240       depth[i]=vcl_fabs(average_depth);
00241 
00242   //Set up point sets for ortho Procrustes
00243   vnl_matrix<double> X(3,N), Y(3,N);
00244   for (unsigned c = 0; c<N; ++c)
00245   {
00246     vgl_point_2d<double> pi = image_pts[c];
00247     vgl_point_3d<double> pw = world_pts[c];
00248     //image points are multiplied by projective scale factor (depth)
00249     X[0][c] = pi.x()*depth[c]; X[1][c] = pi.y()*depth[c]; X[2][c] = depth[c];
00250     // X[0][c] = pi.x();          X[1][c] = pi.y();          X[2][c] = 1.0;
00251     Y[0][c] = pw.x();          Y[1][c] = pw.y();          Y[2][c] = pw.z();
00252   }
00253 
00254   vpgl_ortho_procrustes op(X, Y);
00255   if (!op.compute_ok())
00256     return false;
00257 
00258   vgl_rotation_3d<double> R = op.R();
00259   vnl_matrix_fixed<double, 3, 3> rr = R.as_matrix();
00260 
00261   vnl_vector_fixed<double, 3> t = op.t();
00262 #ifdef CAMERA_DEBUG
00263   vcl_cout << "translation\n" << t << '\n'
00264            << "scale = " << op.s() << '\n'
00265            << "residual = " << op.residual_mean_sq_error() << '\n';
00266 #endif
00267 
00268   vnl_vector_fixed<double, 3> center = -(rr.transpose())*t;
00269   vgl_point_3d<double> vgl_center(center[0],center[1],center[2]);
00270   vpgl_perspective_camera<double> tcam;
00271   tcam.set_calibration(K);
00272   tcam.set_camera_center(vgl_center);
00273   tcam.set_rotation(R);
00274 
00275   //perform a final non-linear optimization
00276   vcl_vector<vgl_homg_point_3d<double> > h_world_pts;
00277   for (unsigned i = 0; i<N; ++i)
00278     h_world_pts.push_back(vgl_homg_point_3d<double>(world_pts[i]));
00279   camera = vpgl_optimize_camera::opt_orient_pos_cal(tcam, h_world_pts, image_pts, 0.00005, 20000);
00280   return true;
00281 }
00282 
00283 
00284 //: Uses the direct linear transform algorithm described in "Multiple
00285 // View Geometry in Computer Vision" to find the projection matrix,
00286 // and extracts the parameters of the camera from this projection matrix.
00287 // Requires: image_pts and world_pts are correspondences. image_pts is
00288 //  the projected form, and world_pts is the unprojected form. There
00289 //  need to be at least 6 points.
00290 // Returns: true if successful. err is filled with the two-norm of the
00291 //  projection error vector. camera is filled with the perspective
00292 //  decomposition of the projection matrix
00293 bool vpgl_perspective_camera_compute::
00294 compute_dlt (const vcl_vector< vgl_point_2d<double> >& image_pts,
00295              const vcl_vector< vgl_point_3d<double> >& world_pts,
00296              vpgl_perspective_camera<double> &camera,
00297              double &err)
00298 {
00299   if (image_pts.size() < 6) {
00300     vcl_cout<<"vpgl_perspective_camera_compute::compute needs at"
00301             << " least 6 points!" << vcl_endl;
00302     return false;
00303   }
00304   else if (image_pts.size() != world_pts.size()) {
00305     vcl_cout<<"vpgl_perspective_camera_compute::compute needs to"
00306             << " have input vectors of the same size!" << vcl_endl
00307             << "Currently, image_pts is size " << image_pts.size()
00308             << " and world_pts is size " << world_pts.size() << vcl_endl;
00309     return false;
00310   }
00311   else //Everything is good!
00312   {
00313     // Two equations for each point, one for the x's, the other for
00314     // the ys
00315     int num_eqns = 2 * image_pts.size();
00316 
00317     // A 3x4 projection matrix has 11 free vars
00318     int num_vars = 11;
00319 
00320     //---------------Set up and solve a system of linear eqns----
00321     vnl_matrix<double> A(num_eqns, num_vars);
00322     vnl_vector<double> b(num_eqns);
00323 
00324     // If the world pt is (x,y,z), and the image pt is (u,v),
00325     // A is of the form
00326     // [...]
00327     // [x, y, z, 1, 0, 0, 0, 0, -u*x, -u*y, -u*z]
00328     // [0, 0, 0, 0, x, y, z, 1, -v*x, -v*y, -v*z]
00329     // [...]
00330     //
00331     // and b is of the form [...; v; u; ...]
00332     for (unsigned int i = 0; i < image_pts.size(); ++i)
00333     {
00334       //Set the first row of A
00335       A.put(2*i, 0, world_pts[i].x());
00336       A.put(2*i, 1, world_pts[i].y());
00337       A.put(2*i, 2, world_pts[i].z());
00338       A.put(2*i, 3, 1.0);
00339 
00340       A.put(2*i, 4, 0.0);
00341       A.put(2*i, 5, 0.0);
00342       A.put(2*i, 6, 0.0);
00343       A.put(2*i, 7, 0.0);
00344 
00345       A.put(2*i, 8, -image_pts[i].x() * world_pts[i].x());
00346       A.put(2*i, 9, -image_pts[i].x() * world_pts[i].y());
00347       A.put(2*i, 10, -image_pts[i].x() * world_pts[i].z());
00348 
00349       //Set the second row of A
00350       A.put(2*i+1, 0, 0.0);
00351       A.put(2*i+1, 1, 0.0);
00352       A.put(2*i+1, 2, 0.0);
00353       A.put(2*i+1, 3, 0.0);
00354 
00355       A.put(2*i+1, 4, world_pts[i].x());
00356       A.put(2*i+1, 5, world_pts[i].y());
00357       A.put(2*i+1, 6, world_pts[i].z());
00358       A.put(2*i+1, 7, 1.0);
00359 
00360       A.put(2*i+1, 8, -image_pts[i].y() * world_pts[i].x());
00361       A.put(2*i+1, 9, -image_pts[i].y() * world_pts[i].y());
00362       A.put(2*i+1, 10, -image_pts[i].y() * world_pts[i].z());
00363 
00364       //Set the current rows of the RHS vector
00365       b[2 * i] = image_pts[i].x();
00366       b[2 * i + 1] = image_pts[i].y();
00367     }
00368 
00369     //Solve the system
00370     vnl_svd<double> svd(A);
00371     vnl_vector<double> x = svd.solve(b);
00372 
00373     //Transform the linearized version into the matrix form
00374     vnl_matrix_fixed<double, 3, 4> proj;
00375 
00376     for (int row = 0; row < 3; row++) {
00377       for (int col = 0; col < 4; col++) {
00378         if (row*4 + col < 11) {
00379           proj.put(row, col, x[row*4 + col]);
00380         }
00381       }
00382     }
00383 
00384     proj.set(2, 3, 1.0);
00385 
00386     //-------------Find the error rate--------------------
00387     err = 0;
00388     for (unsigned int i = 0; i < image_pts.size(); ++i) {
00389       vnl_vector_fixed<double, 4> world_pt;
00390       world_pt[0] = world_pts[i].x();
00391       world_pt[1] = world_pts[i].y();
00392       world_pt[2] = world_pts[i].z();
00393       world_pt[3] = 1.0;
00394 
00395       vnl_vector_fixed<double, 3> projed_pt = proj * world_pt;
00396 
00397       projed_pt[0] /= projed_pt[2];
00398       projed_pt[1] /= projed_pt[2];
00399 
00400       double dx = projed_pt[0] - image_pts[i].x();
00401       double dy = projed_pt[1] - image_pts[i].y();
00402 
00403       err += dx*dy;
00404     }
00405 
00406     //-----Get the camera------------------------------
00407     return vpgl_perspective_decomposition(proj, camera);
00408   }
00409 }
00410 
00411 //: Compute from two sets of corresponding 2D points (image and ground plane).
00412 // \param ground_pts are 2D points representing world points with Z=0
00413 // The calibration matrix of \a camera is enforced
00414 // This computation is simpler than the general case above and only requires 4 points
00415 // Put the resulting camera into \p camera, return true if successful.
00416 bool vpgl_perspective_camera_compute::
00417 compute( const vcl_vector< vgl_point_2d<double> >& image_pts,
00418          const vcl_vector< vgl_point_2d<double> >& ground_pts,
00419          vpgl_perspective_camera<double>& camera )
00420 {
00421   unsigned num_pts = ground_pts.size();
00422   if (image_pts.size()!=num_pts)
00423   {
00424     vcl_cout << "Unequal points sets in"
00425              << " vpgl_perspective_camera_compute::compute()\n";
00426     return false;
00427   }
00428   if (num_pts<4)
00429   {
00430     vcl_cout << "Need at least 4 points for"
00431              << " vpgl_perspective_camera_compute::compute()\n";
00432     return false;
00433   }
00434 
00435   vcl_vector<vgl_homg_point_2d<double> > pi, pg;
00436   for (unsigned i=0; i<num_pts; ++i) {
00437 #ifdef CAMERA_DEBUG
00438     vcl_cout << '('<<image_pts[i].x()<<", "<<image_pts[i].y()<<") -> "
00439              << '('<<ground_pts[i].x()<<", "<<ground_pts[i].y()<<')'<<vcl_endl;
00440 #endif
00441     pi.push_back(vgl_homg_point_2d<double>(image_pts[i].x(),image_pts[i].y()));
00442     pg.push_back(vgl_homg_point_2d<double>(ground_pts[i].x(),ground_pts[i].y()));
00443   }
00444 
00445   // compute a homography from the ground plane to image plane
00446   vgl_h_matrix_2d_compute_linear est_H;
00447   vnl_double_3x3 H = est_H.compute(pg,pi).get_matrix();
00448   if (vnl_det(H) > 0)
00449     H *= -1.0;
00450 
00451   // invert the effects of intrinsic parameters
00452   vnl_double_3x3 Kinv = vnl_inverse(camera.get_calibration().get_matrix());
00453   vnl_double_3x3 A(Kinv*H);
00454   // get the translation vector (up to a scale)
00455   vnl_vector_fixed<double,3> t = A.get_column(2);
00456   t.normalize();
00457 
00458   // compute the closest rotation matrix
00459   A.set_column(2, vnl_cross_3d(A.get_column(0), A.get_column(1)));
00460   vnl_svd<double> svdA(A.as_ref());
00461   vnl_double_3x3 R = svdA.U()*svdA.V().conjugate_transpose();
00462 
00463   // find the point farthest from the origin
00464   int max_idx = 0;
00465   double max_dist = 0.0;
00466   for (unsigned int i=0; i < ground_pts.size(); ++i) {
00467     double d = (ground_pts[i]-vgl_point_2d<double>(0,0)).length();
00468     if (d >= max_dist) {
00469       max_dist = d;
00470       max_idx = i;
00471     }
00472   }
00473 
00474   // compute the unknown scale
00475   vnl_vector_fixed<double,3> i1 = Kinv*vnl_double_3(image_pts[max_idx].x(),image_pts[max_idx].y(),1.0);
00476   vnl_vector_fixed<double,3> t1 = vnl_cross_3d(i1, t);
00477   vnl_vector_fixed<double,3> p1 = vnl_cross_3d(i1, R*vnl_double_3(ground_pts[max_idx].x(),ground_pts[max_idx].y(),1.0));
00478   double s = p1.magnitude()/t1.magnitude();
00479 
00480   // compute the camera center
00481   t *= s;
00482   t = -R.transpose()*t;
00483 
00484   camera.set_rotation(vgl_rotation_3d<double>(R));
00485   camera.set_camera_center(vgl_point_3d<double>(t[0],t[1],t[2]));
00486 
00487   //perform a final non-linear optimization
00488   vcl_vector<vgl_homg_point_3d<double> > h_world_pts;
00489   for (unsigned i = 0; i<num_pts; ++i) {
00490     h_world_pts.push_back(vgl_homg_point_3d<double>(ground_pts[i].x(),ground_pts[i].y(),0,1));
00491     if (camera.is_behind_camera(h_world_pts.back())) {
00492       vcl_cout << "behind camera" << vcl_endl;
00493       return false;
00494     }
00495   }
00496   camera = vpgl_optimize_camera::opt_orient_pos(camera, h_world_pts, image_pts);
00497 
00498   return true;
00499 }
00500 
00501 #endif // vpgl_camera_compute_cxx_