cout, 类型的格式输出

std::endl的含义

常常有 std::cout<<"test"<<std::endl,这是把test先放到标准输出流,cout会对内容进行缓冲,不会立即输出到显示器.有两种方法立即显示:加flush或endl,后者还要换行,这是在缓冲区不满时刷新.有时不加这两个关键字也能显示,是因为缓冲区满了或者长时间未输入.

precision

1
2
3
cout.precision(3);
cout<< 123.567890 <<endl;  // 124
cout<< scientific << 123.567890 <<endl; // 1.236e+02

precision是控制输出浮点数的精度,3表示四舍五入后得到3个有效数字.精度数字超出数字个数时,还按原来数字.

scientific表示科学计数法表示,此时精度数字是小数点位数

类型 标志
unit16_t %hu
unit32_t %u
unit64_t %llu
unit32_t %zu
unsigned int %u
long long int %lld
unit32_t %u
unit32_t %u

欧拉角,旋转矩阵,旋转向量,四元数

欧拉角

参考欧拉角的理解

对于在三维空间里的一个参考系,任何坐标系的取向,都可以用三个欧拉角来表现。参考系又称为实验室参考系,是静止不动的,而刚体坐标系则固定于刚体,随着刚体的旋转而旋转。

规定:XYZ坐标轴是旋转的刚体坐标轴;而xyz坐标轴是静止不动的实验室参考轴。

坐标系XYZ原与参考系xyz重合,旋转后,称xy平面与XY平面的交线为交点线,用英文字母N代表。zXZ顺规的欧拉角可以静态地定义如下:

  • α是x轴与交点线的夹角;

  • β是z轴与Z轴的夹角;

  • γ是交点线与X轴的夹角。


第一绕z轴旋转α,第二绕交点线(即X轴旋转后的轴)旋转β,第三绕Z轴旋转γ. 因此,此过程可分解为三个基本的旋转,从左到右依次代表绕着z轴的旋转、绕着交点线的旋转、绕着Z轴的旋转。即其旋转矩阵为

对于欧拉角,我们规定任何两个连续的旋转,必须是绕着不同的转动轴旋转。因此,一共有 12=3x2x2 种顺规。第一次旋转可以绕三个坐标轴中的任意一轴转动,有3种情况,第二次旋转可以绕除第一次旋转轴外的任意一轴转动,有2种情况,第三次旋转可以绕除第二次旋转轴外的任意一轴转动,有2种情况。

  • 经典欧拉角:z-x-z, x-y-x, y-z-y, z-y-z, x-z-x, y-x-y

  • 泰特-布莱恩角(Tait–Bryan angles):x-y-z, y-z-x, z-x-y, x-z-y, z-y-x, y-x-z

我们平时所说的roll, pitch, yaw 其实是泰特-布莱恩角,我简称之为TB角

可以看出两者的区别是:经典欧拉角的第一个旋转角度和第三个旋转角度都是围绕同一个轴的,而Tait-Bryan角使用三个轴上的旋转角度去表示

  • 内旋和外旋

同样的一个旋转,按旋转的坐标系又可分为内旋和外旋。

内旋是基于自身坐标系的旋转,旋转轴是动态的。内旋的旋转矩阵是按顺序右乘,

外旋是基于外部坐标系的旋转,旋转轴始终不变,也就是RPY。外旋的旋转矩阵是按顺序左乘。

所以欧拉角要知道旋转顺序和是否定轴旋转,而旋转矩阵和四元数则是一个姿态就对应的一个旋转矩阵或四元数。除了万向锁问题,这也是SLAM不常用欧拉角的另一个原因。

在使用欧拉角的场合,大多数情况下都是采取外旋 XYZ


转换

欧拉角和旋转矩阵

《SLAM十四讲》没有讲到这部分。坐标系A旋转到坐标系B,先绕Z轴旋转了yaw,这里遵循右手法则,大拇指朝坐标系方向,四指代表正向。或者说正向旋转是以沿X轴方向看,顺时针旋转为正向,否则为负向。那么相应的旋转矩阵为
绕坐标轴旋转的轴坐标是不会变化的,所以对应位置是1
之后绕旋转后坐标系的Y轴旋转了pitch,之后绕旋转后坐标系X轴旋转了roll,最终的旋转矩阵如下,即依次左乘相应的矩阵

典型举例

如图,大坐标系是base_link,小的是 camera

我们希望的坐标系关系是这样的

变化的过程是camera坐标系做内旋方式,也就是绕自身轴按照Z-Y-X的顺序旋转,即先绕自身轴Z,再绕自身轴Y,最后绕自身轴X,可得旋转矩阵(内旋是右乘)

判断欧拉角的正负: 使用右手系,旋转轴正方向面对观察者时,逆时针方向的旋转是正、顺时针方向的旋转是负。这里先绕Z轴转 -90°,再绕X轴转-90°,camera就能变成希望的样子。

最终的旋转矩阵

打开网站rotationconverter,在左上角的旋转矩阵输入计算的结果,在右下角出现XYZ对应的结果,而我们需要的是ZYX,即[ x: -90, y: 0, z: -90 ],这里并不是简单的把XYZ的结果倒序就行了。

我们要的欧拉角rpy就是 (-1.57, 0, -1.57)

旋转向量

旋转矩阵有几个缺点:SO(3)的旋转矩阵有9个量,但一次旋转只有3个自由度。因此,这种表达式冗余。而且对于旋转矩阵自身也有约束,它必须是正交矩阵,且行列式为1,这些约束会使求解变得困难。扩展到变换矩阵,就是用16个量表达了6个自由度的变换。

