自学SLAM(4)《第二讲:三维物体刚体运动》作业

前言

在这里插入图片描述
小编研究生的研究方向是视觉SLAM,目前在自学,本篇文章为初学高翔老师课的第二次作业。

文章目录

  • 前言
  • 1.熟悉 Eigen 矩阵运算
  • 2.几何运算练习
  • 3.旋转的表达
  • 4.罗德里格斯公式的证明
  • 5.四元数运算性质的验证
  • 6.熟悉 C++11


1.熟悉 Eigen 矩阵运算

设线性⽅程 Ax = b,在 A 为⽅阵的前提下,请回答以下问题:

  1. 在什么条件下, x 有解且唯⼀?
  2. ⾼斯消元法的原理是什么?
  3. QR 分解的原理是什么?
  4. Cholesky 分解的原理是什么?
  5. 编程实现 A 为 100 × 100 随机矩阵时,⽤ QR 和 Cholesky 分解求 x 的程序。你可以参考本次课⽤到的 useEigen 例程。

1.当r(A)=r([A|b])=n时,也就是A满秩,A可逆,方程组有唯一解。

2.高斯消元法如图所示:
在这里插入图片描述
3.QR的分解原理如图所示:在这里插入图片描述
4.Cholesky分解的原理如下图:在这里插入图片描述
5. 编程实现 A 为 100 × 100 随机矩阵时,⽤ QR 和 Cholesky 分解求 x 的程序。你可以参考本次课⽤到的 useEigen 例程。提⽰:你可能需要参考相关的数学书籍或⽂章。请善⽤搜索引擎。 Eigen 固定⼤⼩矩阵最⼤⽀持到 50,所以你会⽤到动态⼤⼩的矩阵。

其实这题就是让我们自己建立一个AX=B,利用Eigen的内置函数求解X
代码如下:

#include <iostream>
using namespace std;
#include <ctime>
#include <Eigen/Core>
#include <Eigen/Dense>
using namespace Eigen;#define MATRIX_SIZE 100//宏定义int main(int argc, char **argv) 
{//建立一个动态矩阵AMatrixXd A;//建立一个100*100的随机矩阵A                                      A = MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE); //保证A为正定矩阵,A=A*A的转置(正定)A = A * A.transpose();                     //建立一个float类型的动态矩阵BMatrix<double, Dynamic, 1> B;   //建立一个100*1的随机矩阵BB = MatrixXd::Random(MATRIX_SIZE, 1);   //建立一个动态矩阵XMatrix<double, Dynamic, 1> X;//建立一个100*1的随机矩阵X                    X = MatrixXd::Random(MATRIX_SIZE, 1);            //QR分解X = A.colPivHouseholderQr().solve(B);            cout << "QR: X = " << X.transpose() << endl;//cholesky分解X = A.ldlt().solve(B);                       cout << "cholesky: X = " << X.transpose() << endl;return 0;
}

运行结果如下:
在这里插入图片描述

Eigen的语法规则,及其用法参考如下:

