【3D激光SLAM】LOAM源代码解析--transformMaintenance.cpp

系列文章目录

·【3D激光SLAM】LOAM源代码解析–scanRegistration.cpp
·【3D激光SLAM】LOAM源代码解析–laserOdometry.cpp
·【3D激光SLAM】LOAM源代码解析–laserMapping.cpp
·【3D激光SLAM】LOAM源代码解析–transformMaintenance.cpp


写在前面

本系列文章将对LOAM源代码进行讲解,在讲解过程中,涉及到论文中提到的部分,会结合论文以及我自己的理解进行解读,尤其是对于其中坐标变换的部分,将会进行详细的讲解

本来是懒得写的,一个是怕自己以后忘了,另外是我在学习过程中,其实没有感觉哪一个博主能讲解的通篇都能让我很明白,特别是坐标变换部分的代码,所以想着自己学完之后,按照自己的理解,也写一个LOAM解读,希望能对后续学习LOAM的同学们有所帮助。

之后也打算录一个LOAM讲解的视频,大家可以关注一下。


文章目录

  • 系列文章目录
  • 写在前面
  • 整体框架
  • 一、变量含义
  • 二、main()函数
  • 三、接收laserMapping的转换信息
  • 四、接收laserOdometry的信息
  • 五、位姿融合
  • 总结


整体框架

LOAM多牛逼就不用多说了,直接开始

先贴一下我详细注释的LOAM代码,在这个版本的代码上加入了我自己的理解。

我觉得最重要也是最恶心的一部分是其中的坐标变换,在代码里面真的看着头大,所以先明确一下坐标系(都是右手坐标系):

  • IMU(IMU坐标系imu):x轴向前,y轴向左,z轴向上
  • LIDAR(激光雷达坐标系l):x轴向前,y轴向左,z轴向上
  • CAMERA(相机坐标系,也可以理解为里程计坐标系c):z轴向前,x轴向左,y轴向上
  • WORLD(世界坐标系w,也叫全局坐标系,与里程计第一帧init重合):z轴向前,x轴向左,y轴向上
  • MAP(地图坐标系map,一定程度上可以理解为里程计第一帧init):z轴向前,x轴向左,y轴向上

坐标变换约定: 为了清晰,变换矩阵的形式与《SLAM十四讲中一样》,即: R A _ B R_{A\_B} RA_B表示B坐标系相对于A坐标系的变换,B中一个向量通过 R A _ B R_{A\_B} RA_B可以变换到A中的向量。

首先对照ros的节点图和论文中提到的算法框架来看一下:

在这里插入图片描述
在这里插入图片描述
可以看到节点图和论文中的框架是一一对应的,这几个模块的功能如下:

  • scanRegistration:对原始点云进行预处理,计算曲率,提取特征点
  • laserOdometry:对当前sweep与上一次sweep进行特征匹配,计算一个快速(10Hz)但粗略的位姿估计
  • laserMapping:对当前sweep与一个局部子图进行特征匹配,计算一个慢速(1Hz)比较精确的位姿估计
  • transformMaintenance:对两个模块计算出的位姿进行融合,得到最终的精确地位姿估计

本文介绍transformMaintenance模块,它就是将laserOdometry和laserMapping两个模块优化得到的当前帧相对于初始帧的坐标变换进行融合,从而得到最终的最优的坐标变换结果。


一、变量含义

首先,介绍一下本程序用到变量的含义,与laserMapping一致:

  • transformBefMapped[6]:从laserMapping模块接收到的,优化前的当前帧相对于初始时刻的位姿变换 T i n i t _ e n d T_{init\_end} Tinit_end
  • transformSum[6]:从laserOdometry模块接收到的,当前帧相对于初始时刻的变换 T i n i t _ s t a r t T_{init\_start} Tinit_start
  • transformAftMapped[6]:经过laserMapping模块优化后的,当前帧相对于初始时刻的位姿变换 T m a p _ e n d T_{map\_end} Tmap_end
  • transformMapped[6]:融合后的当前帧相对于初始帧的坐标变换

一些理解:虽然transformAftMapped[6]我上面写的是 T m a p _ e n d T_{map\_end} Tmap_end,看起来好像是把坐标系换成了map坐标系,但是我觉得这里有两种理解都可以:

  1. AftMapped可以理解为经过laserMapping模块优化后的里程计坐标系下的当前帧end相对于初始帧的坐标变换
  2. 也可以理解为经过laserMapping模块优化,变到了map坐标系

