在读高博关于g2o的一篇讲解博客https://www.cnblogs.com/gaoxiang12/p/5304272.html的时候,我打算顺便跑下作者的示例代码g2o_ba_example,期间遇到了一些问题。
本文顺便作为日志记录学习过程,先简单提一下编译遇到的
1.opencv版本问题
这是由于新旧版本特征提取操作的差异引起的,作者当时用的是opencv2.4.*,其提取特征点是用的cv::ORB对象的构造函数:
cv::ORB orb;
vector<cv::KeyPoint> kp1, kp2;
cv::Mat desp1, desp2;
orb( img1, cv::Mat(), kp1, desp1 );
orb( img2, cv::Mat(), kp2, desp2 );
如果cmake时链接到opencv3以上版本,会有如下报错:
error: no match for call to ‘(cv::ORB) (const cv::Mat&, cv::Mat, std::vectorcv::KeyPoint&, cv::Mat&)’
orb( img1, cv::Mat(), kp1, desp1 );
可以对比以下opencv3提取特征点的操作:
std::vector<KeyPoint> keypoints_1, keypoints_2;
Mat descriptors_1, descriptors_2;
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
...
detector->detect(img_1, keypoints_1);
detector->detect(img_2, keypoints_2);
descriptor->compute(img_1, keypoints_1, descriptors_1);
descriptor->compute(img_2, keypoints_2, descriptors_2);
所以解决办法是要么将源代码改为后一种提取方式,要么在系统中另装opencv2,并在cmakelist中修改链接版本(我另装了opencv2.4.13):
find_package( OpenCV 2.4 REQUIRED )
接下来是本文的重点,即
2.在g2o中使用unique_ptr构造求解器的陷阱
同样是在编译时报错:
error: no matching function for call to ‘g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >::BlockSolver(g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >::LinearSolverType*&)’
...
error: no matching function for call to ‘g2o::OptimizationAlgorithmLevenberg::OptimizationAlgorithmLevenberg(g2o::BlockSolver_6_3*&)’
70 | onAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg( block_solver );
出问题的相关代码行(66-70):
g2o::BlockSolver_6_3::LinearSolverType* linearSolver = new g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> ();
// 6*3 的参数
g2o::BlockSolver_6_3* block_solver = new g2o::BlockSolver_6_3( linearSolver );
// L-M 下降
g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg( block_solver );
查看相应构造函数的定义:
BlockSolver(std::unique_ptr<LinearSolverType> linearSolver);
和
explicit OptimizationAlgorithmLevenberg(std::unique_ptr<Solver> solver);
注意到其参数均为std::unique_ptr(因为我之前已经安装了g2o库,这是来自最新的g2o版本源码定义),很明显作者在写g2o_ba_example这个demo的时候,当时版本的g2o中关于这几个类的构造函数参数不是unique_ptr(因为是c++11之后发布)。
解决办法是要么使用g2o的更早版本:Release 20170730_git · RainerKuemmerle/g2o · GitHub
要么改动代码,将对应的线性方程求解器、BlockSolver对象都设置为unique_ptr。由于感觉只需改动三行代码,于是我将linearSolver和block_solver改成了unique_ptr对象:
// g2o::BlockSolver_6_3::LinearSolverType* linearSolver = new g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> ();
typedef g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> LinearSolverType;
auto linearSolver = g2o::make_unique<LinearSolverType>();
// 6*3 的参数
// g2o::BlockSolver_6_3* block_solver = new g2o::BlockSolver_6_3( linearSolver );
auto block_solver = g2o::make_unique<g2o::BlockSolver_6_3>(linearSolver);
// L-M 下降
// g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg( block_solver );
g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(block_solver);
编译仍然报错找不到匹配的构造函数:
error: no matching function for call to ‘g2o::OptimizationAlgorithmLevenberg::OptimizationAlgorithmLevenberg(std::unique_ptr<g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >, std::default_delete<g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> > > >&)’
74 | tionAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(block_solver);...
/usr/local/include/g2o/stuff/misc.h:54:29: error: no matching function for call to ‘g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >::BlockSolver(std::unique_ptr<g2o::LinearSolverCholmod<Eigen::Matrix<double, 6, 6, 0> >, std::default_delete<g2o::LinearSolverCholmod<Eigen::Matrix<double, 6, 6, 0> > > >&)’
54 | return std::unique_ptr<T>(new T(std::forward<ArgTs>(args)...));
这下给我彻底整懵了,一度以为是中间的模板类型BlockSolverTraits<6, 3>被typedef等一系列别名行为搞出了问题......总之就是琢磨折腾了非常久,最后才弄明白是因为unique_ptr的特性引起的。
unique_ptr禁止拷贝和赋值,查看其定义,在类定义的最下方有两行代码:
// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
“=delete”即表明将拷贝构造和赋值操作禁用了。在我改动的代码中,先将LinearSolverType类型的unique_ptr指针初始化给了linearSolver对象,再将linearSolver这个对象作为参数传给BlockSolver_6_3的构造函数(实质为BlockSolver基类的构造函数);后续同样先初始化block_solver的unique_ptr指针,再传给OptimizationAlgorithmLevenberg的构造函数。
这是不允许的。因为这样行为的本质就是将一个unique_ptr作为参数传给一个新unique_ptr的构造函数,即拷贝构造。
unique_ptr 独占所指向的对象,即在某一时刻,只能有一个unique_ptr指向特定的对象。而拷贝构造以unique_ptr作为参数,会首先需要将其赋值一遍,这违反了设计规则,无法做到。
解决办法:要么使用std::move()移动构造(将std::unique_ptr对象持有的堆内存转移给另外一个对象,其不再持有堆内存的引用,变成一个空的智能指针对象),要么直接嵌套unique_ptr的构造函数,将其指向的堆内存的所有权在一开始就交给同一个对象。
使用std::move()移动构造如下:
typedef g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> LinearSolverType;
auto linearSolver = g2o::make_unique<LinearSolverType>();
auto block_solver = g2o::make_unique<g2o::BlockSolver_6_3>(std::move(linearSolver));
g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(std::move(block_solver));
直接使用构造函数嵌套如下(可读性不太友好):
typedef g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> LinearSolverType;
g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(g2o::make_unique<g2o::BlockSolver_6_3>(g2o::make_unique<LinearSolverType>()));
这两种方法均可以使编译通过。
参考了这几篇介绍拷贝构造和unique_ptr的文章视频:
C++基础——拷贝构造函数_橙予清的zzz~的博客-CSDN博客
探究C++11智能指针之std::unique_ptr - 知乎