#include <iostream>
using namespace std;
#include <ctime>
// Eigen 部分
#include <Eigen/Core>
// 稠密矩阵的代数运算(逆,特征值等)
#include <Eigen/Dense>#define MATRIX_SIZE 50/****************************
* 本程序演示了 Eigen 基本类型的使用
****************************/int main( int argc, char** argv )
{// Eigen 中所有向量和矩阵都是Eigen::Matrix,它是一个模板类。它的前三个参数为:数据类型,行,列// 声明一个2*3的float矩阵Eigen::Matrix<float, 2, 3> matrix_23;// 同时,Eigen 通过 typedef 提供了许多内置类型,不过底层仍是Eigen::Matrix// 例如 Vector3d 实质上是 Eigen::Matrix<double, 3, 1>,即三维向量Eigen::Vector3d v_3d;// 这是一样的Eigen::Matrix<float,3,1> vd_3d;// Matrix3d 实质上是 Eigen::Matrix<double, 3, 3>Eigen::Matrix3d matrix_33 = Eigen::Matrix3d::Zero(); //初始化为零// 如果不确定矩阵大小,可以使用动态大小的矩阵Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic > matrix_dynamic;// 更简单的Eigen::MatrixXd matrix_x;// 这种类型还有很多,我们不一一列举// 下面是对Eigen阵的操作// 输入数据(初始化)matrix_23 << 1, 2, 3, 4, 5, 6;// 输出cout << matrix_23 << 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里你不能混合两种不同类型的矩阵,像这样是错的// Eigen::Matrix<double, 2, 1> result_wrong_type = matrix_23 * v_3d;// 应该显式转换Eigen::Matrix<double, 2, 1> result = matrix_23.cast<double>() * v_3d;cout << result << endl;Eigen::Matrix<float, 2, 1> result2 = matrix_23 * vd_3d;cout << result2 << endl;// 同样你不能搞错矩阵的维度// 试着取消下面的注释,看看Eigen会报什么错// Eigen::Matrix<double, 2, 3> result_wrong_dimension = matrix_23.cast<double>() * v_3d;// 一些矩阵运算// 四则运算就不演示了,直接用+-*/即可。matrix_33 = Eigen::Matrix3d::Random();      // 随机数矩阵cout << matrix_33 << endl << endl;cout << matrix_33.transpose() << endl;      // 转置cout << matrix_33.sum() << endl;            // 各元素和cout << matrix_33.trace() << endl;          // 迹cout << 10*matrix_33 << endl;               // 数乘cout << matrix_33.inverse() << endl;        // 逆cout << matrix_33.determinant() << endl;    // 行列式// 特征值// 实对称矩阵可以保证对角化成功Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver ( matrix_33.transpose()*matrix_33 );cout << "Eigen values = \n" << eigen_solver.eigenvalues() << endl;cout << "Eigen vectors = \n" << eigen_solver.eigenvectors() << endl;// 解方程// 我们求解 matrix_NN * x = v_Nd 这个方程// N的大小在前边的宏里定义,它由随机数生成// 直接求逆自然是最直接的,但是求逆运算量大Eigen::Matrix< double, MATRIX_SIZE, MATRIX_SIZE > matrix_NN;matrix_NN = Eigen::MatrixXd::Random( MATRIX_SIZE, MATRIX_SIZE );Eigen::Matrix< double, MATRIX_SIZE,  1> v_Nd;v_Nd = Eigen::MatrixXd::Random( MATRIX_SIZE,1 );clock_t time_stt = clock(); // 计时// 直接求逆Eigen::Matrix<double,MATRIX_SIZE,1> x = matrix_NN.inverse()*v_Nd;cout <<"time use in normal inverse is " << 1000* (clock() - time_stt)/(double)CLOCKS_PER_SEC << "ms"<< endl;// 通常用矩阵分解来求,例如QR分解,速度会快很多time_stt = clock();x = matrix_NN.colPivHouseholderQr().solve(v_Nd);cout <<"time use in Qr decomposition is " <<1000*  (clock() - time_stt)/(double)CLOCKS_PER_SEC <<"ms" << endl;return 0;
}

2.几何运算练习

下⾯我们来练习如何使⽤ Eigen/Geometry 计算⼀个具体的例⼦。 设有⼩萝⼘ 1⼀号和⼩萝⼘⼆号位于世界坐标系中。⼩萝⼘⼀号的位姿为: q1 = [0:55; 0:3; 0:2; 0:2]; t1 =[0:7;1:1; 0:2]T(q 的第⼀项为实部)。这⾥的 q 和 t 表达的是 Tcw,也就是世界到相机的变换关系。⼩萝⼘⼆号的位姿为 q2 = [−0:1; 0:3; −0:7; 0:2]; t2 = [−0:1; 0:4;0:8]T。现在,⼩萝⼘⼀号看到某个点在⾃⾝的坐标系下,坐标为 p1 = [0:5; −0:1; 0:2]T,求该向量在⼩萝⼘⼆号坐标系下的坐标。请编程实现此事,并提交你的程序。
提⽰:

  1. 四元数在使⽤前需要归⼀化。
  2. 请注意 Eigen 在使⽤四元数时的虚部和实部顺序。
  3. 参考答案为 p2 = [1:08228; 0:663509; 0:686957]T。你可以⽤它验证程序是否正确。
