这几天看了一下高博的VO程序,注释了一下,贴出来。
visual_odometry.cpp
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2016 <copyright holder> <email>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <algorithm>
#include <boost/timer.hpp>
#include "myslam/config.h"
#include "myslam/visual_odometry.h"
#include "myslam/g2o_types.h"
namespace myslam
{
VisualOdometry::VisualOdometry() :
state_ ( INITIALIZING ), ref_ ( nullptr ), curr_ ( nullptr ), map_ ( new Map ), num_lost_ ( 0 ), num_inliers_ ( 0 ), matcher_flann_ ( new cv::flann::LshIndexParams ( 5,10,2 ) )
{
num_of_features_ = Config::get<int> ( "number_of_features" );//特征点的数目
scale_factor_ = Config::get<double> ( "scale_factor" );
level_pyramid_ = Config::get<int> ( "level_pyramid" );//图像金字塔尺度
match_ratio_ = Config::get<float> ( "match_ratio" );//匹配率
max_num_lost_ = Config::get<float> ( "max_num_lost" );//丢帧的最大数量
min_inliers_ = Config::get<int> ( "min_inliers" );//最少内点数
key_frame_min_rot = Config::get<double> ( "keyframe_rotation" );//关键帧的最小旋转矩阵
key_frame_min_trans = Config::get<double> ( "keyframe_translation" );//关键帧的最小平移
map_point_erase_ratio_ = Config::get<double> ( "map_point_erase_ratio" );//map_point_擦除率
orb_ = cv::ORB::create ( num_of_features_, scale_factor_, level_pyramid_ );
}
VisualOdometry::~VisualOdometry()
{
}
bool VisualOdometry::addFrame ( Frame::Ptr frame )
{
switch ( state_ )
{
case INITIALIZING:
{
state_ = OK;
curr_ = ref_ = frame;
// extract features from first frame and add them into map
extractKeyPoints();
computeDescriptors();
addKeyFrame(); // the first frame is a key-frame
break;
}
case OK:
{
curr_ = frame;
curr_->T_c_w_ = ref_->T_c_w_;//由于把关键帧的T_c_w_给了当前帧,所以匹配的时候if ( curr_->isInFrame(p->pos_) )
//判断的是MapPoint是否在关键帧中
//如果当前帧丢失,那么下次还是用关键帧的T_c_w_赋给当前帧。
computeDescriptors();
featureMatching();
poseEstimationPnP();
if ( checkEstimatedPose() == true ) // a good estimation
{
curr_->T_c_w_ = T_c_w_estimated_;
optimizeMap();
num_lost_ = 0;
if ( checkKeyFrame() == true ) // is a key-frame
{
addKeyFrame();//如果是关键帧,ref_ = curr_;
}
}
else // bad estimation due to various reasons
{
num_lost_++;
if ( num_lost_ > max_num_lost_ )
{
state_ = LOST;
}
return false;
}
break;
}
case LOST:
{
cout<<"vo has lost."<<endl;
break;
}
}
return true;
}
//提取关键点 keypoints_curr_
void VisualOdometry::extractKeyPoints()
{
boost::timer timer;
orb_->detect ( curr_->color_, keypoints_curr_ );
cout<<"extract keypoints cost time: "<<timer.elapsed() <<endl;
}
//计算描述子 descriptors_curr_
void VisualOdometry::computeDescriptors()
{
boost::timer timer;
orb_->compute ( curr_->color_, keypoints_curr_, descriptors_curr_ );
cout<<"descriptor computation cost time: "<<timer.elapsed() <<endl;
}
//在上一帧的特征点3D坐标和当前的特征点2D坐标匹配 match_3dpts_ match_2dkp_index_
void VisualOdometry::featureMatching()
{
boost::timer timer;
vector<cv::DMatch> matches;
// select the candidates in map
Mat desp_map;//上一帧观察到的map_points_的描述子
vector<MapPoint::Ptr> candidate;//上一帧观察到的map_points_
for ( auto& allpoints: map_->map_points_ )
{
MapPoint::Ptr& p = allpoints.second;//.second保存的是值MapPoint::Ptr
// check if p in curr frame image 检查地图上的点map_points_哪一个在上一帧,因为T_c_w_是上一帧的
if ( curr_->isInFrame(p->pos_) )
{
// add to candidate
p->visible_times_++; //观察到的map_points_的可视次数加1
candidate.push_back( p );//把观察到的map_points_加入到candidate
desp_map.push_back( p->descriptor_ );//把观察到的map_points_的描述子加入到desp_map
}
}
matcher_flann_.match ( desp_map, descriptors_curr_, matches );//上一帧的map_points_的描述子和这一帧的特征点描述子匹配
// select the best matches
float min_dis = std::min_element (
matches.begin(), matches.end(),
[] ( const cv::DMatch& m1, const cv::DMatch& m2 )
{
return m1.distance < m2.distance;
} )->distance;//找匹配点的最小距离
match_3dpts_.clear();
match_2dkp_index_.clear();
for ( cv::DMatch& m : matches )
{
if ( m.distance < max<float> ( min_dis*match_ratio_, 30.0 ) )
{
match_3dpts_.push_back( candidate[m.queryIdx] );//匹配到的地图点
match_2dkp_index_.push_back( m.trainIdx );//匹配到的当前帧的索引
}
}
cout<<"good matches: "<<match_3dpts_.size() <<endl;
cout<<"match cost time: "<<timer.elapsed() <<endl;
}
//T_c_w_estimated_
void VisualOdometry::poseEstimationPnP()
{
// construct the 3d 2d observations
vector<cv::Point3f> pts3d;
vector<cv::Point2f> pts2d;
for ( int index:match_2dkp_index_ )
{
pts2d.push_back ( keypoints_curr_[index].pt );
}//利用是否在匹配帧中来判断是否添加进去
for ( MapPoint::Ptr pt:maref_tch_3dpts_ )
{
pts3d.push_back( pt->getPositionCV() );
}
Mat K = ( cv::Mat_<double> ( 3,3 ) <<
ref_->camera_->fx_, 0, ref_->camera_->cx_,
0, ref_->camera_->fy_, ref_->camera_->cy_,
0,0,1
);
Mat rvec, tvec, inliers;
cv::solvePnPRansac ( pts3d, pts2d, K, Mat(), rvec, tvec, false, 100, 4.0, 0.99, inliers );
num_inliers_ = inliers.rows;
cout<<"pnp inliers: "<<num_inliers_<<endl;
T_c_w_estimated_ = SE3 (
SO3 ( rvec.at<double> ( 0,0 ), rvec.at<double> ( 1,0 ), rvec.at<double> ( 2,0 ) ),
Vector3d ( tvec.at<double> ( 0,0 ), tvec.at<double> ( 1,0 ), tvec.at<double> ( 2,0 ) )
);
// using bundle adjustment to optimize the pose
typedef g2o::BlockSolver<g2o::BlockSolverTraits<6,2>> Block;// 类模板 每个误差项优化变量维度为6,误差值维度为2
Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>();// 线性方程求解器
Block* solver_ptr = new Block ( linearSolver );// 矩阵块求解器
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg ( solver_ptr );// 梯度下降方法
g2o::SparseOptimizer optimizer;// 图模型
optimizer.setAlgorithm ( solver );// 设置求解器
// 往图中增加顶点,一帧只有一个位姿
g2o::VertexSE3Expmap* pose = new g2o::VertexSE3Expmap();
pose->setId ( 0 );
pose->setEstimate ( g2o::SE3Quat ( //从SE3转g2o::SE3Quat
T_c_w_estimated_.rotation_matrix(), T_c_w_estimated_.translation()
));//直接估计T_c_w不是T_c_r
optimizer.addVertex ( pose );
// edges 每个特征点都对应一个重投影误差,也就有一个边
for ( int i=0; i<inliers.rows; i++ )
{
int index = inliers.at<int> ( i,0 );//内点在匹配点的索引
// 3D -> 2D projection
EdgeProjectXYZ2UVPoseOnly* edge = new EdgeProjectXYZ2UVPoseOnly();
edge->setId ( i );
edge->setVertex ( 0, pose );
edge->camera_ = curr_->camera_.get();//.get()得到指针
edge->point_ = Vector3d ( pts3d[index].x, pts3d[index].y, pts3d[index].z );//匹配点的MapPoint的世界坐标系
edge->setMeasurement ( Vector2d ( pts2d[index].x, pts2d[index].y ) );//匹配点的当前帧像素坐标
edge->setInformation ( Eigen::Matrix2d::Identity() );
optimizer.addEdge ( edge );
// set the inlier map points
match_3dpts_[index]->ref_matched_times_++;
}
optimizer.initializeOptimization();
optimizer.optimize ( 10 );
T_c_w_estimated_ = SE3 (
pose->estimate().rotation(),
pose->estimate().translation()
);
cout<<"T_c_w_estimated_: "<<endl<<T_c_w_estimated_.matrix()<<endl;
}
bool VisualOdometry::checkEstimatedPose()
{
// check if the estimated pose is good
if ( num_inliers_ < min_inliers_ )
{
cout<<"reject because inlier is too small: "<<num_inliers_<<endl;
return false;
}
// if the motion is too large, it is probably wrong
SE3 T_r_c = ref_->T_c_w_ * T_c_w_estimated_.inverse();//判断的是参考帧和当前帧的运动是否剧烈,所以转化为T_r_c
Sophus::Vector6d d = T_r_c.log();
if ( d.norm() > 5.0 )
{
cout<<"reject because motion is too large: "<<d.norm() <<endl;
return false;
}
return true;
}
bool VisualOdometry::checkKeyFrame()
{
SE3 T_r_c = ref_->T_c_w_ * T_c_w_estimated_.inverse();
Sophus::Vector6d d = T_r_c.log();
Vector3d trans = d.head<3>();
Vector3d rot = d.tail<3>();
if ( rot.norm() >key_frame_min_rot || trans.norm() >key_frame_min_trans )
return true;
return false;
}
void VisualOdometry::addKeyFrame()
{
if ( map_->keyframes_.empty() )
{
// first key-frame, add all 3d points into map
for ( size_t i=0; i<keypoints_curr_.size(); i++ )
{
double d = curr_->findDepth ( keypoints_curr_[i] );
if ( d < 0 )
continue;
Vector3d p_world = ref_->camera_->pixel2world (
Vector2d ( keypoints_curr_[i].pt.x, keypoints_curr_[i].pt.y ), curr_->T_c_w_, d
);
Vector3d n = p_world - ref_->getCamCenter(); //MapPoint点世界坐标-参考帧相机坐标系中心
n.normalize(); //归一化
MapPoint::Ptr map_point = MapPoint::createMapPoint(
p_world, n, descriptors_curr_.row(i).clone(), curr_.get()//shared_ptr .get()返回保存的指针
);
map_->insertMapPoint( map_point );
}
}
map_->insertKeyFrame ( curr_ );
ref_ = curr_;//在关键帧时更新参考帧
}
void VisualOdometry::addMapPoints()
{
// add the new map points into map
vector<bool> matched(keypoints_curr_.size(), false);
//match_2dkp_index_是新来的当前帧跟地图匹配时,好的匹配到的关键点在keypoins数组中的索引
//在这里将已经匹配的keypoint索引置为true
for ( int index:match_2dkp_index_ )
matched[index] = true;
for ( int i=0; i<keypoints_curr_.size(); i++ )
{
if ( matched[i] == true )
continue;
double d = ref_->findDepth ( keypoints_curr_[i] );
if ( d<0 )
continue;
Vector3d p_world = ref_->camera_->pixel2world (
Vector2d ( keypoints_curr_[i].pt.x, keypoints_curr_[i].pt.y ),
curr_->T_c_w_, d
);
Vector3d n = p_world - ref_->getCamCenter();
n.normalize();
MapPoint::Ptr map_point = MapPoint::createMapPoint(
p_world, n, descriptors_curr_.row(i).clone(), curr_.get()
);
map_->insertMapPoint( map_point );
}
}
//维持地图点的大小,保证下一帧
void VisualOdometry::optimizeMap()
{
// remove the hardly seen and no visible points
for ( auto iter = map_->map_points_.begin(); iter != map_->map_points_.end(); )
{
if ( !curr_->isInFrame(iter->second->pos_) )//如果map_points_不在当前帧,那么移除
{
iter = map_->map_points_.erase(iter);
continue;
}
float match_ratio = float(iter->second->matched_times_)/iter->second->visible_times_;
//定义匹配率,用匹配次数/可见次数,匹配率过低说明经常见但是没有几次匹配,描述子错误。
if ( match_ratio < map_point_erase_ratio_ )
{
iter = map_->map_points_.erase(iter);
continue;
}
//判断视差角
double angle = getViewAngle( curr_, iter->second );
if ( angle > M_PI/6. )
{
iter = map_->map_points_.erase(iter);
continue;
}
if ( iter->second->good_ == false )
{
// TODO try triangulate this map point
}
iter++;
}
// 一般情况是运动幅度过大了,跟之前的帧没多少交集了,所以增加一下。
if ( match_2dkp_index_.size()<100 )
addMapPoints();
if ( map_->map_points_.size() > 1000 )
{
// TODO map is too large, remove some one
map_point_erase_ratio_ += 0.05;
}
else
map_point_erase_ratio_ = 0.1;
cout<<"map points: "<<map_->map_points_.size()<<endl;
}
double VisualOdometry::getViewAngle ( Frame::Ptr frame, MapPoint::Ptr point )
{
Vector3d n = point->pos_ - frame->getCamCenter();//MapPoint点世界坐标-当前帧相机坐标系中心
n.normalize();//归一化
//返回一个角度,acos()为反余弦,
//向量*乘为:a*b=|a||b|cos<a,b>
//所以单位向量的*乘得到的是两个向量的余弦值,再用acos()即得到角度,返回
//物理意义就是得到参考帧从世界坐标到相机中心和当前帧从世界坐标到相机中心,视角差的角度。
return acos( n.transpose()*point->norm_ );
}
}
g2o_types.cpp
#include "myslam/g2o_types.h"
namespace myslam
{
void EdgeProjectXYZRGBD::computeError()
{
const g2o::VertexSBAPointXYZ* point = static_cast<const g2o::VertexSBAPointXYZ*> ( _vertices[0] );
const g2o::VertexSE3Expmap* pose = static_cast<const g2o::VertexSE3Expmap*> ( _vertices[1] );
_error = _measurement - pose->estimate().map ( point->estimate() );
}
void EdgeProjectXYZRGBD::linearizeOplus()
{
g2o::VertexSE3Expmap* pose = static_cast<g2o::VertexSE3Expmap *> ( _vertices[1] );
g2o::SE3Quat T ( pose->estimate() );
g2o::VertexSBAPointXYZ* point = static_cast<g2o::VertexSBAPointXYZ*> ( _vertices[0] );
Eigen::Vector3d xyz = point->estimate();
Eigen::Vector3d xyz_trans = T.map ( xyz );
double x = xyz_trans[0];
double y = xyz_trans[1];
double z = xyz_trans[2];
_jacobianOplusXi = - T.rotation().toRotationMatrix();
_jacobianOplusXj ( 0,0 ) = 0;
_jacobianOplusXj ( 0,1 ) = -z;
_jacobianOplusXj ( 0,2 ) = y;
_jacobianOplusXj ( 0,3 ) = -1;
_jacobianOplusXj ( 0,4 ) = 0;
_jacobianOplusXj ( 0,5 ) = 0;
_jacobianOplusXj ( 1,0 ) = z;
_jacobianOplusXj ( 1,1 ) = 0;
_jacobianOplusXj ( 1,2 ) = -x;
_jacobianOplusXj ( 1,3 ) = 0;
_jacobianOplusXj ( 1,4 ) = -1;
_jacobianOplusXj ( 1,5 ) = 0;
_jacobianOplusXj ( 2,0 ) = -y;
_jacobianOplusXj ( 2,1 ) = x;
_jacobianOplusXj ( 2,2 ) = 0;
_jacobianOplusXj ( 2,3 ) = 0;
_jacobianOplusXj ( 2,4 ) = 0;
_jacobianOplusXj ( 2,5 ) = -1;
}
void EdgeProjectXYZRGBDPoseOnly::computeError()
{
const g2o::VertexSE3Expmap* pose = static_cast<const g2o::VertexSE3Expmap*> ( _vertices[0] );
_error = _measurement - pose->estimate().map ( point_ );
}
void EdgeProjectXYZRGBDPoseOnly::linearizeOplus()
{
g2o::VertexSE3Expmap* pose = static_cast<g2o::VertexSE3Expmap*> ( _vertices[0] );
g2o::SE3Quat T ( pose->estimate() );
Vector3d xyz_trans = T.map ( point_ );
double x = xyz_trans[0];
double y = xyz_trans[1];
double z = xyz_trans[2];
_jacobianOplusXi ( 0,0 ) = 0;
_jacobianOplusXi ( 0,1 ) = -z;
_jacobianOplusXi ( 0,2 ) = y;
_jacobianOplusXi ( 0,3 ) = -1;
_jacobianOplusXi ( 0,4 ) = 0;
_jacobianOplusXi ( 0,5 ) = 0;
_jacobianOplusXi ( 1,0 ) = z;
_jacobianOplusXi ( 1,1 ) = 0;
_jacobianOplusXi ( 1,2 ) = -x;
_jacobianOplusXi ( 1,3 ) = 0;
_jacobianOplusXi ( 1,4 ) = -1;
_jacobianOplusXi ( 1,5 ) = 0;
_jacobianOplusXi ( 2,0 ) = -y;
_jacobianOplusXi ( 2,1 ) = x;
_jacobianOplusXi ( 2,2 ) = 0;
_jacobianOplusXi ( 2,3 ) = 0;
_jacobianOplusXi ( 2,4 ) = 0;
_jacobianOplusXi ( 2,5 ) = -1;
}
//重投影误差
void EdgeProjectXYZ2UVPoseOnly::computeError()
{//顶点数组中取出顶点,转换成位姿指针类型
const g2o::VertexSE3Expmap* pose = static_cast<const g2o::VertexSE3Expmap*> ( _vertices[0] );
//误差计算,测量值减去估计值,也就是重投影误差
_error = _measurement - camera_->camera2pixel (
pose->estimate().map(point_) );
}
void EdgeProjectXYZ2UVPoseOnly::linearizeOplus()
{ // 重投影误差的雅克比矩阵在书中P164页式7.45
g2o::VertexSE3Expmap* pose = static_cast<g2o::VertexSE3Expmap*> ( _vertices[0] );
g2o::SE3Quat T ( pose->estimate() );
Vector3d xyz_trans = T.map ( point_ );
//求得是变换后的世界坐标系点
double x = xyz_trans[0];
double y = xyz_trans[1];
double z = xyz_trans[2];
double z_2 = z*z;
_jacobianOplusXi ( 0,0 ) = x*y/z_2 *camera_->fx_;
_jacobianOplusXi ( 0,1 ) = - ( 1+ ( x*x/z_2 ) ) *camera_->fx_;
_jacobianOplusXi ( 0,2 ) = y/z * camera_->fx_;
_jacobianOplusXi ( 0,3 ) = -1./z * camera_->fx_;
_jacobianOplusXi ( 0,4 ) = 0;
_jacobianOplusXi ( 0,5 ) = x/z_2 * camera_->fx_;
_jacobianOplusXi ( 1,0 ) = ( 1+y*y/z_2 ) *camera_->fy_;
_jacobianOplusXi ( 1,1 ) = -x*y/z_2 *camera_->fy_;
_jacobianOplusXi ( 1,2 ) = -x/z *camera_->fy_;
_jacobianOplusXi ( 1,3 ) = 0;
_jacobianOplusXi ( 1,4 ) = -1./z *camera_->fy_;
_jacobianOplusXi ( 1,5 ) = y/z_2 *camera_->fy_;
}
}