二、main()函数

main函数依然很简单,就是定义了一些订阅者和发布者,接收/laser_odom_to_init和/aft_mapped_to_init两个坐标变换话题,然后进入相应的回调函数进行融合;然后发布融合后的当前帧相当于初始帧的坐标变换,以及坐标变换。

int main(int argc, char** argv)
{ros::init(argc, argv, "transformMaintenance");ros::NodeHandle nh;ros::Subscriber subLaserOdometry = nh.subscribe<nav_msgs::Odometry> ("/laser_odom_to_init", 5, laserOdometryHandler);ros::Subscriber subOdomAftMapped = nh.subscribe<nav_msgs::Odometry> ("/aft_mapped_to_init", 5, odomAftMappedHandler);ros::Publisher pubLaserOdometry2 = nh.advertise<nav_msgs::Odometry> ("/integrated_to_init", 5);pubLaserOdometry2Pointer = &pubLaserOdometry2;laserOdometry2.header.frame_id = "/camera_init";laserOdometry2.child_frame_id = "/camera";tf::TransformBroadcaster tfBroadcaster2;tfBroadcaster2Pointer = &tfBroadcaster2;laserOdometryTrans2.frame_id_ = "/camera_init";laserOdometryTrans2.child_frame_id_ = "/camera";ros::spin();return 0;
}

三、接收laserMapping的转换信息

接收/aft_mapped_to_init话题的回调函数很简单,就是将接收到的数据,赋值给transformAftMapped[6]和transformBefMapped[6]变量,这两个变量的含义与laserMapping中一致,就不过多解释了。

//接收laserMapping的转换信息
void odomAftMappedHandler(const nav_msgs::Odometry::ConstPtr& odomAftMapped)
{double roll, pitch, yaw;geometry_msgs::Quaternion geoQuat = odomAftMapped->pose.pose.orientation;tf::Matrix3x3(tf::Quaternion(geoQuat.z, -geoQuat.x, -geoQuat.y, geoQuat.w)).getRPY(roll, pitch, yaw);transformAftMapped[0] = -pitch;transformAftMapped[1] = -yaw;transformAftMapped[2] = roll;transformAftMapped[3] = odomAftMapped->pose.pose.position.x;transformAftMapped[4] = odomAftMapped->pose.pose.position.y;transformAftMapped[5] = odomAftMapped->pose.pose.position.z;transformBefMapped[0] = odomAftMapped->twist.twist.angular.x;transformBefMapped[1] = odomAftMapped->twist.twist.angular.y;transformBefMapped[2] = odomAftMapped->twist.twist.angular.z;transformBefMapped[3] = odomAftMapped->twist.twist.linear.x;transformBefMapped[4] = odomAftMapped->twist.twist.linear.y;transformBefMapped[5] = odomAftMapped->twist.twist.linear.z;
}

四、接收laserOdometry的信息

这个回调函数主要是接收到/laser_odom_to_init话题后进行,先根据接收到的数据对相关变量进行赋值操作,然后进入到transformAssociateToMap()函数进行位姿变换融合,最后将融合后的位姿变换发布出去,发布的话题为:

  • /integrated_to_init:融合后的当前帧相对于初始帧(世界坐标系)的位姿变换

另外,广播了/camera相对于/camera_init的坐标变换