#include <iostream>
using namespace std;
#include <Eigen/Core>
#include <Eigen/Geometry>
using namespace Eigen;
int main(int argc, char ** argv)
{//创建小萝卜1和2的位姿q1和q2Quaterniond q1(0.55, 0.3, 0.2, 0.2), q2(-0.1, 0.3, -0.7, 0.2);//四元数归一化(高博的SLAM视屏中的代码解释没有归一化)q1.normalize();q2.normalize();//创建小萝卜1和2的另一个位姿量t1和t2Vector3d t1(0.7, 1.1, 0.2), t2(-0.1, 0.4, 0.8);//创建p1坐标Vector3d p1(0.5, -0.1, 0.2);  //欧式变换矩阵Tc1w和Tc2wIsometry3d Tc1w(q1), Tc2w(q2);Tc1w.pretranslate(t1);// 把平移向量设成(t1)Tc2w.pretranslate(t2);// 把平移向量设成(t2)//计算p2Vector3d p2 = Tc2w*Tc1w.inverse()*p1;cout << p2.transpose() << endl;return 0;
}

运行结果如下,我们可以看到答案正确:
在这里插入图片描述

Eigen/四元数的语法规则,及其用法参考如下:

#include <iostream>
#include <cmath>
using namespace std;
#include <Eigen/Core>
// Eigen 几何模块
#include <Eigen/Geometry>/****************************
* 本程序演示了 Eigen 几何模块的使用方法
****************************/int main ( int argc, char** argv )
{// Eigen/Geometry 模块提供了各种旋转和平移的表示// 3D 旋转矩阵直接使用 Matrix3d 或 Matrix3fEigen::Matrix3d rotation_matrix = Eigen::Matrix3d::Identity();// 旋转向量使用 AngleAxis, 它底层不直接是Matrix,但运算可以当作矩阵(因为重载了运算符)Eigen::AngleAxisd rotation_vector ( M_PI/4, Eigen::Vector3d ( 0,0,1 ) );     //沿 Z 轴旋转 45 度cout .precision(3);cout<<"rotation matrix =\n"<<rotation_vector.matrix() <<endl;                //用matrix()转换成矩阵// 也可以直接赋值rotation_matrix = rotation_vector.toRotationMatrix();// 用 AngleAxis 可以进行坐标变换Eigen::Vector3d v ( 1,0,0 );Eigen::Vector3d v_rotated = rotation_vector * v;cout<<"(1,0,0) after rotation = "<<v_rotated.transpose()<<endl;// 或者用旋转矩阵v_rotated = rotation_matrix * v;cout<<"(1,0,0) after rotation = "<<v_rotated.transpose()<<endl;// 欧拉角: 可以将旋转矩阵直接转换成欧拉角Eigen::Vector3d euler_angles = rotation_matrix.eulerAngles ( 2,1,0 ); // ZYX顺序,即roll pitch yaw顺序cout<<"yaw pitch roll = "<<euler_angles.transpose()<<endl;// 欧氏变换矩阵使用 Eigen::IsometryEigen::Isometry3d T=Eigen::Isometry3d::Identity();                // 虽然称为3d,实质上是4*4的矩阵T.rotate ( rotation_vector );                                     // 按照rotation_vector进行旋转T.pretranslate ( Eigen::Vector3d ( 1,3,4 ) );                     // 把平移向量设成(1,3,4)cout << "Transform matrix = \n" << T.matrix() <<endl;// 用变换矩阵进行坐标变换Eigen::Vector3d v_transformed = T*v;                              // 相当于R*v+tcout<<"v tranformed = "<<v_transformed.transpose()<<endl;// 对于仿射和射影变换,使用 Eigen::Affine3d 和 Eigen::Projective3d 即可,略// 四元数// 可以直接把AngleAxis赋值给四元数,反之亦然Eigen::Quaterniond q = Eigen::Quaterniond ( rotation_vector );cout<<"quaternion = \n"<<q.coeffs() <<endl;   // 请注意coeffs的顺序是(x,y,z,w),w为实部,前三者为虚部// 也可以把旋转矩阵赋给它q = Eigen::Quaterniond ( rotation_matrix );cout<<"quaternion = \n"<<q.coeffs() <<endl;// 使用四元数旋转一个向量,使用重载的乘法即可v_rotated = q*v; // 注意数学上是qvq^{-1}cout<<"(1,0,0) after rotation = "<<v_rotated.transpose()<<endl;return 0;
}

