contrib/mul/m23d/m23d_ortho_flexible_builder.cxx
Go to the documentation of this file.
00001 #include "m23d_ortho_flexible_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 #include <m23d/m23d_correction_matrix_error.h>
00011 #include <m23d/m23d_select_basis_views.h>
00012 #include <m23d/m23d_pure_ortho_projection.h>
00013 
00014 #include <vnl/algo/vnl_svd.h>
00015 #include <vnl/algo/vnl_symmetric_eigensystem.h>
00016 #include <vnl/algo/vnl_levenberg_marquardt.h>
00017 #include <vgl/vgl_vector_3d.h>
00018 
00019 #include <vcl_iostream.h>
00020 #include <vcl_algorithm.h>
00021 #include <vcl_cstdlib.h>  // abort()
00022 #include <vcl_cassert.h>
00023 
00024 
00025 //: Reconstruct structure of 3D points given multiple 2D views
00026 //  Data assumed to be scaled orthographic projections
00027 //  The result is stored in the shape_3d() matrix.
00028 //  The estimated projection matrices are stored in the projections() matrix
00029 //  \param P2D 2ns x np matrix. Rows contain alternating x's and y's from 2D shapes
00030 void m23d_ortho_flexible_builder::reconstruct_with_first_as_basis(const vnl_matrix<double>& P2D,
00031                                                                   unsigned n_modes)
00032 {
00033   partial_reconstruct(P2D,n_modes);
00034   refine();
00035 }
00036 
00037 //: Swap the n rows beginning at i with those at j
00038 inline void m23d_swap_rows(vnl_matrix<double>& M,
00039                            unsigned i, unsigned j, unsigned n)
00040 {
00041   vnl_matrix<double> Mi = M.extract(n,M.cols(),i,0);
00042   M.update(M.extract(n,M.cols(),j,0),i,0);  // Copy rows at j to i
00043   M.update(Mi,j,0);                         // Copy rows from i to j
00044 }
00045 
00046 
00047 //: Reconstruct structure from set of 2d pts
00048 // Formulates measurement matrix P2D then calls reconstruct() function
00049 void m23d_ortho_flexible_builder::reconstruct(
00050    const vcl_vector< vcl_vector< vgl_point_2d<double> > >& pt_vec_list,
00051                             const unsigned& n_modes )
00052 {
00053   // convert pts into a matrix
00054   int nf= pt_vec_list.size();
00055   int n0= pt_vec_list[0].size();
00056   vnl_matrix<double> D(2*nf,n0);
00057   for (int i=0; i<nf; ++i)
00058   {
00059     if ( (unsigned)n0!= pt_vec_list[i].size() )
00060     {
00061       vcl_cerr<< "ERROR m23d_ortho_rigid_builder::reconstruct()\n"
00062               << "problem with different numbers of pts\n"
00063               << "pt_vec_list[0].size()= " << pt_vec_list[0].size() << '\n'
00064               << "pt_vec_list[" << i << "].size()= " << pt_vec_list[i].size()
00065               << vcl_endl;
00066       vcl_abort();
00067     }
00068 
00069     for (int p=0; p<n0; ++p)
00070     {
00071       D(2*i,p)= pt_vec_list[i][p].x();
00072       D(2*i+1,p)= pt_vec_list[i][p].y();
00073     }
00074   }
00075 
00076   reconstruct(D, n_modes);
00077 }
00078 
00079 
00080 //: Reconstruct structure of 3D points given multiple 2D views
00081 //  Data assumed to be scaled orthographic projections
00082 //  The result is stored in the shape_3d() matrix.
00083 //  The estimated projection matrices are stored in the projections() matrix
00084 //  Automatically select views which form a good basis.
00085 //  \param P2D 2ns x np matrix. Rows contain alternating x's and y's from 2D shapes
00086 void m23d_ortho_flexible_builder::reconstruct(const vnl_matrix<double>& P2D, unsigned n_modes)
00087 {
00088   assert(P2D.rows()%2==0);
00089 
00090   set_view_data(P2D);
00091 
00092   vcl_vector<unsigned> basis = m23d_select_basis_views(P2Dc_,n_modes,1000);
00093 
00094   // Swap in basis here
00095   for (unsigned i=0;i<=n_modes;++i)
00096     m23d_swap_rows(P2Dc_,2*i,2*basis[i],2);
00097 
00098   initial_decomposition(n_modes);
00099 
00100   vnl_matrix<double> G;
00101   compute_correction(P_,G);
00102 
00103   // Apply the correction matrix
00104   P_=P_*G;
00105   vnl_svd<double> G_svd(G);
00106   P3D_=G_svd.inverse() * P3D_;
00107 
00108   disambiguate_z();
00109   correct_coord_frame(P_,P3D_);
00110   refine();
00111 
00112   // Swap out basis here, and re-order derived matrices
00113   for (int i=n_modes;i>=0;--i)
00114   {
00115     m23d_swap_rows(P_,2*i,2*basis[i],2);
00116     m23d_swap_rows(P2Dc_,2*i,2*basis[i],2);
00117     m23d_swap_rows(pure_P_,2*i,2*basis[i],2);
00118     m23d_swap_rows(coeffs_,i,basis[i],1);
00119   }
00120 }
00121 
00122 //: Take copy of 2D points and remove CoG from each
00123 void m23d_ortho_flexible_builder::set_view_data(const vnl_matrix<double>& P2D)
00124 {
00125   assert(P2D.rows()%2==0);
00126   unsigned ns = P2D.rows()/2;
00127 
00128   // Take copy of 2D points and remove CoG from each
00129   P2Dc_=P2D;
00130   cog_.resize(ns);
00131   for (unsigned i=0;i<ns;++i)
00132   {
00133     vnl_vector<double> row_x=P2D.get_row(2*i);
00134     vnl_vector<double> row_y=P2D.get_row(2*i+1);
00135     double cog_x = row_x.mean();
00136     double cog_y = row_y.mean();
00137     row_x-=cog_x;
00138     row_y-=cog_y;
00139     P2Dc_.set_row(2*i,row_x);
00140     P2Dc_.set_row(2*i+1,row_y);
00141     cog_[i]=vgl_point_2d<double>(cog_x,cog_y);
00142   }
00143 }
00144 
00145 //: Decompose centred view data to get initial estimate of shape/projection
00146 //  Uncertain up to an affine transformation
00147 void m23d_ortho_flexible_builder::initial_decomposition(unsigned n_modes)
00148 {
00149   unsigned ns = P2Dc_.rows()/2;
00150   unsigned np = P2Dc_.cols();
00151 
00152   // Use SVD to get first estimate of the projection/shape matrices
00153   // These are ambiguous up to a txt affine transformation.
00154   vnl_svd<double> svd(P2Dc_);
00155   unsigned t=3*(1+n_modes);
00156   P_.set_size(2*ns,t);
00157   P3D_.set_size(t,np);
00158   for (unsigned i=0;i<t;++i)
00159   {
00160     P_.set_column(i,svd.W(i)*svd.U().get_column(i));
00161     P3D_.set_row(i,svd.V().get_column(i));
00162   }
00163 
00164   vcl_cout<<"Initial reconstruction error: "<<(P_*P3D_-P2Dc_).rms()<<vcl_endl;
00165 }
00166 
00167 //: Disambiguate the ambiguity in the sign of the z ordinates
00168 // First non-zero element should be negative.
00169 void m23d_ortho_flexible_builder::disambiguate_z()
00170 {
00171   unsigned ns = P2Dc_.rows()/2;
00172   unsigned np = P2Dc_.cols();
00173 
00174   for (unsigned i=0;i<np;++i)
00175   {
00176     if (P3D_(2,i)<0) break;
00177     if (P3D_(2,i)>0)
00178     {
00179       // Flip sign of z elements
00180       for (unsigned j=0;j<np;++j) P3D_(2,j)*=-1;
00181       for (unsigned j=0;j<2*ns;++j) P_(j,2)*=-1;
00182       break;
00183     }
00184   }
00185 }
00186 
00187 
00188 //: Reconstruct structure of 3D points given multiple 2D views
00189 //  Data assumed to be scaled orthographic projections
00190 //  The result is stored in the shape_3d() matrix.
00191 //  The estimated projection matrices are stored in the projections() matrix
00192 //  \param P2D 2ns x np matrix. Rows contain alternating x's and y's from 2D shapes
00193 void m23d_ortho_flexible_builder::partial_reconstruct(const vnl_matrix<double>& P2D,
00194                                                       unsigned n_modes)
00195 {
00196   assert(P2D.rows()%2==0);
00197 
00198   set_view_data(P2D);
00199   initial_decomposition(n_modes);
00200 
00201   vnl_matrix<double> G;
00202   compute_correction(P_,G);
00203 
00204   // Apply the correction matrix
00205   P_=P_*G;
00206   vnl_svd<double> G_svd(G);
00207   P3D_=G_svd.inverse() * P3D_;
00208 
00209   disambiguate_z();
00210   correct_coord_frame(P_,P3D_);
00211 }
00212 
00213 //: Fill a symmetric matrix with elements from v
00214 static vnl_matrix<double> sym_matrix_from_vec(const vnl_vector<double>& v, unsigned n)
00215 {
00216   vnl_matrix<double> S(n,n);
00217   unsigned k=0;
00218    for (unsigned i=0;i<n;++i)
00219      for (unsigned j=0;j<=i;++j,++k)
00220        S(i,j)=S(j,i)=v[k];
00221   return S;
00222 }
00223 
00224 //: Solve for correction matrix for zero mode case
00225 static vnl_matrix<double> am_solve_for_G0(const vnl_matrix<double>& A,
00226                                           const vnl_vector<double>& rhs)
00227 {
00228   unsigned n=3;
00229   vnl_svd<double> svd(A);
00230   vnl_vector<double> q0 = svd.solve(rhs);
00231   vnl_matrix<double> Q0=sym_matrix_from_vec(q0,n);
00232 
00233   // If Gk is the t x 3 matrix, the k-th triplet of columns of G,
00234   // then Gk.Gk'=Q0
00235   // Use eigen decomposition to compute Gk
00236   vnl_symmetric_eigensystem<double> eig(Q0);
00237   vnl_matrix<double> Gk(n,3);
00238   for (unsigned i=0;i<3;++i)
00239   {
00240     //Gk.set_column(i,vcl_sqrt(eig.get_eigenvalue(n-1-i))
00241     //                        *eig.get_eigenvector(n-1-i));
00242 
00243 
00244     // nb critical bit making sure Gk is pos def
00245     double s= vcl_sqrt(  vcl_fabs(eig.get_eigenvalue(n-1-i)) );
00246     Gk.set_column(i, s*eig.get_eigenvector(n-1-i) );
00247   }
00248 
00249   return Gk;
00250 }
00251 
00252 static vnl_matrix<double> am_solve_for_Gk(const vnl_matrix<double>& A,
00253                                           const vnl_vector<double>& rhs,
00254                                           unsigned m, unsigned k)
00255 {
00256   m23d_correction_matrix_error err_fn(A,rhs,m,k);
00257 
00258   vnl_levenberg_marquardt LM(err_fn);
00259 
00260   // Generate g for identity for k-th column.
00261   vnl_vector<double> g(9*(m+1),0.0);
00262   for (unsigned i=0;i<3;++i) g[9*k+4*i]=1.0;
00263 
00264   if (!LM.minimize_using_gradient(g))
00265   {
00266     vcl_cout<<"LM failed!!" << vcl_endl;
00267     vcl_abort();
00268   }
00269 
00270   vcl_cout<<"am_solve_for_Gk (k="<<k<<") RMS="<<err_fn.rms(g)<<vcl_endl;
00271 
00272   // Reshape g into 3(m+1) x 3 matrix
00273   return vnl_matrix<double>(g.data_block(),3*(m+1),3);
00274 }
00275 
00276 #if 0 // refinement of am_solve_for_Gk()
00277 
00278 static vnl_vector<double> vec_from_sym_matrix(const vnl_matrix<double>& S)
00279 {
00280   unsigned n = S.rows();
00281   vnl_vector<double> v((n*(n+1))/2);
00282   unsigned k=0;
00283    for (unsigned i=0;i<n;++i)
00284      for (unsigned j=0;j<=i;++j,++k)
00285        v[k]=S(i,j);
00286   return v;
00287 }
00288 
00289 static vnl_matrix<double> am_solve_for_Gk(const vnl_matrix<double>& A,
00290                                           const vnl_vector<double>& rhs,
00291                                           unsigned m, unsigned k)
00292 {
00293   // Now refine the solution   *** Not sure that this helps ***
00294   vnl_diag_matrix<double> W(nq);
00295   double w0=0.01*svd.W(0);
00296   for (unsigned i=0;i<nq;++i)
00297   {
00298     if (svd.W(i)>w0) W[i]=0.0;
00299     else             W[i]=1.0; //w0*w0/(w0*w0+svd.W(i)*svd.W(i));
00300   }
00301   vnl_matrix<double> K=svd.V()*W*svd.V().transpose();
00302   for (unsigned k=0;k<3;++k)
00303   {
00304     vnl_matrix<double> Q1=Gk*Gk.transpose();
00305     vnl_vector<double> q1=vec_from_sym_matrix(Q1);
00306     vcl_cout<<"Error for q1 = "<<(A*q1-rhs).rms()<<vcl_endl;
00307     vnl_vector<double> q2=q0+K*(q1-q0);
00308 
00309     Q0=sym_matrix_from_vec(q2,n);
00310     vnl_symmetric_eigensystem<double> eig2(Q0);
00311     vcl_cout<<"Eigenvalues: "<<eig2.D.diagonal()<<vcl_endl;
00312     for (unsigned i=0;i<3;++i)
00313     {
00314       Gk.set_column(i,vcl_sqrt(eig2.get_eigenvalue(n-1-i))
00315                               *eig2.get_eigenvector(n-1-i));
00316     }
00317   }
00318   vcl_cout<<"Gk*Gk'="<<Gk*Gk.transpose()<<vcl_endl;
00319 
00320   return Gk;
00321 }
00322 #endif // 0
00323 
00324 static void compute_Gk(const vnl_matrix<double> & M, unsigned k,
00325                        vnl_matrix<double>& Gk)
00326 {
00327   assert(M.cols()>=3);
00328   unsigned m = M.cols()/3 -1;
00329 
00330   vnl_matrix<double> A;
00331   vnl_vector<double> rhs;
00332   m23d_set_q_constraints(M,k,A,rhs);
00333 
00334   if (m==0)
00335     Gk=am_solve_for_G0(A,rhs);
00336   else
00337   {
00338     Gk=am_solve_for_Gk(A,rhs,m,k);
00339   }
00340 
00341   if (k==0)
00342   {
00343     // Compute extra rotation so that the projection matrix for the
00344     // first shape is a scaled identity projection.
00345     vnl_matrix<double> M0=M.extract(2,3*(1+m))*Gk;
00346 
00347     // Compute a rotation matrix for this
00348     vnl_matrix<double> R=m23d_rotation_from_ortho_projection(M0);
00349 
00350     // Apply inverse so that P.G gives unit projection
00351     Gk=Gk*R.transpose();
00352   }
00353 }
00354 
00355 // Compute correction matrix G (t x t) s.t.  M=M1.G and B=(inv(G)).B1
00356 void m23d_ortho_flexible_builder::compute_correction(
00357                            const vnl_matrix<double>& M1,
00358                            vnl_matrix<double>& G)
00359 {
00360   unsigned t = M1.cols();
00361   unsigned n_modes=t/3-1;
00362   G.set_size(t,t);
00363   for (unsigned k=0;k<=n_modes;++k)
00364   {
00365     vnl_matrix<double> Gk;
00366     compute_Gk(M1,k,Gk);
00367     G.update(Gk,0,k*3);
00368   }
00369 }
00370 
00371 #if 0 // unused static function
00372 static double min_row_scale(const vnl_matrix<double>& M)
00373 {
00374   double r1=0,r2=0;
00375   for (unsigned j=0;j<3;++j)
00376   {
00377     r1+=M(0,j)*M(0,j);
00378     r2+=M(1,j)*M(1,j);
00379   }
00380   return vcl_sqrt(vcl_min(r1,r2));
00381 }
00382 #endif // 0
00383 
00384 // Apply rotation matrices to each 3 columns of M (and inverse to rows of B)
00385 // Matrix selected so that projection matrices in each 3 cols have same
00386 // effective rotation.
00387 void m23d_ortho_flexible_builder::correct_coord_frame(vnl_matrix<double>& M,
00388                                                       vnl_matrix<double>& B)
00389 {
00390   unsigned m = M.cols()/3-1;
00391   unsigned ns = M.rows()/2;
00392   unsigned np = B.cols();
00393   // Compute rotation matrix required to arrange that first 2x3 is unit projection
00394   vnl_matrix<double> U0 = m23d_rotation_from_ortho_projection(M.extract(2,3));
00395   M.update(M.extract(2*ns,3) * U0.transpose());
00396   B.update(U0*B.extract(3,np));
00397 
00398   for (unsigned j=1;j<=m;++j)
00399   {
00400     // Due to the ambiguity in the 3rd row of the rotation
00401     // matrix returned by m23d_rotation_from_ortho_projection,
00402     // and the inherent sign ambiguity of the whole matrix,
00403     // there are four possible rotations for each row.
00404     // Compute all rotations, then select the consistent ones.
00405 
00406     vcl_vector<vnl_matrix<double> > U;
00407     for (unsigned i=0;i<ns;++i)
00408     {
00409       vnl_matrix<double> Mi0=M.extract(2,3,2*i,0);
00410       vnl_matrix<double> Mij=M.extract(2,3,2*i,3*j);
00411 #if 0  // **** TEST
00412       if (Mij(0,0)/Mi0(0,0)<0) Mij*=-1;
00413 #endif
00414       vnl_matrix<double> Ui0 = m23d_rotation_from_ortho_projection(Mi0);
00415       vnl_matrix<double> Uij = m23d_rotation_from_ortho_projection(Mij);
00416 #if 0 // DEBUG
00417       vcl_cout<<Mij*Uij.transpose()<<vcl_endl;
00418 #endif
00419       U.push_back(Uij.transpose()*Ui0);
00420 #if 0 // DEBUG
00421       vcl_cout<<"j="<<j<<" i="<<i<<") C1:"<<vcl_endl
00422               <<Uij.transpose()*Ui0<<vcl_endl;
00423 #endif
00424       // Swap sign of 3rd row of Uij
00425       for (unsigned k=0;k<3;++k) Uij(2,k)*=-1;
00426       U.push_back(Uij.transpose()*Ui0);
00427 #if 0 // DEBUG
00428       vcl_cout<<"j="<<j<<" i="<<i<<") C2:"<<vcl_endl
00429               <<Uij.transpose()*Ui0<<vcl_endl
00430               <<"Rel wts: "<<Mij(0,0)/Mi0(0,0)<<vcl_endl;
00431 #endif
00432     }
00433 
00434     // Now find the mean and variance for a variety of possible
00435     // rotation options.
00436     // For each one, select the closest of the four possible matrices
00437     // for each shape
00438     vnl_matrix<double> best_meanU;
00439     double best_rms = 1e99;
00440 
00441     for (unsigned i=0;i<vcl_min(2*ns,40u);++i)
00442     {
00443       vnl_matrix<double> baseR = U[i];
00444       vnl_matrix<double> sumU(3,3,0.0), bestU;
00445       double sum_rms=0.0;
00446 
00447       for (unsigned k=0;k<ns;++k)
00448       {
00449         double d_min=(U[2*k]-baseR).rms();
00450         bestU = U[2*k];
00451         double d1=(U[2*k]+baseR).rms();
00452         if (d1<d_min) { d_min=d1; bestU=-U[2*k]; }
00453         double d2=(U[2*k+1]-baseR).rms();
00454         if (d2<d_min) { d_min=d2; bestU=U[2*k+1]; }
00455         double d3=(U[2*k+1]+baseR).rms();
00456         if (d3<d_min) { d_min=d3; bestU=-U[2*k+1]; }
00457 
00458         sumU+=bestU;
00459         sum_rms+=d_min;
00460       }
00461       sumU/=ns;
00462       sum_rms/=ns;
00463       if (i==0 || sum_rms<best_rms)
00464       {
00465         best_meanU=sumU;
00466         best_rms=sum_rms;
00467       }
00468     }
00469     // Remove scaling effects introduced by averaging
00470     vnl_svd<double> Usvd(best_meanU);
00471     vnl_matrix<double> Uj = Usvd.U()*Usvd.V().transpose();
00472     M.update(M.extract(2*ns,3,0,3*j) * Uj,0,3*j);
00473     B.update(Uj.transpose()*B.extract(3,np,3*j,0),3*j,0);
00474   }
00475 }
00476 
00477 //: Modify projection matrices so they are scaled orthographic projections
00478 //  P = s(I|0)*R
00479 void m23d_ortho_flexible_builder::make_pure_projections()
00480 {
00481   unsigned ns = P_.rows()/2;
00482   unsigned m = P_.cols()/3-1;
00483 
00484   pure_P_.set_size(2*ns,3);
00485   coeffs_.set_size(ns,m+1);
00486 
00487   // Generate identity projection
00488   vnl_matrix<double> PI(2,3),Pzero(2,3,0.0);
00489   PI(0,0)=1; PI(0,1)=0; PI(0,2)=0;
00490   PI(1,0)=0; PI(1,1)=1; PI(1,2)=0;
00491 
00492   // Force first row to be the identity
00493   P_.update(PI,0,0);
00494   pure_P_.update(PI,0,0);
00495   coeffs_(0,0)=1.0;
00496   for (unsigned i=1;i<=m;++i)
00497   {
00498     P_.update(Pzero,0,3*i);
00499     coeffs_(0,i)=0.0;
00500   }
00501 
00502   // Force next m rows to only contain one non-zero pure projection
00503   for (unsigned i=1;i<=m;++i)
00504     for (unsigned j=0;j<=m;++j)
00505     {
00506       if (i==j)
00507       {
00508         vnl_matrix<double> P0=m23d_pure_ortho_projection(P_.extract(2,3,2*i,3*j));
00509         P_.update(P0,2*i,3*j);
00510         pure_P_.update(P0,2*i,0);
00511         coeffs_(i,j)=1.0;
00512       }
00513       else
00514       {
00515         P_.update(Pzero,2*i,3*j);
00516         coeffs_(i,j)=0.0;
00517       }
00518     }
00519 
00520   // Replace each subsequent 2x3 projection with the
00521   // closest pure scaled orthogonal projection.
00522   for (unsigned i=m+1;i<ns;++i)
00523   {
00524     // Generate a (m+1) x 6 matrix, each row containing one projection matrix
00525     vnl_matrix<double> Mi(m+1,6);
00526     for (unsigned j=0;j<=m;++j)
00527     {
00528       Mi(j,0)=P_(2*i,3*j);   Mi(j,1)=P_(2*i,3*j+1);   Mi(j,2)=P_(2*i,3*j+2);
00529       Mi(j,3)=P_(2*i+1,3*j); Mi(j,4)=P_(2*i+1,3*j+1); Mi(j,5)=P_(2*i+1,3*j+2);
00530     }
00531     // Mi should be a'p where a are coeffs and p is mean projection
00532     // |p| should be sqrt(2) for a pure projection (no scaling)
00533     vnl_svd<double> Mi_svd(Mi);
00534     vnl_vector<double> p=Mi_svd.V().get_column(0)*vcl_sqrt(2.0);
00535 
00536     vnl_matrix<double> Pi0 = vnl_matrix<double>(p.data_block(),2,3);
00537     vnl_matrix<double> Pi = m23d_pure_ortho_projection(Pi0);
00538     vnl_vector<double> pi(Pi.data_block(),6);
00539 
00540     // Estimate weight for each basis vector
00541     // Each row of Mi should be a[j]*pi'
00542     // Since pi.pi = 2  (each 3 elements is a unit vector)
00543     // a = 0.5*Mi*pi
00544     vnl_vector<double> a = Mi*pi*0.5;
00545 
00546     for (unsigned j=0;j<=m;++j)
00547       P_.update(Pi*a[j],2*i,3*j);
00548 
00549     pure_P_.update(Pi,2*i,0);
00550     coeffs_.set_row(i,a);
00551   }
00552 }
00553 
00554 //: Refine estimates of projection and structure
00555 void m23d_ortho_flexible_builder::refine()
00556 {
00557   vcl_cout<<"RMS Reconstruction error before refine = "
00558           <<(P2Dc_-P_*P3D_).rms()<<vcl_endl;
00559 
00560   make_pure_projections();
00561   // Re-estimate the 3D shape by solving the linear equation
00562   vnl_svd<double> svd(P_);
00563   P3D_ = svd.pinverse() * P2Dc_;
00564        // Slightly less stable than backsub, but what the heck.
00565 
00566   vcl_cout<<"RMS Reconstruction error after refine = "
00567           <<(P2Dc_-P_*P3D_).rms()<<vcl_endl;
00568 
00569   compute_mean(mean_shape_,mean_coeffs_);
00570 }
00571 
00572 //: Compute the mean 3D shape using the current co-effs
00573 void m23d_ortho_flexible_builder::compute_mean(vnl_matrix<double>& mean_shape,
00574                                                vnl_vector<double>& mean_coeffs)
00575 {
00576   // Compute average for each coefficient
00577   unsigned ns=coeffs_.rows();
00578   unsigned nm=coeffs_.cols();
00579   unsigned np=P3D_.cols();
00580   mean_coeffs.set_size(nm);
00581   mean_coeffs.fill(0.0);
00582   for (unsigned i=0;i<ns;++i) mean_coeffs+=coeffs_.get_row(i);
00583   mean_coeffs/=ns;
00584   mean_shape.set_size(3,np);
00585   mean_shape.fill(0.0);
00586   for (unsigned j=0;j<nm;++j)
00587     mean_shape += P3D_.extract(3,np,3*j,0) * mean_coeffs[j];
00588 }
00589 
00590 //: Return 3D shape i as a 3 x np matrix
00591 vnl_matrix<double> m23d_ortho_flexible_builder::shape(unsigned i) const
00592 {
00593   unsigned np=P3D_.cols();
00594   vnl_matrix<double> S(3,np,0.0);
00595   for (unsigned j=0;j<coeffs_.cols();++j)
00596     S += P3D_.extract(3,np,3*j,0) * coeffs_(i,j);
00597   return S;
00598 }
00599 
00600 
00601 //: Get back 3d pts rotated and shifted for each frame
00602 void m23d_ortho_flexible_builder::get_shape_3d_pts( vcl_vector< vgl_point_3d<double> >& pts ) const
00603 {
00604 #if 0
00605   if (P_.rows() < 2 || P_.cols() != 3 )
00606   {
00607     vcl_cerr<<"ERROR m23d_ortho_flexible_builder::get_shape_3d_pts()\n"
00608             <<"problem with size of P_\n"
00609             <<"P_.rows()= "<<P_.rows()<<'\n'
00610             <<"P_.cols()= "<<P_.cols()<<vcl_endl;
00611     vcl_abort();
00612   }
00613 
00614 
00615   //vnl_matrix<double> newPi = m23d_scaled_ortho_projection(P_.extract(2,3,2*i,0));
00616   //P_.update(newPi,2*i,0);
00617   vnl_matrix<double> P0= P_.extract(2,3);
00618 #endif // 0
00619 
00620   // get projection matrix for first shape
00621   vnl_matrix<double> P0=shape(0);
00622 
00623   // Compute a rotation matrix for this projection
00624   // then update shape and
00625   vnl_matrix<double> R0=m23d_rotation_from_ortho_projection(P0);
00626 
00627   // apply rotation to base shape
00628   vnl_matrix<double> rot_pts_mat=R0* P3D_;
00629   mat_to_3d_pts( pts, rot_pts_mat );
00630 
00631 
00632   int np= pts.size();
00633   vgl_vector_3d<double> tran_vec( cog_[0].x(), cog_[0].y(), 0 );
00634   for (int p=0; p<np; ++p)
00635   {
00636     pts[p]= pts[p] + tran_vec;
00637   }
00638 }
00639 
00640 
00641 //: Return 3d rigid pts, i.e., aligned with first frame
00642 void m23d_ortho_flexible_builder::mat_to_3d_pts(vcl_vector< vgl_point_3d<double> >& pt_vec,
00643                                                 const vnl_matrix<double>& M) const
00644 {
00645 #if 0
00646   // get pts out of P3D_ matrix
00647   if ( M.rows() != 3 )
00648   {
00649     vcl_cerr<<"ERROR m23d_ortho_flexible_builder::mat_to_3d_pts()\n"
00650             <<"problem with size of matrix\n"
00651             <<"M.rows()= "<<M.rows()<<'\n'
00652             <<"M.cols()= "<<M.cols()<<vcl_endl;
00653     vcl_abort();
00654   }
00655 #endif // 0
00656 
00657   int np= M.cols();
00658   pt_vec.resize(np);
00659   for (int i=0; i<np; ++i)
00660   {
00661     pt_vec[i].set( M(0,i), M(1,i), M(2,i) );
00662   }
00663 }
00664