//接收laserOdometry的信息
void laserOdometryHandler(const nav_msgs::Odometry::ConstPtr& laserOdometry)
{double roll, pitch, yaw;geometry_msgs::Quaternion geoQuat = laserOdometry->pose.pose.orientation;tf::Matrix3x3(tf::Quaternion(geoQuat.z, -geoQuat.x, -geoQuat.y, geoQuat.w)).getRPY(roll, pitch, yaw);//得到旋转平移矩阵transformSum[0] = -pitch;transformSum[1] = -yaw;transformSum[2] = roll;transformSum[3] = laserOdometry->pose.pose.position.x;transformSum[4] = laserOdometry->pose.pose.position.y;transformSum[5] = laserOdometry->pose.pose.position.z;transformAssociateToMap();geoQuat = tf::createQuaternionMsgFromRollPitchYaw(transformMapped[2], -transformMapped[0], -transformMapped[1]);laserOdometry2.header.stamp = laserOdometry->header.stamp;laserOdometry2.pose.pose.orientation.x = -geoQuat.y;laserOdometry2.pose.pose.orientation.y = -geoQuat.z;laserOdometry2.pose.pose.orientation.z = geoQuat.x;laserOdometry2.pose.pose.orientation.w = geoQuat.w;laserOdometry2.pose.pose.position.x = transformMapped[3];laserOdometry2.pose.pose.position.y = transformMapped[4];laserOdometry2.pose.pose.position.z = transformMapped[5];pubLaserOdometry2Pointer->publish(laserOdometry2);//发送旋转平移量laserOdometryTrans2.stamp_ = laserOdometry->header.stamp;laserOdometryTrans2.setRotation(tf::Quaternion(-geoQuat.y, -geoQuat.z, geoQuat.x, geoQuat.w));laserOdometryTrans2.setOrigin(tf::Vector3(transformMapped[3], transformMapped[4], transformMapped[5]));tfBroadcaster2Pointer->sendTransform(laserOdometryTrans2);
}

五、位姿融合

这里的位姿融合部分与laserMapping中的求解地图坐标系中end时刻到初始时刻的初始猜测–transformAssociateToMap()函数完全一致。

1.求解位移增量
"transformBefMapped - transformSum"的含义是上一帧相对于初始帧的位移量 与 当前帧相对于初始帧的位移量 的差值,得到的结果是初始帧init坐标系下的位移增量 t i n i t s t a r t − e n d t_{init}^{start-end} tinitstartend

然后将其变换到end时刻:
t i n i t s t a r t − e n d = R e n d _ i n i t ∗ t i n i t s t a r t − e n d = R i n i t _ e n d − 1 ∗ t i n i t s t a r t − e n d R i n i t _ e n d − 1 = R Z X Y − 1 = R − r z R − r x R − r y t_{init}^{start-end} = R_{end\_init} * t_{init}^{start-end} = R_{init\_end}^{-1} * t_{init}^{start-end} \\ R_{init\_end}^{-1} = R_{ZXY}^{-1} = R_{-rz} R_{-rx} R_{-ry} tinitstartend=Rend_inittinitstartend=Rinit_end1tinitstartendRinit_end1=RZXY1=RrzRrxRry
对应于下面代码中所示的变换。

2.求解旋转部分的融合
现在这里的变量含义分别表示为:

  • transformSum:laserOdometry模块的当前帧相对于初始帧的变换 R i n i t _ e n d L R_{init\_end}^L Rinit_endL
  • transformBefMapped:laserMapping模块的当前帧相对于初始帧的变换 R i n i t _ e n d M R_{init\_end}^M Rinit_endM
  • transformAftMapped:laserMapping模块的优化后的当前帧相对于初始帧的变换,也可以理解为当前帧相对于地图坐标系的变换 R m a p _ s t a r t M R_{map\_start}^M Rmap_startM
  • transformMapped:融合后的当前帧相对于初始帧的坐标变换 R m a p _ e n d F R_{map\_end}^F Rmap_endF

那么有如下坐标变换关系:
R m a p _ e n d F = R m a p _ e n d M ∗ R i n i t _ e n d M − 1 ∗ R i n i t _ e n d L = R Z X Y ∗ R Z X Y − 1 ∗ R Z X Y R_{map\_end}^F = R_{map\_end}^M * R_{init\_end}^{M -1} * R_{init\_end}^L = R_{ZXY} * R_{ZXY}^{-1} * R_{ZXY} Rmap_endF=Rmap_endMRinit_endM1Rinit_endL=RZXYRZXY1RZXY