3.旋转的表达

在这里插入图片描述
1. 设有旋转矩阵 R,证明 RT R = I 且 det R = +12

在这里插入图片描述
2. 设有四元数 q,我们把虚部记为ε,实部记为 η,那么 q = (ε,η)。请说明 ε 和 η 的维度。

四元数q有三个虚部和一个实部。
即q=q0+q1i+q2j+q3k
因此ε的维度为3, η的维度为1。

3.第三问
在这里插入图片描述

4.罗德里格斯公式的证明

罗德⾥格斯公式描述了从旋转向量到旋转矩阵的转换关系。设旋转向量长度为 θ,⽅向为 n,那么旋转矩阵 R 为:
R = cos θI − (1 − cos θ)nnT + sin θn^. ------------------------------------------------------------(4)
我们在课程中仅指出了该式成⽴,但没有给出证明。请你证明此式。
在这里插入图片描述
参考:
链接: 罗德里格斯公式的证明

5.四元数运算性质的验证

在这里插入图片描述
在这里插入图片描述

6.熟悉 C++11

请注意本题为附加题。 C++ 是⼀门古⽼的语⾔,但它的标准⾄今仍在不断发展。在 2011 年、 2014 年和 2017 年, C++的标准又进⾏了更新,被称为 C++11, C++14, C++17。其中, C++11 标准是最重要的⼀次更新,让C++发⽣了重要的改变,也使得近年来的 C++ 程序与你在课本上(⽐如谭浩强)学到的 C++ 程序有很⼤的不同。你甚⾄会惊叹这是⼀种全新的语⾔。 C++14 和 C++17 则是对 11 标准的完善与扩充。
越来越多的程序开始使⽤11 标准,它也会让你在写程序时更加得⼼应⼿。本题中,你将学习⼀些 11标准下的新语法。请参考本次作业 books/⽬录下的两个pdf,并回答下⾯的问题。 设有类 A,并有 A 类的⼀组对象,组成了⼀个 vector。现在希望对这个 vector进⾏排序,但排序的⽅式由 A.index 成员⼤⼩定义。

那么,在 C++11 的语法下,程序写成:

1#include <iostream>
2#include <vector>
3#include <algorithm>
4
5 using namespace std;
6
7 class A {
8 public:
9 A(const int& i ) : index(i) {}
10 int index = 0;
11};
12
13 int main() {
14 A a1(3), a2(5), a3(9);
15 vector<A> avec{a1, a2, a3};
16 std::sort(avec.begin(), avec.end(), [](const A&a1, const A&a2) {return a1.index<a2.index;});
17 for ( auto& a: avec ) cout<<a.index<<" ";
18 cout<<endl;
19 return 0;
20 }

请说明该程序中哪些地⽅⽤到了 C++11 标准的内容。提⽰:请关注范围 for 循环、⾃动类型推导、 lambda表达式等内容。

该程序中使用了C++11标准的以下内容:

iostream:使用了C++11中引入的iostream库,用于输入输出流操作。

vector:使用了C++11中引入的vector容器,用于存储和操作A类的实例。

algorithm:使用了C++11中引入的algorithm库,其中的sort函数用于对vector容器中的元素进行排序。

using namespace std;:使用了C++11中引入的namespace别名声明,用于简化对std命名空间的使用。

