contrib/gel/mrc/vpgl/algo/vpgl_bundle_adjust.cxx
Go to the documentation of this file.
00001 // This is gel/mrc/vpgl/algo/vpgl_bundle_adjust.cxx
00002 #include "vpgl_bundle_adjust.h"
00003 //:
00004 // \file
00005 
00006 #include <vnl/vnl_vector_ref.h>
00007 #include <vnl/vnl_double_3.h>
00008 #include <vnl/algo/vnl_sparse_lm.h>
00009 #include <vgl/algo/vgl_rotation_3d.h>
00010 #include <vgl/vgl_point_2d.h>
00011 #include <vgl/vgl_point_3d.h>
00012 
00013 #include <vcl_iostream.h>
00014 #include <vcl_fstream.h>
00015 #include <vcl_algorithm.h>
00016 #include <vcl_cassert.h>
00017 
00018 
00019 //: Constructor
00020 vpgl_bundle_adj_lsqr::
00021   vpgl_bundle_adj_lsqr(const vcl_vector<vpgl_calibration_matrix<double> >& K,
00022                        const vcl_vector<vgl_point_2d<double> >& image_points,
00023                        const vcl_vector<vcl_vector<bool> >& mask,
00024                        bool use_confidence_weights)
00025  : vnl_sparse_lst_sqr_function(K.size(),6,mask[0].size(),3,mask,2,use_gradient),
00026    K_(K),
00027    image_points_(image_points),
00028    use_covars_(false),
00029    use_weights_(use_confidence_weights),
00030    weights_(image_points.size(),1.0),
00031    iteration_count_(0)
00032 {
00033   for (unsigned int i=0; i<K_.size(); ++i)
00034     Km_.push_back(K_[i].get_matrix());
00035 }
00036 
00037 
00038 //: Constructor
00039 //  Each image point is assigned an inverse covariance (error projector) matrix
00040 // \note image points are not homogeneous because they require finite points to measure projection error
00041 vpgl_bundle_adj_lsqr::
00042 vpgl_bundle_adj_lsqr(const vcl_vector<vpgl_calibration_matrix<double> >& K,
00043                      const vcl_vector<vgl_point_2d<double> >& image_points,
00044                      const vcl_vector<vnl_matrix<double> >& inv_covars,
00045                      const vcl_vector<vcl_vector<bool> >& mask,
00046                      bool use_confidence_weights)
00047  : vnl_sparse_lst_sqr_function(K.size(),6,mask[0].size(),3,mask,2,use_gradient),
00048    K_(K),
00049    image_points_(image_points),
00050    use_covars_(true),
00051    use_weights_(use_confidence_weights),
00052    weights_(image_points.size(),1.0),
00053    iteration_count_(0)
00054 {
00055   for (unsigned int i=0; i<K_.size(); ++i)
00056     Km_.push_back(K_[i].get_matrix());
00057 
00058   assert(image_points.size() == inv_covars.size());
00059   vnl_matrix<double> U(2,2,0.0);
00060   for (unsigned i=0; i<inv_covars.size(); ++i)
00061   {
00062     const vnl_matrix<double>& S = inv_covars[i];
00063     if (S(0,0) > 0.0) {
00064       U(0,0) = vcl_sqrt(S(0,0));
00065       U(0,1) = S(0,1)/U(0,0);
00066       double U11 = S(1,1)-S(0,1)*S(0,1)/S(0,0);
00067       U(1,1) = (U11>0.0)?vcl_sqrt(U11):0.0;
00068     }
00069     else if (S(1,1) > 0.0) {
00070       assert(S(0,1) == 0.0);
00071       U(0,0) = 0.0;
00072       U(0,1) = 0.0;
00073       U(1,1) = vcl_sqrt(S(1,1));
00074     }
00075     else {
00076       vcl_cout << "warning: not positive definite"<<vcl_endl;
00077       U.fill(0.0);
00078     }
00079     factored_inv_covars_.push_back(U);
00080   }
00081 }
00082 
00083 
00084 //: Compute all the reprojection errors
00085 //  Given the parameter vectors a and b, compute the vector of residuals e.
00086 //  e has been sized appropriately before the call.
00087 //  The parameters in a for each camera are {wx, wy, wz, tx, ty, tz}
00088 //  where w is the Rodrigues vector of the rotation and t is the translation.
00089 //  The parameters in b for each 3D point are {px, py, pz}
00090 //  the non-homogeneous position.
00091 void
00092 vpgl_bundle_adj_lsqr::f(vnl_vector<double> const& a,
00093                         vnl_vector<double> const& b,
00094                         vnl_vector<double>& e)
00095 {
00096   typedef vnl_crs_index::sparse_vector::iterator sv_itr;
00097   for (unsigned int i=0; i<number_of_a(); ++i)
00098   {
00099     //: Construct the ith camera
00100     vnl_double_3x4 Pi = param_to_cam_matrix(i,a);
00101 
00102     vnl_crs_index::sparse_vector row = residual_indices_.sparse_row(i);
00103     for (sv_itr r_itr=row.begin(); r_itr!=row.end(); ++r_itr)
00104     {
00105       unsigned int j = r_itr->second;
00106       unsigned int k = r_itr->first;
00107 
00108       // Construct the jth point
00109       const double * bj = b.data_block()+index_b(j);
00110       vnl_vector_fixed<double,4> Xj(bj[0], bj[1], bj[2], 1.0);
00111 
00112       // Project jth point with the ith camera
00113       vnl_vector_fixed<double,3> xij = Pi*Xj;
00114 
00115       double* eij = e.data_block()+index_e(k);
00116       eij[0] = xij[0]/xij[2] - image_points_[k].x();
00117       eij[1] = xij[1]/xij[2] - image_points_[k].y();
00118       if (use_covars_){
00119         // multiple this error by upper triangular Sij
00120         vnl_matrix<double>& Sij = factored_inv_covars_[k];
00121         eij[0] *= Sij(0,0);
00122         eij[0] += eij[1]*Sij(0,1);
00123         eij[1] *= Sij(1,1);
00124       }
00125     }
00126   }
00127 
00128   if (use_weights_){
00129     vnl_vector<double> unweighted(e);
00130     for (unsigned int k=0; k<weights_.size(); ++k){
00131       e[2*k]   *= weights_[k];
00132       e[2*k+1] *= weights_[k];
00133     }
00134     // weighted average error
00135     double avg_error = e.rms();
00136     for (unsigned int k=0; k<weights_.size(); ++k){
00137       vnl_vector_ref<double> uw(2,unweighted.data_block()+2*k);
00138       double update = 2.0*avg_error/uw.rms();
00139       if (update < 1.0)
00140         weights_[k] = vcl_min(weights_[k], update);
00141       else
00142         weights_[k] = 1.0;
00143       //vcl_cout << weights_[k] << ' ';
00144       e[2*k]   = unweighted[2*k]   * weights_[k];
00145       e[2*k+1] = unweighted[2*k+1] * weights_[k];
00146     }
00147     vcl_cout << vcl_endl;
00148   }
00149 }
00150 
00151 
00152 //: Compute the residuals from the ith component of a and the jth component of b.
00153 //  This is not normally used because f() has a self-contained efficient implementation
00154 //  It is used for finite-differencing if the gradient is marked as unavailable
00155 void
00156 vpgl_bundle_adj_lsqr::fij(int i, int j, vnl_vector<double> const& ai,
00157                           vnl_vector<double> const& bj, vnl_vector<double>& fij)
00158 {
00159   //: Construct the ith camera
00160   vnl_double_3x4 Pi = param_to_cam_matrix(i,ai.data_block());
00161 
00162   // Construct the jth point
00163   vnl_vector_fixed<double,4> Xj(bj[0], bj[1], bj[2], 1.0);
00164 
00165   // Project jth point with the ith camera
00166   vnl_vector_fixed<double,3> xij = Pi*Xj;
00167 
00168   int k = residual_indices_(i,j);
00169   fij[0] = xij[0]/xij[2] - image_points_[k].x();
00170   fij[1] = xij[1]/xij[2] - image_points_[k].y();
00171   if (use_covars_){
00172     // multiple this error by upper triangular Sij
00173     vnl_matrix<double>& Sij = factored_inv_covars_[k];
00174     fij[0] *= Sij(0,0);
00175     fij[0] += fij[1]*Sij(0,1);
00176     fij[1] *= Sij(1,1);
00177   }
00178   if (use_weights_){
00179     fij[0] *= weights_[k];
00180     fij[1] *= weights_[k];
00181   }
00182 }
00183 
00184 
00185 //: Compute the sparse Jacobian in block form.
00186 void
00187 vpgl_bundle_adj_lsqr::jac_blocks(vnl_vector<double> const& a, vnl_vector<double> const& b,
00188                                  vcl_vector<vnl_matrix<double> >& A,
00189                                  vcl_vector<vnl_matrix<double> >& B)
00190 {
00191   typedef vnl_crs_index::sparse_vector::iterator sv_itr;
00192   for (unsigned int i=0; i<number_of_a(); ++i)
00193   {
00194     //: Construct the ith camera
00195     vnl_double_3x4 Pi = param_to_cam_matrix(i,a);
00196 
00197     // This is semi const incorrect - there is no vnl_vector_ref_const
00198     const vnl_vector_ref<double> ai(number_of_params_a(i),
00199                                     const_cast<double*>(a.data_block())+index_a(i));
00200 
00201     vnl_crs_index::sparse_vector row = residual_indices_.sparse_row(i);
00202     for (sv_itr r_itr=row.begin(); r_itr!=row.end(); ++r_itr)
00203     {
00204       unsigned int j = r_itr->second;
00205       unsigned int k = r_itr->first;
00206       // This is semi const incorrect - there is no vnl_vector_ref_const
00207       const vnl_vector_ref<double> bj(number_of_params_b(j),
00208                                       const_cast<double*>(b.data_block())+index_b(j));
00209 
00210       jac_Bij(Pi,bj,B[k]);   // compute Jacobian B_ij
00211       jac_Aij(Pi,Km_[i],ai,bj,A[k]); // compute Jacobian A_ij
00212       if (use_covars_){
00213         const vnl_matrix<double>& Sij = factored_inv_covars_[k];
00214         A[k] = Sij*A[k];
00215         B[k] = Sij*B[k];
00216       }
00217       if (use_weights_){
00218         A[k] *= weights_[k];
00219         B[k] *= weights_[k];
00220       }
00221     }
00222   }
00223 }
00224 
00225 
00226 //: compute the Jacobian Aij
00227 void
00228 vpgl_bundle_adj_lsqr::jac_Aij(vnl_double_3x4 const& Pi,
00229                               vnl_double_3x3 const& K,
00230                               vnl_vector<double> const& ai,
00231                               vnl_vector<double> const& bj,
00232                               vnl_matrix<double>& Aij)
00233 {
00234   // The translation part.
00235   //=====================
00236   // compute by swapping the role of the translation and point position
00237   // then reused the jac_Bij code
00238   {
00239     vnl_double_3x4 sPi(Pi);
00240     vnl_double_3x3 M = sPi.extract(3,3);
00241     sPi.set_column(3,-(M*bj));
00242     // This is semi const incorrect - there is no vnl_vector_ref_const
00243     const vnl_vector_ref<double> t(3,const_cast<double*>(ai.data_block())+3);
00244 
00245     vnl_matrix<double> Aij_sub(2,3);
00246     jac_Bij(sPi,t,Aij_sub);
00247     Aij.update(Aij_sub,0,3);
00248   }
00249 
00250   // The rotation part.
00251   //==================
00252   // relative translation vector
00253   {
00254     vnl_double_3 t(bj[0]-ai[3], bj[1]-ai[4], bj[2]-ai[5]);
00255 
00256     const double& x = ai[0];
00257     const double& y = ai[1];
00258     const double& z = ai[2];
00259     double x2 = x*x, y2 = y*y, z2 = z*z;
00260     double m2 = x2 + y2 + z2;
00261 
00262     if (m2 == 0.0)
00263     {
00264       Aij(0,0) = 0;
00265       Aij(1,0) = -1;
00266       Aij(0,1) = 1;
00267       Aij(1,1) = 0;
00268       Aij(0,2) = 0;
00269       Aij(1,2) = 0;
00270     }
00271     else
00272     {
00273       double m = vcl_sqrt(m2);  // Rodrigues magnitude = rotation angle
00274       double c = vcl_cos(m);
00275       double s = vcl_sin(m);
00276 
00277       // common trig terms
00278       double ct = (1-c)/m2;
00279       double st = s/m;
00280 
00281       // derivative coefficients for common trig terms
00282       // ds = d/dx_i{st}/x_i
00283       // dc = d/dx_i{ct}/x_i
00284       double dct = s/m/m2;
00285       double dst = c/m2 - dct;
00286       dct -=  2*(1-c)/(m2*m2);
00287 
00288       double utc = t[2]*x*z + t[1]*x*y - t[0]*(y2+z2);
00289       double uts = t[2]*y - t[1]*z;
00290       double vtc = t[0]*x*y + t[2]*y*z - t[1]*(x2+z2);
00291       double vts = t[0]*z - t[2]*x;
00292       double wtc = t[0]*x*z + t[1]*y*z - t[2]*(x2+y2);
00293       double wts = t[1]*x - t[0]*y;
00294 
00295       // projection of the point into normalized homogeneous coordinates
00296       // should be equal to inv(K)*Pi*[bj|1]
00297       double u = ct*utc + st*uts + t[0];
00298       double v = ct*vtc + st*vts + t[1];
00299       double w = ct*wtc + st*wts + t[2];
00300 
00301       double w2 = w*w;
00302 
00303       double dw = dct*x*wtc + ct*(t[0]*z - 2*t[2]*x)
00304                 + dst*x*wts + st*t[1];
00305       Aij(0,0) = (w*(dct*x*utc + ct*(t[2]*z + t[1]*y)
00306                     + dst*x*uts) - u*dw)/w2;
00307       Aij(1,0) = (w*(dct*x*vtc + ct*(t[0]*y - 2*t[1]*x)
00308                     + dst*x*vts - st*t[2]) - v*dw)/w2;
00309 
00310       dw = dct*y*wtc + ct*(t[1]*z - 2*t[2]*y)
00311           + dst*y*wts - st*t[0];
00312       Aij(0,1) = (w*(dct*y*utc + ct*(t[1]*x - 2*t[0]*y)
00313                     + dst*y*uts + st*t[2]) - u*dw)/w2;
00314       Aij(1,1) = (w*(dct*y*vtc + ct*(t[0]*x + t[2]*z)
00315                     + dst*y*vts) - v*dw)/w2;
00316 
00317       dw = dct*z*wtc + ct*(t[0]*x + t[1]*y)
00318           + dst*z*wts;
00319       Aij(0,2) = (w*(dct*z*utc + ct*(t[2]*x - 2*t[0]*z)
00320                     + dst*z*uts - st*t[1]) - u*dw)/w2;
00321       Aij(1,2) = (w*(dct*z*vtc + ct*(t[2]*y - 2*t[1]*z)
00322                     + dst*z*vts + st*t[0]) - v*dw)/w2;
00323     }
00324 
00325     Aij(0,0) *= K(0,0);
00326     Aij(0,0) += Aij(1,0)*K(0,1);
00327     Aij(1,0) *= K(1,1);
00328     Aij(0,1) *= K(0,0);
00329     Aij(0,1) += Aij(1,1)*K(0,1);
00330     Aij(1,1) *= K(1,1);
00331     Aij(0,2) *= K(0,0);
00332     Aij(0,2) += Aij(1,2)*K(0,1);
00333     Aij(1,2) *= K(1,1);
00334   }
00335 }
00336 
00337 
00338 //: compute the Jacobian Bij
00339 void
00340 vpgl_bundle_adj_lsqr::jac_Bij(vnl_double_3x4 const& Pi, vnl_vector<double> const& bj,
00341                               vnl_matrix<double>& Bij)
00342 {
00343   double denom = Pi(2,0)*bj[0] + Pi(2,1)*bj[1] + Pi(2,2)*bj[2] + Pi(2,3);
00344   denom *= denom;
00345 
00346   double txy = Pi(0,0)*Pi(2,1) - Pi(0,1)*Pi(2,0);
00347   double txz = Pi(0,0)*Pi(2,2) - Pi(0,2)*Pi(2,0);
00348   double tyz = Pi(0,1)*Pi(2,2) - Pi(0,2)*Pi(2,1);
00349   double tx  = Pi(0,0)*Pi(2,3) - Pi(0,3)*Pi(2,0);
00350   double ty  = Pi(0,1)*Pi(2,3) - Pi(0,3)*Pi(2,1);
00351   double tz  = Pi(0,2)*Pi(2,3) - Pi(0,3)*Pi(2,2);
00352 
00353   Bij(0,0) = ( txy*bj[1] + txz*bj[2] + tx) / denom;
00354   Bij(0,1) = (-txy*bj[0] + tyz*bj[2] + ty) / denom;
00355   Bij(0,2) = (-txz*bj[0] - tyz*bj[1] + tz) / denom;
00356 
00357   txy = Pi(1,0)*Pi(2,1) - Pi(1,1)*Pi(2,0);
00358   txz = Pi(1,0)*Pi(2,2) - Pi(1,2)*Pi(2,0);
00359   tyz = Pi(1,1)*Pi(2,2) - Pi(1,2)*Pi(2,1);
00360   tx  = Pi(1,0)*Pi(2,3) - Pi(1,3)*Pi(2,0);
00361   ty  = Pi(1,1)*Pi(2,3) - Pi(1,3)*Pi(2,1);
00362   tz  = Pi(1,2)*Pi(2,3) - Pi(1,3)*Pi(2,2);
00363 
00364   Bij(1,0) = ( txy*bj[1] + txz*bj[2] + tx) / denom;
00365   Bij(1,1) = (-txy*bj[0] + tyz*bj[2] + ty) / denom;
00366   Bij(1,2) = (-txz*bj[0] - tyz*bj[1] + tz) / denom;
00367 }
00368 
00369 
00370 //: Fast conversion of rotation from Rodrigues vector to matrix
00371 vnl_matrix_fixed<double,3,3>
00372 vpgl_bundle_adj_lsqr::rod_to_matrix(const double* r) const
00373 {
00374   double x2 = r[0]*r[0], y2 = r[1]*r[1], z2 = r[2]*r[2];
00375   double m = x2 + y2 + z2;
00376   double theta = vcl_sqrt(m);
00377   double s = vcl_sin(theta) / theta;
00378   double c = (1 - vcl_cos(theta)) / m;
00379 
00380   vnl_matrix_fixed<double,3,3> R(0.0);
00381   R(0,0) = R(1,1) = R(2,2) = 1.0;
00382   if (m == 0.0)
00383     return R;
00384 
00385   R(0,0) -= (y2 + z2) * c;
00386   R(1,1) -= (x2 + z2) * c;
00387   R(2,2) -= (x2 + y2) * c;
00388   R(0,1) = R(1,0) = r[0]*r[1]*c;
00389   R(0,2) = R(2,0) = r[0]*r[2]*c;
00390   R(1,2) = R(2,1) = r[1]*r[2]*c;
00391 
00392   double t = r[0]*s;
00393   R(1,2) -= t;
00394   R(2,1) += t;
00395   t = r[1]*s;
00396   R(0,2) += t;
00397   R(2,0) -= t;
00398   t = r[2]*s;
00399   R(0,1) -= t;
00400   R(1,0) += t;
00401 
00402   return R;
00403 }
00404 
00405 
00406 //: Create the parameter vector \p a from a vector of cameras
00407 vnl_vector<double>
00408 vpgl_bundle_adj_lsqr::create_param_vector(const vcl_vector<vpgl_perspective_camera<double> >& cameras)
00409 {
00410   vnl_vector<double> a(6*cameras.size(),0.0);
00411   for (unsigned int i=0; i<cameras.size(); ++i)
00412   {
00413     const vpgl_perspective_camera<double>& cam = cameras[i];
00414     const vgl_point_3d<double>& c = cam.get_camera_center();
00415     const vgl_rotation_3d<double>& R = cam.get_rotation();
00416 
00417     // compute the Rodrigues vector from the rotation
00418     vnl_vector<double> w = R.as_rodrigues();
00419 
00420     double* ai = a.data_block() + i*6;
00421     ai[0]=w[0];   ai[1]=w[1];   ai[2]=w[2];
00422     ai[3]=c.x();  ai[4]=c.y();  ai[5]=c.z();
00423   }
00424   return a;
00425 }
00426 
00427 
00428 //: Create the parameter vector \p b from a vector of 3D points
00429 vnl_vector<double>
00430 vpgl_bundle_adj_lsqr::create_param_vector(const vcl_vector<vgl_point_3d<double> >& world_points)
00431 {
00432   vnl_vector<double> b(3*world_points.size(),0.0);
00433   for (unsigned int j=0; j<world_points.size(); ++j){
00434     const vgl_point_3d<double>& point = world_points[j];
00435     double* bj = b.data_block() + j*3;
00436     bj[0]=point.x();  bj[1]=point.y();  bj[2]=point.z();
00437   }
00438   return b;
00439 }
00440 
00441 
00442 //===============================================================
00443 // Static functions for vpgl_bundle_adjust
00444 //===============================================================
00445 
00446 
00447 vpgl_bundle_adjust::vpgl_bundle_adjust()
00448   : ba_func_(NULL),
00449     use_weights_(false),
00450     use_gradient_(true),
00451     start_error_(0.0),
00452     end_error_(0.0)
00453 {
00454 }
00455 
00456 vpgl_bundle_adjust::~vpgl_bundle_adjust()
00457 {
00458   delete ba_func_;
00459 }
00460 
00461 //: Bundle Adjust
00462 bool
00463 vpgl_bundle_adjust::optimize(vcl_vector<vpgl_perspective_camera<double> >& cameras,
00464                              vcl_vector<vgl_point_3d<double> >& world_points,
00465                              const vcl_vector<vgl_point_2d<double> >& image_points,
00466                              const vcl_vector<vcl_vector<bool> >& mask)
00467 {
00468   // Extract the camera and point parameters
00469   vcl_vector<vpgl_calibration_matrix<double> > K;
00470   a_ = vpgl_bundle_adj_lsqr::create_param_vector(cameras);
00471   b_ = vpgl_bundle_adj_lsqr::create_param_vector(world_points);
00472   for (unsigned int i=0; i<cameras.size(); ++i){
00473     K.push_back(cameras[i].get_calibration());
00474   }
00475 
00476   // do the bundle adjustment
00477   delete ba_func_;
00478   ba_func_ = new vpgl_bundle_adj_lsqr(K,image_points,mask,use_weights_);
00479   vnl_sparse_lm lm(*ba_func_);
00480   lm.set_trace(true);
00481   lm.set_verbose(true);
00482   if (!lm.minimize(a_,b_,use_gradient_))
00483     return false;
00484 
00485   start_error_ = lm.get_start_error();
00486   end_error_ = lm.get_end_error();
00487   num_iterations_ = lm.get_num_iterations();
00488 
00489   // Update the camera parameters
00490   for (unsigned int i=0; i<cameras.size(); ++i)
00491     cameras[i] = ba_func_->param_to_cam(i,a_);
00492   // Update the point locations
00493   for (unsigned int j=0; j<world_points.size(); ++j)
00494     world_points[j] = ba_func_->param_to_point(j,b_);
00495 
00496   return true;
00497 }
00498 
00499 
00500 //: Write cameras and points to a file in VRML 2.0 for debugging
00501 void
00502 vpgl_bundle_adjust::write_vrml(const vcl_string& filename,
00503                                vcl_vector<vpgl_perspective_camera<double> >& cameras,
00504                                vcl_vector<vgl_point_3d<double> >& world_points)
00505 {
00506   vcl_ofstream os(filename.c_str());
00507   os << "#VRML V2.0 utf8\n\n";
00508 
00509   for (unsigned int i=0; i<cameras.size(); ++i){
00510     vnl_double_3x3 K = cameras[i].get_calibration().get_matrix();
00511 
00512     const vgl_rotation_3d<double>& R = cameras[i].get_rotation();
00513     //R.set_row(1,-1.0*R.get_row(1));
00514     //R.set_row(2,-1.0*R.get_row(2));
00515     vgl_point_3d<double> ctr = cameras[i].get_camera_center();
00516     double fov = 2.0*vcl_max(vcl_atan(K[1][2]/K[1][1]), vcl_atan(K[0][2]/K[0][0]));
00517     os  << "Viewpoint {\n"
00518         << "  position    "<< ctr.x() << ' ' << ctr.y() << ' ' << ctr.z() << '\n'
00519         << "  orientation "<< R.axis() << ' '<< R.angle() << '\n'
00520         << "  fieldOfView "<< fov << '\n'
00521         << "  description \"Camera" << i << "\"\n}\n";
00522   }
00523 
00524   os << "Shape {\n  appearance NULL\n    geometry PointSet {\n"
00525      << "      color Color { color [1 0 0] }\n      coord Coordinate{\n"
00526      << "       point[\n";
00527 
00528   for (unsigned int j=0; j<world_points.size(); ++j){
00529     os  << world_points[j].x() << ' '
00530         << world_points[j].y() << ' '
00531         << world_points[j].z() << '\n';
00532   }
00533   os << "   ]\n  }\n }\n}\n";
00534 
00535   os.close();
00536 }
00537