这里的计算公式与laserOdometry模块中的IMU修正部分完全一样:
R m a p _ e n d F = [ c a c y c a c z + s a c x s a c y s a c z c a c y s a c z + s a c x s a c y c a c z c a c x s a c y c a c x s a c z c a c x c a c z − s a c x − s a c y c a c z + s a c x c a c y s a c z s a c y s a c z + s a c x c a c y c a c z c a c x c a c y ] R_{map\_end}^F=\left[ \begin{matrix} cacycacz+sacxsacysacz& cacysacz+sacxsacycacz& cacxsacy\\ cacxsacz& cacxcacz& -sacx\\ -sacycacz+sacxcacysacz& sacysacz+sacxcacycacz& cacxcacy\\ \end{matrix} \right] Rmap_endF= cacycacz+sacxsacysaczcacxsaczsacycacz+sacxcacysaczcacysacz+sacxsacycaczcacxcaczsacysacz+sacxcacycaczcacxsacysacxcacxcacy
R m a p _ e n d M = [ c b c y c b c z + s b c x s b c y s b c z c b c y s b c z + s b c x s b c y c b c z c b c x s b c y c b c x s b c z c b c x c b c z − s b c x − s b c y c b c z + s b c x c b c y s b c z s b c y s b c z + s b c x c b c y c b c z c b c x c b c y ] R_{map\_end}^M=\left[ \begin{matrix} cbcycbcz+sbcxsbcysbcz& cbcysbcz+sbcxsbcycbcz& cbcxsbcy\\ cbcxsbcz& cbcxcbcz& -sbcx\\ -sbcycbcz+sbcxcbcysbcz& sbcysbcz+sbcxcbcycbcz& cbcxcbcy\\ \end{matrix} \right] Rmap_endM= cbcycbcz+sbcxsbcysbczcbcxsbczsbcycbcz+sbcxcbcysbczcbcysbcz+sbcxsbcycbczcbcxcbczsbcysbcz+sbcxcbcycbczcbcxsbcysbcxcbcxcbcy
R i n i t _ e n d M − 1 = [ c b l y c b l z − s b l x s b l y s b l z − c b l x s b l z s b l y c b l z + s b l x c b l y s b l z − c b l y s b l z + s b l x s b l y c b l z c b l x c b l z s b l y s b l z − s b l x c b l y c b l z − c b l x s b l y s b l x c b l x c b l y ] R_{init\_end}^{M -1}=\left[ \begin{matrix} cblycblz-sblxsblysblz& -cblxsblz& sblycblz+sblxcblysblz\\ -cblysblz+sblxsblycblz& cblxcblz& sblysblz-sblxcblycblz\\ -cblxsbly& sblx& cblxcbly\\ \end{matrix} \right] Rinit_endM1= cblycblzsblxsblysblzcblysblz+sblxsblycblzcblxsblycblxsblzcblxcblzsblxsblycblz+sblxcblysblzsblysblzsblxcblycblzcblxcbly
R i n i t _ e n d L = [ c a l y c a l z + s a l x s a l y s a l z c a l y s a l z + s a l x s a l y c a l z c a l x s a l y c a l x s a l z c a l x c a l z − s a l x − s a l y c a l z + s a l x c a l y s a l z s a l y s a l z + s a l x c a l y c a l z c a l x c a l y ] R_{init\_end}^L=\left[ \begin{matrix} calycalz+salxsalysalz& calysalz+salxsalycalz& calxsaly\\ calxsalz& calxcalz& -salx\\ -salycalz+salxcalysalz& salysalz+salxcalycalz& calxcaly\\ \end{matrix} \right] Rinit_endL= calycalz+salxsalysalzcalxsalzsalycalz+salxcalysalzcalysalz+salxsalycalzcalxcalzsalysalz+salxcalycalzcalxsalysalxcalxcaly