class A:使用了C++11中引入的类初始化列表语法,用于对A类的成员变量进行初始化。

vector avec{a1, a2, a3}; :使用了C++11中引入的列表初始化语法,用于初始化vector容器avec并添加元素。

[] (const A&a1, const A&a2) { return a1.index < a2.index; }:使用了C++11中引入的lambda表达式,用作sort函数的排序准则。其中:const A&a1, const A&a2是参数列表,return a1.index<a2.index;是函数体,返回值是布尔型的大小比较结果。

for (auto& a : avec):使用了C++11中引入的范围for循环语法,用于遍历vector容器avec中的元素。用auto关键字实现了自动类型推导,让编译器自动设置变量a的类型;C++引入了基于范围的for循环,不用下标就能访问元素;
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式共识算法及落地

摘要 本文介绍常见的分布式共识算法&#xff0c;使用场景&#xff0c;以及相关已经落地了的程序或框架 1. 为什么要分布式共识算法 在分布式系统中&#xff0c;不同节点之间可能存在网络延迟、故障等原因导致彼此之间存在数据不一致的情况&#xff0c;为了保证分布式系统中的…

[TCP1P 2023] 部分crypto,pwn,reverse

Crypto Final Consensus 这是个AES爆破密钥的题&#xff0c;加密方法是先后用两个密钥加密。远程先给出加密后的flag&#xff0c;然后允许输入值并进行加密。 from Crypto.Cipher import AES import random from Crypto.Util.Padding import pada b"" b b"&…

系统架构师考试科目一:综合知识

某软件公司欲开发一个 Windows 平台上的公告板系统。在明确用户需求后&#xff0c;该公司的 架构师决定采用 Command 模式实现该系统的界面显示部分&#xff0c;并设计 UML 类图如下 图所示。图中与 Command 模式中的 Invoker 角色相对应的类是( ) &#xff0c;与 ConcreteComm…

一篇文章讲懂mysql中的锁

事务的隔离性是由锁来实现的。 为什么需要锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在程序开发中会存在多线程同步的问题&#xff0c;当多个线程并发访问某个数据的时候&#xff0c;尤其是针对一些敏感的数据&#xff08;比如订单、金额等&#xff09;&#x…

C语言 ——宽字符

前言&#xff1a; 过去C语⾔并不适合⾮英语国家&#xff08;地区&#xff09;使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。 C语⾔字符默认是采⽤ASCII编码的&#xff0c;ASCII字符集采⽤的是单字节编码&#xff0c;且只使⽤了单字节中…

Opensuse Tumbleweed快速部署K8S-1.28.2

** #查看你的网卡信息 nmcli device show#设置你的静态IP nmcli connection modify ens32 ipv4.method manual \ipv4.addresses 172.16.20.214/24 ipv4.gateway 172.16.20.1 \ipv4.dns 114.114.114.114#重启网卡生效 nmcli c down ens32 && nmcli c up ens32#添加DNS …

代码随想录算法训练营第二十九天 | 回溯算法总结

​ 代码随想录算法训练营第二十九天 | 回溯算法总结 1. 组合问题 1.1 组合问题 在77. 组合中&#xff0c;我们开始用回溯法解决第一道题目&#xff1a;组合问题。 回溯算法跟k层for循环同样是暴力解法&#xff0c;为什么用回溯呢&#xff1f;回溯法的魅力&#xff0c;用递…

解决appium或selenium使用时driver.find_element_by_xpath中间有删除线问题

一、问题描述 Darren洋在公司电脑搭建完成appium后准备运行appium2.0版本执行脚本时发现执行脚本中的driver.find_element_by_xpath中间有删除线&#xff0c;说明较高版本的appium及selenium中该方法已被弃用。 二、解决办法 该问题解决办法为将driver.find_element_by_xpath()…

Linux线程--创建及等待

1.进程与线程 典型的UNIX/Linux进程可以看成只有一个控制线程&#xff1a;一个进程在同一时刻只做一件事情。有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时刻做不止一件事&#xff0c;每个线程各自处理独立的任务。  线程是操作系统能够进行运算调度的…

