Latex 常用语法

常见符号

\hat x        
\bar x        
\div        
\times        
\sin\theta        
a\in A        
\frac{2}{4}        
点乘:a \cdot b
叉乘:a \times b
除以:a \div b

中括号: \left[ \frac{a}{b} \right]
大括号: \left{ \frac{a}{b} \right}

希腊字母

\xi        
\pi        
\alpha        
\beta        
\lambda        
\gamma \delta \epsilon \zeta \eta \theta        
\Sigma\sigma\varsigma        

集合

\mathbb Z         整数集

特殊符号

\bigoplus        
\oplus        

变体

斜体     \mathit{0123456789}        

粗体     \boldsymbol{0123456789}        

花体,用于集合     \mathbb{ABCD}        

向量

\vec{}可以表示向量,使用更为普遍的是:

\overrightarrow{AB}        

\overleftarrow{AB}        

默认情况下,在 LaTeX 中输入向量\vec x,向量 x 上头会出现一个箭头而非加粗:

要使用向量的粗体格式,改用 \boldsymbol 即可。

范数: \Vert x \Vert

换行命令

\\:换行,在博客环境下不好用
\\[offset]:换行,并且与下一行的行间距为原来行间距+offset
\newline:与\相同,能在博客环境下换行
\linebreak:强制换行,与\newline的区别为\linebreak的当前行分散对齐

\\会自动让下一行缩进,也就是形成一个段落,有时不是我们需要的
回车出一个空行,不会造成缩进

矩阵

\left[ \begin{matrix} R & t \\ 0 & 1 \\ \end{matrix} \right]

常用公式

\mathop{min}\limits_{x}{\frac{1}{2}\left|f(x)\right|_2^2}\tag1

\varepsilon^* = arg\ \mathop{max} \limits{\varepsilon \in \mathcal{W}} \sum\limits{k=1}^{K}M\

数学模式

用两个美元括号括起来是一般的数学模式,例如 $y=x+1$ ,结果是斜体的,这是行内模式.
还有行间模式:

结果是换行显示公式

行内公式也可以使用 (…) 或者 \begin{math} ... \end{math} 来插入。
行间公式也可以使用 [ … ] 或者 \begin{displaymath} ... \end{displaymath} 来插入。


Eigen(一) 安装配置 和 基础

Eigen是一个基于C++模板的线性代数库,安装后只需要包含头文件即可,它没有库文件,所以不需要链接,一般是INCLUDE_DIRECTORIES(/usr/include/eigen3)

安装:sudo apt-get install libeigen3-dev,这是最新版本

查看Eigen的版本:不能用命令查看,版本在Macros.h,在/usr/local/include/eigen3/Eigen/src/Core/util,可以执行命令: grep 'define EIGEN_....._VERSION\>' $(locate Macros.h | grep Core/util),结果是三个版本部分的宏定义。目前我所用为3.2.92

卸载Eigen:删除所有Eigen的文件即可

1
2
3
sudo rm -rf /usr/include/eigen3 /usr/lib/cmake/eigen3 /usr/share/doc/libeigen3-dev /usr/share/pkgconfig/eigen3.pc /var/lib/dpkg/info/libeigen3-dev.list /var/lib/dpkg/info/libeigen3-dev.md5sums
sudo updatedb
locate eigen

如果locate之后没有结果,那就是卸载成功了

要安装其他版本,就需要下载对应得deb安装,比如libeigen3-dev_3.2.0-8_all.deb,我使用gdebi安装时失败,报警 A later version is already installed ,最终发现还是要用dpkg安装,这一点以前没有发现,其他软件包应该也会有这样问题

矩阵Eigen::Matrix基本使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <Eigen/Core>  // 核心文件
#include <Eigen/Dense> // 包含了常用函数,稠密矩阵的运算
#include <Eigen/Eigen>  // 稀疏部分

// 矩阵基本使用
Eigen::Matrix<float, 2, 3> m23;
m23.setZero(2,3); // 矩阵置为0,参数分别为行数和列数
cout << "After m23 initilized!" << endl;
cout<<m23<<endl; // 可以将矩阵放入标准输出流,输出结果就是矩阵形式,源码应当做了空格和换行处理
m23<<1,2,3,4,5,6;  // 矩阵赋值的常用方式
cout << "After m23 set value!" << endl;
m23(1,2) = 54;   // 单独给某元素赋值
// 输出元素的值
 cout<<m23(1,2)<<endl;


Eigen::Matrix<double,2,2> information;
information.fill(0.0); // 矩阵元素全赋值为 0.0

// 常用的类型Matrix3Xd和Matrix3Xf其实是动态矩阵,定义如下
// typedef Matrix< double, 3, Dynamic > Eigen::Matrix3Xd
// typedef Matrix< float, 3, Dynamic > Eigen::Matrix3Xf
// Eigen::Matrix3d 三阶方阵

Eigen::Matrix<float, 2, 3> m;
m.setRandom(2,3); // 矩阵用随机值初始化,但每次随机值似乎都是一样的
m = m23; // 将另一矩阵的值赋给m
m<<3,4,7,12,-2,10;
cout<<m23+m<<endl; // 矩阵加法
cout<<m23 * m.transpose()<<endl; // 矩阵乘法

Eigen::Matrix3d m33
m33<<1,6,3,4,12,5,7,8,9;
cout<<m33<<endl;
cout<<m33.rows()<<endl; // 行数
cout<<m33.cols()<<endl; // 列数
cout << "Column major input:" << endl << m33 << "\n";
// 获取第一列的元素
cout << "The first column is:" << endl << m33.col(0) << "\n";
cout << "The last column is: " << endl << m33.rightCols(1) << "\n";
cout << "The first row is: " << endl << m33.topRows<1>() << endl;
cout << "The last row is: " << endl << m33.bottomRows<1>() << endl;
// rowwise和colwise两个函数很奇怪,不要使用
cout<<m33.transpose()<<endl; // 转置矩阵
cout<<m33.sum()<<endl; // 所有元素的和
cout<<m33.trace()<<endl; // 矩阵的迹
cout<<10*m33<<endl; // 矩阵数乘
cout<<m33.isUnitary()<<endl; // 判断该矩阵是否是酉矩阵(矩阵各列是否为标准正交基)
cout<<m33.conjugate()<<endl; // 共轭矩阵
cout<<m33.adjoint()<<endl; // 伴随矩阵
cout<<m33.inverse()<<endl; // 逆矩阵,若不存在会报错
cout<<m33.determinant()<<endl; // 行列式结果,若不存在会报错