然后使用对应位置的值相等,就得到了修正后的累计变换acx、acy、acz,计算如下:
a c x = − a r c s i n ( R 2 , 3 ) = − a r c s i n ( − s b c x ∗ ( s a l x ∗ s b l x + c a l x ∗ c a l y ∗ c b l x ∗ c b l y + c a l x ∗ c b l x ∗ s a l y ∗ s b l y ) − c b c x ∗ c b c z ∗ ( c a l x ∗ s a l y ∗ ( c b l y ∗ s b l z − c b l z ∗ s b l x ∗ s b l y ) − c a l x ∗ c a l y ∗ ( s b l y ∗ s b l z + c b l y ∗ c b l z ∗ s b l x ) + c b l x ∗ c b l z ∗ s a l x ) − c b c x ∗ s b c z ∗ ( c a l x ∗ c a l y ∗ ( c b l z ∗ s b l y − c b l y ∗ s b l x ∗ s b l z ) − c a l x ∗ s a l y ∗ ( c b l y ∗ c b l z + s b l x ∗ s b l y ∗ s b l z ) + c b l x ∗ s a l x ∗ s b l z ) ) a c y = a r c t a n ( R 1 , 3 / R 3 , 3 ) a c z = a r c t a n ( R 2 , 1 / R 2 , 2 ) acx = -arcsin(R_{2,3}) = -arcsin(-sbcx*(salx*sblx + calx*caly*cblx*cbly + calx*cblx*saly*sbly) - cbcx*cbcz*(calx*saly*(cbly*sblz - cblz*sblx*sbly) - calx*caly*(sbly*sblz + cbly*cblz*sblx) + cblx*cblz*salx) - cbcx*sbcz*(calx*caly*(cblz*sbly - cbly*sblx*sblz) - calx*saly*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sblz) ) \\ acy = arctan(R_{1,3}/R_{3,3}) \\ acz = arctan(R_{2,1}/R_{2,2}) acx=arcsin(R2,3)=arcsin(sbcx(salxsblx+calxcalycblxcbly+calxcblxsalysbly)cbcxcbcz(calxsaly(cblysblzcblzsblxsbly)calxcaly(sblysblz+cblycblzsblx)+cblxcblzsalx)cbcxsbcz(calxcaly(cblzsblycblysblxsblz)calxsaly(cblycblz+sblxsblysblz)+cblxsalxsblz))acy=arctan(R1,3/R3,3)acz=arctan(R2,1/R2,2)

3.将位移增量转换到map坐标系
t m a p i n c r e m e n t = R m a p _ e n d F ∗ t e n d i n c r e m e n t R m a p _ e n d F = R Z X Y = R y R x R z t_{map}^{increment} = R_{map\_end}^F * t_{end}^{increment} \\ R_{map\_end}^F = R_{ZXY} = R_y R_x R_z tmapincrement=Rmap_endFtendincrementRmap_endF=RZXY=RyRxRz

4.求解平移部分的初始猜测
这里注意一点:上面求出来的增量使用的事start时刻的累积位移减去end时刻的累计位移,所以这里在求解时也是减号,如下:
t m a p _ e n d F = t m a p _ e n d M + t m a p e n d − s t a r t = t m a p _ s t a r t M − t m a p s t a r t − e n d t_{map\_end}^F = t_{map\_end}^M + t_{map}^{end-start} = t_{map\_start}^M - t_{map}^{start-end} tmap_endF=tmap_endM+tmapendstart=tmap_startMtmapstartend

我在上面声明变量时提到了:地图坐标系map,一定程度上可以理解为里程计第一帧init,这个意思就是可以理解为map坐标系和初始时刻坐标系init以及世界坐标系w是重合的,而laserMapping中虽然写的是变换到了map坐标系,也可以理解为仍然是当前帧end相对于初始帧init的坐标变换,只是经过了laserMapping模块优化,所以这里的 t m a p _ e n d F t_{map\_end}^F tmap_endF也可以写成 t i n i t _ e n d F t_{init\_end}^F tinit_endF这个解释只是为了符合作者代码中坐标变换时发布的是/camera_init到/camera的变换,所以这里写 t m a p _ e n d F t_{map\_end}^F tmap_endF也没问题。

