SLAM算法与工程实践——SLAM基本库的安装与使用(1):Eigen库

SLAM算法与工程实践系列文章

下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此

SLAM算法与工程实践系列文章链接


下面是专栏地址:

SLAM算法与工程实践系列专栏


文章目录

  • SLAM算法与工程实践系列文章
    • SLAM算法与工程实践系列文章链接
    • SLAM算法与工程实践系列专栏
  • 前言
  • SLAM算法与工程实践——SLAM基本库的安装与使用(1):Eigen库
  • Eigen
    • 安装
    • 查询版本
    • 基本使用
      • eigenMatrix.cpp
        • 包含头文件
        • Eigen::Matrix
        • 初始化
        • 访问矩阵元素
        • 矩阵运算
        • 特征值
        • 解方程
        • 矩阵分解
      • eigenGeometry.cpp
        • 旋转向量
        • 欧拉角
        • 四元数
      • visualizeGeometry.cpp
      • 特征值计算
      • 出现的错误
    • 常见用法


前言

这个系列的文章是分享SLAM相关技术算法的学习和工程实践


SLAM算法与工程实践——SLAM基本库的安装与使用(1):Eigen库

Eigen

Eigen库官网:https://eigen.tuxfamily.org/index.php?title=Main_Page

Eigen 3 官方文档:https://eigen.tuxfamily.org/dox/

安装

Eigen3是一个纯头文件的库,这个特点让使用者省去了很多安装和环境配置的麻烦

直接安装:

sudo apt-get install libeigen3-dev

在这里插入图片描述

或者下载源码解压缩安装包

git clone https://github.com/eigenteam/eigen-git-mirror.git
cd eigen-git-mirror
mkdir build
cd build
cmake ..
sudo make install

#安装后 头文件安装在/usr/local/include/eigen3/
#移动头文件
sudo cp -r /usr/local/include/eigen3/Eigen /usr/local/include

备注:在很多程序中 include 时经常使用 #include <Eigen/Dense> 而不是使用 #include <eigen3/Eigen/Dense> 所以要做下处理

查询版本

参考:

查看Eigen、CMake、ceres、opencv版本

找到eigen本地目录下的Macros.h头文件查看对应的版本。

执行如下命令:

sudo nano /usr/include/eigen3/Eigen/src/Core/util/Macros.h

可以看到Eigen的版本为3.3.7

在这里插入图片描述

基本使用

头文件

在这里插入图片描述

一般情况下,只需要:

#include <Eigen/Core>
#include <Eigen/Dense>

Eign中对各种形式的表达方式总结如下。请注意每种类型都有单精度和双精度两种数据类型,而且和之前一样,不能由编译器自动转换。下面以双精度为例,你可以把最后的 d 改成 f ,即得到单精度的数据结构。

  • 旋转矩阵(3×3):Eigen:Matrix.3d。
  • 旋转向量(3×1):Eigen:AngleAxisd。
  • 欧拉角(3×1):Eigen:Vector3d。
  • 四元数(4×1):Eigen:Quaterniond
  • 欧氏变换矩阵(4×4):Eigen:Isometry3d。
  • 仿射变换(4×4):Eigen:Affine3d。
  • 射影变换(4×4):Eigen:Projective.3d。

参考代码中对应的CMakeLists即可编译此程序。在下面的程序中,演示了如何使用Eigen中的旋转矩阵、旋转向量、欧拉角和四元数。我们用这几种旋转方式旋转一个向量v,发现结果是一样的。