// 矩阵乘法, m23模板类型是float,需要转换,注意cast不能自动提示
Eigen::Matrix<double, 2, 1> m21 = m23.cast<double>() * v3d;

由于元素级运算在矩阵中很常见,所以Eigen对于Matrix其实内置了一些函数,均以cwise开头。mat.cwiseAbs()元素取绝对值、mat.cwiseSqrt()逐元素开根号、mat.cwiseMin()将两个矩阵中相应位置的最小值组成一个新矩阵

Eigen提供了一些基础的算法,如sum(),求所有元素和;prod(),求所有元素之积;mean(),求元素平均值;minCoeff(),最小元素;maxCoeff(),最大元素;trace(),求迹

Eigen::Isometry3d

Eigen::Isometry3d相当于一个四维矩阵

1
2
3
4
5
6
7
8
9
Eigen::AngleAxisd  v(M_PI / 3, Eigen::Vector3d(1,0,0).normalized() );
Eigen::Quaterniond q(1,0,0,0);
Eigen::Vector3d translate(1,2,3);
// 初始化为单位矩阵
Eigen::Isometry3d T = Eigen::Isometry3d::Identity();
T.translate(translate);
// T.rotate(q.matrix() ) 或者 T.rotate(v)
T.rotate(q);
cout << endl << T.matrix() << endl;

rotate函数比较强大,可接受的参数有四元数、旋转矩阵、旋转向量

必须要调用 Isometry3d::Identity()对它进行初始化,后面的translate()rotate()函数才有用。

稀疏矩阵

使用方式和普通矩阵不同

1
2
3
4
Eigen::SparseMatrix<double> hessian(2, 2);
// 注意稀疏矩阵的初始化方式,无法使用<<初始化
hessian.insert(0, 0) = 2.0;
hessian.insert(1, 1) = 2.0;

从子矩阵和向量拼接成新矩阵

比如现在有一个3X3的矩阵,一个行向量和一个列向量,二者的最后一个元素相同,要拼接成4X4的矩阵

mat = mat1.block(i, j, p, q); // 从矩阵 mat1 的 i 行 j 列开始获取一个 p 行 q 列的子矩阵
mat = mat1.block<p, q>(i, j); // 从矩阵 mat1 的 i 行 j 列开始获取一个 p 行 q 列的子矩阵(动态矩阵)

1
2
3
4
5
6
7
8
9
Eigen::Matrix4d m;
Eigen::Matrix3d rotation;
// 给 rotation赋值
m.block(0,0,3,3) = rotation;

Eigen::Vector4d Vcol, Vrow;
// 给 Vcol和Vrow 赋值
m.col(3) = Vcol;
m.row(3) = Vrow.transpose();

向量Eigen::Vector

1
2
3
4
5
6
7
8
9
10
11
12
// 常用这种方式,Xd 代表double型的任意行数向量,但后面的参数必须指定行数
Eigen::VectorXd v(3);
v<< 1,2,5; // 必须不多不少传入所有的元素值,否则报错
v(2) = 9; // 给第三个元素赋值


Eigen::VectorXd vv(12);
vv<< 1,2,3,4,5,6,7,8,9,10,11,12;
// 前三行
cout<< vv.block(0,0, 3,1).transpose() <<endl<<endl;
cout<< vv.block(3,0, 3,1) <<endl<<endl;
cout<< vv.block(6,0, 3,1) <<endl<<endl;

指定了3行,必须传入3个值,否则运行报错。

我们可以用Vector3f::UnitX(), Vector3f::UnitYVector3f::UnitZ()这三个函数表示三个方向的列向量,也就是输出结果为:

1
2
3
4
5
X   Y   Z

1 0 0
0 1 0
0 0 1

其实向量只是一个特殊的矩阵,但是Eigen也为它单独提供了一些简化的块操作,如下三种形式:

head tail segment

  • 获取向量的前n个元素:vector.head(n);

  • 获取向量尾部的n个元素:vector.tail(n);

  • 获取从向量的第i个元素开始的n个元素:vector.segment(i,n);

1
2
3
4
5
6
Eigen::Vector4d v(4); 
v<< 1,2,3,4;

cout << "head: "<<v.head(1) <<endl; // 1
cout << v.segment(1,2) <<endl; // 2 3
cout << "tail: "<<v.tail(1) <<endl; // 4

Vector 转 Matrix

1
2
3
4
Eigen::Vector3d v;
v << 1,2,3;
// 这个转换十分罕见
Eigen::Matrix<double, 3, 1> m = v.template cast<double>();

从Eigen::Vector构建对角矩阵,其中Vector的元素占据矩阵的主对角线

1
2
Eigen::Vector3d v(1,2,3);
Eigen::Matrix3d m = v.asDiagonal();

从多个Eigen::Vector拼接成Eigen::Matrix

1
2
3
4
5
6
7
8
Eigen::Vector3d v(3);
v<< 2.1, 4.9, 0.4;
std::vector<Eigen::Vector3d> vv;
for(int i=0;i<4;i++)
vv.push_back( v );
// 拼接成3X4的矩阵
Eigen::Map<Eigen::Matrix3Xd> matrix(&vv[0].x(), 3, 4); //首元素地址,行,列
cout << matrix <<endl;

Map类用于通过C++中普通的连续指针或者数组 (指针地址连续)来构造Eigen里的Matrix类,这就好比Eigen里的Matrix类的数据和raw C++array 共享了一片地址,也就是引用。

有个庞大的Matrix类,在一个大循环中要不断读取Matrix中的一段连续数据,如果每次都用block operation 去引用数据会太繁琐。于是就事先将这些数据构造成若干Map,那么以后循环中直接操作Map就行了。

