视觉SLAM十四讲笔记-3-03

视觉SLAM十四讲笔记-3-03


参考链接: link
高翔,张涛,等. 视觉 SLAM 十四讲:从理论到实践[M]. 电子工业出版社, 2019.

3.6 实践:Eigen几何模块

3.6.1 Eigen几何模块的数据演示

新建 useGeometry 文件夹,在该文件夹下打开 VS Code

mkdir useGeometry
cd useGeometry
code .

请添加图片描述
然后在该文件夹下新建 launch.json 文件和 tasks.json 文件。
直接参考这篇博客 link 中的步骤,进行创建。

//launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "g++ - 生成和调试活动文件",
            "type": "cppdbg",
            "request":"launch",
            "program":"${workspaceFolder}/build/useGeometry",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启动整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "Build",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}
//tasks.json
{
	"version": "2.0.0",
	"options":{
		"cwd": "${workspaceFolder}/build"   //指明在哪个文件夹下做下面这些指令
	},
	"tasks": [
		{
			"type": "shell",
			"label": "cmake",   //label就是这个task的名字,这个task的名字叫cmake
			"command": "cmake", //command就是要执行什么命令,这个task要执行的任务是cmake
			"args":[
				".."
			]
		},
		{
			"label": "make",  //这个task的名字叫make
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"command": "make",  //这个task要执行的任务是make
			"args": [

			]
		},
		{
			"label": "Build",
			"dependsOrder": "sequence", //按列出的顺序执行任务依赖项
			"dependsOn":[				//这个label依赖于上面两个label
				"cmake",
				"make"
			]
		}
	]
}

由于要使用 eigen 库,因此需要在 CMakeLists.txt 中将 eigen 的头文件添加进去,这里直接使用绝对路径,添加完的 CMakeLists.txt 如下:

cmake_minimum_required(VERSION 3.0)

project(EIGENMATRIX)

#在g++编译时,添加编译参数,比如-Wall可以输出一些警告信息
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

#一定要加上这句话,加上这个生成的可执行文件才是可以Debug的,不然不加或者是Release的话生成的可执行文件是无法进行调试的
set(CMAKE_BUILD_TYPE Debug)

#添加头文件
include_directories(include)
include_directories("/usr/include/eigen3")
#或者通过绝对路径来进行添加,include_directories(${CMAKE_SOURCE_DIR}/include)
#其中${CMAKE_SOURCE_DIR}就是VSCODE_C_LEARNING_3这个文件夹的绝对路径

add_executable(useGeometry useGeometry.cpp)

然后写个简单的 main 函数测试下,保存,按下 F5 发现可以正常编译。
开始学习本节代码。

#include <iostream>
#include <cmath>
using namespace std;
#include <Eigen/Core>
#include <Eigen/Geometry>
using namespace Eigen;
//本程序演示了 Eigen 几何模块的使用方法
int main()
{
    //Eigen/Geometry模块提供了各种旋转和平移的表示
    //3D旋转矩阵直接使用Matrix3d和Matrix3f,赋值为单位阵
    Matrix3d rotation_matrix = Matrix3d::Identity();
    //cout << "rotation_matrix:" << rotation_matrix << endl;
    //旋转向量使用AngleAxis,它底层不直接是Matrix,但运算可以当做矩阵(因为重载了运算符)
    AngleAxisd rotation_vector(M_PI / 4, Vector3d(0, 0, 1)); //沿z轴旋转了45度
    cout.precision(3);
    cout << "rotation matrix = \n" << rotation_vector.matrix() << endl; //用matrix()转换为矩阵
    //阵也可以直接赋值
    rotation_matrix = rotation_vector.toRotationMatrix();
    //用AngleAxis可以进行坐标变换,Vector3d为Eigen::Matrix<double, 3, 1>
    Vector3d v(1, 0, 0);
    Vector3d v_rotated = rotation_vector * v;
    cout << "(1,0,0) after rotation(by angle axis) = " << v_rotated.transpose() << endl;
    //或者使用旋转矩阵
    v_rotated = rotation_matrix * v;
    cout << "(1,0,0) after rotation(by matrix) = " << v_rotated.transpose() << endl; 

    //欧拉角:可以将旋转矩阵直接转换成欧拉角
    Vector3d euler_angles = rotation_matrix.eulerAngles(2, 1, 0); //ZYX顺序, 即roll pitch yaw顺序
    cout << "yaw pitch roll = " << euler_angles.transpose() << endl;

    //欧氏变换矩阵使用Eigen::Isometry
    Isometry3d T = Isometry3d::Identity(); //虽然称为3D,实质上是4*4的矩阵
    T.rotate(rotation_vector); //按照rotation_vector进行旋转
    T.pretranslate(Vector3d(1,3,4)); //把平移向量设为(1,3,4)

    cout << "Transform matrix = \n" << T.matrix() << endl;

    //用变换矩阵进行坐标变换
    Vector3d v_transformed  = T * v; //相当于 R * v + t
    cout << "v transformed = " << v_transformed.transpose() << endl;

    //对于仿射变换和射影变换,使用Eigen::Affine3d和Eigen::Projective3d即可,略

    //四元数
    //可以直接将AngleAxis赋值给四元数,反之亦然
    Quaterniond q = Quaterniond(rotation_vector);
    cout << "quaternion from rotation vector = " << q.coeffs().transpose() << endl; //请注意coeffs的顺序是(x,y,z,w),w为实部,前三者为虚部
    //也可以将旋转矩阵赋值给它
    q = Quaterniond(rotation_matrix);
    cout << "quaternion from rotation matrix= " << q.coeffs().transpose() << endl;
    //使用四元数旋转一个向量,使用重载的乘法即可
    v_rotated  = q * v; //注意数学上是 qvq^{-1}
    cout << "(1,0,0) after rotation = " << v_rotated.transpose() << endl;
    //用常规向量乘法表示,则计算如下啊:
    cout << "should ba equal to " << (q * Quaterniond(0, 1, 0, 0) * q.inverse()).coeffs().transpose() << endl;


    return 0;
}

