Windows+Visual Studio下生成g2o库教程(结合Cholmod库)

转载请注明出处:http://my.csdn.net/ye_shen_wei_mian

近期初步接触并初步学习了有关SLAM的知识,发现g2o库在SLAM中有非常的重要的应用,特别是应用在以图的形式表达的bundle adjustment的优化。经过一段时间的学习,发现大部分研究和使用g2o的人都是基于Linux系统下的,感觉这和g2o在Windows系统下的非常难配置不无关系。g2o库大致依赖于Qt5,Eigen,CSparse,cholmod等库,而其中以Cholmod库在Windows里尤其难配置,而错综复杂的依赖关系又涉及到了例如AMD,BTF,CAMD,CCOLAMD,COLAMD,KLU,CXSparse,SPQR等等等等,。在ubuntu底下,这些问题相对还容易解决,个人的经验是通过dpkg的命令一个一个依赖库进行安装。而在Windows系统下这些问题更加棘手。

在网上搜寻了很久资料,大致发现两篇很有价值的博客,可供参考。本人也是基于这两篇博客的基础上对g2o库在WIndows系统+Visual Studio下进行生成的。一篇博客是cc_sunny的关于SLAM的那些事——通用图优化(G2O)环境搭配(windows8.1 vs2013),网址是http://blog.csdn.net/aptx704610875/article/details/51245143。而另外一篇博客是xiamentingtao的博客 Eigen+suitesparse for windows 安装 ,网址是 http://m.blog.csdn.net/article/details?id=50100549。

值得注意的是,本人刚开始也是根据cc_sunny的博客对g2o进行生成的,生成的过程很顺利没有出什么问题,但是后来跑实际的代码的时候发现,作者遗漏了结合Cholmod库对g2o库进行编译,所以这样的话,g2o库中与cholmod库有关的函数和Optimizer都不能使用了,而与Cholmod库有关的g2o库功能恰恰又非常有用和常用,所以务必需要找到能够结合Cholmod库一起对g2o进行生成的方法。

但是从网上获取到的信息看,单独对Cholmod库进行生成貌似非常艰难。然而万幸的是,从xiamentingtao的博客 Eigen+suitesparse for windows 安装 一文中,惊奇的发现suitesparse库中已经包含了cholmod库,而且github上已经有可以基于Windows系统对suitesparse-metis库进行生成的开源项目。于是安装cholmod库的任务可以转化成在windows系统下安装suitesparse库了。

不得不说上述这两篇的博客体现了各自作者非常用心和仔细的态度,在这里我也对这两位博主的用心付出表示感谢,按照他们的教程的步骤进行配置和操作,可以顺利的完成所要达到的目的,因此相应的步骤我在这里并不再赘述了,只对这两个教程的东西进行融会贯通和补充说明,以及阐述总体流程以及一些别的注意事项。因此具体的步骤请翻阅这两篇博客,一步一步的操作相应的步骤即可。


本人电脑配置:Win7+VS2010 以及 Win10+ VS2013都成功配置好了,cmake用的是cmake3的版本,我配置的是Win32的版本,64位的版本流程大致相同


总体流程:

(1)上Eigen的网站下载Eigen3的库,解压到自己的电脑中,根据我的发现,Eigen库是不需要build的,解压出来,配置好属性表就可以了,这个不难

(2)进行suitesparse-metis库的安装。

