SLAM学习

  • Post author:
  • Post category:其他



目录


目录


CH1 简介




应用1:定位


应用2:建图


算法框架


知识树


发展趋势


CH2


CH2~2


CH3


CH3~Eigen库的学习


1.eigen库的链接:


2. 定义方式


3. 输入数据


4. 矩阵基本运算


CH3~各种旋转的表示方式


一. 旋转向量


二. 欧拉角


三. 四元数


四. eigen库的实战


CH3~pangolin库的初级使用


CH3~课后习题


CH1 简介

1. Simutaneous localization and Mapping   解决定位与建图的问题,为路径规划做准备

2.


应用1:定位

弥补民用GPS精度的不足,室内无GPS,救援,反恐。

扫地机器人

AGV(Automated Guided Vehicle)

应用2:建图

VR、游戏、三位全景漫游

算法框架


分为激光slam和视觉slam


知识树

发展趋势

1. 消费级3D相机的广泛应用,无人驾驶技术的发展

2. 视觉slam越来越重要

3. 多传感器融合是大方向

4. 有适合的刚需场景

5. 入门难,人才缺口越来越大

6. 可替代性不强

CH2



时间:2021.6.6

1. 结构:场景的远近和大小

2. 单目相机有尺度不确定性:没办法确定二维图像的绝对大小。因此,需要双目相机和深度相机

3. 双目相机测定距离是通过对比左右眼图像实现的。

其缺点是:需要的计算资源大

4. RGB-D相机:有点像激光

缺点:测量范围债、容易受日光影响(因此主要用于室内)。

5. SLAM 框架(图是自己画的,可能丑了点)

6. SLAM的数学表示

其中第一个方程为

位置方程



Xk表示

位姿


,Uk表示运动传感器的读入,Wk是该过程加入的噪声;

是第二个方程为

观测方程

:Zk表示观测数据,Xk是位置,Vk是观测里的噪声

这个方程在

二维

的具体化为:

x1、x2确定位置,θ为转角(姿态),三个确定位姿

r为小萝卜于一个路标的距离(对应度量地图),Φ为夹角

CH2~2



时间:2021.6.11

今天主要学习了在Ubuntu18.04的环境下编程

1. 在一开始,我连创建一个文本文档都不会,还是在网上查了之后才知道要用gedit;

2. g++作为编译器,可以来编辑文件:在创建了helloslam.cpp之后,可以用在teminal 运行g++ helloslam.cpp来生成可执行文件。

3. 而cmake可以使链接等事情更加方便,通过创建一个CMakeLists.txt来实现此功能

先创建Helloslam.cpp、libHelloslam.cpp、libHelloslam.h


//在CMakeLists.txt中

project(Helloslam)             //声明文件

add_exectable(Helloslam Helloslam.cpp)  //添加源文件(可执行)

add_library(hello libHelloslam.cpp)              //添加库文件,

add_library(hello_shared SHARED libHelloslam.cpp) 生成共享库

target_link_libraries(Helloslam hello_shared)  //使Helloslam可执行文件和hello_shared共性库链接起来(前提是Helloslam里有include “libHelloslam.h”,libHelloslam.cpp对应的声明)

然后:创建一个build来储存中间文件


//teminal里

cd build

cmake ..

make

./Helloslam

即可执行

3. 在Kdevelop这个IDE里,可以直接省略teminal里的东西

值得注意的是,在里面CMakeLists.txt要添加set(CMAKE_BUILD_TYPE “Debug”),然后去run那里添加一个启动器;

还有一个搞我心态的一点:修改之后一定要点击左上角的build,才能执行不然会显示没有修改。

CH3



时间:2021.6.19



此次内容的学习大多于线代挂钩,如坐标变换(正交变换),旋转矩阵(正交阵的一个特殊例子)


1. 外积

(叉乘)可以用矩阵a^b来表示,其中a^表示该矩阵为反对称矩阵(A的转置=负A)aXb=a^b                       a^=
\begin{pmatrix} 0 & -a3 & a2\\ a3 & 0 & -a1\\ -a2 & a1 & 0 \end{pmatrix}



2. 欧氏变换

:(坐标变换)

前面为坐标系1,后面为坐标系2

表示坐标系2

旋转变化

到坐标系1,R称为旋转矩阵(列向量两两正交,|A|=+1)

