文章目錄
內容總覽:VO框架的實現,對書上的代碼進行理解,寫了一部分註釋,很久以前看的了,如果有需要再回頭再捋一遍,解決解決當時疑惑的點
程序框架/數據結構
庫/目錄結構
bin: 存放可執行的二進制
include/myslam: 存放slam頭文件.h
src: 存放源代碼文件,主要是.cpp
test: 存放測試文件,也是.cpp
lib: 存放編譯好的庫文件
config: 存放配置文件
cmake_modules: 第三方庫的cmake文件,在使用如ceres等庫時使用
這裏都是自己寫的頭文件,不容易和別的庫混淆
基本數據結構
幀:好理解
路標:特徵點,可以放到地圖中進行匹配來估計位姿
配置文件:一些常用的不變的量,如內參,比例等
座標變換:各種座標之間轉換直接定義一個大類
Camera類
頭文件:
//單孔RGBDmodel
class Camera
{
public:
typedef std::shared_ptr<Camera> Ptr; //定義了一個智能指針類型,可用於傳遞參數
float fx_, fy_, cx_, cy_, depth_scale_; //內參
Camera();
Camera( float fx, float fy, float cx, float cy, float depth_scale=0):
fx_( fx ), fy_( fy ), cx_( cx ), cy_( cy ), depth_scale_( depth_scale )
{}
//座標變換 ,2 表示two = to,SE3來表達相機位姿
Vector3d world2camera( const Vector3d& p_w, const SE3& T_c_w );
Vector3d camera2world( const Vector3d& p_c, const SE3& T_c_w );
Vector2d camera2pixel( const Vector3d& p_c );
Vector3d pixel2camera( const Vector2d& p_p, double depth = 1 );
Vector3d pixel2world( const Vector2d& p_p, const SE3& T_c_w, double depth = 1 );
Vector2d world2pixel( const Vector3d& p_w,const SE3& T_c_w );
};
功能理解:
1.儲存相機的內參和外參,座標轉換,外參用參數的形式輸入。
2.先定義了一個智能指針,然後是內參,構造函數初始化深度爲0
3.座標變換都用的李代數
4.類都被命名空間namespace myslam包裹,可以防止不小心定義出別的庫的同名函數
實現方法.cpp:
namespace myslam //這東西不套上,代碼補全出不來
{
Camera::Camera()
{
}
Vector3d Camera::world2camera( const Vector3d & p_w, const SE3& T_c_w )
{
return T_c_w * p_w;
}
Vector3d Camera::camera2world( const Vector3d& p_c, const SE3& T_c_w )
{
return T_c_w.inverse() * p_c;
}
Vector2d Camera::camera2pixel( const Vector3d& p_c )
{
return Vector2d(
fx_ * p_c( 0, 0 ) / p_c( 2, 0 ) + cx_ ,
fy * p_c( 1, 0 ) / p_c( 2, 0 ) + cy_
);
}
Vector2d Camera::pixel2camera( const Vector2d&p_p, double depth)
{
return Vector3d(
( p_p( 0, 0 ) - cx_ ) * depth/fx_,
( p_p( 1, 0 ) - cy_ ) * depth/fy_,
depth
);
}
Vector2d Camera::world2pixel( const Vector3d&p_w, const SE3&T_c_w)
{
return camera2pixel( world2camera( p_w, T_c_w ) );
//利用了兩個變換一起
}
Vector3d Camera::pixel2world( const Vector2d&p_p, const SE3&T_c_w, double depth)
{
return camera2world( pixel2camera( p_p, depth ), T_c_w );
}
}
就是些座標變化的實現
Frame類
頭文件:
class Frame
{
public:
typedef std::shared_ptr<Frame> Ptr;
unsigned long id_; //這一幀的編號
double time_stamp_; //被記錄的時間,用於後期的匹配,時間戳
SE3 T_c_w_; //變換矩陣
Camera::Ptr camera; //針孔攝像機模型
Mat color_, depth_; //圖像的顏色和深度矩陣
public: //數據成員
Frame() ;
Frame( long id, double time_stamp=0, SE3 T_c_w = SE3(), Camera::Ptr camera=nullptr,
Mat color=Mat(), Mat depth=Mat() );
~Frame() ;
//製造Frame函數
static Frame::Ptr createFrame();
//尋找給定關鍵點的深度
double findDepth( const cv::KeyPoint& kp );
//獲取攝像機中心
Vector3d getCamCenter() const;
//檢查點在不在這幀上
bool isInFrame( const Vector3d& pt_world );
};
//路標點類,用當前幀提取到的特徵點和地圖中的特徵點匹配,來估計相機運動,需要存儲描述子
//記錄並存儲點被觀測和匹配的次數,作爲評價好壞的指標
class MapPoint
{
public:
typedef shared_ptr<MapPoint> Ptr;
unsigned long_id; //ID
Vector3d pos_; //世界位置
Vector3d norm_; //視野的範數的方向
Mat descriptor_; //匹配的描述子
int observed_times_; //匹配次數???
int correct_times_; //估計次數???
MapPoint();
MapPoint( long id, Vector3d position, Vector3d norm );
//建造函數
static MapPoint::Ptr createMapPoint();
};
幀的自我屬性:
編號,時間戳,攝像機模型,變換矩陣,圖像的顏色和深度
功能:
1.創建以一個Frame,返回帶編號的Frame指針
2.獲得攝像機中心(T的轉置的位移向量)
3.尋找給定關鍵點的深度(深度圖已有)
4.檢測某點是否在該幀上(根據攝像機座標和像素座標判斷)
MapPoint類
class MapPoint
{
public:
typedef shared_ptr<MapPoint> Ptr;
unsigned long_id; //ID
Vector3d pos_; //世界位置
Vector3d norm_; //視野的範數的方向
Mat descriptor_; //匹配的描述子
int observed_times_; //觀測次數
int correct_times_; //匹配次數
MapPoint();
MapPoint( long id, Vector3d position, Vector3d norm );
//建造函數,都是空的,就是帶的編號
static MapPoint::Ptr createMapPoint();
};
路標點類,會估計點的世界座標,用當前幀提取到的特徵點和地圖中的特徵點匹配,來估計相機運動,需要存儲描述子。
記錄並存儲點被觀測和匹配的次數,作爲評價好壞的指標
Map類
class Map
{
public:
typedef shared_ptr<Map> Ptr;
//需要隨即訪問,還需要隨時插入和刪除
unordered_map<unsigned long, MapPoint::Ptr > map_points_; //全部路標
unordered_map<unsigned long, Frame::Ptr > keyframes_; //全部關鍵幀
Map() {}
void insertKeyFrame( Frame::Ptr frame );
void insertMapPoint( MapPoint::Ptr map_point );
};
功能:管理所有路標點,添加、刪除等操作
Config類
頭文件:
class Config
{
private:
static std::shared_ptr<Config> config_;
cv::FileStorage file_; //可以讀取YAML文件,存放各種參數,file_[key] 調用
Config () {} //寫成單件模式,構造函數爲私有
public:
~Config(); //析構函數,銷燬時關閉函數
//設置一個新配置文件,創建對象並讀取參數文件,析構函數也在其中
static void setParameterFile( const std::string& filename );
//載入參數值,用的是模板函數,爲的是可以獲取任意類型的參數值
template< typename T >
static T get( const std::string& key )
{
return T( Config::config_->file_[key] )l
}
};
單件模式singleton,在程序任意地方都可以隨時提供參數值
功能:設置、讀取參數文件,因爲有多種類型,所以用模板函數
實現函數:
void Config::setParameterFile( const std::string& filename )
{
if ( config_ == nullptr )
config_ = shared_ptr<Config>( new Config ); //沒有指針就定義一個
config_->file_ = cv::FileStorage( filename.c_str(), cv::FileStorage::READ ); //給智能指針的對象指向賦值
if ( config_->file_.isOpened() == false )
{
//如果文件false,說明文件不存在//是不是計數器起效?
std::cerr<<" parameter file "<<filename<<" does not exise."<<std::end;
config_->file_.release();
return ;
}
}
Config::~Config() //析構函數,關閉文件
{
if ( file.isOpened() )
file_.release();
}
shared_ptr<Config> Config::config_ = nullptr;
我們使用智能指針,優點是可以自動析構
讀取的文件儲存在FileStorage類中
使用範例:
1.在參數文件加入:“Camera.fx:500”。
2. myslam::Config::setParameterFile(“parameter.yaml”);
3. double fx=myslam::Config::get(“Camera.fx”);
基本的VO,特徵提取和匹配
兩兩幀
設定參考幀和當前幀,以參考幀爲座標系,把當前幀和參考幀進行特徵匹配,並估計運動關係
思路
/* 思路:每次兩個幀比較,一個參考幀(上一幀),一個當前幀,二者進行特徵匹配(以參考幀爲座標系),
* 並估計運動關係,得 T_c_w = T_c_r * T_r_w
* 當前相對世界座標系的變化 = 當前相對參考的座標變換 * 參考相對世界座標系的變換
* 求VO流程:
* 1.對新來的當前幀,提取關鍵點和描述子
* 2.如果系統沒初始化,就把當前幀當作參考幀,返回1
* 3.估計參考幀和當前幀的運動
* 4.如果成功,就把當前幀設爲參考幀,返回1
* 5.如果不成功,計算連續不成功次數,超過某個次數就置VO爲丟失狀態,
* 算法退出。沒超過該次數,就返回1
*/```
## 算法
```cpp
class VisualOdometry
{
public:
typedef shared_ptr<VisualOdometry> Ptr;
enum VOState { //狀態,是否初始化,是否丟失
INITIALIZING=-1,
OK = 0,
LOST
};
VOState state_; //目前的VO狀態,initialize or ok or lost
Map::Ptr map_; //裝有全部的幀和特徵點??
Frame::Ptr ref_: //參考幀
Frame::Ptr curr_; //當前幀
cv::Ptr<cv::ORB> orb_; //orb檢測和計算方式
vector<cv::Point3f> pts_3d_ref_; //參考幀中的3d點
vector<cv::KeyPoint> keypoints_curr_; //現在幀的特徵點
Mat descriptors_curr;
Mat descriptors_ref;
vector<cv::DMatch> feature_matched_;
SE3 T_c_r_estimated_; //參考到現在的變換矩陣估計
int num_inliers_; //內點
int num_lost_;
//parameters
int num_of_features_;
double scale_factor_;
int level_pyramid_; //number of pyramid levels
float match_ratio_; //ratio for selecting good matches
int max_num_lost_;
int min_inliers_;
double key_frame_min_rot; // minimal rotation of tow key-frames
double key_frame_min_trans;
public:
VisualOdometry();
~VisualOdometry();
bool addFrame( Frame::Ptr frame );
protected:
//inner operation
void extractKeyPoints();
void computeDescriptors();
void featureMatching();
void poseEstimationPnP();
void setRef3DPoints();
void addKeyFrame();
bool checkEstimatedPose();
bool checkKeyFrame();
};
參數:
全部的幀和特徵點,估計變換矩陣,變換最小程度限制
功能:
1.增加幀
2.提取特徵點
3.計算描述子
4.特徵匹配
5.pnp估計位姿
6.設置參考3d點
7.檢驗估計點
3.檢驗關鍵幀
bool myslam::VisualOdometry::addFrame(Frame::Ptr frame)
{
switch( state_ )
{
case INITALIZING:
{
state_ = OK;
curr_ = ref_ = frame; //第一個
map_->insertKeyFrame( frame );
//from first frame,ref and curr
extractKeyPoints();
computeDescriptors();
setRef3DPoints();
break;
}
case OK:
{ //直接用類的參數,真方便
curr_ = frame;
extractKeyPoints();
computeDescriptors();
featureMatching();
poseEstimationPnP();
//checkEstimatedPose中有內點數量和移動最大距離限制
if ( checkEstimatedPose() == true ) //a good //poseEstimationPnP
{
curr_->T_c_w_ = T_c_r_estimated_ * ref_->T_c_w_;
ref_ = curr_;
setRef3DPoints();
num_lost = 0; //set 0
if ( checkKeyFrame() == true )
{
addKeyFrame();
}
}
else
{
num_lost_++ ; //以每組(ref and curr)爲單位
if ( num_lost_ > max_num_lost_ )
{
state_ = LOST;
}
return false;
}
break;
}
case LOST:
{
cout<<"vo has lost."<<endl;
break;
}
}
return true;
}
新增Frame,內可判斷是否lost,內置各種函數,如獲取特徵點,計算描述子等,算完直接加入到新Frame屬性中
可估計位姿,自帶檢驗算法,檢驗內點數量和最大移動距離限制判斷是一個好的估計位姿
如果是個關鍵幀,就加入到關鍵幀中
根據丟失幀數量判斷是否丟幀
運行
if ( argc != 2 )
{
cout<<"usage: run_vo parameter_file"<<endl;
return 1;
}
myslam::Config::setParameteFile( argv[1] );
myslam::VisualOdometry::Ptr vo ( new myslam::VisualOdometry );
string dataset_dir = myslam::Config::get<string> ( "dataset_dir" );
cout<<"dataset: "<<dataset_dir<<endl;
ifstream fin (dataset_dir + "/associate.txt" );
if ( !fin )
{
cout<<"please generate the associate file called associate.txt!"<<endl;
return 1;
}
vector<string> rgb_files, depth_files;
vector<double> rgb_times, depth_times;
while( !fin.eof() ) //讀取各種數據
{
string rgb_time, rgb_file, depth_time, depth_file;
fin>>rgb_time>>rgb_file>>depth_time>>depth_file;
rgb_times.push_back( atof( rgb_time.c_str() ) );
depth_times.push_back( atof( depth_time.c_str() ) );
rgb_files.push_back( dataset_dir + "/" + rgb_file );
depth_files.push_back( dataset_dir + "/" + depth_file );
if ( fin.good() == false )
break;
}
1.準備工作,讀取各種數據
myslam::Camera::Ptr camera( new myslam::Camera );
//visualization,初始化並設置各種關於世界座標系和攝像機座標系的信息
cv::viz::Viz3d vis("Visual Odometry");
cv::viz::WCoordinateSystem world_coor(1.0), camera_coor(0.5); //座標系設置,此處可能是比例
cv::Point3d cam_pos( 0, -1.0, -1.0 ), camera_focal_point(0,0,0), cam_y_dir(0,1,0); //一些重要的點的位置
cv::Affine3d cam_pose = cv::viz::makeCameraPose(cam_pos, cam_focal_point, cam_y_dir ); //what???
vis.setViewerPose( cam_pose ); //視野設置???,應該就是這相機前面視野
//渲染和顯示???
world_coor.setRenderingProperty(cv::viz::LINE_WIDTH, 2.0); //渲染屬性
camera_coor.setRenderingProperty(cv::viz::LINE_WIDTH,1.0);
vis.showWidget("World", world_coor);
vis.showWidget("Camera", camera_coor);
//讀取幀信息,並添加到vo裏計算
cout<<"real total"<<rgb_files.size() <<" entried" <<endl;
for( int i=0; i<rgb_files.size(); i++)
{
Mat color = cv::imread( rgb_files[i] );
Mat depth = cv:imread( depth_files[i], -1);
if ( color.data == nullptr || depth.data==nullptr )
break;
myslam::Frame::Ptr pFrame = myslam::Frame::createFrame();
pFrame->camera_ = camera;
pFrame->color_ = color;
pFrame->depth_ = depth;
pFrame->time_stamp_ = rgb_times[i];
boost::timer timer;
vo->addFrame( pFrame );
cout<<"VO cost time: "<<timer.elapsed()<<endl;
if ( vo->state_ == myslam::VisualOdometry::LOST )
break;
SE3 Tcw = pFrame->T_c_w.inverse();
//show th map and the camera cam_pose
//M構造分兩部分
cv::Affine3d M(
cv::Affine3d::Mat3(
Tcw.rotation_matrix()(0,0),Tcw.rotation_matrix()(0,1),Tcw.rotation_matrix()(0,2),
Tcw.rotation_matrix()(1,0),Tcw.rotation_matrix()(1,1),Tcw.rotation_matrix()(1,2),
Tcw.rotation_matrix()(2,0),Tcw.rotation_matrix()(2,1),Tcw.rotation_matrix()(2,2)
),
cv::Affine3d::Vec3(
Tcw.translation()(0,0),Tcw.translation()(1,0),Tcw.translation()(2,0)
)
);
cv::imshow("image", color);
cv::waitKey(1);
vis.setWidgetPose("Camera", M);
vis.spinOnce(1, false);
}
2.初始化視覺里程計,相機的內參,映射,視野設置和相機映射相同
3.渲染屬性
4.讀取幀的信息,放入VO中計算
(1)創建幀,給幀輸入圖片的各種信息
(2)將創建的幀的屬性放入到addFrame函數中計算
5.得出矩陣M
存在的問題
/*效果並不好
* 1.只用RANSAC容易受噪聲影響 solve:用RANSAC作初始值,再用非線性優化求最優值
* 2.特徵點多是邊緣,所以特徵點的深度不準確 solve:將特徵點也優化
* 3.幀與幀的比較太依賴幀,不好的幀甚至會導致漂移 solve:讓當前幀與地圖匹配
* 4.特徵點的提取和匹配耗時太多 solve:直接法,光流法```
# 優化PnP的結果
## 方法
RANSCA提供初始值,pnp求相機位姿,g2o優化(最小化衝投影誤差)相機位姿
## g2o
**頭文件**:
```cpp
class EdgeProjectXYZ2UVPoseOnly: public g2o::BaseUnaryEdge<2, Eigen::Vector2d, g2o::VertexSE3Expmap>
{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
virtual void computeError();
virtual void linearizeOplus();
virtual bool read( std::istream& in ) {}
virtual bool write(std::ostream& os) const {};
Vector3d point_; //方便計算重投影誤差和呀可比
Camera* camera_;
};```
**實現函數**:
```cpp
void EdgeProjectXYZ2UVPoseOnly::computeError()
{
const g2o::VertexSE3Expmap * pose = static_cast<const g2o::VertexSE3Expmap*> ( _vertices[0] );
_error = _measure - camera->camera2pixel(
pose->estimate().map(point_) //重投影誤差
);
}
void EdgeProjectXYZ2UVPoseOnly::linearizeOplus()
//jacobin,優化一個位姿。是一個一元邊
{
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_;
}
修改PnP
void visual_odometry::poseEstimationPnP()
{
typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 2> Block;
Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>();
Block* solver_ptr = new Block( linearSolver );
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg( solver_ptr );
);
//稀疏優化,設置優化器,優化方法是LM
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm( solver );
//設置頂點,即要優化的向量,即相機位姿
g2o::VertexSE3Expmap * pose = new g2o::VertexSE3Expmap();
pose->setId( 0 );
pose->setEstimate( g2o::SE3Quat(
T_c_r_estimated_.rotation_matrix(), T_c_r_estimated_.translation()
));
optimizer.addVertex( pose );
//edges,設RANSAC已經提取完Inline點,現對這些點進行g2o優化
for ( int i=0; i<inliers.rows; i++)
{
int index = inliers.at<int>(i,0);
//3D->2D映射,設置邊的一系列
EdgeProjectXYZ2UVPoseOnly * edge = new EdgeProjectXYZ2UVPoseOnly();
edge->setId(i); //設置邊的編號
edge->setVertex(0, pose); //設置邊連接的頂點,就是相機位姿
edge->camera_ = curr_->camera_.get();
edge->point_ = Vector3d( pts3d[Index].x, pts3d[index].y, pts3d[index].z);//什麼點?
edge->setMeasurement( Vector2d(pts2d[Index].x, pts2d[index].y) ); //測量值
//信息矩陣就是協方差的逆,表示每條邊誤差分擔多少,單位矩陣就是誤差分攤
edge->setInformation(Eigen::Matrix2d::Identity() );
optimizer.addEdge( edge );
}
optimizer.initializeOptimization();
//10是迭代次數
optimizer.optimize(10);
T_c_r_estimated_ = SE3(
pose->estimate().rotation(),
pose->estimate().translation()
);
//上面都是對pose進行優化,先給點(來自RANSAC),然後設爲頂點,用邊優化,
//優化完了再賦值給初始值向量
以PnP結果爲初始值,調用g2o優化
1.先設置優化器
2.設置頂點,各種信息及編號
optimizer.addVertex( pose );
3.設置邊,將邊與頂點連接
optimizer.addEdge( edge );
局部地圖
思路
將VO匹配到的特徵點放到地圖中,並將當前幀和地圖點進行匹配,計算位姿
這樣每一幀都能有所貢獻
MapPoint
class MapPoint
{
public:
typedef shared_ptr<MapPoint> Ptr;
unsigned long_id_;
static unsigned long factory_id_; //建造id
bool good_;
Vector3d pos_; //position in world
Vector3d norm_; //normal of viewing direction
Mat descriptor_; //descriptor for matching
list<Frame*> observed_frames_; //key-frames that can observe this MapPoint
int matched_times_; //inliner matched_times_
int visible_times_; //being visible in current frame
MapPoint(); //對地圖點的構建,每次都有元素進入
MapPoint(
unsigned long id,
const Vector3d& position,
const Vector3d& norm,
Frame* frame = nullptr,
const Mat& descriptor=Mat()
);
inline cv::Point3f getPositionCV() const{
return cv::Point3f( pos_(0,0), pos_(1,0), pos_(2,0) );
}
static MapPoint::Ptr createMapPoint(); //新建,比上面少個id
static MapPoint::Ptr createMapPoint(
const Vector3d& pos_world,
const vector3d& norm_,
const Mat& descriptor,
Frame* frame
);
此處的地圖:將各幀的特徵點緩存到一處,構成的特徵點的集合,稱爲地圖
類的屬性:
關鍵幀,匹配描述子,匹配、觀測次數,位置
VisuOdometry類的修改
添加關鍵幀:
void VisualOdometry::addKeyFrame()
{
if ( map_->keyframe_.empty() ) //if not have keyframe
{
//first frame , add all 3d point 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(); //world * cameracenter ???
n.normalize();//正則化,是對焦距的處理?
MapPoint::Ptr map_point = MapPoint::createMapPoint(
p_world, n, descriptors_curr_.row(i).clone(), curr_.get()
);
map_->insertMapPoint( map_point );
}
}
map_->insertKeyFrame( curr_ ); //current frame match with mappoint,compute pose
ref_ = curr_;
}
在提取第一幀特徵帶你之後,將第一幀所有的特徵點放入地圖中
1.根據像素座標算出世界座標,深度在camera類裏有
2.算出normalize,調用creatMapPoint函數創造特徵點
3.將創造出來的MapPoint加入到map中
4.添加關鍵幀
map優化(匹配點)增刪:
void VisualOdometry::optimizeMap()
{
//remove the hardly seen and no visible points
for( auto iter = map_->map_point_.begin(); iter !=map_->map_point_.end(); )
{
if ( !curr_->isInFrame(iter->second->pos_) ) //不在幀裏就去掉
{
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_point_.erase(iter);
continue;
}
if ( iter->second->good_ == false ) //三角測量?
{
}
iter++;
}
if ( match_2dkp-index_.size() < 100 ) //匹配點數量不夠的話,加一些地圖點
addMapPoints();
if( map_->map_points_.size() > 1000 ) //太多了就刪一個,同時要增加刪除的比例
{
iter = map_->map_point_.erase(iter);
map_point_erase_ratio_ += 0.05;
}
else
map_point_erase_ratio_ = 0.1;
cout << "map points : " << map_->map_points_.size() << endl;
}
在後續的幀中,使用OptimizeMap對地圖進行優化
刪除:
1.不在幀裏的
2.匹配比例小,刪一部分
3.轉動角度過大
4.匹配點太多,大於閾值。同時增加刪除的比例
增加:
1.匹配點數量太少,少於閾值
特徵匹配:
void VisualOdometry::featureMatching()
{
boost::timer timer;
vector<cv::DMatch> matches; //存放所有匹配到的點
//select the candidates in map
Mat desp_map; //構建地圖的描述子矩陣
vector<MapPoint::Ptr> candidate;
//allpoint
for ( auto& allpoints: map_->map_points_ )
{
MapPoint::Ptr& p = allpoints.second;
//check if p in curr frame image
//在視野內就入選
if( curr_->isInFrame(p->pos_) )
{
//add to candidate
p->visible_times_++;
candidate.push_back(p);
desp_map.push_back( p->descriptor_ );
}
}
matches_flann_.match( desp_map, descriptors_curr, matches);
//select the best matches,distance is min
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] ); //query 測試下標
match_2dkp_index_.push_back( m.trainIdx ); //樣本圖像特徵帶你描述符下標
}
}
cout<<" good matched: " << match_3dpts_.size() << endl;
cout << " mathc cost time: "<<timer.elapsed() << endl;
匹配之前,選出一些視野內的候選點,將它們與當前幀的特徵描述子進行匹配
1.選出內點,添加構建地圖的描述子矩陣
2.根據兩個描述子計算,將匹配信息放入matches,先搞了波最小距離(平均值?)
3.根據距離,選比最小還小的匹配點,但是如果最小距離大於某閾值,那就取小於該閾值的匹配點
缺點:
1.容易丟失,一旦丟失,就要等相機轉回來,否則就要重置VO
2.軌跡漂移,主要是誤差累計引起的
c++知識
智能指針 shared_ptr
創建:要聲明類型 shared_ptr p1;
默認初始化:智能指針中保存着空指針。
使用方法和指針類似
make_shared必須初始化,返回指向此類型shared_ptr //shared_ptr p3 = make_shared(42)
計數器:如表12.2
自動銷燬:當沒有shared_ptr再指向這個對象時,這個對象就會被銷燬
自動釋放內存:erase刪除不需要的元素後,會自動釋放相關聯的內存
直接初始化智能指針:
shared_ptr p1 = new int(1024);//錯誤!
shared_ptr p2( new int(1024) );//正確!
//特徵匹配,拿出一些inline和當前幀的特徵描述子匹配
void VisualOdometry::featureMatching()
{
boost::timer timer;
vector<cv::DMatch> matches; //存放所有匹配到的點?
//select the candidates in map
Mat desp_map; //存放匹配到的描述子
vector<MapPoint::Ptr> candidate;
//allpoint
for ( auto& allpoints: map_->map_points_ )
{
MapPoint::Ptr& p = allpoints.second;
//check if p in curr frame image
if( curr_->isInFrame(p->pos_) )
{
//add to candidate
p->visible_times_++;
candidate.push_back(p);
desp_map.push_back( p->descriptor_ );
}
}
matches_flann_.match( desp_map, descriptors_curr, matches);
//select the best matches,distance is min
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] ); //query 測試下標
match_2dkp_index_.push_back( m.trainIdx ); //樣本圖像特徵帶你描述符下標
}
}
cout<<" good matched: " << match_3dpts_.size() << endl;
cout << " mathc cost time: "<<timer.elapsed() << endl;
}