//odometry的运动估计和mapping矫正量融合之后得到的最终的位姿transformMapped
void transformAssociateToMap()
{//平移后绕y轴旋转(-transformSum[1])float x1 = cos(transformSum[1]) * (transformBefMapped[3] - transformSum[3]) - sin(transformSum[1]) * (transformBefMapped[5] - transformSum[5]);float y1 = transformBefMapped[4] - transformSum[4];float z1 = sin(transformSum[1]) * (transformBefMapped[3] - transformSum[3]) + cos(transformSum[1]) * (transformBefMapped[5] - transformSum[5]);//绕x轴旋转(-transformSum[0])float x2 = x1;float y2 = cos(transformSum[0]) * y1 + sin(transformSum[0]) * z1;float z2 = -sin(transformSum[0]) * y1 + cos(transformSum[0]) * z1;//绕z轴旋转(-transformSum[2])transformIncre[3] = cos(transformSum[2]) * x2 + sin(transformSum[2]) * y2;transformIncre[4] = -sin(transformSum[2]) * x2 + cos(transformSum[2]) * y2;transformIncre[5] = z2;float sbcx = sin(transformSum[0]);float cbcx = cos(transformSum[0]);float sbcy = sin(transformSum[1]);float cbcy = cos(transformSum[1]);float sbcz = sin(transformSum[2]);float cbcz = cos(transformSum[2]);float sblx = sin(transformBefMapped[0]);float cblx = cos(transformBefMapped[0]);float sbly = sin(transformBefMapped[1]);float cbly = cos(transformBefMapped[1]);float sblz = sin(transformBefMapped[2]);float cblz = cos(transformBefMapped[2]);float salx = sin(transformAftMapped[0]);float calx = cos(transformAftMapped[0]);float saly = sin(transformAftMapped[1]);float caly = cos(transformAftMapped[1]);float salz = sin(transformAftMapped[2]);float calz = cos(transformAftMapped[2]);float srx = -sbcx*(salx*sblx + calx*cblx*salz*sblz + calx*calz*cblx*cblz)- cbcx*sbcy*(calx*calz*(cbly*sblz - cblz*sblx*sbly)- calx*salz*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sbly)- cbcx*cbcy*(calx*salz*(cblz*sbly - cbly*sblx*sblz) - calx*calz*(sbly*sblz + cbly*cblz*sblx) + cblx*cbly*salx);transformMapped[0] = -asin(srx);float srycrx = sbcx*(cblx*cblz*(caly*salz - calz*salx*saly)- cblx*sblz*(caly*calz + salx*saly*salz) + calx*saly*sblx)- cbcx*cbcy*((caly*calz + salx*saly*salz)*(cblz*sbly - cbly*sblx*sblz)+ (caly*salz - calz*salx*saly)*(sbly*sblz + cbly*cblz*sblx) - calx*cblx*cbly*saly)+ cbcx*sbcy*((caly*calz + salx*saly*salz)*(cbly*cblz + sblx*sbly*sblz)+ (caly*salz - calz*salx*saly)*(cbly*sblz - cblz*sblx*sbly) + calx*cblx*saly*sbly);float crycrx = sbcx*(cblx*sblz*(calz*saly - caly*salx*salz)- cblx*cblz*(saly*salz + caly*calz*salx) + calx*caly*sblx)+ cbcx*cbcy*((saly*salz + caly*calz*salx)*(sbly*sblz + cbly*cblz*sblx)+ (calz*saly - caly*salx*salz)*(cblz*sbly - cbly*sblx*sblz) + calx*caly*cblx*cbly)- cbcx*sbcy*((saly*salz + caly*calz*salx)*(cbly*sblz - cblz*sblx*sbly)+ (calz*saly - caly*salx*salz)*(cbly*cblz + sblx*sbly*sblz) - calx*caly*cblx*sbly);transformMapped[1] = atan2(srycrx / cos(transformMapped[0]), crycrx / cos(transformMapped[0]));float srzcrx = (cbcz*sbcy - cbcy*sbcx*sbcz)*(calx*salz*(cblz*sbly - cbly*sblx*sblz)- calx*calz*(sbly*sblz + cbly*cblz*sblx) + cblx*cbly*salx)- (cbcy*cbcz + sbcx*sbcy*sbcz)*(calx*calz*(cbly*sblz - cblz*sblx*sbly)- calx*salz*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sbly)+ cbcx*sbcz*(salx*sblx + calx*cblx*salz*sblz + calx*calz*cblx*cblz);float crzcrx = (cbcy*sbcz - cbcz*sbcx*sbcy)*(calx*calz*(cbly*sblz - cblz*sblx*sbly)- calx*salz*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sbly)- (sbcy*sbcz + cbcy*cbcz*sbcx)*(calx*salz*(cblz*sbly - cbly*sblx*sblz)- calx*calz*(sbly*sblz + cbly*cblz*sblx) + cblx*cbly*salx)+ cbcx*cbcz*(salx*sblx + calx*cblx*salz*sblz + calx*calz*cblx*cblz);transformMapped[2] = atan2(srzcrx / cos(transformMapped[0]), crzcrx / cos(transformMapped[0]));x1 = cos(transformMapped[2]) * transformIncre[3] - sin(transformMapped[2]) * transformIncre[4];y1 = sin(transformMapped[2]) * transformIncre[3] + cos(transformMapped[2]) * transformIncre[4];z1 = transformIncre[5];x2 = x1;y2 = cos(transformMapped[0]) * y1 - sin(transformMapped[0]) * z1;z2 = sin(transformMapped[0]) * y1 + cos(transformMapped[0]) * z1;transformMapped[3] = transformAftMapped[3] - (cos(transformMapped[1]) * x2 + sin(transformMapped[1]) * z2);transformMapped[4] = transformAftMapped[4] - y2;transformMapped[5] = transformAftMapped[5] - (-sin(transformMapped[1]) * x2 + cos(transformMapped[1]) * z2);
}