按下 F5 输出:

rotation matrix = 
 0.707 -0.707      0
 0.707  0.707      0
     0      0      1
(1,0,0) after rotation(by angle axis) = 0.707 0.707     0
(1,0,0) after rotation(by matrix) = 0.707 0.707     0
yaw pitch roll = 0.785    -0     0
Transform matrix = 
 0.707 -0.707      0      1
 0.707  0.707      0      3
     0      0      1      4
     0      0      0      1
v transformed = 1.71 3.71    4
quaternion from rotation vector =     0     0 0.383 0.924
quaternion from rotation matrix=     0     0 0.383 0.924
(1,0,0) after rotation = 0.707 0.707     0
should ba equal to 0.707 0.707     0     0

想进一步了解 Eigen 的几何模块的读者可以参考 link

3.6.2实际的坐标变换例子

根上述例子一样创建工程,然后编写下面程序,进行调试。

/*
小萝卜一号和小萝卜二号都位于世界坐标系下。记世界坐标系为 W,小萝卜们的坐标系分别为 R1 和 R2.
小萝卜一号的位姿为 q1 = [0.35, 0.2, 0.3, 0.1]^T, t1 = [0.3, 0.1, 0.1]^T
小萝卜二号的位姿为 q1 = [-0.5, 0.4, -0.1, 0.2]^T, t1 = [-0.1, 0.5, 0.3]^T
这里的q 和 t 表达的是 T_{R_[k}], W},k = 1,2,即世界坐标系到相机坐标系的变换关系
现在小萝卜一号看到某个点在自身坐标系下的坐标为 p_{R1} = [0.5, 0, 0.2]^T,求该向量在小萝卜二号坐标系下的坐标
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <Eigen/Core>
#include <Eigen/Geometry>
using namespace std;
using namespace Eigen;
int main()
{
    Quaterniond q1(0.35, 0.2, 0.3, 0.1), q2(-0.5, 0.4, -0.1, 0.2);
    //四元数在使用前要初始化
    q1.normalize();
    q2.normalize();
    Vector3d t1(0.3, 0.1, 0.1), t2(-0.1, 0.5, 0.3);
    Vector3d p1(0.5, 0, 0.2);

    Isometry3d T1w(q1), T2w(q2);
    T1w.pretranslate(t1);
    T2w.pretranslate(t2);

    Vector3d p2 = T2w * T1w.inverse() * p1;
    cout << endl << p2.transpose() << endl;


    return 0;
}

输出:

-0.0309731    0.73499   0.296108

3.7 可视化演示

3.7.1 显示运动轨迹

首先,通过某种方式记录了一个机器人的运动轨迹,现在想把它画在一个窗口中。假设轨迹文件存储在 trajectory.txt 中,每一行用下面的格式存储:
t i m e , t x , t y , t z , q x , q y , q z , q w time, t_x,t_y,t_z,q_x,q_y,q_z,q_w time,tx,ty,tz,qx,qy,qz,qw
其中, time 指该位姿的记录时间, t 为平移, q 为旋转四元数,均为世界坐标系到机器人坐标系记录。下面从文件中读取这些轨迹,并显示在一个窗口中。绘制轨迹:其实是机器人(相机)坐标系的原点在世界坐标系中的坐标。
考虑机器人坐标系的原点 O R O_{R} OR,此时的 O W O_{W} OW 就是这个原点在世界坐标系下的坐标。
O W = T W R ∗ O R O_{W} = T_{WR} * O_{R} OW=TWROR
这里需要安装基于 OpenGL 的 Pangolin 库。
slam 14 讲中是从 link 处下载的 Pangolin,我也用以下命令进行安装:

//安装依赖库
sudo apt install libgl1-mesa-dev
sudo apt install libglew-dev
sudo apt install cmake

//建议安装的库
sudo apt install libpython2.7-dev
sudo apt install pkg-config
sudo apt install libegl1-mesa-dev libwayland-dev libxkbcommon-dev wayland-protocols

//安装源文件
git clone https://github.com/stevenlovegrove/Pangolin.git
cd Pangolin
mkdir build
cd build
cmake ..
make
make install

但是在运行到 c m a k e . . cmake .. cmake.. 时会报错误:请添加图片描述
在网上找了解决的办法,参考链接: link 但还是没有解决。
重新找思路,在其他群中看到一个 Pangolin 的版本,下载下来编译安装,成功安装上。
安装过程见 link
新建文件夹 plotTrajectory, 并在文件夹中打开 VS Code

mkdir plotTrajectory
cd plotTrajectory
code .

按照以前的流程建立工程,并建立文件:
请添加图片描述
创建 launch.json,tasks.json

//launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "g++ - 生成和调试活动文件",
            "type": "cppdbg",
            "request":"launch",
            "program":"${workspaceFolder}/build/plotTrajectory",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启动整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "Build",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}
//tasks.json
{
	"version": "2.0.0",
	"options":{
		"cwd": "${workspaceFolder}/build"   //指明在哪个文件夹下做下面这些指令
	},
	"tasks": [
		{
			"type": "shell",
			"label": "cmake",   //label就是这个task的名字,这个task的名字叫cmake
			"command": "cmake", //command就是要执行什么命令,这个task要执行的任务是cmake
			"args":[
				".."
			]
		},
		{
			"label": "make",  //这个task的名字叫make
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"command": "make",  //这个task要执行的任务是make
			"args": [

			]
		},
		{
			"label": "Build",
			"dependsOrder": "sequence", //按列出的顺序执行任务依赖项
			"dependsOn":[				//这个label依赖于上面两个label
				"cmake",
				"make"
			]
		}
	]
}

编写 CMakeLists.txt,这里除了要添加 Eigen 的头文件外,还要添加 Panlogin 的头文件和库文件:

cmake_minimum_required(VERSION 3.0)

project(EIGENMATRIX)

#在g++编译时,添加编译参数,比如-Wall可以输出一些警告信息
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11")

#一定要加上这句话,加上这个生成的可执行文件才是可以Debug的,不然不加或者是Release的话生成的可执行文件是无法进行调试的
set(CMAKE_BUILD_TYPE Debug)

#添加头文件
include_directories(include)
#Eigen库只需要添加头文件
include_directories("/usr/include/eigen3")
#或者通过绝对路径来进行添加,include_directories(${CMAKE_SOURCE_DIR}/include)
#其中${CMAKE_SOURCE_DIR}就是VSCODE_C_LEARNING_3这个文件夹的绝对路径

#添加Pangolin依赖
find_package(Pangolin REQUIRED)
include_directories(${Pangolin_INCLUDE_DIRS})
add_executable(plotTrajectory plotTrajectory.cpp)
target_link_libraries(plotTrajectory ${Pangolin_LIBRARIES})

然后开始编写 plotTrajectory.cpp:

#include <pangolin/pangolin.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <unistd.h>
#include <iostream>
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()
{
    vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses;
    //虽然会爆红,但是不影响编译
    ifstream fin(trajectory_file);
    cout << "trajectory_file:" << trajectory_file << endl;
    if(!fin)
    {
        cout << "cannot find trajectory file at " << trajectory_file << endl;
        return 1;
    }
    //把每一个点的坐标及变换矩阵数据读进来,将数据弄成变换矩阵的形式存到vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses中
    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 << "real total" << poses.size() << "pose entries" << endl;

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

void DrawTrajectory(vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses)
{
    //create pangolin window and plot the trajectory
    pangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    pangolin::OpenGlRenderState s_cam(
        pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),
        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 / 768.0f).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);
        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);
            glVertex3d(Ow[0], Ow[1], Ow[2]);
            glVertex3d(Xw[0], Xw[1], Xw[2]);
            glColor3f(0.0, 1.0, 0.0);
            glVertex3d(Ow[0], Ow[1], Ow[2]);
            glVertex3d(Yw[0], Yw[1], Yw[2]);
            glColor3f(0.0, 0.0, 1.0);
            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);
    }

}

运行结果:
请添加图片描述

3.7.2 显示相机的位姿

创建文件夹 visualizeGeometry ,并在该文件夹下打开 VS Code。

mkdir visualizeGeometry
cd visualizeGeometry/
code .

和 3.7.1 一样创建工程,并编写 launch.json 和 tasks.json, CMakeLists.txt,只不过在 launch.json 和 CMakeLists.txt 中将可执行文件的名字改为 visualizeGeometry。
然后编写 visualizeGeometry.cpp:

#include <iostream>
using namespace std;
#include <iomanip>

#include <Eigen/Core>
#include <Eigen/Geometry>
using namespace Eigen;

#include <pangolin/pangolin.h>

struct RotationMatrix {
  Matrix3d matrix = Matrix3d::Identity();
};

ostream &operator<<(ostream &out, const RotationMatrix &r) {
  out.setf(ios::fixed);
  Matrix3d matrix = r.matrix;
  out << '=';
  out << "[" << setprecision(2) << matrix(0, 0) << "," << matrix(0, 1) << "," << matrix(0, 2) << "],"
      << "[" << matrix(1, 0) << "," << matrix(1, 1) << "," << matrix(1, 2) << "],"
      << "[" << matrix(2, 0) << "," << matrix(2, 1) << "," << matrix(2, 2) << "]";
  return out;
}

istream &operator>>(istream &in, RotationMatrix &r) {
  return in;
}

struct TranslationVector {
  Vector3d trans = Vector3d(0, 0, 0);
};

ostream &operator<<(ostream &out, const TranslationVector &t) {
  out << "=[" << t.trans(0) << ',' << t.trans(1) << ',' << t.trans(2) << "]";
  return out;
}

istream &operator>>(istream &in, TranslationVector &t) {
  return in;
}

struct QuaternionDraw {
  Quaterniond q;
};

ostream &operator<<(ostream &out, const QuaternionDraw quat) {
  auto c = quat.q.coeffs();
  out << "=[" << c[0] << "," << c[1] << "," << c[2] << "," << c[3] << "]";
  return out;
}

istream &operator>>(istream &in, const QuaternionDraw quat) {
  return in;
}

int main(int argc, char **argv) {
  pangolin::CreateWindowAndBind("visualize geometry", 1000, 600);
  glEnable(GL_DEPTH_TEST);
  pangolin::OpenGlRenderState s_cam(
    pangolin::ProjectionMatrix(1000, 600, 420, 420, 500, 300, 0.1, 1000),
    pangolin::ModelViewLookAt(3, 3, 3, 0, 0, 0, pangolin::AxisY)
  );

  const int UI_WIDTH = 500;

  pangolin::View &d_cam = pangolin::CreateDisplay().
    SetBounds(0.0, 1.0, pangolin::Attach::Pix(UI_WIDTH), 1.0, -1000.0f / 600.0f).
    SetHandler(new pangolin::Handler3D(s_cam));

  // ui
  pangolin::Var<RotationMatrix> rotation_matrix("ui.R", RotationMatrix());
  pangolin::Var<TranslationVector> translation_vector("ui.t", TranslationVector());
  pangolin::Var<TranslationVector> euler_angles("ui.rpy", TranslationVector());
  pangolin::Var<QuaternionDraw> quaternion("ui.q", QuaternionDraw());
  pangolin::CreatePanel("ui").SetBounds(0.0, 1.0, 0.0, pangolin::Attach::Pix(UI_WIDTH));

  while (!pangolin::ShouldQuit()) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    d_cam.Activate(s_cam);

    pangolin::OpenGlMatrix matrix = s_cam.GetModelViewMatrix();
    Matrix<double, 4, 4> m = matrix;

    RotationMatrix R;
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
        R.matrix(i, j) = m(j, i);
    rotation_matrix = R;

    TranslationVector t;
    t.trans = Vector3d(m(0, 3), m(1, 3), m(2, 3));
    t.trans = -R.matrix * t.trans;
    translation_vector = t;

    TranslationVector euler;
    euler.trans = R.matrix.eulerAngles(2, 1, 0);
    euler_angles = euler;

    QuaternionDraw quat;
    quat.q = Quaterniond(R.matrix);
    quaternion = quat;

    glColor3f(1.0, 1.0, 1.0);

    pangolin::glDrawColouredCube();
    // draw the original axis
    glLineWidth(3);
    glColor3f(0.8f, 0.f, 0.f);
    glBegin(GL_LINES);
    glVertex3f(0, 0, 0);
    glVertex3f(10, 0, 0);
    glColor3f(0.f, 0.8f, 0.f);
    glVertex3f(0, 0, 0);
    glVertex3f(0, 10, 0);
    glColor3f(0.2f, 0.2f, 1.f);
    glVertex3f(0, 0, 0);
    glVertex3f(0, 0, 10);
    glEnd();

    pangolin::FinishFrame();
  }
}

运行结果:
请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值