Eigen::Map

Map类用于通过C++中普通的连续指针或者数组来构造Eigen里的Matrix类,这就好比Eigen里的Matrix类的数据和raw C++array 共享了一片地址,也就是引用。 比如有个API只接受普通的C++数组,但又要对普通数组进行线性代数操作,那么用它构造为Map类。 实际上Map类并没有自己申请一片空内存,只是一个引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using DynamicArray = Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic>;

std::vector<int> cells{1,2,3,4,5,6,7,8,9};
for(auto& v: cells)
cout << v << " ";
cout << endl << endl;
// 维度不要超过 3x3,否则出现invalid数据
Eigen::Map<const DynamicArray> full_correspondence_costs(cells.data(), 3, 3);
cout << full_correspondence_costs <<endl;


int data[] = {1,2,3,4,5,6,7,8,9};
Eigen::Map<Eigen::RowVectorXi> m(data, 4);
cout << "The mapped vector v is: " << m << "\n";

运行结果:
1
2
3
4
1 4 7
2 5 8
3 6 9
The mapped vector v is: 1 2 3 4

参考:
Eigen矩阵库使用说明


Linux问题积累

升级内核至 4.15

安装包下载地址根据系统类型,例如amd64下载4个包含generic的安装包,然后执行sudo dpkg -i *.deb进行安装,然后重启

解压tar失败

如果用命令 tar -zxvf xxx.tar 解压的话会出现提示:

1
2
3
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error exit delayed from previous errors

然后用file命令查看了文件的格式 发现是POSIX tar archive 格式的,用命令 tar -xvf xxx.tar成功解压好了

bug: ls 不显示文件

scp文件到远程机后,在远程机已经locate到了文件,在文件管理器里也看到了,但是用llls却看不到,这是ubuntu18.04的bug

在Linux拷贝数据到U盘时,最好不要马上直接拔U盘,因为拷贝数据会花一定时间,可能导致拷贝失败

could not get lock /var/lib/dpkg/lock -open

apt-get命令安装一些软件包时,总报错:E:could not get lock /var/lib/dpkg/lock -open

出现这个问题的原因可能是有另外一个程序正在运行,导致资源被锁不可用。而导致资源被锁的原因,可能是上次安装时没正常完成,而导致出现此状况。

解决方法:输入以下命令

1
2
sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock

执行 updatedb报错: updatedb: can not open a temporary file for /var/lib/mlocate/mlocate.db
必须是root才能执行命令,sudo updatedb。有时可能又报错:*/var/lib/mlocate/mlocate.db' is locked (probably by an earlier updatedb) 此时稍等一会再运行命令即可。

Qt编译错误 usr/bin/ld cannot find

linux环境编译应用程式或lib的source code时常常会出现如下的错误讯息:
/usr/bin/ld: cannot find -lxxx
例如:

1
2
3
4
/usr/bin/ld: cannot find -lGL
/usr/bin/ld: cannot find -lc
/usr/bin/ld: cannot find -lltdl
/usr/bin/ld: cannot find -lpulse

其中xxx即表示函式库文件名称,如上例的:libc.so、libltdl.so.

命名规则:lib+库名(即xxx)+.so

一般是lib没有装或版本不对,解决方法:

  1. 先搜索lib,规则是apt-cache search libXXX-dev或者用apt-file,因为库的完整名称不确定。例如:
1
2
apt-cache search libgl-dev
apt-cache search libpulse-dev
  1. 根据搜索的结果安装库,有时结果很多,需要正确选择。例如:apt-get install libgl1-mesa-dev

参考:Qt 编译常见错误:usr/bin/ld: cannot find

安装扩展名sh的软件包

安装从网上下载的软件包时出错,扩展名为sh

原因在于它实际不是sh脚本文件,而是二进制文件,用./file形式运行

软件包有未满足的依赖关系

在linux上安装某个库时,经常出现依赖项版本不匹配的情况,可以一级一级找到出问题的依赖项,一般是现有版本比要安装的版本还要高

上图所示是gcc的版本不匹配,这种问题就要用到aptitude,运行命令:sudo aptitude install gfortran-5,首先会显示出问题的依赖项,然后给出解决方案,一般第一种都不是我们想要的,选n后看第二种,再不合适就继续n,直到找到需要的解决方案为止

ubuntu中Qt无法输入中文的问题

前提是安装了fcitx

1
2
3
4
5
sudo cp /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so /home/user/Qt5.13.0/5.13.0/gcc_64/plugins/platforminputcontexts

sudo cp /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so /home/user/Qt5.13.0/Tools/QtCreator/lib/Qt/plugins/platforminputcontexts

sudo cp /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so /home/user/Qt_ROS_Developer/latest/lib/Qt/plugins/platforminputcontexts

version * not found

我用20.04,从22.04拷贝过来Groot,结果运行报错

1
2
3
4
5
6
7
8
9
10
11
12
13
~/Groot/build$ ldd Groot | grep not
./Groot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./Groot)
./Groot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./Groot)
./Groot: /lib/x86_64-linux-gnu/libQt5Core.so.5: version `Qt_5.15' not found (required by ./Groot)
./Groot: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by ./Groot)
./Groot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by libbehavior_tree_editor.so)
./Groot: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by libbehavior_tree_editor.so)

./Groot: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.30' not found (required by libbehavior_tree_editor.so)
./Groot: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by libbehavior_tree_editor.so)
./Groot: /lib/x86_64-linux-gnu/libstdc++.so.6: version `CXXABI_1.3.13' not found (required by libbehavior_tree_editor.so)