Android Studio快速实现Flutter应用的国际化和多语言支持

文章目录 Flutter实现国际化和多语言支持添加依赖库Android Studio 安装flutter Intl插件项目初始化增加语言app中使用国际化在应用中切换语言&#xff1a;运行应用 总结easy_localization 插件intl 包Flutter GetX 包flutter_i18n 插件JSON 文件 Flutter实现国际化和多语言支持…

01、Python 的数据类型

目录 数据类型Python变量具有如下两个特征&#xff1a;输出变量 标识符规则整型四种表示形式浮点数复数 数据类型 使用Python变量 Python的基础类型 Python变量具有如下两个特征&#xff1a; 变量无需声明即可直接赋值&#xff1a;对一个不存在的变量赋值就相当于定义了一个…

html表格标签

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><!--表格table 行 tr 列 td --> <table border"1px"><tr> <!--colsp…

mac 升级node到指定版本

node版本14.15.1升级到最新稳定版18.18.2 mac系统 先查看一下自己的node版本 node -v开始升级 第一步 清除node的缓存 sudo npm cache clean -f第二步 安装n模块【管理模块 n是管理 nodejs版本】 sudo npm install -g n第三步升级node sudo n stable // 把当前系统的 Node…

【试题039】 多个逻辑或例题

题目&#xff1a;设int n;,执行表达式(n0)||(n1)||(n2)||(n3)后,n的值是&#xff1f;代码分析&#xff1a; //设int n; , 执行表达式(n 0) || (n 1) ||(n 2) ||(n 3)后, n的值是?int n;printf("n%d\n", (n 0) || (n 1) || (n 2) || (n 3));//分析&#xff1…

[Spring] SpringBoot2 简介(一)—— 基础配置

目录 一、SpringBoot 简介 1、Spring 的缺点 2、SpringBoot 功能 二、SpringBoot 入门案例 1、实现步骤 2、访问服务器 3、入门小结 4、Idea 快速构建 SpringBoot 工程 5、起步依赖无需版本号 6、主启动类的在项目中的位置&#xff08;*重要*&#xff09; 三、Sprin…

【Unity3D编辑器拓展】Unity3D的IMGUI、GUI、GUILayout、EditorGUI、EditorGUILayout、OnGUI【全面总结】

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中&#xff0c;常常会遇到要使用OnGUI的地方。 也会遇到…

如何在linux服务器上安装Anaconda与pytorch

如何在linux服务器上安装Anaconda与pytorch 1&#xff0c;安装anaconda1.1 下载anaconda安装包1.2 安装anaconda1.3 设计环境变量1.4 安装完成验证 2 Anaconda安装pytorch2.1 创建虚拟环境2.2 查看现存环境2.3 激活环境2.4 选择合适的pytorch版本下载2.5 检测是否安装成功&…

SOME/IP, DDS 还是 MQTT

如今&#xff0c;用户希望将他们的汽车根据个人偏好进行定制&#xff0c;通过添加功能并定期进行更新&#xff0c;就像他们对待移动设备一样。实现这些期望属性的一个构建模块是基于 Internet Protocol&#xff08;IP&#xff09;的通信&#xff1b;IP为新的设计模式打开了大门…

Nautilus Chain 与 Coin98 生态达成合作,加速 Zebec 生态亚洲战略进程

目前&#xff0c;行业内首个模块化 Layer3 架构公链 Nautilus Chain 已经上线主网&#xff0c;揭示了模块化区块链领域迎来了全新的进程。在主网上线后&#xff0c;Nautilus Chain 将扮演 Zebec 生态中最重要的底层设施角色&#xff0c;并将为 Zebec APP 以及 Zebec Payroll 规…

Selenium的find_element()与find_elements()和By的几种方法

打印索引元素的文本属性 def print_list(coordinate_list):print(当前项目地块数&#xff1a;, len(coordinate_list))for i in range(0, len(coordinate_list)):print(i)print(coordinate_list[i].text)看一下By支持的方法 class By:"""Set of supported loc…