第3讲 变换矩阵与齐次坐标

从上一讲的内容中可以知道一次完整的欧式变换(旋转+平移)可以用式子:\vec{a^{'}}=R\vec{a}+\vec{t}来表示。

假设我们现在对\vec{a}做了两次欧式变换:R_{1},\vec{t_{1}}R_{2},\vec{t_{2}},得到的向量分别为\vec{b}\vec{c},用上式来表示就是:

\vec{b}=R_{1}\vec{a}+\vec{t_{1}}\vec{c}=R_{2}\vec{b}+\vec{t_{2}}

于是从\vec{a}\vec{c}的变换就可以写成\vec{c}=R_{2}(R_{1}\vec{a}+\vec{t_{1}})+\vec{t_{2}}

这不是一个线性关系,可以想象到,如果经过多次的欧式变换,那么这个式子就会变的异常的复杂。

引入变换矩阵和齐次坐标就是为了解决这个问题。

变换矩阵和齐次坐标

我们可以将上面从\vec{a}\vec{c}变换的式子改写成用矩阵表示的形式:

在上面的式子中,我们在一个三维向量的末尾添加了数字1,使得左边变成了四维向量,称为齐次坐标。

将上面的矩阵打开就是下面的式子:\begin{bmatrix} \vec{a^{'}} \\ 1 \end{bmatrix} = \begin{bmatrix} R\vec{a}+\vec{t} \\ 1 \end{bmatrix}

对于这个四维向量,我们可以把旋转和平移写在一个矩阵里面,通过引入矩阵T使得整个关系变成线性关系。矩阵T称为变换矩阵。

关于齐次坐标,还有下面这些需要知道。

      通过添加最后一维,我们用四个实数来描述了一个三维向量,这显然多了一个自由度,但是这样允许我们把变换写成线性的形式。在齐次坐标中,某个点{\color{Red} x}的每个分量同时乘以一个非零常数{\color{Red} k}后,仍然表示同一个点。因此一个点的具体坐标值不是唯一的。[1,1,1,1]^{T}[2,2,2,2]^{T}表示的是同一个点。

      但是,当最后一项不为0时,我们总是可以把所有坐标分量除以最后一项,强制最后一项为1,从而得到一个点唯一的坐标表示。(也就是将非齐次坐标转换为齐次坐标)。如:a^{'}=[x,y,z,w]^{T}=[x/w,y/w,z/w,1]^{T}

通过齐次坐标和变换矩阵,两次变换(多次变换也是如此)的累加就可以有很好的形式:

\vec{b}=T_{1}\vec{a}\vec{c}=T_{2}\vec{b}  =====>    \vec{c}=T_{2}T_{1}\vec{a}

实践部分:Eigen库使用

      Eigen是一个c++开源线性代数库。它提供了快速的有关矩阵的线性代数运算,包括解方程等功能。关于Eigen的更多资料可以在这里找到。

1、Eigen库的安装

如果在ubuntu上还没有安装Eigen库的话,可以先使用apt-get命令安装Eigen:

sudo apt-get install libeigen3-dev

我已经安装过了,执行之后提示如下:

Eigen头文件的默认位置在 /usr/include/eigen3/ 中。

与其他库相比,Eigen的特殊之处在于:它是一个纯用头文件搭建起来的库。这意味着你只能找到它的头文件,而没有.so或者.a那样的二进制文件。在使用的时候,也只需要引入Eigen的头文件即可,不需要链接库文件(因为根本就没有库文件)。

2、调用Eigen库进行矩阵运算

首先,简单的介绍介绍一下Matrix class:Eigen库以矩阵为基本数据单元,它是一个模板类,定义如下:     

Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>

三个参数的含义分别为:数据类型、行数、列数。

例如,下面语句定义了一个4*4的float类型的矩阵:

Eigen::Matrix<float, 4, 4> matrix4f;

使用Eigen库的时候,由于Eigen库没有库文件,因此不需要使用target_link_directories语句,只需要使用一个链接头文件目录的语句include_directories,如下:

include_directories("/usr/include/eigen3")

源文件内容:

#include <iostream>
#include "Eigen/Dense"

using namespace std;

int main() {
    //定义一个2*3的int矩阵和一个3*2的int矩阵
    Eigen::Matrix<int, 2, 3> matrix3i_a;
    Eigen::Matrix<int, 3, 2> matrix3i_b;

    //输入矩阵matrix3i_a数据
    cout<<"matrix3i_a的内容为:"<<endl;
    matrix3i_a<<1, 2, 3, 4, 5, 6;
    cout<<matrix3i_a<<endl;

    //输入矩阵matrix3i_b数据
    for(int i=0; i<3; i++){
        for(int j=0; j<2; j++){
            matrix3i_b(i, j)=2*i;
        }
    }
    cout<<"matrix3i_b的内容为:"<<endl;
    cout<<matrix3i_b<<endl;

    //matrix3i_a和matrix3i_b进行四则运算
    cout<<"matrix3i_a*matrix3i_b的结果为:"<<endl;
    cout<<matrix3i_a*matrix3i_b<<endl;

    cout<<"matrix3i_a的转置为:"<<endl;
    cout<<matrix3i_a.transpose()<<endl;
    cout<<"matrix3i_a的各个元素的和为:"<<endl;
    cout<<matrix3i_a.sum()<<endl;

    return 0;
}

CMakeLists.txt的内容:

cmake_minimum_required(VERSION 3.12)
project(use_eigen)

set(CMAKE_CXX_STANDARD 14)

#添加头文件
include_directories("/usr/include/eigen3")

add_executable(use_eigen main.cpp)

上面的程序中需要注意的就是:输入矩阵数据的时候,使用的是输出运算符而不是输入运算符。(这里的运算符都是重载之后的运算符)。

这一篇暂时就先学到这里了,明天开始学习旋转向量、欧拉角、四元数。

猜你喜欢

转载自blog.csdn.net/llfjcmx/article/details/82747302
今日推荐