同时,也演示了如何在程序中转换这几种表达方式。想进一步了解Eigen的几何模块的读者可以参考(http://eigen.tuxfamily.org/dox/group__TutorialGeometry.html)

注意:

程序代码通常和数学表示有一些细微的差别。例如,通过运算符重载,四元数和三维向量可以直接计算乘法,但在数学上则需要先把向量转成虚四元数,再利用四元数乘法进行计算,同样的情况也适用于变换矩阵乘三维向量的情况。总体而言,程序中的用法会比数学公式更灵活。

eigenMatrix.cpp

包含头文件
#include <ctime>	// 用来计时
// Eigen 核心部分
#include <Eigen/Core>
// 稠密矩阵的代数运算(逆,特征值等)
#include <Eigen/Dense>

一般多用到这两个头文件,Dense 里面其实已经包含了 Core 中的内容,只写 #include <Eigen/Dense> 即可

Eigen 中所有向量和矩阵都是 Eigen::Matrix,它是一个模板类。它的前三个参数为:数据类型,行,列

声明一个2*3的float矩阵

Matrix<float, 2, 3> matrix_23;
Eigen::Matrix

详细解读见官网文档:https://eigen.tuxfamily.org/dox/group__TutorialMatrixClass.html

Eigen 通过 typedef 提供了许多内置类型,不过底层仍是 Eigen::Matrix

例如 Vector3d 实质上是 Eigen::Matrix<double, 3, 1>,即三维向量

Vector3d v_3d;

这是一样的

Matrix<float, 3, 1> vd_3d;

将鼠标移动到 Vector3d 处,可以看到

typedef Eigen::Matrix<double, 3, 1> Eigen::Vector3d

Matrix3d 实质上是 Eigen::Matrix<double, 3, 3>

Matrix3d matrix_33 = Matrix3d::Zero(); //初始化为零

如果不确定矩阵大小,可以使用动态大小的矩阵

Matrix<double, Dynamic, Dynamic> matrix_dynamic;

更简单的

MatrixXd matrix_x;

MatrixXd 表示动态大小的矩阵,其定义为

typedef Eigen::Matrix<double, -1, -1> Eigen::MatrixXd

这种类型还有很多,我们不一一列举

初始化

下面是对Eigen阵的操作

输入数据(初始化),这里默认按行输入

matrix_23 << 1, 2, 3, 4, 5, 6;

输出

cout << "matrix 2x3 from 1 to 6: \n" << matrix_23 << endl;
访问矩阵元素

用()访问矩阵中的元素

cout << "print matrix 2x3: " << endl;
for (int i = 0; i < 2; i++) {
	for (int j = 0; j < 3; j++) cout << matrix_23(i, j) << "\t";
	cout << endl;
}
矩阵运算

矩阵和向量相乘(实际上仍是矩阵和矩阵)

v_3d << 3, 2, 1;
vd_3d << 4, 5, 6;

但是在Eigen里你不能混合两种不同类型的矩阵,像这样是错的

Matrix<double, 2, 1> result_wrong_type = matrix_23 * v_3d;

应该显式转换

Matrix<double, 2, 1> result = matrix_23.cast<double>() * v_3d;
cout << "[1,2,3;4,5,6]*[3,2,1]=" << result.transpose() << endl;

Matrix<float, 2, 1> result2 = matrix_23 * vd_3d;
cout << "[1,2,3;4,5,6]*[4,5,6]: " << result2.transpose() << endl;

注意:这里输出时转置是为了节省空间,因为计算结果为列向量,将其转置后为行向量,显示比较方便

在这里插入图片描述>

同样你不能搞错矩阵的维度
试着取消下面的注释,看看Eigen会报什么错

// Eigen::Matrix<double, 2, 3> result_wrong_dimension = matrix_23.cast<double>() * v_3d;

一些矩阵运算
四则运算就不演示了,直接用±*/即可。

matrix_33 = Matrix3d::Random();      // 随机数矩阵
cout << "random matrix: \n" << matrix_33 << endl;
cout << "transpose: \n" << matrix_33.transpose() << endl;      // 转置
cout << "sum: " << matrix_33.sum() << endl;            // 各元素和
cout << "trace: " << matrix_33.trace() << endl;          // 迹
cout << "times 10: \n" << 10 * matrix_33 << endl;               // 数乘
cout << "inverse: \n" << matrix_33.inverse() << endl;        // 逆
cout << "det: " << matrix_33.determinant() << endl;    // 行列式

结果如下

在这里插入图片描述

特征值

实对称矩阵可以保证对角化成功

matrix_33 = Matrix3d::Random();      // 随机数矩阵
SelfAdjointEigenSolver<Matrix3d> eigen_solver(matrix_33.transpose() * matrix_33);
cout << "Eigen values = \n" << eigen_solver.eigenvalues() << endl;
cout << "Eigen vectors = \n" << eigen_solver.eigenvectors() << endl;

这里 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> 的意义为:

class Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d>
Computes eigenvalues and eigenvectors of selfadjoint matrices

模板参数:
_MatrixType – the type of the matrix of which we are computing the eigendecomposition; this is expected to be an instantiation of the Matrix class template. A matrix

结果如下;

在这里插入图片描述

解方程

我们求解 matrix_NN * x = v_Nd 这个方程,即求解 A x = b Ax=b Ax=b

N的大小在前边的宏里定义,它由随机数生成

直接求逆自然是最直接的,但是求逆运算量大

#define MATRIX_SIZE 50

Matrix<double, MATRIX_SIZE, MATRIX_SIZE> matrix_NN
      = MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
  matrix_NN = matrix_NN * matrix_NN.transpose();  // 保证半正定
  Matrix<double, MATRIX_SIZE, 1> v_Nd = MatrixXd::Random(MATRIX_SIZE, 1);

clock_t time_stt = clock(); // 计时
  // 直接求逆
Matrix<double, MATRIX_SIZE, 1> x = matrix_NN.inverse() * v_Nd;
cout << "time of normal inverse is "
       << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
cout << "x = " << x.transpose() << endl;

结果为:

在这里插入图片描述

矩阵分解

矩阵分解详见:https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html

通常用矩阵分解来求,例如QR分解,速度会快很多

Matrix<double, MATRIX_SIZE, MATRIX_SIZE> matrix_NN
      = MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
  matrix_NN = matrix_NN * matrix_NN.transpose();  // 保证半正定
  Matrix<double, MATRIX_SIZE, 1> v_Nd = MatrixXd::Random(MATRIX_SIZE, 1);

time_stt = clock();
x = matrix_NN.colPivHouseholderQr().solve(v_Nd);
cout << "time of Qr decomposition is "
     << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
cout << "x = " << x.transpose() << endl;

结果为:

在这里插入图片描述

在QR分解中

#include <iostream>
#include <Eigen/Dense>
 
int main()
{
   Eigen::Matrix3f A;
   Eigen::Vector3f b;
   A << 1,2,3,  4,5,6,  7,8,10;
   b << 3, 3, 4;
   std::cout << "Here is the matrix A:\n" << A << std::endl;
   std::cout << "Here is the vector b:\n" << b << std::endl;
   Eigen::Vector3f x = A.colPivHouseholderQr().solve(b);
   std::cout << "The solution is:\n" << x << std::endl;
}

输出结果:

Here is the matrix A:
 1  2  3
 4  5  6
 7  8 10
Here is the vector b:
3
3
4
The solution is:
-2
 1
 1

在本例中,colPivHouseholderQr() 方法返回类 ColPivHouse holderQR 的对象。由于这里的矩阵是 Matrix3f 类型的,所以这一行可能被替换为:

ColPivHouseholderQR<Matrix3f> dec(A);
Vector3f x = dec.solve(b);

对于正定矩阵,还可以用cholesky分解来解方程

time_stt = clock();
x = matrix_NN.ldlt().solve(v_Nd);
cout << "time of ldlt decomposition is "
     << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
cout << "x = " << x.transpose() << endl;

结果为:

在这里插入图片描述

此处,ColPivHouseholderQR是一个带有列主的QR分解。对于本教程来说,这是一个很好的折衷方案,因为它适用于所有矩阵,同时速度很快。

以下是一些其他分解的表格,您可以根据矩阵、您试图解决的问题以及您想要进行的权衡进行选择:

DecompositionMethodRequirements on the matrixSpeed (small-to-medium)Speed (large)Accuracy
PartialPivLUpartialPivLu()Invertible+++++
FullPivLUfullPivLu()None-- -+++
HouseholderQRhouseholderQr()None+++++
ColPivHouseholderQRcolPivHouseholderQr()None+-+++
FullPivHouseholderQRfullPivHouseholderQr()None-- -+++
CompleteOrthogonalDecompositioncompleteOrthogonalDecomposition()None+-+++
LLTllt()Positive definite+++++++
LDLTldlt()Positive or negative semidefinite++++++
BDCSVDbdcSvd()None--+++
JacobiSVDjacobiSvd()None-- - -+++

要获得不同分解的真实相对速度的概述,请查看此benchmark .

方阵是对称的,对于过约束矩阵,报告的时间包括计算对称协方差矩阵的成本 A T A A^TA ATA 对于前四个基于 Cholesky 和 LU 的求解器,用*****符号表示(表的右上角部分)。计时以毫秒为单位,因素与LLT分解有关, LLT分解速度最快,但也是最不通用和鲁棒的。

solver/size8x8100x1001000x10004000x400010000x810000x10010000x100010000x4000
LLT0.050.425.83374.556.79 *30.15 *236.34 *3847.17 *
LDLT0.07 (x1.3)0.65 (x1.5)26.86 (x4.6)2361.18 (x6.3)6.81 (x1) *31.91 (x1.1) *252.61 (x1.1) *5807.66 (x1.5) *
PartialPivLU0.08 (x1.5)0.69 (x1.6)15.63 (x2.7)709.32 (x1.9)6.81 (x1) *31.32 (x1) *241.68 (x1) *4270.48 (x1.1) *
FullPivLU0.1 (x1.9)4.48 (x10.6)281.33 (x48.2)-6.83 (x1) *32.67 (x1.1) *498.25 (x2.1) *-
HouseholderQR0.19 (x3.5)2.18 (x5.2)23.42 (x4)1337.52 (x3.6)34.26 (x5)129.01 (x4.3)377.37 (x1.6)4839.1 (x1.3)
ColPivHouseholderQR0.23 (x4.3)2.23 (x5.3)103.34 (x17.7)9987.16 (x26.7)36.05 (x5.3)163.18 (x5.4)2354.08 (x10)37860.5 (x9.8)
CompleteOrthogonalDecomposition0.23 (x4.3)2.22 (x5.2)99.44 (x17.1)10555.3 (x28.2)35.75 (x5.3)169.39 (x5.6)2150.56 (x9.1)36981.8 (x9.6)
FullPivHouseholderQR0.23 (x4.3)4.64 (x11)289.1 (x49.6)-69.38 (x10.2)446.73 (x14.8)4852.12 (x20.5)-
JacobiSVD1.01 (x18.6)71.43 (x168.4)--113.81 (x16.7)1179.66 (x39.1)--
BDCSVD1.07 (x19.7)21.83 (x51.5)331.77 (x56.9)18587.9 (x49.6)110.53 (x16.3)397.67 (x13.2)2975 (x12.6)48593.2 (x12.6)

*****: 此分解不支持对过度约束问题的直接最小二乘求解,并且报告的时间包括形成对称协方差矩阵的成本 A T A A^TA ATA.

eigenGeometry.cpp

旋转向量

Eigen/Geometry 模块提供了各种旋转和平移的表示

3D 旋转矩阵直接使用 Matrix3d 或 Matrix3f

Matrix3d rotation_matrix = Matrix3d::Identity();

旋转向量使用 AngleAxis,详情见:https://eigen.tuxfamily.org/dox/classEigen_1_1AngleAxis.html

AngleAxisf for float
AngleAxisd for double

注意:

此类的目的不是用来存储旋转变换,而是为了更容易地创建其他旋转(Quaternion, rotation Matrix)和变换对象。

设置 AngleAxis 对象时,必须将其初始化为弧度制的角度归一化的轴矢量。

如果轴向量未归一化,则角度轴对象表示无效旋转

其构造为:

Eigen::AngleAxis< Scalar_ >::AngleAxis	(	const Scalar & 	angle,
const MatrixBase< Derived > & 	axis 
)	

由其衍生出的AngleAxisd 的定义如下

typedef Eigen::AngleAxis<double> Eigen::AngleAxisd

它底层不直接是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()转换成矩阵

注意:这里使用M_PI时要包含头文件#include <cmath>

也可以直接赋值

rotation_matrix = rotation_vector.toRotationMatrix();

结果为:

在这里插入图片描述

用 AngleAxis 可以进行坐标变换

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顺序,即yaw-pitch-roll顺序
cout << "yaw pitch roll = " << euler_angles.transpose() << endl;

结果为:

在这里插入图片描述

欧氏变换矩阵使用 Eigen::Isometry,其定义为

typedef Eigen::Transform<double, 3, 1> Eigen::Isometry3d

详见:https://eigen.tuxfamily.org/dox/classEigen_1_1Hyperplane.html#afb4d86eb3d2bb8311681067df71499de

使用

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 tranformed = " << v_transformed.transpose() << endl;

注意:这里已经对乘号做了重载,所以T为四维,乘以3维的v,可以得到答案

结果为:

在这里插入图片描述

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

四元数

详见:

https://eigen.tuxfamily.org/dox/classEigen_1_1Quaternion.html

https://eigen.tuxfamily.org/dox/classEigen_1_1QuaternionBase.html

可以直接把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;

结果为:

在这里插入图片描述

这里的coeffs()的返回类型为Vector4d 即4维向量

inline Eigen::Vector4d &Eigen::Quaterniond::coeffs()

使用四元数旋转一个向量,使用重载的乘法即可

v_rotated = q * v; // 注意数学上是qvq^{-1},这里做了符号重载而已
cout << "(1,0,0) after rotation = " << v_rotated.transpose() << endl;

结果为:

在这里插入图片描述

用常规向量乘法表示,则应该如下计算

cout << "should be equal to " << (q * Quaterniond(0, 1, 0, 0) * q.inverse()).coeffs().transpose() << endl;

结果为:

在这里插入图片描述

注意:

Eigen库中的四元素存储排列为:前三位为虚部,第四维为实部。

但是初始化时,仍然为第一维为实部,后面三维为虚部,即

四元数Eigen::Quaterniond 的正确初始化顺序为Eigen::Quaterniond(w,x,y,z)

而 coeffs的顺序是(x,y,z,w),w 为实部,前三者为虚部

在这里插入图片描述

Warning

Note the order of the arguments: the real w coefficient first, while internally the coefficients are stored in the following order: [x, y, z, w]

书本上的定义为:第一维为实部,后面三维为虚部

visualizeGeometry.cpp

特征值计算

参考:

Eigen矩阵运算库快速上手

Eigen::SelfAdjointEigenSolver

Eigen::SelfAdjointEigenSolver类计算自伴随矩阵的特征值和特征向量,头文件是#include <Eigen/Eigenvalues>。对于标量 λ \lambda λ 和向量 v v v ,使得 A v = λ V Av=\lambda V Av=λV。SelfAdjointEigenSolver类功能就是计算自伴随矩阵的特征值特征向量

自伴随矩阵主对角线上的元素都是实数的,其特征值也是实数。如果 D D D特征值对角线上的对角矩阵, V V V 是以特征向量为列的矩阵,则 a = V D V − 1 a=VDV^{-1} a=VDV1 (对于自伴矩阵,矩阵 V V V 总是可逆的),这称为特征分解

特征值及对应的特征向量计算,在矩阵分析中占有重要位置。基于Eigen的特征值计算如下:

Eigen::MatrixXd m = Eigen::MatrixXd::Random(3,3);
//构造一个实对称矩阵,SelfAdjointEigenSolver模板类,专门计算特征值和特征向量
Eigen::MatrixXd mTm = m.transpose() * m;//构成中心对其的协方差矩阵
 
//计算
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigen_solver(mTm);
 
//取出特征值和特征向量
Eigen::VectorXd eigenvalues = eigen_solver.eigenvalues();
Eigen::MatrixXd eigenvectors = eigen_solver.eigenvectors();
 
Eigen::VectorXd v0 = eigenvectors.col(0);// 因为特征值一般按从小到大排列,所以col(0)就是最小特征值对应的特征向量

出现的错误

将角轴转换为旋转矩阵时,提示

不存在从 "Eigen::Matrix<double, 3, 3, 0, 3, 3> () const" 转换到 "Eigen::Matrix<double, 3, 3, 0, 3, 3>" 的适当构造函数C/C++(415)

在这里插入图片描述

toRotationMatrix() 方法要加冒号

Eigen::Matrix3d fai1_SO3 =  Eigen::AngleAxisd(fai1.norm(),fai1.normalized()).toRotationMatrix();
  Eigen::Matrix3d fai2_SO3=  Eigen::AngleAxisd(theta_fai2,a).toRotationMatrix();

常见用法

参考:

Eigen::MatrixXd和VectorXd的用法注意

Eigen高阶操作总结 — 子矩阵、块操作

Eigen学习(五)块操作

1、行优先和列优先

矩阵默认是列优先,向量只能是列优先.注意:在Eigen中行优先的矩阵会在其名字中包含有row,否则就是列优先。

2、<<输入是一行一行输入,不管该矩阵是否是行优先还是列优先.

在Eigen中重载了"<<"操作符,通过该操作符即可以一个一个元素的进行赋值,也可以一块一块的赋值。另外也可以使用下标进行复

3\索引:MatrixXd矩阵只能用(),VectorXd不仅能用()还能用[]

在矩阵的访问中,行索引总是作为第一个参数,需注意Eigen中遵循大家的习惯让矩阵、数组、向量的下标都是从0开始。矩阵元素的访问可以通过()操作符完成,例如m(2,3)即是获取矩阵m的第2行第3列元素(注意行列数从0开始)

4、重置矩阵大小

当前矩阵的行数、列数、大小可以通过rows(),cols()和size()来获取,对于动态矩阵可以通过resize()函数来动态修改矩阵的大小.

需注意:

(1) 固定大小的矩阵是不能使用resize()来修改矩阵的大小;

(2) resize()函数会析构掉原来的数据,因此调用resize()函数之后将不能保证元素的值不改变。
(3) 使用“=”操作符操作动态矩阵时,如果左右边的矩阵大小不等,则左边的动态矩阵的大小会被修改为右边的大小。

5、MatrixXd和Vector2d的构造 注意!

矩阵的构造函数中只提供行列数、元素类型的构造参数,而不提供元素值的构造,对于比较小的、固定长度的向量提供初始化元素的定义,

6、矩阵的块操作:有三种使用方法:

matrix.block(i,j, p, q) : 表示返回从矩阵(i, j)开始,每行取p个元素,每列取q个元素;

matrix.block<p,q>(i, j) :<p, q>可理解为一个p行q列的子矩阵,该定义表示从原矩阵中第(i, j)开始,获取一个p行q列的子矩阵;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/224708.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

外包干了一个月,技术明显进步。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;19年通过校招进入南京某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了3年的功能测试…

springcloud多环境部署打包 - maven 篇

背景 在使用 springboot 和sringcloudnacos开发项目过程中&#xff0c;会有多种环境切换&#xff0c;例如开发环境&#xff0c;测试环境&#xff0c;演示环境&#xff0c;生产环境等&#xff0c;我们通过建立多个 yml 文件结合 profiles.active 属性进行环境指定&#xff0c;但…

面试常问的dubbo的spi机制到底是什么?(下)

前文回顾 前一篇文章主要是讲了什么是spi机制&#xff0c;spi机制在java、spring中的不同实现的分析&#xff0c;同时也剖析了一下dubbo spi机制的实现ExtensionLoader的实现中关于实现类加载以及实现类分类的源码。 一、实现类对象构造 看实现类对象构造过程之前&#xff0c;先…

当使用RSA加密,从手机前端到服务器后端的请求数据存在+

将转成了空格&#xff0c;导致解密出错 将空格转成了

MySQL系列(二)——日志篇

一、有哪些日志 MySQL应该是我们用的最多&#xff0c;也算是最熟悉的数据库了。那么&#xff0c;MySQL中有哪些日志了&#xff0c;或者你知道的有哪些日志了&#xff1f; 首先&#xff0c;我们能接触到的&#xff0c;一般我们排查慢查询时&#xff0c;会去看慢查询日志。如果…

红队专题-开源资产扫描系统-ARL资产灯塔系统

ARL资产灯塔系统 安装说明问题 &#xff1a; 安装说明 源码地址 https://github.com/TophantTechnology/ARL https://github.com/TophantTechnology/ARL/wiki/Docker-%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85-ARL 安装环境 uname -a Linux VM-24-12-centos 3.10.0-1160.49.1.e…

Git多人协作(二)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言 上节&#xff1a;Git多人协作(一) 上次我们模拟了多人在一个分支上进行开发&#xff0c;并且是在远程直接新建的分支&#xff0c;而后我们本地进行拉取&#xff1b;本节我们将模拟多人分别在多分支上进行开发&#xf…

Azure Machine Learning - Azure OpenAI 服务使用 GPT-35-Turbo and GPT-4

通过 Azure OpenAI 服务使用 GPT-35-Turbo and GPT-4 环境准备 Azure 订阅 - 免费创建订阅已在所需的 Azure 订阅中授予对 Azure OpenAI 服务的访问权限。 目前&#xff0c;仅应用程序授予对此服务的访问权限。 可以填写 https://aka.ms/oai/access 处的表单来申请对 Azure Op…

网络基础入门---使用udp协议改进程序

目录标题 前言改进一&#xff1a;单词翻译程序准备工作transform函数的实现init_dictionary函数的实现transform函数的实现其他地方的修改测试 改进二&#xff1a;远程指令执行程序popenexecCommand函数实现测试 改进三&#xff1a;群聊程序Usr类onlineUser类adduserdelUserisO…

mixamo根动画导入UE5问题:滑铲

最近想做一个跑酷游戏&#xff0c;从mixamo下载滑铲动作后&#xff0c;出了很多动画的问题。花了两周时间&#xff0c;终于是把所有的问题基本上都解决了。 常见问题&#xff1a; 1.【动画序列】人物不移动。 2.【动画序列】人物移动朝向错误。 3.【蒙太奇】人物移动后会被拉回…

TensorRT Provider 与TensorRT Native的对比

TensorRT Provider 的优势为&#xff1a; TensorRT EP 可以实现与本机 TensorRT 的性能等价。使用 TensorRT EP 的好处之一是&#xff0c;如果模型中存在不受支持的 TensorRT 操作&#xff0c;就可以运行不能在本机 TensorRT 中运行的模型。这些操作将自动退回到其他 EP&#…

8051单片机的CPU组成与四个并行I/O端口

AT89S51的CPU与并行I/O端口 本文主要涉及8051的CPU组成以及并行的4个I/O端口。CPU&#xff0c;主要由运算器&#xff08;ALU&#xff09;和控制器&#xff08;CU&#xff09;构成&#xff1b;4个双向的8位并行I/O端口&#xff0c;分别记为P0、P1、P2和P3 文章目录 AT89S51的CPU…

Swagger——接口文档自动生成和测试

目录 1 介绍2 使用步骤 1 介绍 Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是&#xff1a; 使得前后端分离开发更加方便&#xff0c;有利于团队协作 接口的文档在线自动生成&…

厦门排水管网监测系统,实时感知城市健康

在厦门城市化建设的步伐中&#xff0c;有一个不可或缺的环节&#xff0c;那就是排水管网监测系统。它就像城市生命线上的守护者&#xff0c;默默地守护着城市的正常运行&#xff0c;防止内涝等问题的出现。 排水管网监测系统是城市基础设施建设中的重要一环&#xff0c;其重要性…

金鸣表格文字识别大师扫描仪使用技巧

所需硬件&#xff1a;PC&#xff08;电脑&#xff09;、扫描仪 所需软件&#xff1a;金鸣表格文字识别大师5.0以上版本&#xff08;以下简称“本软件”&#xff09; 实现功能&#xff1a;直接用扫描仪扫描图片并将其转换为可编辑的excel或word. 实现原理&#xff1a;本软件利…

Qt OpenCV 学习(二):两个简单图片识别案例

1. 寻找匹配物体 1.1 mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <opencv2/opencv.hpp>#include <QImage> #include <QString> #include <QPixmap>QT_BEGIN_NAMESPACE namespace Ui { class Main…

配置OSS后如何将服务器已有文件上传至OSS,推荐使用ossutil使用

1.下载安装ossutil sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash2.交互式配置生成配置文件 ossutil config 根据提示分别设置配置文件路径、设置工具的语言、Endpoint、AccessKey ID、AccessKey Secret和STSToken参数&#xff0c;STSToken留…

【QT】容器类的迭代

迭代器(iterator)为访问容器类里的数据项提供了统一的方法&#xff0c;Qt有两种迭代器类&#xff1a;Java类型的迭代器和STL类型的迭代器。 Java类型的迭代器更易于使用&#xff0c;且提供一些高级功能&#xff0c;而STL类型的迭代器效率更高。 Qt还提供一个关键字foreach&…

Self-supervised Graph Learning for Recommendation 详解

目录 摘要 引言 预备知识 方法 3.1 图结构数据增强 3.2 对比学习 3.3 多任务学习 3.4 理论分析 摘要 基于用户-物品图的推荐表示学习已经从使用单一 ID 或交互历史发展到利用高阶邻居。这导致了图卷积网络(GCNs)在推荐方面的成功&#xff0c;如 PinSage 和 LightGCN。尽管具…

[Firefly-Linux] RK3568 pca9555芯片驱动详解

文章目录 一、PAC9555 介绍二、ITX-3568JQ PAC9555 使用2.1 原理图2.2 设备树三、RK3568 I2C 介绍四、PAC9555 驱动4.1 介绍4.2 数据结构4.3 驱动分析一、PAC9555 介绍 PAC9555 是一种高性能、低功耗 I/O 扩展芯片,能够提供 16 个 GPIO 通道,每个通道可以单独配置为输入或输…