总结

到此为止,整个LOAM的讲解就结束了!!

我的感觉就是看LOAM的论文,有一种“作者说的好有道理,确实就是这样啊”的感觉,但是如果要是让自己想,就想不出来这么牛逼的算法,它的代码也写的比较漂亮。

代码的运行就不单独开一篇文章写了,只要装好了依赖,编译很顺畅,也没报什么错,我找了一个数据集测试了一下,也没问题,测试的数据里放在了文章开头提到的我的github仓库的bag文件夹中,运行结果点云图放在了pcl文件夹中,放一张结果截图。

在这里插入图片描述

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

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

相关文章

湖南省人民政府副省长秦国文一行莅临麒麟信安调研

&#xff08;通讯员 付瑞、周沁怡&#xff09;8月21日&#xff0c;湖南省人民政府副省长秦国文在长沙调研计算产业&#xff0c;省政府副秘书长季心诠、省科技厅厅长李志坚、省工信厅副厅长彭涛、市人民政府副市长肖正波、湘江新区&#xff08;长沙高新区&#xff09;管委会副主…

SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第二天)Mybatis的深入学习

SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录&#xff08;第二天&#xff09;Mybatis的深入学习&#xff08;增删改查的操作&#xff09; 上一篇我们的项目搭建好了&#xff0c;也写了简答的Junit测试类进行测试&#xff0c;可以正确映射…

Lnton羚通算法算力云平台如何在OpenCV-Python中使用cvui库创建复选框

CVUI 之 复选框 Python import numpy as np import cv2 import cvuidef checkbox_test():WINDOW_NAME Checkbox-Testchecked [False]# 创建画布frame np.zeros((300, 400, 3), np.uint8)# 初始化窗口cvui.init(WINDOW_NAME)while True:# 画布填色frame[:] (100, 200, 100…

利用屏幕水印学习英语单词,无打扰英语单词学习

1、利用屏幕水印学习英语单词&#xff0c;不影响任何鼠标键盘操作&#xff0c;不影响工作 2、利用系统热键快速隐藏&#xff08;ALT1键 隐藏与显示&#xff09; 3、日积月累单词会有进步 4、软件下载地址: 免安装&#xff0c;代码未加密&#xff0c;安全的屏幕水印学习英语…

机器人TF坐标系变换与一些可视化工具的应用

TF坐标在ROS中是一个非常重要的概念&#xff0c;因为机器人在做日常操作任务的时候&#xff0c;对于其所在位置和朝向是需要时刻知道的&#xff0c;而机器人是由很多节点组成的协同任务&#xff0c;对于每个部件&#xff0c;我们需要知道它的位姿(位置和朝向)&#xff0c;这使得…

链表OJ题

今天讲一些关于链表的Oj题&#xff0c;相信你看完对链表又提升一个档次。 题目一 思路一 遍历一遍链表是Val值得时候free这个&#xff0c;然后我们往后走&#xff0c;一直走到末尾空指针得时候&#xff0c;新链表就是我们得答案&#xff0c;那我们用代码来表示一下吧。 struct…