上面说的欧拉角是绕三个正交轴的依次旋转,其实任意旋转都可以用一个旋转轴和一个旋转角来表示。这只需要一个旋转向量即可,也就是说一个三维向量表示了旋转,再加上一个平移向量,这样就是6个自由度,没有冗余了。

四元数

Quaternion用一个冗余的变量解决了欧拉角的Singularity问题,在运算插值时也比较方便,因此四元数是程序中表示旋转最常用的一种表示方法

四元数——>旋转矩阵——>欧拉角 这个过程转出来的欧拉角,不可能是想要的。因为同一个四元数,可以用2个欧拉角来表示,而这个方法得到的结果有可能是用转角大于2π的方式表达的.

最好不要直接从四元数转欧拉角后,处理向量的旋转.

四元数加减法: 四元数的加法只需要将分量相加就可以了

四元数的共轭是让四元数的向量部分取负,记做 (w, -x, -y, -z)。 四元数和它的共轭代表相反的角位移,因为相当于旋转轴反向。

四元数的逆表示一个反向的旋转,定义为四元数的共轭除以它的模的平方,四元数和逆的乘积为实数 1。 如果是单位四元数,那么逆就是共轭

四元数的插值不能用简单的线性插值,而是用slerp。因为当时间t匀速变化时,代表姿态矢量的角速度变化并不均匀

Eigen::Quaterniond::Identity().slerp(t, q_last_curr)能够实现四元数的球面插值。
t ∈ [0, 1]为插值点,q_last_curr为两帧之间的旋转四元数,即针对两帧之间的旋转而线性插入一个四元数。

cartographer中重新实现的slerp

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
template <typename T>
std::array<T, 4> SlerpQuaternions(const T* const start,
const T* const end,
double factor)
{
// Angle 'theta' is the half-angle "between" quaternions. It can be computed
// as the arccosine of their dot product.
const T cos_theta = start[0] * end[0] + start[1] * end[1] +
start[2] * end[2] + start[3] * end[3];
// Avoid using ::abs which would cast to integer.
const T abs_cos_theta = ceres::abs(cos_theta);
// If numerical error brings 'cos_theta' outside [-1 + epsilon, 1 - epsilon]
// interval, then the quaternions are likely to be collinear.
T prev_scale(1. - factor);
T next_scale(factor);
if (abs_cos_theta < T(1. - 1e-5)) {
const T theta = acos(abs_cos_theta);
const T sin_theta = sin(theta);
prev_scale = sin((1. - factor) * theta) / sin_theta;
next_scale = sin(factor * theta) / sin_theta;
}
if (cos_theta < T(0.)) {
next_scale = -next_scale;
}
return {{prev_scale * start[0] + next_scale * end[0],
prev_scale * start[1] + next_scale * end[1],
prev_scale * start[2] + next_scale * end[2],
prev_scale * start[3] + next_scale * end[3]}};
}

参考:
四元数和欧拉角互相转换
欧拉角与旋转矩阵


搭建Hexo博客

Disqus评论

这个教程操作即可

快捷上传图片

以前一直用flicker,每次都到网站上传,然后复制图片的网址,操作实在太麻烦了,而且flicker有上传图片限制。今天终于看到一种简便的方法,使用一个Hexo Editor的工具,截图之后复制图片文本,比如![](/20190616210334907/20190616091008239.png),然后右键选择上传SM.MS,这时图片会上传到图床SM.MS,这个图床应当没有上传限制,并且把图片的网址直接复制到剪贴板:![](https://i.loli.net/2019/06/16/5d064094f300180845.png)

问题

hexo g 报错

1
2
3
4
5
6
7
8
9
INFO  Start processing
FATAL Something's wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
TypeError: ejs:13
11| <title><%-: post.title %></title>
12| <pubTime><%= post.date.toDate().toISOString() %></pubTime>
>> 13| <%if(post.tags){ post.tags.toArray().forEach(function(tag){ %>
14| <tag><%= tag.name %></tag>
15| <% })}/*%>
16| <content><%-: post.content%></content>

将hexo-generator-baidu-sitemap文件删除掉即可

另一个可能的原因:把一个未写完的.md文件放到了_posts文件夹同名下,也就是放到了source文件夹下

hexo d报错


结果是git配置和SSH Key的问题

还在hexo博客的根目录,执行ssh-keygen,根据提示一直按回车即可,一般生成的位置在C:\Users\username\.ssh,找到id_rsa.pub后打开,到github网站的Settings中新建一个SSH key,将id_rsa.pub中的内容复制到Key中

git config --global配置git的用户名和邮箱

通过命令ssh-add解决每次操作都需要输入key的密码的问题

使用ssh -v git@github.com测试连接,如果看到验证通过就是成功了



algolia搜索

整个配置过程参见

如果搜索成功,在algolia网站的indices项目可以测试搜索结果

问题1

开始一直安装algolia失败,发现是把package.json设置为只读了

问题2

安装配置成功后,打开博客发现有了搜索按钮但没出现搜索条,在网页上按F12打开网页源码发现有报错,所以是浏览器问题而不是algolia的问题.


解决方法:还用npm安装hexo插件hexo-all-minifier,重新执行hexo cleanhexo algolia,再hexo g hexo d,打开博客应该成功了.

问题3

搜索时会发现不在post目录的文章也能搜索到,但是打开是404

参考:
Youtube上的视频教程


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 Orin NX

地平线x3m

Nano

英特尔 猎豹峡谷 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命令的使用 && 正则表达式