{a}' = A^{-1}a=A^{T} a

若是加上平移

{a}' = A^{-1}a + t=A^{T} a + t

{a}' = A^{-1}a + t=A^{T} a + t = R_{12} a_{2}+t_{12}


其中:
R_{12}
表示从2坐标系变化到1坐标系,
t_{12}
表示坐标系1原点坐标指向坐标系2原点坐标的向量(指向被减向量)



3. 因为不是线性变化(有一个t)所以把非线性问题转化为线性问题

这样太麻烦,转化为

注意,此时三维问题用四维表示

T称为

变换矩阵

CH3~Eigen库的学习

1.eigen库的链接:

首先,按下快捷键



Ctrl + Shift + P



,输入


edit configuration


,选择


C/Cpp: Edit Configurations...


,此时会在工程的配置目录

.vscode

下生成一个配置文件:

c_cpp_properties.json,

(注:json文件中是不能添加注释在里面的),添加一行:”/usr/include/eigen3” 在includePath里面,如下

"includePath": [
                "${workspaceFolder}/**",
                "/usr/include/eigen3" 
            ],

Eigen 库的路径要写成#include 的形式,写#include 的形式可能vscode识别不出来


2. 定义方式


方式1:在开头添加一个模板:

template

using Mat2 = Matrix;

以后Mat2相当于一个像int类型的关键字,申明的都是2X2的矩阵


方式2:Vector2d vec;

定义了一个2维向量,2d可以变成3d,就是三维向量



方式3:直接用Matrixmat;(推荐)

这种方式是比较推荐的的方式



方式4:用MatrixXd mat(m,n);(推荐)

与上面区别不大,默认为double类型

3. 输入数据

mat<

实际上这个<<是在eigen库里进行了重载的。


特殊矩阵的输入:

MatrixXd zero=MatrixXd::Zero(3,2);             //纯0阵

MatrixXd one=MatrixXd::Ones(3,3);            //纯1阵

MatrixXd E=MatrixXd::Identity(4,4);            //单位阵

MatrixXd rand=MatrixXd::Random(5,2);     //随机阵,值-1到1

#include <iostream>
#include <eigen3/Eigen/Dense>
using namespace std;
using namespace Eigen;      //

template <typename T>
using Mat2 = Matrix<T,2,2>;//Mat2定义成2X2的矩阵
int main(void)
{
#if 0
    Mat2<int> mat1; //自定的定义方式
    mat1<<6,1,      //重载的<<
          1,3;
    cout<<mat1<<endl<<endl;

    Vector2d vec1;      //eigen自带的定义方式1
    vec1<<1,2;
    cout<<vec1<<endl<<endl;

    Matrix< double, 2, 3 > matrix_NN;//方式2:定义一个2X3的double矩阵

    MatrixXd mat(2,2);      //更常用的方法
    for(int i=0;i<2;i++)
        for(int j=0;j<2;j++)
            mat(i,j)=2;
    //也可以使用mat<<2,2,2,2;
    cout<<mat<<endl<<endl;

    //常见的矩阵
    MatrixXd zero=MatrixXd::Zero(3,2);      //纯0阵
    MatrixXd one=MatrixXd::Ones(3,3);       //纯1阵
    MatrixXd E=MatrixXd::Identity(4,4);     //单位阵
    MatrixXd rand=MatrixXd::Random(5,2);    //随机阵,值-1到1

    cout<<zero<<endl<<endl<<one<<endl<<endl<<E
        <<endl<<endl<<rand<<endl;
#endif
    MatrixXd mat2=MatrixXd::Ones(2,2);                 //若是不赋值,则直接打印出来为纯0阵
    cout<<mat2<<endl<<endl<<mat2.rows()<<' '<<mat2.cols()<<endl;              
    MatrixXd mat3(3,3);
    for(int i=0;i<2;i++)
        for(int j=0;j<2;j++)
            mat3(i,j)=2;
    mat2=mat3;                          //强制转换会使其变为等号后的长宽
                      //这个变成3X3,前2X2是mat3的2X2,其他变为0
                      //2 2 0
                      //2 2 0
                      //0 0 0
    cout<<mat2<<endl<<endl<<mat2.rows()<<' '<<mat2.cols()<<endl;               
    return 0;
}

4. 矩阵基本运算

