vo.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002 * Software License Agreement (BSD License)
00003 *
00004 *  Copyright (c) 2009, Willow Garage, Inc.
00005 *  All rights reserved.
00006 *
00007 *  Redistribution and use in source and binary forms, with or without
00008 *  modification, are permitted provided that the following conditions
00009 *  are met:
00010 *
00011 *   * Redistributions of source code must retain the above copyright
00012 *     notice, this list of conditions and the following disclaimer.
00013 *   * Redistributions in binary form must reproduce the above
00014 *     copyright notice, this list of conditions and the following
00015 *     disclaimer in the documentation and/or other materials provided
00016 *     with the distribution.
00017 *   * Neither the name of the Willow Garage nor the names of its
00018 *     contributors may be used to endorse or promote products derived
00019 *     from this software without specific prior written permission.
00020 *
00021 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 *  POSSIBILITY OF SUCH DAMAGE.
00033 *********************************************************************/
00034 
00035 
00036 //
00037 // A class for visual odometry
00038 //
00039 
00040 #include <vslam_system/vo.h>
00041 #include <cstdio>
00042 
00043 using namespace std;
00044 using namespace frame_common;
00045 using namespace Eigen;
00046 using namespace sba;
00047 
00048 namespace vslam
00049 {
00050 
00051   // initialize the VO structures
00052   voSt::voSt(boost::shared_ptr<pe::PoseEstimator> pose_estimator, int ws, int wf, int mini, double mind, double mina)
00053   {
00054     // estimator
00055     pose_estimator_ = pose_estimator;
00056 
00057     // params
00058     wsize = ws;                 // total window size
00059     wfixed = wf;                // size of fixed portion
00060     mindist = mind;             // meters
00061     minang  = mina;             // radians
00062     mininls = mini;             // inliers
00063     doPointPlane = true;        // true if point-plane matches are included
00064 
00065     // set up structures
00066     sba.useCholmod(true);
00067 
00068     // for RANSAC
00069     srand(time(NULL));
00070   }
00071 
00072   // TODO <fnew> is not changed, can be declared const
00073   bool voSt::addFrame(const Frame &fnew)
00074   {
00075     int nframes = frames.size();
00076     bool init = nframes == 0;
00077 
00078     // check if we need to remove oldest frame
00079     if (nframes >= wsize)
00080       removeFrame();
00081 
00082     // get RW position given relative position
00083     Quaterniond fq;
00084     Vector4d trans;
00085     int inl = 0;
00086     if (init)                     // use zero origin
00087       {
00088         trans = Vector4d(0,0,0,1);
00089         fq.coeffs() = Vector4d(0,0,0,1);
00090       }
00091     else                        // else find pose relative to previous frame
00092       {
00093         // check for initial pose estimate from last matched frame
00094         Frame &refFrame = frames.back();
00095 
00096         inl = pose_estimator_->estimate(refFrame,fnew);
00097         fq = Quaterniond(pose_estimator_->rot);
00098         trans.head(3) = pose_estimator_->trans;
00099         trans(3) = 1.0;
00100 
00101         // check for keyframe
00102         double dist = pose_estimator_->trans.norm();
00103         double angledist = fq.angularDistance(fq.Identity());
00104         if(pose_estimator_->getMethod() == pe::PoseEstimator::Stereo)
00105         {
00106           //if (dist < maxdist && inl > mininls)
00107           // Even if it's closer than the max distance, let it in if there's a 
00108           // small number of inliers because we're losing inliers.
00109           if (((dist < mindist && angledist < minang) && (inl > mininls))) // check for angle as well
00110           {
00111             // not a keyframe, set up translated keypoints in ref frame
00112             cout << "[Stereo VO] Skipping frame " << (mindist) << " " << (minang) << " " << (inl) << "/" << (mininls) << endl;
00113             refFrame.setTKpts(trans,fq);
00114             return false;
00115           }
00116         }
00117         else
00118         {
00119           if(inl < mininls) return false;
00120           if((pose_estimator_->getMethod() == pe::PoseEstimator::PnP && dist < mindist) && inl > mininls)
00121           {
00122             cout << "dist = " << dist << " maxdist = " << mindist << " inl = " << inl
00123                 << " mininls = " << mininls << endl;
00124             return false;       // no keyframe
00125           }
00126         }
00127       }
00128 
00129     Matrix<double,3,4> f2w, f2w_frame0, f2w_frame1;
00130     if (!init)
00131       {
00132         // rotation
00133         Node &nd0 = sba.nodes.back(); // last node
00134         Quaterniond fq0;
00135         fq0 = nd0.qrot;
00136         fq = fq*fq0;                  // RW rotation
00137   
00138         // translation
00139         if (isnan(fq.x()) || isnan(fq.y()) || isnan(fq.z()) || isnan(fq.w()))
00140           return false; // Not a keyframe, not a valid node.
00141           
00142         transformF2W(f2w,nd0.trans,fq0);
00143         trans.head(3) = f2w*trans;
00144         //      cout << endl << f2w << endl << endl;
00145         //      cout << trans.head(3).transpose() << endl << endl;
00146         
00147         transformF2W(f2w_frame0,nd0.trans,nd0.qrot);
00148       }
00149 
00150     // Add node for the frame, setting it to fixed if we're iniitializing,
00151     // floating otherwise.
00152     sba.addNode(trans, fq, fnew.cam, init);
00153 
00154     int ndi = sba.nodes.size()-1;   // index of this new node
00155     
00156     Node &nd1 = sba.nodes.back();
00157     transformF2W(f2w_frame1,nd1.trans,nd1.qrot);
00158     //f2w_frame0 = f2w;
00159 
00160     // copy and save this frame
00161     frames.push_back(fnew);
00162     Frame &f1 = frames.back();
00163     // set up point indices to sentinel value -1 indicating unassigned
00164     f1.ipts.assign(f1.kpts.size(), -1);
00165 
00166     // for first frame, just return
00167     if (init) 
00168       {
00169         init = false;
00170         return true;
00171       }
00172 
00173     // get previous frame
00174     Frame &f0 = *(frames.end()-2);
00175     nframes = frames.size();
00176 
00177     // add connections to previous frame
00178     addProjections(f0, f1, frames, sba, pose_estimator_->inliers, f2w_frame0, ndi-1, ndi, &ipts);
00179     
00180     // do SBA, setting up fixed frames
00181     int nfree = wsize-wfixed;
00182     if (nframes <= nfree)
00183       sba.nFixed = 1;
00184     else
00185       sba.nFixed = wfixed - (wsize - nframes);
00186 
00187     cout << "[Stereo VO] Inliers: " << inl << "  Nodes: " << sba.nodes.size() << "   Points: " << sba.tracks.size() << endl;
00188     sba.verbose = 0;
00189     sba.doSBA(4,1.0e-5,0);          // dense version
00190 
00191     // Do pointcloud matching and add the projections to the system.
00192     // Rot,trans is wrong, should be from updated SBA values
00193     if (pointcloud_proc_ && doPointPlane)
00194     {
00195       pointcloud_proc_->match(f0, f1, pose_estimator_->trans, Quaterniond(pose_estimator_->rot), pointcloud_matches_);
00196       addPointCloudProjections(f0, f1, sba, pointcloud_matches_, f2w_frame0, f2w_frame1, ndi-1, ndi, &ipts);
00197     }
00198     
00199     return true;
00200   } // end addFrame
00201 
00202 
00203   // removes the oldest node from the sba system
00204   // this is a pain because we're using indices rather than pointers
00205   void voSt::removeFrame()
00206   {
00207     vector<int> pidx(sba.tracks.size()); // point index for re-indexing
00208 
00209     // run through tracks, resetting node indices by -1 and removing
00210     //   references to node 0
00211     int tn = 0;
00212     for(size_t i=0; i<sba.tracks.size(); i++)
00213       {
00214         ProjMap &prjs = sba.tracks[i].projections;
00215         if (prjs.size() == 0) continue;
00216         int n = 0;
00217         for(ProjMap::iterator itr = prjs.begin(); itr != prjs.end(); itr++)
00218           {
00219             Proj &prj = itr->second;
00220             prj.ndi -= 1;
00221             if (prj.ndi >= 0) n++;
00222             else prj.isValid = false;
00223           }
00224         if (n < 2)              // remove this guy
00225           pidx[i] = -1;
00226         else                    // keep this guy
00227           pidx[i] = tn++;
00228       }
00229 
00230     //    cout << "[RemoveFrame] Removing " << sba.tracksSt.size() - tn << " tracks" << endl;
00231 
00232     // now remake the tracks and points, removing useless ones
00233     for (int i=0; i<(int)pidx.size(); i++)
00234       {
00235         if (pidx[i] < 0 || pidx[i] == i) continue;
00236         sba.tracks[pidx[i]] = sba.tracks[i];
00237         ipts[pidx[i]] = ipts[i];
00238       }
00239     sba.tracks.resize(tn);
00240     ipts.resize(tn);
00241 
00242     // finally, go through frames and adjust point indices
00243     sba.nodes.erase(sba.nodes.begin()); // erase oldest node
00244     frames.erase(frames.begin()); // erase oldest frame
00245     for (int i=0; i<(int)frames.size(); i++)
00246       {
00247         Frame &f = frames[i];
00248         for (int j=0; j<(int)f.ipts.size(); j++)
00249           if (f.ipts[j] >= 0)
00250             f.ipts[j] = pidx[f.ipts[j]];
00251         for (int j=0; j<(int)f.pl_ipts.size(); j++)
00252           if (f.pl_ipts[j] >= 0)
00253             f.pl_ipts[j] = pidx[f.pl_ipts[j]];
00254             
00255       }
00256       
00257     // Redo point indeces of point-plane projections
00258     for(size_t i=0; i<sba.tracks.size(); i++)
00259       {
00260         ProjMap &prjs = sba.tracks[i].projections;
00261         if (prjs.size() == 0) continue;
00262         for(ProjMap::iterator itr = prjs.begin(); itr != prjs.end(); itr++)
00263           {
00264             Proj &prj = itr->second;
00265             if (prj.pointPlane && prj.plane_point_index >= 0)
00266             {
00267               prj.plane_point_index = pidx[prj.plane_point_index];
00268               prj.plane_node_index -= 1;
00269               if (prj.plane_node_index < 0 || prj.plane_point_index < 0)
00270                 prj.isValid = false;
00271             }
00272           }
00273       }
00274   }
00275 
00276   
00277   // transfer most recent frame to an external SBA system
00278   void voSt::transferLatestFrame(std::vector<fc::Frame, Eigen::aligned_allocator<fc::Frame> > &eframes,
00279                                  SysSBA &esba)
00280   {
00281     bool init = esba.nodes.size() == 0;
00282 
00283     // latest frame
00284     Frame &f1 = eframes.back();
00285 
00286     // set up point indices to NULL
00287     f1.ipts.assign(f1.kpts.size(), -1);
00288     f1.pl_ipts.assign(f1.pl_pts.size(), -1);
00289 
00290     // ADD THIS LINE SO THAT PL_PTS ARE CREATED CORRECTLY IN LARGE SBA:
00291     f1.pl_ipts.assign(f1.pl_pts.size(), -1);
00292 
00293     // add a frame ??? already passed in correct frame...
00294     //    f1 = frames.back();         // most recent frame in VO
00295 
00296     // get RW position given relative position
00297     Quaterniond fq;
00298     Vector4d trans;
00299     if (init)                   // use zero origin
00300       {
00302         //trans = Vector4d(0,-1.0,0,1);
00303         //fq.coeffs() = Vector4d(-0.16,0.0,0,1);
00304         //fq.normalize();
00305         trans = Vector4d(0,0,0,1);
00306         fq.coeffs() = Vector4d(0,0,0,1);
00307       }
00308     else
00309       {
00311         
00312         transformN2N(trans, fq, *(sba.nodes.end()-2), *(sba.nodes.end()-1));
00313         
00314         //fq = Quaterniond(pose_estimator_->rot);
00315         //trans.head(3) = pose_estimator_->trans;
00316         //trans(3) = 1.0;
00317       }
00318 
00319     Matrix<double,3,4> f2w, f2w_frame0, f2w_frame1;
00320     if (!init)
00321       {
00322         // rotation
00323         Node &nd0 = esba.nodes.back(); 
00324         Quaterniond fq0;
00325         fq0 = nd0.qrot;
00326         fq = fq0*fq;            // RW rotation
00327   
00328         // translation
00329         transformF2W(f2w,nd0.trans,fq0);
00330         trans.head(3) = f2w*trans;
00331         
00332         transformF2W(f2w_frame0,nd0.trans,nd0.qrot);
00333         //      cout << endl << f2w << endl << endl;
00334         //      cout << trans.head(3).transpose() << endl << endl;
00335       }
00336 
00337     // Add node for the frame, setting it to fixed if we're iniitializing,
00338     // floating otherwise.
00339     esba.addNode(trans, fq, f1.cam, init);
00340 
00341     int ndi = esba.nodes.size()-1;   // index of this new node
00342 
00343     Node &nd1 = esba.nodes.back();
00344     transformF2W(f2w_frame1,nd1.trans,nd1.qrot); 
00345     
00346     if (init) 
00347       return;
00348 
00351     Frame &f0 = *(eframes.end()-2);
00352     addProjections(f0, f1, eframes, esba, pose_estimator_->inliers, f2w_frame0, ndi-1, ndi, NULL);
00353     esba.doSBA(3,1.0e-4,SBA_SPARSE_CHOLESKY);
00354     if (doPointPlane)
00355       addPointCloudProjections(f0, f1, esba, pointcloud_matches_, f2w_frame0, f2w_frame1, ndi-1, ndi, NULL);
00356   }
00357 
00358 
00359   // find transform between two frames
00360   // first frame given by <frameId>, second is <n> frames ahead
00361   // returns second frame id
00362   int voSt::findTransform(int frameId, int n, Vector4d &trans, Quaterniond &qr,
00363                            Matrix<double,6,6> &prec)
00364   {
00365     // find frame with index frameId
00366     int fi = 0;
00367     for (; fi < (int)frames.size(); fi++)
00368       {
00369         if (frameId == frames[fi].frameId)
00370           break;
00371       }
00372     if (fi >= (int)frames.size())
00373       return -1;                // didn't find the frame
00374 
00375     // now check for nth frame ahead
00376     int fi1 = fi+n;
00377     if (fi1 >= (int)frames.size())
00378       fi1 = frames.size()-1;
00379 
00380     // get transform between nodes
00381     Node &nd0 = sba.nodes[fi];
00382     Node &nd1 = sba.nodes[fi1];
00383     transformN2N(trans,qr,nd0,nd1);
00384     // just do a diagonal precision matrix for now
00385     prec.setIdentity();
00386     prec(3,3) = prec(4,4) = prec(5,5) = 10;
00387 
00388     return frames[fi1].frameId;
00389   }
00390 
00391 
00392   // find node index of associated frameId
00393   int voSt::findNode(int frameId)
00394   {
00395     int fi = 0;
00396     for (; fi < (int)frames.size(); fi++)
00397       {
00398         if (frameId == frames[fi].frameId)
00399           return fi;
00400       }
00401     return -1;
00402   }
00403 
00404   // add connections between frames, based on keypoint matches
00405   void addProjections(fc::Frame &f0, fc::Frame &f1, 
00406                       std::vector<fc::Frame, Eigen::aligned_allocator<fc::Frame> > &frames,
00407                       SysSBA &sba, const std::vector<cv::DMatch> &inliers,
00408                       const Matrix<double,3,4>& f2w, int ndi0, int ndi1, std::vector<int>* ipts)
00409   {
00410     // set up array to kill duplicate matches
00411     vector<bool> matched0(f0.ipts.size(),0);
00412     vector<bool> matched1(f1.ipts.size(),0);
00413     
00414     // Whether the frame we are adding is stereo or not.
00415     // Not sure this would do the right thing in the case of stereo-mono matches.
00416     bool stereo = f1.isStereo;
00417 
00418     // add points and projections
00419     for (int i=0; i<(int)inliers.size(); i++)
00420       {
00421         int i0 = inliers[i].queryIdx;
00422         int i1 = inliers[i].trainIdx;
00423 
00424         if (matched0[i0]) continue;
00425         if (matched1[i1]) continue;
00426 
00427         if (f0.goodPts[i0] == 0) continue;
00428 
00429         matched0[i0] = true;
00430         matched1[i1] = true;
00431 
00432         int pti;
00433 
00434         if (f0.ipts[i0] < 0 && f1.ipts[i1] < 0)    // new point
00435           {
00436             pti = sba.tracks.size();
00437             f0.ipts[i0] = pti;
00438             f1.ipts[i1] = pti;
00439 
00440             Vector4d pt;
00441             pt.head(3) = f2w*f0.pts[i0]; // transform to RW coords
00442             pt(3) = 1.0;
00443             sba.addPoint(pt);
00444             if (ipts)
00445               ipts->push_back(-1);  // external point index
00446 
00447             Vector3d ipt = getProjection(f0, i0);
00448             sba.addProj(ndi0, pti, ipt, stereo);
00449 
00450             // projected point, ul,vl,ur
00451             ipt = getProjection(f1, i1);
00452             sba.addProj(ndi1, pti, ipt, stereo);
00453           }
00454 
00455         else if (f0.ipts[i0] >= 0 && f1.ipts[i1] >= 0) // merge two tracks
00456           {
00457             if (f0.ipts[i0] != f1.ipts[i1]) // different tracks
00458               {
00459                 int tri = sba.mergeTracksSt(f0.ipts[i0],f1.ipts[i1]);
00460                 if (tri >= 0)   // successful merge
00461                   {
00462                     // update the ipts in frames that connect to this track
00463                     ProjMap &prjs = sba.tracks[tri].projections;
00464                     for(ProjMap::iterator itr = prjs.begin(); itr != prjs.end(); itr++)
00465                       {
00466                         Proj &prj = itr->second;  
00467                         if (tri == f0.ipts[i0])
00468                           substPointRef(frames[prj.ndi].ipts, tri, f1.ipts[i1]);
00469                         else
00470                           substPointRef(frames[prj.ndi].ipts, tri, f0.ipts[i0]);
00471                       }
00472                   }
00473               }
00474           }
00475 
00476         else if (f1.ipts[i1] < 0)                 // add to previous point track
00477           {
00478             pti = f0.ipts[i0];
00479             f1.ipts[i1] = pti;
00480           
00481             // projected point, ul,vl,ur
00482             Vector3d ipt = getProjection(f1, i1);
00483             sba.addProj(ndi1, pti, ipt, stereo);
00484           }
00485         else if (f0.ipts[i0] < 0)                 // add to previous point track
00486           {
00487             pti = f1.ipts[i1];
00488             f0.ipts[i0] = pti;
00489           
00490             // projected point, ul,vl,ur
00491             Vector3d ipt = getProjection(f0, i0);
00492             sba.addProj(ndi0, pti, ipt, stereo);
00493           }
00494       }
00495   }
00496   
00497   // Pointcloud matches, copied from above. Think of a more elegant way of doing this.
00498   void addPointCloudProjections(fc::Frame &f0, fc::Frame &f1, 
00499                       SysSBA &sba, const std::vector<cv::DMatch> &inliers,
00500                       const Matrix<double,3,4>& f2w_frame0, 
00501                       const Matrix<double,3,4>& f2w_frame1, 
00502                       int ndi0, int ndi1, std::vector<int>* ipts)
00503   {
00504     // add points and projections
00505     double covariance = 1.0;
00506     Matrix3d covar;
00507     covar <<  covariance, 0, 0,
00508       0, covariance, 0, 
00509       0, 0, covariance;
00510 
00511     covariance = 100.0;
00512     Matrix3d cv2;
00513     cv2 <<  covariance, 0, 0,
00514       0, covariance, 0, 
00515       0, 0, covariance;
00516 
00517     for (int i=0; i<(int)inliers.size(); i++)
00518       {
00519         int i0 = inliers[i].queryIdx;
00520         int i1 = inliers[i].trainIdx;
00521 
00522         int pti;
00523 
00524         if (f0.pl_ipts[i0] < 0)  // new point
00525           {
00526             pti = sba.tracks.size();
00527             f0.pl_ipts[i0] = pti;
00528 
00529             Vector4d pt;
00530             pt.head<3>() = f2w_frame0*f0.pl_pts[i0]; // transform to RW coords
00531             pt(3) = 1.0;
00532 
00533             //            cout << "f0 point: " << pt.transpose() << endl;
00534 
00535             sba.addPoint(pt);
00536             if (ipts)
00537               ipts->push_back(-1);  // external point index
00538 
00539             sba.addStereoProj(ndi0, pti, f0.pl_kpts[i0]);
00540             sba.setProjCovariance(ndi0, pti, cv2);
00541           }
00542         if (f1.pl_ipts[i1] < 0) // new point
00543           {
00544             pti = sba.tracks.size();
00545             f1.pl_ipts[i1] = pti;
00546             
00547             Vector4d pt;
00548             pt.head<3>() = f2w_frame1*f1.pl_pts[i1]; // transform to RW coords
00549             pt(3) = 1.0;
00550 
00551             //            cout << "f1 point: " << pt.transpose() << endl;
00552 
00553             sba.addPoint(pt);
00554             if (ipts)
00555               ipts->push_back(-1);  // external point index
00556             
00557             sba.addStereoProj(ndi1, pti, f1.pl_kpts[i1]);
00558             sba.setProjCovariance(ndi1, pti, cv2);
00559           }
00560         
00561         // Add point-to-plane projections
00562         
00563         // First, figure out normals in world coordinate frame:
00564         Vector3d normal0 = f0.pl_normals[i0].head<3>();
00565         Vector3d normal1 = f1.pl_normals[i1].head<3>();
00566         
00567         // Then add the forward and backward projections.
00568         sba.addPointPlaneMatch(ndi0, f0.pl_ipts[i0], normal0, ndi1, f1.pl_ipts[i1], normal1);
00569         
00570         // Add covariance for just whichever projections we have.
00571         sba.setProjCovariance(ndi1, f0.pl_ipts[i0], covar);
00572         sba.setProjCovariance(ndi0, f1.pl_ipts[i1], covar);
00573       }
00574   }
00575   
00576   Vector3d getProjection(fc::Frame &frame, int index)
00577   {
00578     Vector3d proj;
00579     proj(0) = frame.kpts[index].pt.x;
00580     proj(1) = frame.kpts[index].pt.y;
00581     proj(2) = proj(0)-frame.disps[index];
00582     return proj;
00583   }
00584 
00585   // substitute point references
00586   void substPointRef(std::vector<int> &ipts, int tri0, int tri1)
00587   {
00588     for (int i=0; i<(int)ipts.size(); i++)
00589       {
00590         if (ipts[i] == tri1)
00591           ipts[i] = tri0;
00592       }
00593   }
00594                     
00595 } // end namespace vslam
00596 


vslam_system
Author(s): Kurt Konolige, Patrick Mihelich, Helen Oleynikova
autogenerated on Thu Jan 2 2014 12:12:32