rabbitMQ服务自动停止(已解决

1、 在rabbitmq的sbin目录下操作 rabbitmq-plugins enable rabbitmq_management 2、 自己去rabbitmq_server-3.7.5文件夹下创建一个data&#xff0c;再执行这个命令&#xff08;用自己的目录哈 set RABBITMQ_BASED:\RabbitTools\RabbitMQ\rabbitmq_server-3.7.5\data 然后去配…

基于YOLOV8模型的西红柿目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOV8模型的西红柿目标检测系统可用于日常生活中检测与定位西红柿目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训练数…

微信小程序:模板使用

目录 模板的优点&#xff1a; 一、静态模板创建 二、静态模板使用 1.*.wxml引入模板 2.模板使用 3.*.wxss引入模板的样式 三、动态模板创建 四、动态模板使用 1.*.wxml引入模板 2.模板使用 3.*.js定义动态数据 五、结果展示 总结 模板的优点&#xff1a; 有利于保持网…

element-ui中二次封装一个带select的form组件

带select的form组件 样式 代码 <template><el-form-item label"是否有" class"append" prop"tag"><el-form-itemprop"isShare"><el-select v-model"query.tag"><el-option v-for"(item, …

2023年中秋月饼市场趋势分析(月饼京东销售数据分析)

中秋将至&#xff0c;月饼作为节令食品将再次掀起消费热潮。今年月饼市场的需求如何呢&#xff0c;是更受欢迎还是热度有所降低&#xff0c;结合数据我们一起来看今年月饼市场的销售表现。 在这里&#xff0c;我们分别选取了2022年第31周-32周和2023年第31周-32周&#xff08;…

算法通关村第5关【青铜】| Hash和队列的特征

1.Hash基础 &#xff08;1&#xff09;基础 哈希也称为散列&#xff0c;通过算法变成固定长度的输出值&#xff0c;存入对应的位置 例如这个算法为取模算法&#xff0c;indexnumber 模 7 存入1到15 &#xff08;2&#xff09;碰撞处理 当多个元素映射到同一位置上时就产生…

API 接口选择那个?RESTful、GraphQL、gRPC、WebSocket、Webhook

大家好&#xff0c;我是比特桃。目前我们的生活紧紧地被大量互联网服务所包围&#xff0c;互联网上每天都有数百亿次API调用。API 是两个设备相互通讯的一种方式&#xff0c;人们在手机上每次指尖的悦动&#xff0c;背后都是 API 接口的调用。 本文将列举常见的一些 API 接口&…

利用console提高写bug的效率

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 自从入坑前端后&#xff0c;日常写bug就没离开过console。 要说用得多&#xff0c;不如说是console.log用得多&#xff0c;console.warn和console.erro…

2023年最佳JavaScript框架:React、Vue、Angular和Node.js的比较

文章目录 React&#xff1a;构建用户界面的首选Vue&#xff1a;简单优雅的前端框架Angular&#xff1a;Google支持的全面框架Node.js&#xff1a;服务器端的JavaScript运行环境比较不同框架的优势与劣势React&#xff1a;Vue&#xff1a;Angular&#xff1a;Node.js&#xff1a…

怎么查看小程序中的会员信息

商家通过查看会员信息&#xff0c;可以更好地了解用户&#xff0c;并为他们提供更个性化的服务和推荐。接下来&#xff0c;就将介绍如何查看会员信息。 商家在管理员后台->会员管理处&#xff0c;可以查看到会员列表。支持搜索会员的卡号、手机号和等级。还支持批量删除会员…

【Python】数据分析工具之各种图

1 漏斗图 漏斗图描述状态阶段的顺序递进关系&#xff0c;属于序列类图表 import plotly.express as pxdata dict(number[39, 27.4, 20.6, 11, 2],stage["Website visit", "Downloads", "Potential customers", "Requested price", &…

K8s+Docker+KubeSphere+DevOps笔记

K8sDockerKubeSphereDevOps 前言一、阿里云服务器开通二、docker基本概念1.一次构建、到处运行2、docker基础命令操作3、docker进阶操作1.部署redis中间件2.打包docker镜像 三、kubernetes 大规模容器编排系统1、基础概念&#xff1a;1、服务发现和负载均衡2、存储编排3、自动部…

QTreeWidget——信号处理

文章目录 基本属性信号一、信号种类二、信号测试1、currentItemChanged、itemCollapsed、itemExpanded三个信号的测试2、itemActivated信号3、 itemChanged信号4、其余信号的测试代码&#xff08;包含以上代码&#xff09; 基本属性 信号 一、信号种类 //当前项发生变化时触…

uniapp 顶部头部样式

<u-navbartitle"商城":safeAreaInsetTop"true"><view slot"left"><image src"/static/logo.png" mode"" class"u-w-50 u-h-50"></image></view></u-navbar>