1)加减乘照样,应该也是经过了运算符重载

2)逆、转置、行列式等:

    cout<<"transpose"<<endl<<mat2.transpose()<<endl;    //转置矩阵

    cout<<"inverse"<<endl<<mat2.inverse()<<endl;        //逆矩阵

    cout<<"adjoint"<<endl<<mat2.adjoint()<<endl;        //伴随矩阵

    cout<<"conjugate"<<endl<<mat2.conjugate()<<endl;    //共轭矩阵

    cout<<"各元素数和"<<endl<<mat2.sum()<<endl;           //数和

    cout<<"行列式"<<endl<<mat2.determinant()<<endl;      //行列式

    cout<<"迹"<<endl<<mat2.trace()<<endl;               //迹

    mat2.setZero();                                     //清0

3) 求特征值、特征向量:

eigen_solver.eigenvalues();

eigen_solver.eigenvectors();

4)矩阵分块操作

a.向量分块:

vec.head(n);

vec.tail(n);

vec.block(m,n);

b.矩阵分块

mat.block(1,0,2,4);                 //以(1,0)位置的点为标准,向下取2行,向右取4列

或者:

mat.block<2,4>(1,0);

#include <iostream>         //块操作
#include <eigen3/Eigen/Dense>
using namespace std;
using namespace Eigen;

int main(void)
{
    MatrixXd mat1(9,1);
    mat1<<1,2,3,4,5,6,7,8,9;
    cout<<"mat1"<<endl<<mat1<<endl;
    ArrayXd vec(9);                 
    vec<<1,2,3,4,5,6,7,8,9;
    cout<<"head:"<<endl<<vec.head(3)<<endl;//前3个
    cout<<"tail:"<<endl<<vec.tail(3)<<endl;//后三个
    cout<<"segment"<<endl<<vec.segment(3,6)<<endl;    //4到7的元素

    MatrixXd mat=MatrixXd::Identity(4,4);
    cout<<mat.row(3)<<endl;         //第4行
    cout<<mat.col(1)<<endl;         //第2列
    cout<<mat.block(1,0,2,4)<<endl<<endl; //以(1,0)位置的点为标准,向下取2行,向右取4列

    cout<<mat.block<2,4>(1,0)<<endl;      //同上
    return 0;
}

CH3~各种旋转的表示方式

time:2021.7.13

一. 旋转向量


def:任意一个旋转可以用一个旋转向量表示,该向量的方向与旋转轴一致,长度为
\theta
,表示为
\theta \vec{n}

旋转向量与旋转矩阵的转换:


R=Icosθ+(1-cosθ)n*


n


T+n^sinθ

\theta=arccos\frac{tr(R)-1}{2}

二. 欧拉角

def:把一个旋转分解为3次绕不同方向的旋转

以ZYX为例(绕旋转之后的轴)



不足:存在万向锁问题,即若绕y轴的旋转为
\pm 90^{\circ}
时(

第二次旋转

),第一次旋转和第三次旋转的轴相同,失去了一个自由度。也称奇异性。

三. 四元数

def:
\vec{q}=q_{0}+q_{1}\vec{i}+q_{2}\vec{j}+q_{3}\vec{k}=(s,\vec{v})^{T}

任意

单位四元数

可以表示一个旋转,若四元数不是单位四元数,要进行归一化。


1.虚部的含义

i,j,k之间有以下关系

i2 = j2 = k2 = -1

ij = k,ji =-k

jk = i, kj = -i

ki= j, ik = -j

乘以i对应着旋转180° , 而
i^{2}=-1
意味着绕i轴旋转360° 后得到一个相反的东西。 这个东西要旋转两周才会和它原先的样子相等。


2.运算规则

1.  加减

2. 乘

6. 数乘

k\vec{q}=(ks,k\vec{v})


3. 与其他表示旋转的量的转换

1)四元数到旋转矩阵

首先引入:e

易证:

设p’和p都是虚四元数(用来表示位置)

所以:


2)四元数到旋转向量


四. eigen库的实战

(和十四讲里的不同,是自己做完再试一次的)

1. 自制思维导图

2. 代码

#include <iostream>
#include <eigen3/Eigen/Geometry>
#include <eigen3/Eigen/Core>
#include <cmath>