./Groot: /lib/x86_64-linux-gnu/libQt5Core.so.5: version `Qt_5.15' not found (required by libbehavior_tree_editor.so)

以缺少GLIBCXX_3.4.30为例,执行strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX 发现 GLIBCXX只到 3.4.28,确实是 not found

两个文件一模一样,即md5值一样,但是依赖库的版本可能不同,这样两文件运行的结果也有可能不同。

检查系统日志

检查日志文件的大小: journalctl --disk-usage

清除最近5天的日志: sudo journalctl --vacuum-time=5d


机器人的硬件平台

Jetson AGX Xavier

8核ARM v8架构CPU、32TOPS算力的GPU、32GB的内存、32GB的固态硬盘。

计算环境包含CUDA、cudNN和TensorRT,这些不需要一个个装,直接在jetpack中一次性装好了。Xavier放在开放的环境,环境温度24度,Xavier上的风扇开到最大,工作一小时后GPU芯片的温度是34度左右

价格9000左右, 使用Xavier对开发人员的要求相对高一些,你不仅要具备机器人的专业知识,还要有C++和CUDA编程的能力,这也是短时间内Xavier产品无法在国内机器人上普及的原因

英特尔 猎豹峡谷 NUC11PAH

NUC并不好用,无论11代还是7代,都有偶然死机和断电的现象。

我所用的是NUC11PAH i7,价格4000左右。4核8线程,主频2.8GHz,集成显卡。具备GPU。包装缺电源线,需要自己购买。

使用时要进入BIOS取消安全检查。

全志MR133

参考:
史上最全Jetson TX1使用介绍
英特尔猎豹峡谷 NUC11PAHi7 开箱+使用评测
NUC 11 迷你电脑性能套件


ROS的缺陷及ROS2的优化
  1. roscore(或者说master)的问题。在进行压力测试时,系统连续运行长了,master莫名宕机,某个节点可能突然失效。单master结构不利于多个机器人组成的集群
  2. 严重依赖Ubuntu,不同的Ubuntu版本对应不同的ROS的版本
  3. ROS延迟很大,断网再连接时会挂掉
  4. 参数服务器机制设计不好,程序退出但roscore不退出时,参数没有更新,或者说还留在参数服务器里
  5. 安装与运行体积较大,运行在配置较低的ARM上会占用过多资源
  6. ROS通信的本质是XML-RPC机制,造成了实时性差,传递数据时需要发送一大堆无用的xml节点,没办法实现毫秒级的机器人控制,容易经常受到带宽和处理性能的影响
  7. 安全性较差,通信数据是开放式的,没有加密,只要在网络中的节点都可以轻松获取
  8. ROS不支持时间同步,多台机器人之间的同步得先使用linux的工具进行同步
  9. 在ROS1中节点启动是无序的,更不能暂停节点。ROS1系统设计时并没有考虑节点启动时可能存在的互相依赖。但在实际生产使用环境中,某些节点能正常工作的前提是其他一些节点已经启动。


a. ROS 2采用全新的架构,底层基于DDS(Data Distribution Service)通信机制,支持实时性、嵌入式、分布式、多操作系统,ROS2支持的系统包括Linux、windows、Mac、RTOS,甚至是单片机等没有操作系统的裸机。

b. ROS 2的通讯系统是基于DDS,取消了master,同时在内部提供了DDS的抽象层实现,有了这个抽象层,用户就可以不去关注底层的DDS使用了哪个商家的API。

c. ROS2基于DDS进行数据传输,而DDS基于RTPS的去中心化的通信框架,这就去除了对roscore的依赖,系统的稳定性强,对资源的消耗也得到了降低。

d. 由于ROS 缺少Qos机制,topic的稳定性与质量难以保证;ROS2则提供了Qos机制,对通信的实时性、完整性、历史追溯等功能有了支持,这便大幅加强了框架功能,避免了高速系统难以适用等问题。

e. ROS2中引入了节点生命周期管理的概念,各个节点的状态是可管理的。比如如果想从建图功能切到导航功能。在ROS1中,需要先将建图功能的程序杀干净,然后再启动导航程序。在这个场景里,大可让建图程序休眠,而不用杀掉。切换功能时只需要激活相应功能的节点即可。


Win和Linux的环境变量

系统环境变量对所有用户起作用,而用户环境变量只对当前用户起作用 例如:把java的bin目录加入到path变量下面,那么它就是系统环境变量,所用用户登陆,在命令行输入java都会有java的帮助信息出来。而如果你在某个用户的变量下面新建一个变量,那么它就只对这个用户有用,当你以其他用户登陆时这个变量就和不存在一样。

常用的几个环境变量,对Windows和Linux通用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# (动态库搜索路径) 程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/

# (静态库搜索路径) 程序编译期间查找动态链接库时指定查找共享库的路径
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/

# 执行程序搜索路径
export PATH=$PATH:/usr/local/protobuf/bin/

# c程序头文件搜索路径
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/
# c++程序头文件搜索路径
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/

# pkg-config 路径
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/

比如安装了protobuf之后,可执行程序是protoc,但无法识别,就需要把它的路径加到PATH里:export PATH=$PATH:/usr/protobuf_3.7/bin

JAVA用到的环境变量

主要有3个:JAVA_HOME、CLASSPATH、PATH:

  1. JAVA_HOME指向的是JDK的安装路径,如x:\JDK_1.4.2,在这路径下你应该能够找到bin、lib等目录。

  2. PATH环境变量原来Windows里面就有,只需修改一下,指向JDK的bin目录,这样在控制台下面编译、执行程序时就不需要再键入一大串路径了。设置方法是保留原来的PATH的内容,并在其中加上%JAVA_HOME%\bin
    (注,如果你对DOS批处理不了解,你可能不明白%%引起来的内容是什么意思;其实这里是引用上一步设定好的环境变量JAVA_HOME,你写成x:\JDK_1.4.2也是可以的;你可以打开一个控制台窗口,输入echo %JAVA_HOME%来看一下你的设置结果) : PATH=%JAVA_HOME%\bin;%PATH%
    同样,%PATH%是引用以前你设置的PATH环境变量。

  3. CLASSPATH=.\;%JAVA_HOME%\lib\tools.jar
    首先要注意的是最前面的.\;,这个是告诉JDK,搜索CLASS时先查找当前目录的CLASS文件——为什么这样搞,这是由于LINUX的安全机制引起的,(因为WINDOWS默认的搜索顺序是先搜索当前目录的,再搜索系统目录的,再搜索PATH环境变量设定的)。

不在窗口设置,修改bat文件的方法如下:

1
2
3
set JAVA_HOME=x:\JDK_1.4.2 
set PATH=%JAVA_HOME%\bin;%PATH%
set CLASSPATH=.\;%JAVA_HOME%\lib\tools.jar

非root不能直接ifconfig的问题

非root用户输入ifconfig会显示:
bash: ifconfig: command not found

使用whereis ifconfig可以发现ifconfig在sbin,非root用户需要sbin/ifconfig才能使用。
可以将sbin加入环境变量,修改.bashrc文件,在最后添加:

1
export PATH=$PATH:/sbin

参考:ifconfig: command not found 解决办法


cmake教程(五)函数

调用cmake文件

比如在CMakeLists.txtinclude("${YOUR_CMAKE_DIR}/functions.cmake"),然后可以调用其中的函数。

target_include_directories

file GLOB_RECURSE

格式为 file(GLOB variable [RELATIVE path] [globbingexpressions]...)

典型应用:

1
2
file(GLOB_RECURSE cpps *.cpp)
message(STATUS "cpps: ${cpps}")

GLOB_RECURSE 与GLOB类似,区别在于它会遍历匹配目录的所有文件以及子目录下面的文件。

1
2
3
4
5
file(GLOB_RECURSE ALL_SRCS "*.cc" "*.h")
file(GLOB_RECURSE ALL_TESTS "*_test.cc")
file(GLOB_RECURSE ALL_EXECUTABLES "*_main.cc")
list(REMOVE_ITEM ALL_SRCS ${ALL_TESTS})
list(REMOVE_ITEM ALL_SRCS ${ALL_EXECUTABLES})

option

option(<variable> "<help_text>" [value])
对于value,不给定或给定其他值都默认 OFF

实际使用时可以作为开关使用

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.5)
project(test_6)

option(OPENGL_VIEW "Enable GLUT OpenGL point cloud view" ON)
if(OPENGL_VIEW)
...
endif()

foreach

foreach与C++里的功能一样,语法略有不同

1
2
3
4
5
set(dirs abc foo nfk)         # 三个字符串加到dirs里面
message(STATUS "dirs: ${dirs}")
foreach(dir ${dirs})
message(STATUS "dir: ${dir}")
endforeach(dir ${dirs})

运行结果:

1
2
3
4
-- dirs:    abc;foo;nfk
-- dir: abc
-- dir: foo
-- dir: nfk


grep命令搜索文本

grep可以设置搜索结果的颜色,查看环境变量发现已经做了设置:alias grep='grep --color=auto',最好不要再修改

跳过二进制文件

执行grep命令时,出现匹配到二进制文件,说明grep 的字符串在一个不开源的二进制文件源码中有用到,一般可以跳过,在命令最后加--binary-files=without-match,最好加入上面的alias里面

搜索以</set为行首的结果,跳过二进制文件:

1
grep -r  '^</set'  .  -n  --binary-files=without-match

搜索包含完整单词setup的结果,跳过二进制文件:

1
grep -r  '\<setup\>'  . -n  --binary-files=without-match

这样不会显示包含setups的结果

从结果中排除某字符串

grep用于从检索结果中获取含有某字符串的结果,但是如果想要的结果是排除某字符串的,可以使用grep -v string。最典型的应用就是:ps aux | grep process | grep -v grep,用于查看进程process,去掉grep本身的进程

获得apriltag_ros_continuous进程的个数
ps -ef | grep apriltag_ros_continuous | grep -v grep | wc -l

grep命令搜索多个字符串

1
grep 'fatal\|error\|critical'  test.log

需要注意的是,如果要搜索的字符串包含空格,需要用双引号将其括起来

正则表达式

常用正则表达式的格式符:

1
2
3
4
5
6
7
8
9
^: 行首锚定
$: 行尾锚定
^PATTERN$: 用于PATTERN来匹配整行

\<: 词首锚定
\>: 词尾锚定
\<PATTERN\>: 匹配完整单词
. (小数点):代表『一定有一个任意字节』的意思;
* (星号):代表『重复前一个字符, 0 到无穷多次』的意思,为组合形态

常用的是词首和词尾锚定,

1
2
grep -n 'g..d' file.txt    # g 与 d 之间一定要存在两个字节
grep -n '\<good\>' file.txt # 找出完整的good

指定文件类型

--include可以指定搜索的文件类型,例如:

1
grep -r "网络" . -n --include "*.sh"

搜索时只搜索sh文件中的文本

排除文件类型

--exclude可以排除文件类型,例如:

1
grep -r "网络" . -n --exclude "*.bak"

搜索时排除bak文件类型

另一种方式

查找内容含有abc的文件: grep "abc" *

搜索 ——

-符号比较特殊,这样是错误的,不会有结果

1
2
grep "-----" test.cc    # 无论加不加 -rn
grep -e "-----" .

正确的

1
2
grep -e "-----" test.cc
grep -- "-----" *.cc

参考:
grep命令的使用 && 正则表达式


Ubuntu系统装机和初始化

安装

必须用U盘启动方式进入系统,否则会报错:
Alt text
步骤:

  1. 使用PowerISO和Ubuntu的ISO压缩包制作启动U盘
  2. 先选择”try me without install”模式,进入系统后看看有什么问题,比如无线网能不能连接,没什么问题后,退出重启,按流程装好Ubuntu系统
  3. 插上U盘重新启动Ubuntu,选择”try me without install”模式。如果没有从U盘启动,按住F2进入BIOS,到BOOT标签,修改启动的优先顺序为U盘启动为第一优先
  4. 从”try me”模式进入系统后,打开GParted
  5. umount硬盘,然后按提示进行Resize partition
  6. 每次分盘要等待一段时间,完成后拔出U盘重启电脑
  7. 更改各磁盘的 label
    输入 sudo fdisk -l,然后按照sudo e2label /dev/sda1 "diskname"的格式修改

安装 Qt

安装Qt必须根据系统是32位还是64位,从官网下载对应版本的Qt安装包,然后./qt-***,在安装完成后缺少编译器和调试器。从软件安装程序里安装CMake,然后安装gcc和GDB:

1
sudo apt-get install gcc && gdb

  ,完成后运行程序可能报错:cannot find -lGL collect2:error:ld returned 1 exit status 因为缺少opengl库
在联网状态下:sudo apt-get libgl1-mesa-dev

解决没声音问题 & 安装PulseAudio

在终端运行sudo alsamixer,按M键把关闭的都大开(关闭的都显示的是MM),然后调节到合适的位置.
Alt text

如果耳机还是没声音,可是试着运行sudo gedit /etc/modprobe.d/alsa-base.conf
然后在最后一行加入

1
#enable headphoneoptions snd-hda-intel power_save=10 power_save_controller=N model=6stack-dig

然后运行 sudo apt-get remove alsa-base

重启电脑后运行 sudo apt-get install alsa-base

另外可以安装PulseAudio音量控制软件,性能更好

1
sudo apt-get install pavucontrol

禁止窗口 System program problem detected 弹出

sudo gedit /etc/default/apport,将参数1改为0

Ubuntu 18 的最大最小关闭布局

Ubuntu是关闭在左侧,结果Ubuntu18又弄到了右侧。于是想要调整,需要安装dconf-editor

1
sudo apt install dconf-editor

进入/org/gnome/desktop/wm/preferences/button-layout,改为 close,maximize,minimize:

放大标题栏

font的放大只适用于字体,窗口的标题栏还是太小,尤其最大最小按钮。可以做如下设置

但是ubuntu 18.04里没有这个选项了,只能在19.10版本之后

安装字体

从Windows复制或从网上下载后,放到 /usr/share/fonts即可

安装中文输入法

  1. 安装Chinese语言包
    鼠标依次点击System Settings–>Language Support–>Install/Remove Languages
    选中Chinese,点击Apply应用即可,等待下载安装完成。

  2. 这里完成的只是中文语言包的安装,还并不能使用中文输入法。所谓iBus pinyin输入法,这个pinyin输入法是基于iBus框架。
    安装ibus框架:

    1
    sudo apt-get install ibus ibus-clutter ibus-gtk ibus-gtk3 ibus-qt4

    启动ibus框架:im-config -s ibus
    安装ibus拼音:sudo apt-get install ibus-pinyin

  3. 重启电脑后到Language Support选择fcitx框架,到Text Entry添加输入法

参考:
Ubuntu安装Ibus拼音
ibus官方主页

trash-cli 不直接删除文件

这个工具是把文件放到了回收站,不是永久删除

sudo apt install -y trash-cli

  • trash-put 把文件或目录移动到回收站
  • trash-empty 清空回收站
  • trash-list 列出回收站文件
  • trash-restore 恢复回收站文件
  • trash-rm 删除回收站文件

安装oh-my-zsh

  1. 运行cat /ect/shells可知目前安装的shell,一般默认是bash
  2. 运行sudo apt-get install -y zsh安装zsh,再运行上面的命令发现多了一个:/bin/bash
  3. 安装oh-my-zsh: git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
  4. zsh --version查看zsh的版本,用chsh -s (which zsh) 切换shell至zsh,重启电脑
  5. 修改主题,vim ~/.zshrc修改配置文件,修改ZSH_THEME="ys",重启终端
  6. 安装插件。这个插件会记录你常去的那些目录,然后做一下权重记录,可以用这个命令看到你的习惯:j --stat,如果这个里面有你的记录,那你就只要敲最后一个文件夹名字即可进入,比如program:j program,就可以直接到:/usr/program
  7. 安装zsh-syntax-highlighting, 编辑配置文件,plugins=(zsh-syntax-highlighting)
    刷新下配置:source ~/.zshrc
  8. 安装powerline

使用时发现一个终端会记录其他终端的命令历史,这样使用向上箭头时,得到的不是希望的命令,这是因为zsh默认共享了命令历史,在~/.zshrc里添加 setopt no_share_history 即可解决

参考:安装oh-my-zsh

安装 unity tweak tool

1
sudo apt-get install unity-tweak-tool

启动:unity-tweak-tool

安装缺失文件:

1
2
3
sudo apt-get install notify-osd

sudo apt-get install overlay-scrollbar

添加桌面程序的启动

1
2
3
4
5
6
7
[Desktop Entry]
Version=1.0
Name=Groot
Exec=/usr/local/lib/groot/Groot
Icon=/home/user/Pictures/groot.ico
Type=Application
Terminal=false

如果运行后没有打开,很可能是执行有问题,单独运行Exec部分试试

开机启动

Utilities-Tweaks-Startup Applications里设置

设置系统快捷键

打开系统设置-Keyboard 键盘设置,以shutter的截图为例:

单击右侧 Disabled,然后快速按下 Ctrl+Alt+A就成功了

安装cairo-dock

实现类似Mac的效果

1
2
3
sudo add-apt-repository ppa:cairo-dock-team/ppa 
sudo apt-get update
sudo apt-get install cairo-dock cairo-dock-plug-ins

启动:cairo-dock

剪贴板命令

sudo apt-get install xclip
xclip可以将内容输出到‘X’的剪切板中, 命令echo "Hello, world" | xclip -selection clipboard,可以做成alias,这样很实用

更改鼠标风格

下载鼠标风格文件后,将整个文件夹放到/usr/share/icons,然后到Tweak Tool中就能选择了。

解决Ubuntu双显示器屏幕边缘鼠标粘滞问题

笔记本在ubuntu下使用了扩展屏幕,使得两个显示屏可以协同工作,但是每次鼠标在两个屏幕直接移动的时候,总是被屏幕边缘粘住,不让移到另一个屏幕上,除非以很快的速度移动才能把鼠标从一个屏幕移动到另一个屏幕上。

在桌面点击屏幕右上角账户名旁边的关机按钮,在弹出的菜单里选择”显示“,打开显示配置窗口,你会发现里面有一个选项叫做粘滞边缘,只要把这个选项关闭就可以了。

定制窗口侧边栏的默认书签

打开文件~/.config/user-dirs.dirs,然后修改如下:

1
2
3
XDG_DESKTOP_DIR="$HOME/桌面"
XDG_TEMPLATES_DIR="$HOME/"
XDG_PUBLICSHARE_DIR="$HOME/"

桌面最好不要修改,否则HOME下的文件都会显示在桌面上.再执行xdg-user-dirs-gtk-update,重启.

效果如下:

CPU跑满测试

安装stresssudo apt-get install stress

运行下面命令,可以使得6核CPU跑满,-c是指CPU,6是指6核,具体几核可以打开jtop查看。如用于其他jetson板卡,根据具体CPU核数更改下面命令即可。

1
stress -c 6


cmake教程(三)find_package和pkg_check_modules

find_package

find_package用于加载第三方库,可以将需要的部分指定为组件,例如

1
2
3
4
5
6
7
8
set(PACKAGE_DEPENDENCIES
cartographer_ros_msgs
eigen_conversions
geometry_msgs
urdf
visualization_msgs
)
find_package(catkin REQUIRED COMPONENTS ${PACKAGE_DEPENDENCIES})

  • REQUIRED可选字段:表示一定要找到包,找不到的话就立即停掉整个cmake。而如果不指定REQUIRED则cmake会继续执行。
  • COMPONENTS: 可选字段,表示查找的包中必须要找到的组件,如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行。


使用Find_Package寻找模块时,每一个模块都会产生如下变量:

1
2
3
_FOUND
_INCLUDE_DIR
_LIBRARY or _LIBRARIES

如果_FOUND为真,需要把_INCLUDE_DIR加入到INCLUDE_DIRECTORIES中,_LIBRARY加入到TARGET_LINK_LIBRARIES

module 模式

find_package将先到module路径下查找 Find<name>.cmake。首先它搜索 ${CMAKE_MODULE_PATH}中的所有路径,然后搜索 /usr/share/cmake-3.5/Modules.比如find_package(Boost)搜索的文件是/usr/share/cmake-3.5/Modules/FindBoost.cmake

如果在CMakeLists.txt中没有下面的指令:

1
set(CMAKE_MODULE_PATH  "Findxxx.cmake文件所在的路径")

那么cmake不会搜索CMAKE_MODULE_PATH指定的路径,此时cmake会搜索第二优先级的路径.

如果cmake需要的是Find<name>.cmake,但是没在路径找到,可以添加路径到环境变量:

1
set(CMAKE_MODULE_PATH  ${CMAKE_MODULE_PATH} "Findxxx.cmake文件所在的路径")

config 模式

如果按照module模式未找到,cmake将会查找 <Name>Config.cmake<lower-case-name>-config.cmake 文件。cmake会优先搜索自定义的xxx_DIR路径。如果在CMakeLists中没有下面的指令:

1
set(xxx_DIR  "xxxConfig.cmake文件所在的路径")

那么cmake就不会搜索xxx_DIR指定的路径.而是到/usr/local/lib/cmake/xxx/中搜索,比如/usr/local/lib/cmake/yaml-cpp/yaml-cpp-config.cmake,如果还没有就失败了。一般情况下,对第三方库执行make install都会把cmake文件放到这个文件夹里,如果编译报错就需要找到文件然后手动复制过去,但是不是光复制xxxConfig.cmake,还要有xxxTargets.cmake,否则find_package就会报错:
缺abslTargets.cmake.png

上面两种添加路径到环境变量的方法要根据寻找的文件进行设置,比如我在QtCreator+Cmake中使用OpenCV,直接find_package(OpenCV REQUIRED)会报错 找不到OpenCVConfig.cmake ,先用locate尝试寻找FindOpenCV.cmakeOpenCVConfig.cmake,发现只有电脑上只存在后者,所以要用config模式: set(OpenCVDIR "/opt/ros/kinetic/share/OpenCV-3.3.1-dev")



按上面的方法一般都能寻找库成功,如果还是失败,就用下面这种 优先级最高的强制方法: </font>

1
find_package(OpenCV REQUIRED PATHS /usr/local/share/OpenCV NO_DEFAULT_PATH) 

相关的宏

cmake找到任意一个之后就会执行这个文件,然后这个文件执行后就会设置好一些cmake变量。比如下面的变量(NAME表示库的名字,比如可以用Opencv 代表Opencv库):

1
2
3
4
<NAME>_FOUND	# 布尔量
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDES
<NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS
<NAME>_DEFINITIONS

我们可以在CMakeList中用下面代码检验find_package的结果:

1
2
3
4
5
6
7
8
9
10
11
find_package(but_velodyne  REQUIRED)
if (but_velodyne_FOUND)
MESSAGE (STATUS "definitions: ${ButVELODYNE_DEFINITIONS}")
MESSAGE (STATUS "include dirs: ${ButVELODYNE_INCLUDE_DIRS}")
MESSAGE (STATUS "lib dirs: ${ButVELODYNE_LIBRARY_DIRS}")

include_directories(${ButVELODYNE_INCLUDE_DIRS})
target_link_libraries (helloworld ${ButVELODYNE_LIBRARY_DIRS})
else()
MESSAGE (STATUS " but_velodyne not found")
endif(but_velodyne_FOUND)

如果cmake在两种模式提供的路径中没有找到对应的Findxxx.cmake和xxxConfig.cmake文件,此时系统就会提示最上面的那些错误信息。

ROS中的find_package

对于catkin_make,它会搜索ROS Package的安装目录,不必用set指定搜索目录.比如std_msgs对应的文件路径在/opt/ros/kinetic/share/std_msgs/cmake/std_msgsConfig.cmake.这两个文件是库文件安装时自己安装的,将自己的路径硬编码到其中。

ROS中常常需要多个package,比如roscpp,rospy,std_msgs。我们可以写成:

1
2
find_package(roscpp REQUIRED)
find_package(std_msgs REQUIRED)

这样的话,每个依赖的package都会产生几个变量,这样很不方便。所以还有另外一种方式:
1
2
3
4
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)

这样,它会把所有pacakge里面的头文件和库文件等等目录加到一组变量上,比如catkin_INCLUDE_DIRS,这样我们就可以用这个变量查找需要的文件了,最终就只产生一组变量了。


REQUIRED 参数:其含义是指是否是工程必须的,表示如果包没有找到的话,cmake的过程会终止,并输出警告信息。对应于Find.cmake模块中的 NAME_FIND_REQUIRED 变量。

COMPONENTS参数:在REQUIRED选项之后,或者如果没有指定REQUIRED选项但是指定了COMPONENTS选项,在它们的后面可以列出一些与包相关(依赖)的部件清单

编译ROS工作空间的问题

ROS的工作空间用的时间一长,就会创建很多package,有些package的编译又用到了其他的package,这时单纯使用catkin_make就会出现问题。比如package A用到了B,B里又用到了C。即使catkin_make --pkg也不行,因为它会把所有的package都处理一遍,此时处理到A时就会报错,会显示找不到B和C的cmake文件。它们的路径在devel/share/B/cmake

比较笨的方法就是在A的CMakeLists里先把B和C注释掉,同样在B的CMakeLists里把A注释掉,先用catkin_make --pkg C编译C,再依次编译B和A。

找了好长时间没找到很好的解决方法,最后只能预先编译好cmake文件放到SVN上,以后就算B和C的程序改变,也可以编译。

Qt库的情况

以上问题都还简单,问题是在ROS中调用Qt的情况,以Core模块为例,我们有下面的代码:

1
2
3
4
5
6
7
8
find_package(Qt5 COMPONENTS Core Xml)
target_link_libraries(bin Qt5::Core Qt5::Xml)

if(Qt5Core_FOUND)
MESSAGE(STATUS "##### ${Qt5Core_VERSION}")
MESSAGE(STATUS "##### ${Qt5Core_INCLUDE_DIRS}")
MESSAGE(STATUS "##### ${Qt5Core_LIBRARIES}")
endif(Qt5Core_FOUND)

运行结果是:

1
2
3
--  5.5.1
-- /usr/include/x86_64-linux-gnu/qt5/; /usr/include/x86_64-linux-gnu/qt5/QtCore; /usr/lib/x86_64-linux-gnu/qt5//mkspecs/linux-g++-64
-- Qt5::Core

首先注意:能使用Qt5的CMake最低版本是 3.1.0,Qt库的宏规则实际仍然符合cmake,不同的地方在于不需要`命令,只要target_link_libraries就足够,它会自动添加相应的include directories,compile definitions`等等

Qt5Core对应的cmake文件在Linux上有两个位置:

1
2
3
/home/user/Qt5.11.1/5.11.1/gcc_64/lib/cmake/Qt5Core/Qt5CoreConfig.cmake

/usr/lib/x86_64-linux-gnu/cmake/Qt5Core/Qt5CoreConfig.cmake

经过测试,CMakeLists中起作用的应当是第二个,应当是安装Qt时,在此目录生成了文件.这个路径与环境变量Qt5_DIR相近,在CMakeLists中用MESSAGE函数能看到:/usr/lib/x86_64-linux-gnu/cmake/Qt5

Boost

Boost比较特殊,cmake对它有特别照顾,使用命令cmake --help-module FindBoost可以看到极为详细的使用方法.使用Boost有时要加上REQUIRED COMPONENTS XXX,这是在搜索已经编译的库,但不会检查只有头文件的库.比如threadsystem要加入COMPONENTS但asio不需要.

cmake中使用Boost的filesystem,thread模块:

1
2
3
4
5
6
7
find_package(Boost COMPONENTS system filesystem thread REQUIRED)

target_link_libraries(mytarget
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
)

必须得加上system模块

pkg_check_modules

pkg_check_modules是 CMake 自己的 pkg-config 模块的一个用来简化的封装:你不用再检查 CMake 的版本,加载合适的模块,检查是否被加载,等等,参数和传给 find_package 的一样:先是待返回变量的前缀,然后是包名(pkg-config 的)。这样就定义了<prefix>_INCLUDE_DIRS和其他的这类变量,后续的用法就与find_package一致。

当安装某些库时(例如从RPM,deb或其他二进制包管理系统),会包括一个后缀名为 pc 的文件,它会放入某个文件夹下(依赖于系统设置,例如,Linux 为该库文件所在文件夹/lib/pkgconfig),并把该子文件夹加入pkg-config的环境变量PKG_CONFIG_PATH作为搜索路径。
pkg_check_modules实质上是检测系统中的 pkg-config 是否存在指定的 .pc 文件。

在我的电脑上执行echo $PKG_CONFIG_PATH,结果是:

1
/home/user/Robot/workspace/devel/lib/pkgconfig:/opt/ros/kinetic/lib/pkgconfig:/opt/ros/kinetic/lib/x86_64-linux-gnu/pkgconfig

bash.rc里没有设置,但是能获得这个环境变量,这是因为我们的环境变量里设置了

1
2
source /opt/ros/kinetic/setup.bash
source /home/user/Robot/workspace/devel/setup.bash



在cmake中使用pkg_check_modules时,就会去上面的路径里搜索pc文件,例如

1
2
3
4
# bfl (Bayesian Filtering Library)是一个使用pkg-config的第三方库
# 先搜索cmake自己的PkgConfig模块,才能使用pkg_check_modules
find_package(PkgConfig)
pkg_check_modules(BFL REQUIRED orocos-bfl)

其中PkgConfig的路径在/opt/ros/kinetic/share/ros/core/rosbuild/FindPkgConfig.cmake

搜索对应库文件,发现在以下路径:

1
2
/opt/ros/kinetic/lib/liborocos-bfl.so
/opt/ros/kinetic/lib/pkgconfig/orocos-bfl.pc

现在我们可以获得对应的宏,使用这个库了:

1
2
include_directories(${BFL_INCLUDE_DIRS})
link_directories(${BFL_LIBRARY_DIRS})

参考:
CMake Manual For Qt
Boost的编译库列表