contrib/mul/m23d/m23d_ortho_rigid_builder.cxx
Go to the documentation of this file.
00001 #include "m23d_ortho_rigid_builder.h"
00002 //:
00003 // \file
00004 // \author Tim Cootes
00005 // \brief Implementation of the Tomasi & Kanade reconstruction algorithm
00006 
00007 #include <m23d/m23d_rotation_from_ortho_projection.h>
00008 #include <m23d/m23d_scaled_ortho_projection.h>
00009 #include <m23d/m23d_set_q_constraint.h>
00010 
00011 #include <vnl/vnl_inverse.h>
00012 #include <vnl/algo/vnl_svd.h>
00013 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00014 #include <vgl/vgl_vector_3d.h>
00015 
00016 #include <vcl_iostream.h>
00017 #include <vcl_cassert.h>
00018 #include <vcl_cstdlib.h>
00019 
00020 //: Reconstruct structure from set of 2d pts
00021 // Formulates measurement matrix P2D then calls reconstruct function above
00022 void m23d_ortho_rigid_builder::reconstruct(const vcl_vector< vcl_vector< vgl_point_2d<double> > >& pt_vec_list )
00023 {
00024   // convert pts into a matrix
00025   int nf= pt_vec_list.size();
00026   int n0= pt_vec_list[0].size();
00027   vnl_matrix<double> D(2*nf,n0);
00028   for (int i=0; i<nf; ++i)
00029   {
00030     if ( (unsigned)n0!= pt_vec_list[i].size() )
00031     {
00032       vcl_cerr<<"ERROR m23d_ortho_rigid_builder::reconstruct()\n"
00033               <<"problem with different numbers of pts\n"
00034               <<"pt_vec_list[0].size()= "<<pt_vec_list[0].size()<<'\n'
00035               <<"pt_vec_list["<<i<<"].size()= "<<pt_vec_list[i].size()<<'\n';
00036       vcl_abort();
00037     }
00038 
00039     for (int p=0; p<n0; ++p)
00040     {
00041        D(2*i,p)= pt_vec_list[i][p].x();
00042        D(2*i+1,p)= pt_vec_list[i][p].y();
00043     }
00044   }
00045 
00046   reconstruct(D);
00047 }
00048 
00049 
00050 //: Reconstruct structure of 3D points given multiple 2D views
00051 //  Data assumed to be scaled orthographic projections.
00052 //  The result is stored in the shape_3d() matrix.
00053 //  The estimated projection matrices are stored in the projections() matrix.
00054 //  \param P2D 2ns x np matrix. Rows contain alternating x's and y's from 2D shapes
00055 void m23d_ortho_rigid_builder::reconstruct(const vnl_matrix<double>& P2D)
00056 {
00057   assert(P2D.rows()%2==0);
00058   unsigned nf = P2D.rows()/2;
00059   unsigned np = P2D.cols();
00060 
00061   // Take copy of 2D points and remove CoG from each
00062   P2Dc_=P2D;
00063   cog_.resize(nf);
00064   for (unsigned i=0;i<nf;++i)
00065   {
00066     vnl_vector<double> row_x=P2D.get_row(2*i);
00067     vnl_vector<double> row_y=P2D.get_row(2*i+1);
00068     double cog_x = row_x.mean();
00069     double cog_y = row_y.mean();
00070     row_x-=cog_x;
00071     row_y-=cog_y;
00072     P2Dc_.set_row(2*i,row_x);
00073     P2Dc_.set_row(2*i+1,row_y);
00074     cog_[i]=vgl_point_2d<double>(cog_x,cog_y);
00075   }
00076 
00077   // Use SVD to get first estimate of the projection/shape matrices
00078   // These are ambiguous up to a 3x3 affine transformation.
00079   vnl_svd<double> svd(P2Dc_);
00080   P_.set_size(2*nf,3);
00081   P3D_.set_size(3,np);
00082   vnl_matrix<double> W(3,3);
00083   W.fill(0);
00084 
00085   for (unsigned i=0;i<3;++i)
00086   {
00087     P_.set_column(i, /* svd.W(i)* */ svd.U().get_column(i) );
00088     P3D_.set_row(i, svd.V().get_column(i) );
00089     W(i,i)= vcl_sqrt( svd.W(i) );
00090   }
00091 
00092 #if 0
00093   vcl_cout<<"W= "<<W<<'\n'
00094           <<"P_.rows()= "<<P_.rows()<<'\n'
00095           <<"P_.cols()= "<<P_.cols()<<'\n'
00096           <<"P3D_.rows()= "<<P3D_.rows()<<'\n'
00097           <<"P3D_.cols()= "<<P3D_.cols()<<vcl_endl;
00098 #endif
00099   P_= P_*W;
00100   P3D_= W*P3D_;
00101 #if 0
00102   vcl_cout<<"P_.extract(2,3)= "<<P_.extract(2,3)<<'\n'
00103           <<"P3D_.extract(3,5)= "<<P3D_.extract(3,5)<<vcl_endl;
00104 #endif
00105 
00106 
00107   // start of new method
00108 
00109   //using notation from orig paper
00110   //ie
00111   //i QQt i=1
00112   //j QQt j=1
00113   //i QQt j=0
00114   // find this matrix Q
00115   vnl_matrix<double> Q; // will be 3x3
00116   find_correction_matrix( Q, P_);
00117 
00118   // apply correction
00119   P_= P_*Q;
00120   P3D_=vnl_inverse(Q) * P3D_; // Q.transpose() * P3D_;
00121 
00122   // align model frame with first frame
00123   // Now need to apply an additional rotation so that the
00124   // first projection matrix is approximately the identity.
00125   vnl_matrix<double> P0=P_.extract(2,3);
00126 
00127   vcl_cout<<"P0= "<<P0<<vcl_endl;
00128 
00129   // Compute a rotation matrix for this projection
00130   vnl_matrix<double> R0=m23d_rotation_from_ortho_projection(P0);
00131 
00132   // apply rotation to mean shape
00133   P_= P_*R0.transpose();
00134 
00135   // apply reverse rotation to projections
00136   P3D_=R0* P3D_;
00137 
00138 
00139 #if 0 // commented out
00140 
00141   // older method (still works fine!)
00142   // uses fewer constraints
00143   vnl_matrix<double> Q;
00144   find_correction_matrix_alt( Q, P_);
00145 
00146   //apply correction + rotation at same time
00147 
00148   // Now need to apply an additional rotation so that the
00149   // first projection matrix is approximately the identity.
00150 
00151   vnl_matrix<double> P0=P_.extract(2,3)*Q;
00152 
00153 #if 0
00154   vcl_cout<<"P_.extract(2,3)= "<<P_.extract(2,3)<<'\n'
00155           <<"Q= "<<Q<<'\n'
00156           <<"P0= "<<P0<<vcl_endl;
00157 #endif // 0
00158 
00159   // Compute a rotation matrix for this
00160   vnl_matrix<double> R=m23d_rotation_from_ortho_projection(P0);
00161 
00162   vcl_cout<<"P0*Rt\n"<<P0*R.transpose()<<vcl_endl;
00163 
00164   // Apply inverse so that P.Q gives unit projection
00165   // ie apply rotation to the correction matrix
00166   Q=Q*R.transpose();
00167 
00168   // Apply the correction matrix
00169   P_=P_*Q;
00170   P3D_=vnl_inverse(Q) * P3D_;
00171 #endif // 0
00172 
00173 
00174   // Disambiguate the ambiguity in the sign of the z ordinates
00175   // First non-zero element should be negative.
00176   // nb mainly for benefit of test program!
00177   for (unsigned i=0;i<np;++i)
00178   {
00179     if (P3D_(2,i)<0) break;
00180     if (P3D_(2,i)>0)
00181     {
00182       // Flip sign of z elements
00183       flip_z_coords();
00184       break;
00185     }
00186   }
00187 }
00188 
00189 //: Flip z coords
00190 // may need to do this to fix z coord ambiguity
00191 void m23d_ortho_rigid_builder::flip_z_coords()
00192 {
00193   vcl_cerr<<"flipping z coords!\n";
00194 
00195   unsigned np = P3D_.cols();
00196   for (unsigned j=0;j<np;++j)
00197   {
00198     P3D_(2,j)*=-1;
00199   }
00200 
00201   unsigned nf = P_.rows();
00202   for (unsigned j=0;j<nf;++j)
00203   {
00204     P_(j,2)*=-1;
00205   }
00206 }
00207 
00208 //: find matrix Q using constraints on matrix P which must contain orthonormal projects in each (2*3) submatrix for each frame old method
00209 void m23d_ortho_rigid_builder::find_correction_matrix_alt( vnl_matrix<double>& Q,
00210                                                            const vnl_matrix<double>& P)
00211 {
00212   // Apply orthogonality constraints to estimate affine correction matrix G
00213   unsigned nq = 6;
00214   int nf= P.rows()/2;
00215   unsigned n_con = 2*nf+1;
00216 
00217   // Set up constraints on elements of L=QQt
00218   // L symmetric, encoded using elements i,j<=i in the vector q
00219   // q obtained by solving Aq=rhs
00220   vnl_matrix<double> A(n_con,nq);
00221   vnl_vector<double> rhs(n_con);
00222 
00223   unsigned c=0;
00224   // If P0 is projection matrix for first shape, arrange that P0.P0'=I
00225   vnl_vector<double> px0 = P.get_row(0);
00226   vnl_vector<double> py0 = P.get_row(1);
00227   m23d_set_q_constraint1(A,rhs,c,px0,px0,1); ++c;
00228   m23d_set_q_constraint1(A,rhs,c,py0,py0,1); ++c;
00229   m23d_set_q_constraint1(A,rhs,c,px0,py0,0); ++c;
00230 
00231   // These constraints aim to impose orthogonality on rows of projection
00232   // matrices.
00233   for (unsigned i=1;i<(unsigned)nf;++i)
00234   {
00235     vnl_vector<double> pxi = P.get_row(2*i);
00236     vnl_vector<double> pyi = P.get_row(2*i+1);
00237     m23d_set_q_constraint2(A,rhs,c,pxi,pyi); ++c;
00238     m23d_set_q_constraint1(A,rhs,c,pxi,pyi,0); ++c;
00239   }
00240 
00241   assert(c==n_con);
00242 
00243   vnl_svd<double> svd_A(A);
00244   vcl_cout<<"Singular values of constraints: "<<svd_A.W().diagonal()<<vcl_endl;
00245 
00246   vnl_vector<double> q = svd_A.solve(rhs);
00247   vcl_cout<<"RMS Error in q = "<<(A*q-rhs).rms()<<vcl_endl;
00248 
00249   vnl_matrix<double> L(3,3);
00250   c=0;
00251   for (unsigned i=0;i<3;++i)
00252     for (unsigned j=0;j<=i;++j,++c)
00253       L(i,j)=L(j,i)=q[c];
00254 #if 0
00255   vcl_cout<<"L= "<<L<<vcl_endl;
00256 #endif
00257   // If G is the 3 x 3 correction matrix, then G.G'=Q
00258   // Use cholesky decomposition to compute G
00259   vnl_symmetric_eigensystem<double> eig(L);
00260 #if 0
00261   vnl_matrix<double> Q(3,3);
00262 #endif
00263   Q.set_size(3,3);
00264   vcl_cout<<"Eigenvalues: "<<eig.D.diagonal()<<vcl_endl;
00265   for (unsigned i=0;i<3;++i)
00266   {
00267     // Deal with case where Q is not pos-definite (ie has -ive eigenvalues).
00268     //double s = 0.00001;
00269     //if (eig.get_eigenvalue(2-i)>0.0)
00270     //  s = vcl_sqrt(eig.get_eigenvalue(2-i));
00271 
00272     // nb critical bit making sure Q is pos def
00273     double s= vcl_sqrt(  vcl_fabs(eig.get_eigenvalue(2-i)) );
00274     Q.set_column(i,s*eig.get_eigenvector(2-i));
00275   }
00276 
00277 #if 0
00278   vcl_cout<<"Q= "<<Q<<vcl_endl;
00279 #endif
00280 }
00281 
00282 
00283 //: find matrix Q using constraints on matrix P which must contain orthonormal projects in each (2*3) submatrix for each frame
00284 void m23d_ortho_rigid_builder::find_correction_matrix( vnl_matrix<double>& Q,
00285                                                        const vnl_matrix<double>& P)
00286 {
00287   int nf= P.rows()/2;
00288 
00289   // build ortho constraint matrix G
00290   // where GI=c
00291   // G= 3nf * 6
00292   // I= 6 * 1
00293   // c= 3nf * 1
00294 
00295   // then
00296   //L = [I(1) I(2) I(3);
00297   //     I(2) I(4) I(5);
00298   //      I(3) I(5) I(6)];
00299   // and L=QQt
00300 
00301   // based on
00302   //i QQt i=1
00303   //j QQt j=1
00304   //i QQt j=0
00305   vnl_matrix<double> G(nf*3,6);
00306   vnl_vector<double> con_vec(nf*3);
00307   for (int f=0; f<nf; ++f)
00308   {
00309     // 3 constraints per frame
00310     vnl_vector<double> i_vec= P.get_row(f*2);
00311     vnl_vector<double> j_vec= P.get_row(f*2+1);
00312 #if 0
00313     vcl_cout<<"f="<<f<<vcl_endl
00314             <<"i_vec= "<<i_vec<<vcl_endl
00315             <<"j_vec= "<<j_vec<<vcl_endl;
00316 #endif
00317     //i QQt i=1
00318     int r0= f*3;
00319     vnl_vector<double> c0_vec;
00320     compute_one_row_of_constraints( c0_vec, i_vec, i_vec );
00321     G.set_row(r0,c0_vec);
00322     con_vec(r0)=1;
00323 
00324     //j QQt j=1
00325     int r1= f*3+1;
00326     vnl_vector<double> c1_vec;
00327     compute_one_row_of_constraints( c1_vec, j_vec, j_vec );
00328     G.set_row(r1,c1_vec);
00329     con_vec(r1)=1;
00330 
00331     //i QQt j=0
00332     int r2= f*3+2;
00333     vnl_vector<double> c2_vec;
00334     compute_one_row_of_constraints( c2_vec, i_vec, j_vec );
00335     G.set_row(r2,c2_vec);
00336     con_vec(r2)=0;
00337   }
00338 
00339   // solve for I
00340   //GI=con
00341   vnl_svd<double> svd_G(G);
00342   vnl_vector<double> I= svd_G.pinverse()*con_vec;
00343 
00344   //3x3 matrix L
00345   vnl_matrix<double> L(3,3);
00346   L(0,0)= I(0);
00347   L(0,1)= I(1);
00348   L(0,2)= I(2);
00349   L(1,0)= I(1);
00350   L(1,1)= I(3);
00351   L(1,2)= I(4);
00352   L(2,0)= I(2);
00353   L(2,1)= I(4);
00354   L(2,2)= I(5);
00355 
00356   // solve QQt= L
00357   // use symmetric eigen decomposition L= V*D*Vt
00358 #if 0
00359   vcl_cout<<"L= "<<L<<vcl_endl;
00360 #endif
00361 
00362   vnl_symmetric_eigensystem<double> eig(L);
00363 #if 0
00364   vcl_cout<<"eig.V= "<<eig.V<<vcl_endl
00365           <<"eig.D= "<<eig.D<<vcl_endl;
00366   vnl_matrix<double> Q(3,3);
00367 #endif
00368   Q.set_size(3,3);
00369   vcl_cout<<"Eigenvalues: "<<eig.D.diagonal()<<vcl_endl;
00370   for (unsigned i=0;i<3;++i)
00371   {
00372     // Deal with case where Q is not pos-definite (ie has -ive eigenvalues).
00373 #if 0
00374     double s=0.00001; // = vcl_sqrt(0.00001);
00375     if (eig.get_eigenvalue(2-i)>0.0)
00376     {
00377       s = vcl_sqrt(eig.get_eigenvalue(2-i));
00378     }
00379 #endif
00380 
00381     // nb critical bit making sure Q is pos def
00382     double s= vcl_sqrt(  vcl_fabs(eig.get_eigenvalue(2-i)) );
00383     Q.set_column(i,s*eig.get_eigenvector(2-i) );
00384   }
00385 #if 0
00386   vcl_cout<<"Q= "<<Q<<vcl_endl;
00387 #endif
00388 }
00389 
00390 //: find matrix Q using constraints on matrix P which must contain from two rows of a projection matrix (a+b) find six constraints used to compute (QQt) symmetric matrix
00391 void m23d_ortho_rigid_builder::compute_one_row_of_constraints( vnl_vector<double>& c,
00392                                                                const vnl_vector<double>& a,
00393                                                                const vnl_vector<double>& b)
00394 {
00395   c.set_size(6);
00396   c(0)=a(0)*b(0);
00397   c(1)=a(1)*b(0)+a(0)*b(1);
00398   c(2)=a(2)*b(0)+a(0)*b(2);
00399   c(3)=a(1)*b(1);
00400   c(4)=a(2)*b(1)+a(1)*b(2);
00401   c(5)=a(2)*b(2);
00402 }
00403 
00404 
00405 //: Modify projection matrices so they are scaled orthographic projections
00406 //  $ P = s(I|0)*R $
00407 void m23d_ortho_rigid_builder::make_pure_projections()
00408 {
00409   unsigned nf = P_.rows()/2;
00410 
00411   // Force first to be identity
00412   P_(0,0)=1; P_(0,1)=0; P_(0,2)=0;
00413   P_(1,0)=0; P_(1,1)=1; P_(1,2)=0;
00414 
00415   // Replace each subsequent 2x3 projection with the
00416   // closest pure scaled orthogonal projection.
00417   for (unsigned i=1;i<nf;++i)
00418   {
00419     vnl_matrix<double> newPi = m23d_scaled_ortho_projection(P_.extract(2,3,2*i,0));
00420     P_.update(newPi,2*i,0);
00421   }
00422 }
00423 
00424 //: Refine estimates of projection and structure
00425 void m23d_ortho_rigid_builder::refine()
00426 {
00427   make_pure_projections();
00428   // Re-estimate the 3D shape by solving the linear equation
00429   vnl_svd<double> svd(P_);
00430   P3D_ = svd.pinverse() * P2Dc_;
00431   // Slightly less stable than backsub, but what the heck
00432   //(you live on the edge Tim!-dac)
00433 }
00434 
00435 
00436 //: Return 3d rigid pts
00437 // I.e., aligned with first frame
00438 void m23d_ortho_rigid_builder::mat_to_3d_pts(vcl_vector< vgl_point_3d<double> >& pt_vec,
00439                                              const vnl_matrix<double>& M) const
00440 {
00441   // get pts out of P3D_ matrix
00442   if ( M.rows() != 3 )
00443   {
00444     vcl_cerr<<"ERROR m23d_ortho_rigid_builder::mat_to_3d_pts()\n"
00445             <<"problem with size of matrix\n"
00446             <<"M.rows()= "<<M.rows()<<'\n'
00447             <<"M.cols()= "<<M.cols()<<'\n';
00448     vcl_abort();
00449   }
00450 
00451   int np= M.cols();
00452   pt_vec.resize(np);
00453   for (int i=0; i<np; ++i)
00454   {
00455     pt_vec[i].set( M(0,i), M(1,i), M(2,i) );
00456   }
00457 }
00458 
00459 //: Get back 3d pts rotated and shifted for each frame
00460 void m23d_ortho_rigid_builder::recon_shapes(vcl_vector< vcl_vector< vgl_point_3d<double> > >& pt_vec_list ) const
00461 {
00462   if (P_.rows() < 2 || P_.cols() != 3 )
00463   {
00464     vcl_cerr<<"ERROR m23d_ortho_rigid_builder::recon_shapes()\n"
00465             <<"problem with size of P_\n"
00466             <<"P_.rows()= "<<P_.rows()<<'\n'
00467             <<"P_.cols()= "<<P_.cols()<<'\n';
00468     vcl_abort();
00469   }
00470 
00471   unsigned nf= P_.rows()/2;
00472   pt_vec_list.resize(nf);
00473   for (unsigned i=0;i<nf;++i)
00474   {
00475     //vnl_matrix<double> newPi = m23d_scaled_ortho_projection(P_.extract(2,3,2*i,0));
00476     //P_.update(newPi,2*i,0);
00477     vnl_matrix<double> P0= P_.extract(2,3,2*i,0);
00478 
00479     // Compute a rotation matrix for this projection
00480     // then update shape and
00481     vnl_matrix<double> R0=m23d_rotation_from_ortho_projection(P0);
00482 
00483     // apply rotation to base shape
00484     vnl_matrix<double> rot_pts_mat=R0* P3D_;
00485     mat_to_3d_pts( pt_vec_list[i], rot_pts_mat );
00486 
00487     // apply cog_ translation to each pt vector
00488     int np= pt_vec_list[i].size();
00489     vgl_vector_3d<double> tran_vec( cog_[i].x(), cog_[i].y(), 0 );
00490     for (int p=0; p<np; ++p)
00491     {
00492       pt_vec_list[i][p]= pt_vec_list[i][p] + tran_vec;
00493     }
00494   }
00495 }
00496 
00497 
00498 //: Get back 3d pts rotated and shifted for each frame
00499 void m23d_ortho_rigid_builder::get_shape_3d_pts( vcl_vector< vgl_point_3d<double> >& pts ) const
00500 {
00501   if (P_.rows() < 2 || P_.cols() != 3 )
00502   {
00503     vcl_cerr<<"ERROR m23d_ortho_rigid_builder::get_shape_3d_pts()\n"
00504             <<"problem with size of P_\n"
00505             <<"P_.rows()= "<<P_.rows()<<'\n'
00506             <<"P_.cols()= "<<P_.cols()<<'\n';
00507     vcl_abort();
00508   }
00509 
00510 
00511   //vnl_matrix<double> newPi = m23d_scaled_ortho_projection(P_.extract(2,3,2*i,0));
00512   //P_.update(newPi,2*i,0);
00513   vnl_matrix<double> P0= P_.extract(2,3);
00514 
00515   // Compute a rotation matrix for this projection
00516   // then update shape and
00517   vnl_matrix<double> R0=m23d_rotation_from_ortho_projection(P0);
00518 
00519   // apply rotation to base shape
00520   vnl_matrix<double> rot_pts_mat=R0* P3D_;
00521   mat_to_3d_pts( pts, rot_pts_mat );
00522 
00523 
00524   int np= pts.size();
00525   vgl_vector_3d<double> tran_vec( cog_[0].x(), cog_[0].y(), 0 );
00526   for (int p=0; p<np; ++p)
00527   {
00528     pts[p]= pts[p] + tran_vec;
00529   }
00530 }
00531