using namespace std;
using namespace Eigen;
/*
旋转:
    旋转向量
    旋转矩阵
    欧拉角
    四元数

变换:
    欧氏变换矩阵
*/
int main(void)
{
    Vector3d vec(0,0,1);
    //表示旋转的几种方法的转换

    //旋转向量向旋转矩阵
    AngleAxisd rovector(M_PI/3,Vector3d(1,0,0));
    Matrix3d romatrix=rovector.matrix();
    cout<<"旋转矩阵="<<endl<<romatrix<<endl;
    cout<<"旋转之后="<<endl<<rovector*vec<<endl<<endl;
    cout<<"旋转之后="<<endl<<romatrix*vec<<endl<<endl;
    //旋转矩阵向欧拉角
    Vector3d euleranger = romatrix.eulerAngles(0,1,2);
    cout<<"欧拉角"<<endl<<euleranger.transpose()<<endl<<endl;
    //旋转向量向四元数
    Quaterniond q (rovector);
    cout<<"四元数="<<endl<<q.coeffs()<<endl;            //coeffs(q1,q2,q3,q0)
    cout<<"旋转之后="<<endl<<q*vec<<endl<<endl;
    //旋转矩阵向四元数
    Quaterniond q1 (romatrix);
    cout<<"四元数="<<endl<<q1.coeffs()<<endl;
    cout<<"旋转之后="<<endl<<q1*vec<<endl<<endl;

    //表示转换:平移加旋转,欧氏变换矩阵
    Vector3d tranvector (0,1,0);                    //平移参数
    Isometry3d isomatrix=Isometry3d::Identity();    //初始化
    isomatrix.rotate(rovector);                     //旋转部分赋值(R)
    isomatrix.pretranslate(tranvector);             //平移部分赋值(t)
    cout<<"欧氏变换矩阵="<<endl<<isomatrix.matrix()<<endl;
    /*
        R   t
        0   1
        实际数据:
        1         0         0         0
        0       0.5 -0.866025         1
        0  0.866025       0.5         0
        0         0         0         1
    */
    cout<<"变换之前="<<endl<<vec<<endl<<endl;
    cout<<"旋转之后="<<endl<<romatrix*vec<<endl<<endl;
    cout<<"变换之后="<<endl<<isomatrix*vec<<endl<<endl;
}

CH3~pangolin库的初级使用

时间:7月16日

单开这个节来说是要对自己的态度进行深刻的反思。我这一步从7月14就开始了,但当天尝试了很多次没有成功,就有点挫败,打算7月15再开始重新尝试。但7月15当天可能还是逃离挫败心理,没有立即开始尝试,而是想着早上先看番剧,下午再学。就这样,我一天看了一部24集的番剧,一点都没有学习。然后今天开始再次尝试,居然半个小时就搞定了,我突然有点怀疑我之前在害怕什么了。无非是按照教程一步步来,不过,我还是 没有习惯用cmake,太麻烦了,所以现在开始 要习惯在vscode下开始用cmake。

下面是成果图:

CH3~课后习题:

1~3,6题:

https://zhuanlan.zhihu.com/p/369180479

4:

5:

#include <iostream>
#include <Eigen/Dense>
using namespace std;
using namespace Eigen;

int main(void)
{
    MatrixXd origin_matrix=MatrixXd::Random(5,5);
    cout<<"origin matrix is"<<endl<<origin_matrix<<endl;
    Matrix3d sub_matrix=origin_matrix.block(0,0,3,3);
    cout<<"sub matrix is"<<endl<<sub_matrix<<endl;
    sub_matrix=MatrixXd::Identity(3,3);
    cout<<"now sub matrix is"<<endl<<sub_matrix<<endl;
}

补:review


1.

不同

类型

的矩阵不能进行运算;如double类型不能和float类型进行运算,Eigen库里自定义的都      是double类型的。如下,matrix_23是double,v_3d是float,这么解决:

 Eigen::Matrix<double,2,1> result=matrix_23.cast<double>()*v_3d;


2.

计算特征向量必须保证方阵是

   SelfAdjointEigenSolver<Matrix3d> eigen_solver(matrix_33);

其中直接Matrix<double,3,3>只能计算特征值,而上面都可以。


3.

对Matrix_33*x=v_Nd;

QR分解:x=Matrix_33.colPivHouseholderQr().solve(v_Nd);

