个人平时编译代码倾向于用轻量级和跨平台的方式,比如最常用的Cmake编译,还有VScode编译等。然而轻量级可能容易满足,跨平台因为各种原因往往受阻。最近正好有个合适的例子解决这个小问题,小小总结一下。方便起见,以高翔《视觉SLAM十四讲》第三章中Eigen库调用的eigenMatrix.cpp代码为例,源代码如下:
#include <iostream>
using namespace std;
#include <ctime>
// Eigen 部分
#include <Eigen/Core>
// 稠密矩阵的代数运算(逆,特征值等)
#include <Eigen/Dense>
#define MATRIX_SIZE 50
/****************************
* 本程序演示了 Eigen 基本类型的使用
****************************/
int main(int argc, char** argv)
{
// Eigen 中所有向量和矩阵都是Eigen::Matrix,它是一个模板类。它的前三个参数为:数据类型,行,列
// 声明一个2*3的float矩阵
Eigen::Matrix<float, 2, 3> matrix_23;
// 同时,Eigen 通过 typedef 提供了许多内置类型,不过底层仍是Eigen::Matrix
// 例如 Vector3d 实质上是 Eigen::Matrix<double, 3, 1>,即三维向量
Eigen::Vector3d v_3d;
// 这是一样的
Eigen::Matrix<float, 3, 1> vd_3d;
// Matrix3d 实质上是 Eigen::Matrix<double, 3, 3>
Eigen::Matrix3d matrix_33 = Eigen::Matrix3d::Zero(); //初始化为零
// 如果不确定矩阵大小,可以使用动态大小的矩阵
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic > matrix_dynamic;
// 更简单的
Eigen::MatrixXd matrix_x;
// 这种类型还有很多,我们不一一列举
// 下面是对Eigen阵的操作
// 输入数据(初始化)
matrix_23 << 1, 2, 3, 4, 5, 6;
// 输出
cout << matrix_23 << endl;
// 用()访问矩阵中的元素
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++)
cout << matrix_23(i, j) << "\t";
cout << endl;
}
// 矩阵和向量相乘(实际上仍是矩阵和矩阵)
v_3d << 3, 2, 1;
vd_3d << 4, 5, 6;
// 但是在Eigen里你不能混合两种不同类型的矩阵,像这样是错的
// Eigen::Matrix<double, 2, 1> result_wrong_type = matrix_23 * v_3d;
// 应该显式转换
Eigen::Matrix<double, 2, 1> result = matrix_23.cast<double>() * v_3d;
cout << result << endl;
Eigen::Matrix<float, 2, 1> result2 = matrix_23 * vd_3d;
cout << result2 << endl;
// 同样你不能搞错矩阵的维度
// 试着取消下面的注释,看看Eigen会报什么错
// Eigen::Matrix<double, 2, 3> result_wrong_dimension = matrix_23.cast<double>() * v_3d;
// 一些矩阵运算
// 四则运算就不演示了,直接用+-*/即可。
matrix_33 = Eigen::Matrix3d::Random(); // 随机数矩阵
cout << matrix_33 << endl << endl;
cout << matrix_33.transpose() << endl; // 转置
cout << matrix_33.sum() << endl; // 各元素和
cout << matrix_33.trace() << endl; // 迹
cout << 10 * matrix_33 << endl; // 数乘
cout << matrix_33.inverse() << endl; // 逆
cout << matrix_33.determinant() << endl; // 行列式
// 特征值
// 实对称矩阵可以保证对角化成功
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver(matrix_33.transpose() * matrix_33);
cout << "Eigen values = \n" << eigen_solver.eigenvalues() << endl;
cout << "Eigen vectors = \n" << eigen_solver.eigenvectors() << endl;
// 解方程
// 我们求解 matrix_NN * x = v_Nd 这个方程
// N的大小在前边的宏里定义,它由随机数生成
// 直接求逆自然是最直接的,但是求逆运算量大
Eigen::Matrix< double, MATRIX_SIZE, MATRIX_SIZE > matrix_NN;
matrix_NN = Eigen::MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
Eigen::Matrix< double, MATRIX_SIZE, 1> v_Nd;
v_Nd = Eigen::MatrixXd::Random(MATRIX_SIZE, 1);
clock_t time_stt = clock(); // 计时
// 直接求逆
Eigen::Matrix<double, MATRIX_SIZE, 1> x = matrix_NN.inverse() * v_Nd;
cout << "time use in normal inverse is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
// 通常用矩阵分解来求,例如QR分解,速度会快很多
time_stt = clock();
x = matrix_NN.colPivHouseholderQr().solve(v_Nd);
cout << "time use in Qr decomposition is " << 1000 * (clock() - time_stt) / (double)CLOCKS_PER_SEC << "ms" << endl;
return 0;
}
高翔博士给出了在Linux环境下用cmake编译的CMakelists.txt
cmake_minimum_required( VERSION 2.8 )
project( useEigen )
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-O3" )
# 添加Eigen头文件
include_directories( "/usr/include/eigen3" )
# in osx and brew install
# include_directories( /usr/local/Cellar/eigen/3.3.3/include/eigen3 )
add_executable( eigenMatrix eigenMatrix.cpp )
Linux环境对开发自然是很爽的啦,这个Linux环境下的CMakelists.txt我就不用测试了(即使有问题,解决也很easey!)下面着重说说对开发不太友好的Windows环境下如何编译这个调用第三方Eigen库的源代码。
1、第一种方式:Visual Studio2019编译
首先,打开Visual Studio2019 IDE,在菜单栏选择文件=>新建=>项目,创建新项目(选C++空项目),配置新项目
(1)创建C++新项目
(2)配置eigentest新项目
然后,在右侧解决方案=>源文件=>添加=>现有项,添加eigenMatrix.cpp源代码。
再然后,在菜单栏项目=>属性=>C/C++=>常规=>附加包含目录中添加Eigen库的安装目录, 最后在配置选择release/debug模式,平台选择x64/x86平台,点击确定即可完成
在菜单栏点击本地Windows调试器编译结果如下:
客观地说,VS编译的方式中规中矩挺好的。但是细心点可以发现,对于本例就一个cpp文件的代码,配置编译竟然需要这么多环节,更不用说VS IDE启动慢、耗资源的问题了。
2、第二种编译方式:VS Code
可能微软也意识到VS问题了,才开发的VS Code。不像VS是Windows系统环境专用的IDE,VS Code其实就是个轻量级的编辑器,不过VS Code可以跨平台:除了在Windows系统环境可以用外,在Linux系统环境和Mac OS环境下也可以使用。当然喽,VS Code每次编译都要配置一遍,主要配置三个json文件:c_cpp_properterties.json,launch.json和tasks.json(这三个文件在源代码根目录下的.vscode隐藏文件中)。其实配置多了会发现这里有个小技巧:根据个人PC环境(操作系统及编译器等)配置一套基础版的.vscode的json文件,然后开发新项目时将整个基础版的.vscode文件夹copy到新项目文件夹根目录下;如果不调用第三方库的话,新项目就可以直接编译,如果新项目要调用第三方库,在json文件中配置第三方库即可。由于之前我根据自己的PC环境配置过一套.vscode的json文件,所以我就直接将整个.vscode文件夹copy到eigenMatrix.cpp文件根目录下了(官网和网上说这块配置的很多,这里就不细说了),具体如下:
c_cpp_properterties.json文件:
{
"configurations": [
{
"name": "GCC",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/TDM-GCC-64/bin/g++.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}
launch.json文件:
{
"version": "0.2.0",
"configurations": [
{
"name": "g++.exe - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "C:\\TDM-GCC-64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: g++.exe build active file"
}
]
}
tasks.json文件:
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "C/C++: g++.exe build active file",
"command": "C:\\TDM-GCC-64\\bin\\g++.exe",
"args": ["-g", "${file}", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe"],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
由于需要调用第三方Eigen库,需要在tasks.json文件“args”字段中添加"-I","D:\\eigen-3.3.9\\"
在VS Code菜单栏点击Run或按Ctrl+F5编译后结果如下:
细心点的话可以发现,实际上时执行了编译命令: Executing task: C:\TDM-GCC-64\bin\g++.exe -g D:\Codes\c-cpp_work\cpp_work\EigenTest\eigenMatrix.cpp -o D:\Codes\c-cpp_work\cpp_work\EigenTest\eigenMatrix.exe -I D:\eigen-3.3.9\
虽然VS Code编译已经满足轻量级、跨平台编译的要求了,但是如果不用我发现的小技巧的话配置json文件还是略显繁琐,而且可能会经常遇到配置json文件不知道该在哪配置还有配置路径时斜杠/,反斜杠\,双反斜杠\\等问题。给追求轻量级、跨平台编译的开发人员来说缺了点爽快的感觉。
3、第三种编译方式:cmake编译
跟在Linux系统环境下cmake编译一样,写个CMakeLists.txt配置文件就可以了。只不过在Windows系统环境下可能第三方库是自定义安装的, find_package(XXX REQUIRED)去找的话有可能找不到导致编译失败。所以为防止查找失败最好指定第三方库的安装目录 include_directories("D:/yyy") 。话不多说,直接贴我的配置如下:
cmake_minimum_required (VERSION 2.6)
project (EigenTest)
add_executable (eigentest eigenMatrix.cpp)
#设置Eigen库路径
#include_directories("D:/eigen-3.3.9/")
find_package (Eigen)
if (Eigen_FOUND)
include_directories(${EIGEN_INCLUDE_DIRS})
target_link_libraries (eigentest ${EIGEN_LIBRARIES})
message("Find_package:${EIGEN_INCLUDE_DIRS}")
else(Eigen_FOUND)
include_directories("D:/eigen-3.3.9/")
target_link_libraries (eigentest ${EIGEN_LIBRARIES})
message("Not find_package:${EIGEN_INCLUDE_DIRS}")
endif(Eigen_FOUND)
这步跟高翔博士在Linux系统环境下的CMakeList.txt配置相比,可以认为只是根据个人PC环境修改了Eigen库的include_directories。配置完CMakeList.txt后,编译步骤如下:
(1)在eigenMatrix.cpp代码根目录新建build文件夹
(2)进入build文件夹路径栏输入cmd,进入DOS模式
(3)cmake编译(在DOS界面命令行执行:cmake .. -G"Unix Makefiles")
(4)make生成可执行文件(在DOS界面命令行执行:make)
(5)运行可执行exe文件(在DOS界面命令行执行:eigentest.exe)
eigentest.exe执行结果如下:
cmake是根本意义上的轻量级、快平台的编译方式,个人比较推崇。但是在Windows系统环境下有很多坑:
比如上面提到的第三方库自定义安装后find_package就找不到了;
再比如编译时cmake ..常常会失败,所以你仔细看帖子的话会发现我用的命令是cmake .. -G"Unix Makefiles"
还有就是在Linux环境安装cmake很简单用apt-get install cmake 就好了,但是在Windows系统环境下除了安装cmake外还需要安装make命令,不然老会报错make不是命令!
本贴以Eigen库为例做了简单介绍,其他比如OpenCV等第三方库也可按照类似方式配置。