请翻阅 xiamentingtao的博客 Eigen+suitesparse for windows 安装 ,按照作者说的去下载 suitesparse-metis-for-windows 这个开源项目(网址:https://github.com/jlblancoc/suitesparse-metis-for-windows/tree/v1.3.0),虽然作者是用VS2008和cmake2.8,但是都无所谓。要仔细的按照作者所说的去解压路径以及修改CmakeLists.txt,CUDA用不用都行反正我是没用它所以也不知道CUDA7在这底下是不是真的如作者所说的有bug。另外,作者在步骤5.编译和安装那里选的是对INSTALL工程进行build,我建议最好对ALL_BUILD在Debug和Release下都进行build。而这样子做,作者说的第6部中的“ SuiteSparseConfig.cmake应该位于install路径下”就不会出现了,但是也并不太大关系一切还是正常的。而第6部那幅图也不是cmake的结果,应该是build完后的结果,不管它。

到这里suitesparse-metis库算是已经build完了。

然后把你解压出来的suitesparse-metis-for-windows-1.3.0\lapack_windows\x32(我用的是32位的所以选这个)下面的东西分别全部复制到 你解压出来的suitesparse-metis-for-windows-1.3.0\build\bin中的Debug和Release。

在环境变量路径Path中添加  你解压出来的suitesparse-metis-for-windows-1.3.0\build\bin\Debug 和 你解压出来的suitesparse-metis-for-windows-1.3.0\build\bin\Release


(3)进行g2o相关的依赖库和g2o自身的生成

这个时候我们就要翻看cc_sunny的关于SLAM的那些事——通用图优化(G2O)环境搭配(windows8.1 vs2013) 这篇博客了。

Note:cc_sunny的博客里面对各个库都进行了32位和64位版本都进行了build,但如果你只是想要32位或只是想要64位的,那么可以只对32或64进行Build即可。出于本人个人的需要,本人只Build了32位的版本。

第一步的编译Eigen个人认为就不需要做了,没有安装Qt5版本的朋友可以按照博主的博客对Qt5和它的VS插件的过程进行安装,而libQGLViewer_2.6.3只生成32位的版本的情况下,libQGLViewer生成成功后,博主所说的那些lib和dll文件都在 你解压libQGLVIewer的目录\libQGLViewer-2.6.3\QGLViewer文件夹里,在之后在cmake中对g2o进行配置的时候,相应于libQGLViewer的路径和lib文件就在这个目录里找到就行了。

这个时候来到了这篇博客的重头戏了,毫无疑问就是在cmake对g2o进行配置了。其实相当大的部分的配置步骤和cc_sunny博客中的g2o的cmake步骤并无差异,但是这个时候我们要在cmake中配置好之前步骤(2)中已经build好的suitesparse-metis库,这也是我们这篇博客的主要目的。

首先还是按照cc_sunny的博客在cmake中对g2o除了suitesparse-metis库以外的库进行配置。总体并无太大的差别,只有Eigen库的cmake配置有一点小出入,现在只要在Ungrouped Entries中的EIGEN3_INCLUDE_DIR配置对Eigen解压出来的那个文件夹的路径即可,我的是 F:/Eigen3/eigen3。然后按cc_sunny的方法把QGLVIEWER等都配置好。CSPARSE可以不用改它,用cmake默认的g2o里面的版本就行。

BLAS库我没有配置,有兴趣的朋友可以配置下看看。

而这里重点当然是CHOLMOD这个地方的配置啦。CHOLMOD_INCLUDE_DIR应当是你解压了的suitesparse-metis-for-windows-1.3.0\SuiteSparse\CHOLMOD\Include这个路径,然后CHOLMOD_LIBRARY应当是suitesparse-metis-for-windows-1.3.0\build\Debug\libcholmodd.lib这个文件。

注意!!!极其重要:使用VS2010来操作的朋友切记不能勾选G2O下的G2O_USE_OPENMP,否则之后会报一个有关于OpenMPMutex的错误又要删掉build好的g2o库重新再build一次,挺崩溃的,我也不知道为什么会这样。VS2013的朋友则没这个烦恼。



做好了上面的步骤以后,点Configure,这个时候应该只有Ungrouped Entries这一项有红色,发现Ungrouped Entries要我们填写AMD_LIBRARY这个库(.lib文件)的路径,还是回去suitesparse-metis-for-windows-1.3.0\build\Debug\这里找,找到libamdd.lib这个文件配置好。

再点Configure,发现还是像刚才的情况,只不过这个时候要我们配置COLAMD_LIBRARY的路径。配置好以后再按Configure再进行配置,不断地重复这一过程,直到最后Ungrouped Entries不再出现红色框为止。最后cmake中应当是这样的:

Windows+Visual Studio下生成g2o库教程(结合Cholmod库)_第1张图片

Windows+Visual Studio下生成g2o库教程(结合Cholmod库)_第2张图片


这样才算是在cmake中把g2o给配置好了,点击Generate。

按照cc_sunny的第5、6步,在VS下对g2o.sln进行build然后添加环境变量即可。

(4)在VS下配置suitesparse-metis库

这部就需要注意了,由于刚才是ALL_BUILD而不是INSTALL的关系,因此配置过程与这博客中略有不同。首先在属性管理器页面中debug和release下分别新建suitesparse_debug_v1和suite_release_v1属性表(名字喜欢怎么取都可以,不一定要和我一样),在这两个属性表中,我们将要配置suitesparse和metis库。如图:(g2o_debug和g2o_release同理,是用来配置g2o的,图里面PCL的属性表不要管它)



点开suitesparse_debug_v1,首先配置VC++目录底下的包含目录。本人是将suitesparse-metis的压缩包直接解压到F:\suitesparse文件夹的,所以我要配置的东西有这么多,如下图:


东西有点多,但务必一个一个弄好,值得注意的是上图倒数第三行的SuiteSparse_config目录虽然没有Include文件夹,但翻看目录底下发现其实这个目录下是有一个头文件的,不配置的话之后会报没找到这个头文件的错。这里也可以看到涉及库之多以及依赖关系的混乱。

然后配置库目录:(下图是Debug的属性表下的,Release属性表下就把第一行最后的那个Debug改成Release就可以了)


然后配置链接器->附加依赖项:

Debug属性表下:

libamdd.lib
libbtfd.lib
libcamdd.lib
libccolamdd.lib
libcholmodd.lib
libcolamdd.lib
libcxsparsed.lib
libklud.lib
libldld.lib
libspqrd.lib
libumfpackd.lib
suitesparseconfigd.lib
libblas.lib
liblapack.lib
metisd.lib


Release属性表下:

libamd.lib
libbtf.lib
libcamd.lib
libccolamd.lib
libcholmod.lib
libcolamd.lib
libcxsparse.lib
libklu.lib
libldl.lib
libspqr.lib
libumfpack.lib
metis.lib
suitesparseconfig.lib
libblas.lib
liblapack.lib

到这里,suitesparse-metis库在VS里的配置就算完成了。


(5)g2o库在VS下的配置。因为较为简单,也就是像之前那样在Debug和Release下分别配置属性表里的包含目录、库目录和依赖项。我就不多说了,截个Debug模式下的图吧,大家照着配置就行。

Windows+Visual Studio下生成g2o库教程(结合Cholmod库)_第3张图片

附加依赖项:

Debug:

g2o_interface_d.lib
g2o_parser_d.lib
g2o_simulator_d.lib
g2o_types_sim3_d.lib
g2o_types_icp_d.lib
g2o_types_slam3d_addons_d.lib
g2o_types_sba_d.lib
g2o_types_slam2d_addons_d.lib
g2o_types_slam3d_d.lib
g2o_calibration_odom_laser_d.lib
g2o_solver_slam2d_linear_d.lib
g2o_types_data_d.lib
g2o_types_sclam2d_d.lib
g2o_solver_structure_only_d.lib
g2o_types_slam2d_d.lib
g2o_solver_pcg_d.lib
g2o_solver_dense_d.lib
g2o_solver_eigen_d.lib
g2o_tutorial_slam2d_d.lib
g2o_viewer_d.lib
g2o_hierarchical_d.lib
g2o_solver_csparse_d.lib
g2o_cli_d.lib
g2o_core_d.lib
g2o_stuff_d.lib
g2o_csparse_extension_d.lib
g2o_ext_freeglut_minimal_d.lib
g2o_opengl_helper_d.lib
g2o_ext_csparse_d.lib


Release:

g2o_simulator.lib
g2o_types_sim3.lib
g2o_types_icp.lib
g2o_types_slam3d_addons.lib
g2o_types_sba.lib
g2o_types_slam3d.lib
g2o_types_slam2d_addons.lib
g2o_calibration_odom_laser.lib
g2o_solver_slam2d_linear.lib
g2o_types_data.lib
g2o_types_sclam2d.lib
g2o_types_slam2d.lib
g2o_solver_pcg.lib
g2o_solver_structure_only.lib
g2o_solver_eigen.lib
g2o_solver_dense.lib
g2o_tutorial_slam2d.lib
g2o_viewer.lib
g2o_hierarchical.lib
g2o_solver_csparse.lib
g2o_cli.lib
g2o_core.lib
g2o_stuff.lib
g2o_interface.lib
g2o_csparse_extension.lib
g2o_ext_freeglut_minimal.lib
g2o_parser.lib
g2o_ext_csparse.lib
g2o_opengl_helper.lib


这样g2o在VS下是配置好了。这里的最后一步是把g2o build的目标文件夹下,也就是下图的where to build the binaries的路径对应的那个文件夹下的g2o文件夹里有个config.h头文件,把它复制粘贴到where to build the source code对应的那个文件夹下的g2o文件夹底下。


(6)写到这里写的我自己也有点小崩溃,但是现在终于到了守得云开见月明的时候了。稍微改动了下高博的博客 深入理解图优化与g2o:g2o篇(网址:http://www.cnblogs.com/gaoxiang12/p/5304272.html) 里的代码,不然在VS2010下会出错,VS2013可以直接用高博的代码没问题。

/**
* BA Example
* Author: Xiang Gao
* Date: 2016.3
* Email: [email protected]
*
* 在这个程序中,我们读取两张图像,进行特征匹配。然后根据匹配得到的特征,计算相机运动以及特征点的位置。这是一个典型的Bundle Adjustment,我们用g2o进行优化。
*/

// for std
#include <iostream>
// for opencv 
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <boost/concept_check.hpp>
// for g2o
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/core/robust_kernel_impl.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/cholmod/linear_solver_cholmod.h>
#include <g2o/types/slam3d/se3quat.h>
#include <g2o/types/sba/types_six_dof_expmap.h>


using namespace std;
using namespace cv;

// 寻找两个图像中的对应点,像素坐标系
// 输入:img1, img2 两张图像
// 输出:points1, points2, 两组对应的2D点
int     findCorrespondingPoints(const cv::Mat& img1, const cv::Mat& img2, vector<cv::Point2f>& points1, vector<cv::Point2f>& points2);

// 相机内参
double cx = 325.5;
double cy = 253.5;
double fx = 518.0;
double fy = 519.0;

int main()
{

	

	// 读取图像
	cv::Mat img1 = cv::imread("near.jpg",-1);
	cv::Mat img2 = cv::imread("far.jpg",-1);

	// 找到对应点
	vector<cv::Point2f> pts1, pts2;
	if (findCorrespondingPoints(img1, img2, pts1, pts2) == false)
	{
		cout << "匹配点不够!" << endl;
		return 0;
	}
	cout << "找到了" << pts1.size() << "组对应特征点。" << endl;
	// 构造g2o中的图
	// 先构造求解器
	g2o::SparseOptimizer    optimizer;
	// 使用Cholmod中的线性方程求解器
	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);

	optimizer.setAlgorithm(algorithm);
	optimizer.setVerbose(false);

	// 添加节点
	// 两个位姿节点
	for (int i = 0; i<2; i++)
	{
		g2o::VertexSE3Expmap* v = new g2o::VertexSE3Expmap();
		v->setId(i);
		if (i == 0)
			v->setFixed(true); // 第一个点固定为零
		// 预设值为单位Pose,因为我们不知道任何信息
		v->setEstimate(g2o::SE3Quat());
		optimizer.addVertex(v);
	}
	// 很多个特征点的节点
	// 以第一帧为准
	for (size_t i = 0; i<pts1.size(); i++)
	{
		g2o::VertexSBAPointXYZ* v = new g2o::VertexSBAPointXYZ();
		v->setId(2 + i);
		// 由于深度不知道,只能把深度设置为1了
		double z = 1;
		double x = (pts1[i].x - cx) * z / fx;
		double y = (pts1[i].y - cy) * z / fy;
		v->setMarginalized(true);
		v->setEstimate(Eigen::Vector3d(x, y, z));
		optimizer.addVertex(v);
	}

	// 准备相机参数
	g2o::CameraParameters* camera = new g2o::CameraParameters(fx, Eigen::Vector2d(cx, cy), 0);
	camera->setId(0);
	optimizer.addParameter(camera);

	// 准备边
	// 第一帧
	vector<g2o::EdgeProjectXYZ2UV*> edges;
	for (size_t i = 0; i<pts1.size(); i++)
	{
		g2o::EdgeProjectXYZ2UV*  edge = new g2o::EdgeProjectXYZ2UV();
		edge->setVertex(0, dynamic_cast<g2o::VertexSBAPointXYZ*>   (optimizer.vertex(i + 2)));
		edge->setVertex(1, dynamic_cast<g2o::VertexSE3Expmap*>     (optimizer.vertex(0)));
		edge->setMeasurement(Eigen::Vector2d(pts1[i].x, pts1[i].y));
		edge->setInformation(Eigen::Matrix2d::Identity());
		edge->setParameterId(0, 0);
		// 核函数
		edge->setRobustKernel(new g2o::RobustKernelHuber());
		optimizer.addEdge(edge);
		edges.push_back(edge);
	}
	// 第二帧
	for (size_t i = 0; i<pts2.size(); i++)
	{
		g2o::EdgeProjectXYZ2UV*  edge = new g2o::EdgeProjectXYZ2UV();
		edge->setVertex(0, dynamic_cast<g2o::VertexSBAPointXYZ*>   (optimizer.vertex(i + 2)));
		edge->setVertex(1, dynamic_cast<g2o::VertexSE3Expmap*>     (optimizer.vertex(1)));
		edge->setMeasurement(Eigen::Vector2d(pts2[i].x, pts2[i].y));
		edge->setInformation(Eigen::Matrix2d::Identity());
		edge->setParameterId(0, 0);
		// 核函数
		edge->setRobustKernel(new g2o::RobustKernelHuber());
		optimizer.addEdge(edge);
		edges.push_back(edge);
	}

	cout << "开始优化" << endl;
	optimizer.setVerbose(true);
	optimizer.initializeOptimization();
	optimizer.optimize(10);
	cout << "优化完毕" << endl;

	//我们比较关心两帧之间的变换矩阵
	g2o::VertexSE3Expmap* v = dynamic_cast<g2o::VertexSE3Expmap*>(optimizer.vertex(1));
	Eigen::Isometry3d pose = v->estimate();
	cout << "Pose=" << endl << pose.matrix() << endl;

	// 以及所有特征点的位置
	for (size_t i = 0; i<pts1.size(); i++)
	{
		g2o::VertexSBAPointXYZ* v = dynamic_cast<g2o::VertexSBAPointXYZ*> (optimizer.vertex(i + 2));
		cout << "vertex id " << i + 2 << ", pos = ";
		Eigen::Vector3d pos = v->estimate();
		cout << pos(0) << "," << pos(1) << "," << pos(2) << endl;
	}

	// 估计inlier的个数
	int inliers = 0;
	for (vector<g2o::EdgeProjectXYZ2UV*>::iterator e = edges.begin();e!=edges.end();++e)
	{
        (*e)->computeError();
		// chi2 就是 error*\Omega*error, 如果这个数很大,说明此边的值与其他边很不相符
		if ((*e)->chi2() > 1)
		{
			cout << "error = " << (*e)->chi2() << endl;
		}
		else
		{
			inliers++;
		}
	}

	cout << "inliers in total points: " << inliers << "/" << pts1.size() + pts2.size() << endl;
	optimizer.save("ba.g2o");
	std::system("pause");
	return 0;
}


int     findCorrespondingPoints(const cv::Mat& img1, const cv::Mat& img2, vector<cv::Point2f>& points1, vector<cv::Point2f>& points2)
{
	cv::ORB orb;
	vector<cv::KeyPoint> kp1, kp2;
	cv::Mat desp1, desp2;
	orb(img1, cv::Mat(), kp1, desp1);
	orb(img2, cv::Mat(), kp2, desp2);
	cout << "分别找到了" << kp1.size() << "和" << kp2.size() << "个特征点" << endl;

	cv::Ptr<cv::DescriptorMatcher>  matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");

	double knn_match_ratio = 0.8;
	vector< vector<cv::DMatch> > matches_knn;
	matcher->knnMatch(desp1, desp2, matches_knn, 2);
	vector< cv::DMatch > matches;
	for (size_t i = 0; i<matches_knn.size(); i++)
	{
		if (matches_knn[i][0].distance < knn_match_ratio * matches_knn[i][1].distance)
			matches.push_back(matches_knn[i][0]);
	}

	if (matches.size() <= 20) //匹配点太少
		return false;

	for (vector< cv::DMatch >::iterator m = matches.begin();m!=matches.end();++m)
	{
		points1.push_back(kp1[(*m).queryIdx].pt);
		points2.push_back(kp2[(*m).trainIdx].pt);
	}

	return true;
}


至于代码里的near.jpg和far.jpg就别找我要啦,随便两张不同角度拍摄的图片都可以,找对应的特征点匹配而已。

跑下如无意外应该就能成功了。


嗯这篇博客就到此为止啦,希望大家都能顺利地在Windows+VS底下用上g2o库拉。谢谢观看。

你可能感兴趣的:(windows,Build,Visual,Studio,G2O,cholmod)