cholesky分解:x=Matrix_33.Idlt().solve(v_Nd);


4.

旋转向量中的n为单位向量。


5.

Rn=n;


6.

任意单位四元数描述一个旋转

pangolin的学习

要包含unistd.h

#include <pangolin/pangolin.h>
#include <Eigen/Core>
#include <unistd.h>

// 本例演示了如何画出一个预先存储的轨迹

using namespace std;
using namespace Eigen;

// path to trajectory file
string trajectory_file = "./trajectory.txt";

void DrawTrajectory(vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>>);

int main(int argc, char **argv) {

  vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses;
  ifstream fin(trajectory_file);
  if (!fin) {
    cout << "cannot find trajectory file at " << trajectory_file << endl;
    return 1;
  }

  while (!fin.eof()) {
    double time, tx, ty, tz, qx, qy, qz, qw;
    fin >> time >> tx >> ty >> tz >> qx >> qy >> qz >> qw;
    Isometry3d Twr(Quaterniond(qw, qx, qy, qz));
    Twr.pretranslate(Vector3d(tx, ty, tz));
    poses.push_back(Twr);
  }
  cout << "read total " << poses.size() << " pose entries" << endl;

  // draw trajectory in pangolin
  DrawTrajectory(poses);
  return 0;
}

/*******************************************************************************************/
void DrawTrajectory(vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses) {
  //创建窗口,(窗口名字,长,宽)
  pangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 1000);
  glEnable(GL_DEPTH_TEST);//深度测试
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  pangolin::OpenGlRenderState s_cam(
    pangolin::ProjectionMatrix(1024, 1000, 420, 420, 320, 240, 0.1, 1000),//(h,w,fx,fy,cx,cy,最近距离,最远距离)
    pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0)     //
  );

  pangolin::View &d_cam = pangolin::CreateDisplay()
    .SetBounds(0.0, 1.0, 0.0, 1.0, -1024.0f / 1000.0f)//边界(左,右,上,下,长宽比),0表示碰到边界
    .SetHandler(new pangolin::Handler3D(s_cam));      //新建一个相机

  while (pangolin::ShouldQuit() == false) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清空颜色和深度缓存,刷新显示
    d_cam.Activate(s_cam);//激活显示并设置状态矩阵
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//使用红,绿,蓝以及AFA值来清除颜色缓冲区的,并且都被归一化在(0,1)之间的值,其实就是清空当前的所有颜色
    glLineWidth(2);
    for (size_t i = 0; i < poses.size(); i++) {
      // 画每个位姿的三个坐标轴
      Vector3d Ow = poses[i].translation();
      Vector3d Xw = poses[i] * (0.1 * Vector3d(1, 0, 0));
      Vector3d Yw = poses[i] * (0.1 * Vector3d(0, 1, 0));
      Vector3d Zw = poses[i] * (0.1 * Vector3d(0, 0, 1));
      glBegin(GL_LINES);//开始
      glColor3f(1.0, 0.0, 0.0);//x轴用red
      glVertex3d(Ow[0], Ow[1], Ow[2]);
      glVertex3d(Xw[0], Xw[1], Xw[2]);
      glColor3f(0.0, 1.0, 0.0);//y轴用green
      glVertex3d(Ow[0], Ow[1], Ow[2]);
      glVertex3d(Yw[0], Yw[1], Yw[2]);
      glColor3f(0.0, 0.0, 1.0);//z轴用blue
      glVertex3d(Ow[0], Ow[1], Ow[2]);
      glVertex3d(Zw[0], Zw[1], Zw[2]);
      glEnd();//结束
    }
    // 画出连线
    for (size_t i = 0; i < poses.size(); i++) {
      glColor3f(0.0, 0.0, 0.0);
      glBegin(GL_LINES);
      auto p1 = poses[i], p2 = poses[i + 1];
      glVertex3d(p1.translation()[0], p1.translation()[1], p1.translation()[2]);
      glVertex3d(p2.translation()[0], p2.translation()[1], p2.translation()[2]);
      glEnd();
    }

    pangolin::FinishFrame();
    usleep(5000);   // sleep 5 ms
  }
}


自己改了一点代码

该了y轴和连线的颜色,顺便改了窗口长宽比

y
去掉了中间的连线



版权声明:本文为m0_54225834原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。