aloam学习笔记(四)

news2024/11/27 11:44:11

对于laserMapping.cpp源码的学习,这部分的主要功能是接受前端传来的数据,构建地图。

一、main函数部分

	ros::init(argc, argv, "laserMapping");
	ros::NodeHandle nh;

	float lineRes = 0;
	float planeRes = 0;
	nh.param<float>("mapping_line_resolution", lineRes, 0.4);
	nh.param<float>("mapping_plane_resolution", planeRes, 0.8);
	printf("line resolution %f plane resolution %f \n", lineRes, planeRes);
	downSizeFilterCorner.setLeafSize(lineRes, lineRes,lineRes);
	downSizeFilterSurf.setLeafSize(planeRes, planeRes, planeRes);

这里是ros节点的初始化,然后设置了两个变量lineRes和planeRes,为了配置体素滤波器。然后下面就是设置了两个体素降采样滤波器downSizeFiterCorner和downSizeFilterSurf。

	ros::Subscriber subLaserCloudCornerLast = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_corner_last", 100, laserCloudCornerLastHandler);

	ros::Subscriber subLaserCloudSurfLast = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_surf_last", 100, laserCloudSurfLastHandler);

	ros::Subscriber subLaserOdometry = nh.subscribe<nav_msgs::Odometry>("/laser_odom_to_init", 100, laserOdometryHandler);

	ros::Subscriber subLaserCloudFullRes = nh.subscribe<sensor_msgs::PointCloud2>("/velodyne_cloud_3", 100, laserCloudFullResHandler);

这部分是订阅部分,订阅了前端发出的4个话题:

laser_cloud_corner_last、laser_cloud_surf_last、laser_odom_to_init、velodyne_cloud_3。

三个点云数据格式一个里程计格式。

	pubLaserCloudSurround = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_surround", 100);

	pubLaserCloudMap = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_map", 100);

	pubLaserCloudFullRes = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_cloud_registered", 100);

	pubOdomAftMapped = nh.advertise<nav_msgs::Odometry>("/aft_mapped_to_init", 100);

	pubOdomAftMappedHighFrec = nh.advertise<nav_msgs::Odometry>("/aft_mapped_to_init_high_frec", 100);

	pubLaserAfterMappedPath = nh.advertise<nav_msgs::Path>("/aft_mapped_path", 100);

发布6个话题。

	for (int i = 0; i < laserCloudNum; i++)
	{
		laserCloudCornerArray[i].reset(new pcl::PointCloud<PointType>());
		laserCloudSurfArray[i].reset(new pcl::PointCloud<PointType>());
	}

	std::thread mapping_process{process};

	ros::spin();

然后就是重新设置大小,这两个对象laserCloudCornerArray和laserCloudSurfArray是对后端地图的处理,相当于使用数组指针,维护作用。之后就进入线程运算里面,然后ros::spin()死循环。

二、subscriber回调函数处理

std::queue<sensor_msgs::PointCloud2ConstPtr> cornerLastBuf;
std::queue<sensor_msgs::PointCloud2ConstPtr> surfLastBuf;
std::queue<sensor_msgs::PointCloud2ConstPtr> fullResBuf;
std::queue<nav_msgs::Odometry::ConstPtr> odometryBuf;
std::mutex mBuf;

定义了4个队列用于存放之后订阅的话题指针,还有一个多线程的对象mBuf。

void laserCloudCornerLastHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudCornerLast2)
{
	mBuf.lock();
	cornerLastBuf.push(laserCloudCornerLast2);
	mBuf.unlock();
}

void laserCloudSurfLastHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudSurfLast2)
{
	mBuf.lock();
	surfLastBuf.push(laserCloudSurfLast2);
	mBuf.unlock();
}

void laserCloudFullResHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudFullRes2)
{
	mBuf.lock();
	fullResBuf.push(laserCloudFullRes2);
	mBuf.unlock();
}

比较简单,就是把订阅到的指针constptr放到前面的queue里面,然后设置线程锁。

void laserOdometryHandler(const nav_msgs::Odometry::ConstPtr &laserOdometry)
{
	mBuf.lock();
	odometryBuf.push(laserOdometry);
	mBuf.unlock();

	// high frequence publish
	Eigen::Quaterniond q_wodom_curr;
	Eigen::Vector3d t_wodom_curr;
	q_wodom_curr.x() = laserOdometry->pose.pose.orientation.x;
	q_wodom_curr.y() = laserOdometry->pose.pose.orientation.y;
	q_wodom_curr.z() = laserOdometry->pose.pose.orientation.z;
	q_wodom_curr.w() = laserOdometry->pose.pose.orientation.w;
	t_wodom_curr.x() = laserOdometry->pose.pose.position.x;
	t_wodom_curr.y() = laserOdometry->pose.pose.position.y;
	t_wodom_curr.z() = laserOdometry->pose.pose.position.z;

	Eigen::Quaterniond q_w_curr = q_wmap_wodom * q_wodom_curr;
	Eigen::Vector3d t_w_curr = q_wmap_wodom * t_wodom_curr + t_wmap_wodom; 

	nav_msgs::Odometry odomAftMapped;
	odomAftMapped.header.frame_id = "/camera_init";
	odomAftMapped.child_frame_id = "/aft_mapped";
	odomAftMapped.header.stamp = laserOdometry->header.stamp;
	odomAftMapped.pose.pose.orientation.x = q_w_curr.x();
	odomAftMapped.pose.pose.orientation.y = q_w_curr.y();
	odomAftMapped.pose.pose.orientation.z = q_w_curr.z();
	odomAftMapped.pose.pose.orientation.w = q_w_curr.w();
	odomAftMapped.pose.pose.position.x = t_w_curr.x();
	odomAftMapped.pose.pose.position.y = t_w_curr.y();
	odomAftMapped.pose.pose.position.z = t_w_curr.z();
	pubOdomAftMappedHighFrec.publish(odomAftMapped);
}

对于订阅的里程计的处理,先把它push进相对应的queue里面。

然后设置一个eigen形式的四元数q_wodom_curr,一个平移向量t_wodom_curr,将订阅到的laserOdometry的相关数据赋值给它们。后面的部分是对于odom坐标系和map坐标系之间的转换。

T_{map-cur} = T_{map2odom}*T_{odom-cur}

上面是公式,不知道怎么在csdn里面打上下划线。

求的就是Tmap2odom这个变量。

 具体的公式如上图

	Eigen::Quaterniond q_w_curr = q_wmap_wodom * q_wodom_curr;

这里是旋转部分,对应上图最后矩阵的第一行第一列。

Eigen::Vector3d t_w_curr = q_wmap_wodom * t_wodom_curr + t_wmap_wodom; 

这里是平移部分,对应上图第一行第二列。

后面的部分就是设置一个新的odomery然后赋值,转换成ros消息格式,最后发布出去。

三、主线程process处理部分

首先判断四个queue里面是否有数据,有的话才进行操作。

			while (!odometryBuf.empty() && odometryBuf.front()->header.stamp.toSec() < cornerLastBuf.front()->header.stamp.toSec())
				odometryBuf.pop();
			if (odometryBuf.empty())
			{
				mBuf.unlock();
				break;
			}

以cornerLastBuf中存放的数据的时间戳为基准,如果小于这个时间戳就pop使用下一个数据,对于其它3个queue都是这样处理,相当于一种时间同步的设置。

			timeLaserCloudCornerLast = cornerLastBuf.front()->header.stamp.toSec();
			timeLaserCloudSurfLast = surfLastBuf.front()->header.stamp.toSec();
			timeLaserCloudFullRes = fullResBuf.front()->header.stamp.toSec();
			timeLaserOdometry = odometryBuf.front()->header.stamp.toSec();

			if (timeLaserCloudCornerLast != timeLaserOdometry ||
				timeLaserCloudSurfLast != timeLaserOdometry ||
				timeLaserCloudFullRes != timeLaserOdometry)
			{
				printf("time corner %f surf %f full %f odom %f \n", timeLaserCloudCornerLast, timeLaserCloudSurfLast, timeLaserCloudFullRes, timeLaserOdometry);
				printf("unsync messeage!");
				mBuf.unlock();
				break;
			}

取出时间戳进行判断,因为在前端设置的时间戳都是相等的,如果时间戳不想等就会报错break,一般是不会发生。

			laserCloudCornerLast->clear();
			pcl::fromROSMsg(*cornerLastBuf.front(), *laserCloudCornerLast);
			cornerLastBuf.pop();

			laserCloudSurfLast->clear();
			pcl::fromROSMsg(*surfLastBuf.front(), *laserCloudSurfLast);
			surfLastBuf.pop();

			laserCloudFullRes->clear();
			pcl::fromROSMsg(*fullResBuf.front(), *laserCloudFullRes);
			fullResBuf.pop();

转换消息格式,从ros转换成pcl格式,便于使用pcl库。

			q_wodom_curr.x() = odometryBuf.front()->pose.pose.orientation.x;
			q_wodom_curr.y() = odometryBuf.front()->pose.pose.orientation.y;
			q_wodom_curr.z() = odometryBuf.front()->pose.pose.orientation.z;
			q_wodom_curr.w() = odometryBuf.front()->pose.pose.orientation.w;
			t_wodom_curr.x() = odometryBuf.front()->pose.pose.position.x;
			t_wodom_curr.y() = odometryBuf.front()->pose.pose.position.y;
			t_wodom_curr.z() = odometryBuf.front()->pose.pose.position.z;
			odometryBuf.pop();
Eigen::Quaterniond q_wodom_curr(1, 0, 0, 0);
Eigen::Vector3d t_wodom_curr(0, 0, 0);

将前端里程计的数据转换成eigen的数据。

			while(!cornerLastBuf.empty())
			{
				cornerLastBuf.pop();
				printf("drop lidar frame in mapping for real time performance \n");
			}

			mBuf.unlock();

保证数据的实时性将所有的cornerLastBuf清空掉。

void transformAssociateToMap()
{
	q_w_curr = q_wmap_wodom * q_wodom_curr;
	t_w_curr = q_wmap_wodom * t_wodom_curr + t_wmap_wodom;
}

然后是为了,优化设置一个比较好的初值。

			int centerCubeI = int((t_w_curr.x() + 25.0) / 50.0) + laserCloudCenWidth;
			int centerCubeJ = int((t_w_curr.y() + 25.0) / 50.0) + laserCloudCenHeight;
			int centerCubeK = int((t_w_curr.z() + 25.0) / 50.0) + laserCloudCenDepth;

后端部分是scan to map,但这个map不能太大,太大的话会导致计算机内存爆炸。所以设置了一个范围,对于这个范围内的map进行配准优化。centerCubeI、centerCubeJ和centerCubeK就是用来设置这个范围的的中心点。

			if (t_w_curr.x() + 25.0 < 0)
				centerCubeI--;
			if (t_w_curr.y() + 25.0 < 0)
				centerCubeJ--;
			if (t_w_curr.z() + 25.0 < 0)
				centerCubeK--;

向零取整

下面是对于局部地图变化的设置:

			while (centerCubeI < 3)
			{
				for (int j = 0; j < laserCloudHeight; j++)
				{
					for (int k = 0; k < laserCloudDepth; k++)
					{ 
						int i = laserCloudWidth - 1;
						pcl::PointCloud<PointType>::Ptr laserCloudCubeCornerPointer =
							laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k]; 
						pcl::PointCloud<PointType>::Ptr laserCloudCubeSurfPointer =
							laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
						for (; i >= 1; i--)
						{
							laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
								laserCloudCornerArray[i - 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
							laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
								laserCloudSurfArray[i - 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
						}
						laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
							laserCloudCubeCornerPointer;
						laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
							laserCloudCubeSurfPointer;
						laserCloudCubeCornerPointer->clear();
						laserCloudCubeSurfPointer->clear();
					}
				}

				centerCubeI++;
				laserCloudCenWidth++;
			}

如果centerCubeI小于3,说明在这个map要到边缘了,所以将这个区域的cube往另一个方向挪动,我是这么理解的。j是高的索引,k是深度的索引,i就是宽的索引,然后i是从最大开始。laserCloudCubeCornerPointer是一个tmp,用来临时存放最后那一层的cube。之后就是在for循环内进行转换,这里是将三维的数据存放在一维的数组里面。最后是将tmp赋值给i=0后的cube。然后也同时把面点也处理了。

之后的都是相似,它这里设置了centerCubeI都是在3到18的范围认为是正常的。超过这个范围都需要进行移动。

			int laserCloudValidNum = 0;
			int laserCloudSurroundNum = 0;

			for (int i = centerCubeI - 2; i <= centerCubeI + 2; i++)
			{
				for (int j = centerCubeJ - 2; j <= centerCubeJ + 2; j++)
				{
					for (int k = centerCubeK - 1; k <= centerCubeK + 1; k++)
					{
						if (i >= 0 && i < laserCloudWidth &&
							j >= 0 && j < laserCloudHeight &&
							k >= 0 && k < laserCloudDepth)
						{ 
							laserCloudValidInd[laserCloudValidNum] = i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k;
							laserCloudValidNum++;
							laserCloudSurroundInd[laserCloudSurroundNum] = i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k;
							laserCloudSurroundNum++;
						}
					}
				}
			}

这部分就是在之前取的cube里面再取一小部分用来做配准。然后就来存放这小部分的点云的index到laserCloudValidInd数组里面,这是一个125大小的数组。

			laserCloudCornerFromMap->clear();
			laserCloudSurfFromMap->clear();
			for (int i = 0; i < laserCloudValidNum; i++)
			{
				*laserCloudCornerFromMap += *laserCloudCornerArray[laserCloudValidInd[i]];
				*laserCloudSurfFromMap += *laserCloudSurfArray[laserCloudValidInd[i]];
			}
			int laserCloudCornerFromMapNum = laserCloudCornerFromMap->points.size();
			int laserCloudSurfFromMapNum = laserCloudSurfFromMap->points.size();

这里的laserCloudCornerFromMap和laserCloudSurfFromMap是真正的局部地图。然后就使用laserCloudValidInd数组记录的点云的位置赋值累加给这两个量。并算出有多少个点云。

			pcl::PointCloud<PointType>::Ptr laserCloudCornerStack(new pcl::PointCloud<PointType>());
			downSizeFilterCorner.setInputCloud(laserCloudCornerLast);
			downSizeFilterCorner.filter(*laserCloudCornerStack);
			int laserCloudCornerStackNum = laserCloudCornerStack->points.size();

			pcl::PointCloud<PointType>::Ptr laserCloudSurfStack(new pcl::PointCloud<PointType>());
			downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
			downSizeFilterSurf.filter(*laserCloudSurfStack);
			int laserCloudSurfStackNum = laserCloudSurfStack->points.size();

这部分就是分别对两个当前帧进行下采样计算。

					ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);
					ceres::LocalParameterization *q_parameterization =
						new ceres::EigenQuaternionParameterization();
					ceres::Problem::Options problem_options;

					ceres::Problem problem(problem_options);
					problem.AddParameterBlock(parameters, 4, q_parameterization);
					problem.AddParameterBlock(parameters + 4, 3);

这里和前端一样都是对于ceres的设置,设置了核函数,设置了四元数加法优化,最后添加到了problem中去。

下面是线特征的处理部分:

					for (int i = 0; i < laserCloudCornerStackNum; i++)
					{
						pointOri = laserCloudCornerStack->points[i];
						//double sqrtDis = pointOri.x * pointOri.x + pointOri.y * pointOri.y + pointOri.z * pointOri.z;
						pointAssociateToMap(&pointOri, &pointSel);
						kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis); 

开始遍历所有的边缘点,取出赋值给pointOri,然后使用函数pointAssociateToMap进行处理。

void pointAssociateToMap(PointType const *const pi, PointType *const po)
{
	Eigen::Vector3d point_curr(pi->x, pi->y, pi->z);
	Eigen::Vector3d point_w = q_w_curr * point_curr + t_w_curr;
	po->x = point_w.x();
	po->y = point_w.y();
	po->z = point_w.z();
	po->intensity = pi->intensity;
	//po->intensity = 1.0;
}

直接使用pi的值然后进行一个坐标变换之后赋值给po。然后使用kdtree来寻找里这个点最近的5个点。

						if (pointSearchSqDis[4] < 1.0)
						{ 
							std::vector<Eigen::Vector3d> nearCorners;
							Eigen::Vector3d center(0, 0, 0);
							for (int j = 0; j < 5; j++)
							{
								Eigen::Vector3d tmp(laserCloudCornerFromMap->points[pointSearchInd[j]].x,
													laserCloudCornerFromMap->points[pointSearchInd[j]].y,
													laserCloudCornerFromMap->points[pointSearchInd[j]].z);
								center = center + tmp;
								nearCorners.push_back(tmp);
							}
							center = center / 5.0;

首先判断这五个点中距离最远的点是否大于1m如果大于1m这不进行后面的运算。然后设置一个vector来存放这5个点,并累加到center上然后push进去。最后求center的均值。

							Eigen::Matrix3d covMat = Eigen::Matrix3d::Zero();
							for (int j = 0; j < 5; j++)
							{
								Eigen::Matrix<double, 3, 1> tmpZeroMean = nearCorners[j] - center;
								covMat = covMat + tmpZeroMean * tmpZeroMean.transpose();
							}

计算这五个点的协方差covMat。

							Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> saes(covMat);

使用Eigen的特征值分解器。

Eigen::Vector3d unit_direction = saes.eigenvectors().col(2);

取出特征向量最大的那个赋值给unit_direction。

							Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);
							if (saes.eigenvalues()[2] > 3 * saes.eigenvalues()[1])
							{ 
								Eigen::Vector3d point_on_line = center;
								Eigen::Vector3d point_a, point_b;
								point_a = 0.1 * unit_direction + point_on_line;
								point_b = -0.1 * unit_direction + point_on_line;

								ceres::CostFunction *cost_function = LidarEdgeFactor::Create(curr_point, point_a, point_b, 1.0);
								problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
								corner_num++;	
							}	

这里是构造了两个虚拟的点point_a和point_b,为了构建约束关系。

下面是构建面约束的部分:

					int surf_num = 0;
					for (int i = 0; i < laserCloudSurfStackNum; i++)
					{
						pointOri = laserCloudSurfStack->points[i];
						//double sqrtDis = pointOri.x * pointOri.x + pointOri.y * pointOri.y + pointOri.z * pointOri.z;
						pointAssociateToMap(&pointOri, &pointSel);
						kdtreeSurfFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);

和线约束的部分一样。

						Eigen::Matrix<double, 5, 3> matA0;
						Eigen::Matrix<double, 5, 1> matB0 = -1 * Eigen::Matrix<double, 5, 1>::Ones();

平面方程是:

       AX1+BX2+CX3+D = 0,把D除去,然后1换到右边。

构建了AX=B里面的A和B,这里和平面方程不是一个A和B。

							for (int j = 0; j < 5; j++)
							{
								matA0(j, 0) = laserCloudSurfFromMap->points[pointSearchInd[j]].x;
								matA0(j, 1) = laserCloudSurfFromMap->points[pointSearchInd[j]].y;
								matA0(j, 2) = laserCloudSurfFromMap->points[pointSearchInd[j]].z;
								//printf(" pts %f %f %f ", matA0(j, 0), matA0(j, 1), matA0(j, 2));
							}

将最近的5个面点填充到A里面。

							Eigen::Vector3d norm = matA0.colPivHouseholderQr().solve(matB0);
							double negative_OA_dot_norm = 1 / norm.norm();
							norm.normalize();

求解出norm法向量,然后求逆并归一化。这里需要区别一下eigen中norm函数和normalize函数的作用:

Eigen中norm、normalize、normalized的区别_dzxia920的博客-CSDN博客_eigen normalize

 简单来说negative_OA_dot_norm就是norm的二范数的倒数,然后将norm归一化了。方便之后求距离。注意平面方程中的D已经除过去了。

							bool planeValid = true;
							for (int j = 0; j < 5; j++)
							{
								// if OX * n > 0.2, then plane is not fit well
								if (fabs(norm(0) * laserCloudSurfFromMap->points[pointSearchInd[j]].x +
										 norm(1) * laserCloudSurfFromMap->points[pointSearchInd[j]].y +
										 norm(2) * laserCloudSurfFromMap->points[pointSearchInd[j]].z + negative_OA_dot_norm) > 0.2)
								{
									planeValid = false;
									break;
								}
							}
							Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);
							if (planeValid)
							{
								ceres::CostFunction *cost_function = LidarPlaneNormFactor::Create(curr_point, norm, negative_OA_dot_norm);
								problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
								surf_num++;
							}
						}

这部分是计算点到平面的距离。根据点到平面公式,首先计算这5个点是否小于0.2。然后放入ceres进行优化。

struct LidarPlaneNormFactor
{

	LidarPlaneNormFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d plane_unit_norm_,
						 double negative_OA_dot_norm_) : curr_point(curr_point_), plane_unit_norm(plane_unit_norm_),
														 negative_OA_dot_norm(negative_OA_dot_norm_) {}

	template <typename T>
	bool operator()(const T *q, const T *t, T *residual) const
	{
		Eigen::Quaternion<T> q_w_curr{q[3], q[0], q[1], q[2]};
		Eigen::Matrix<T, 3, 1> t_w_curr{t[0], t[1], t[2]};
		Eigen::Matrix<T, 3, 1> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
		Eigen::Matrix<T, 3, 1> point_w;
		point_w = q_w_curr * cp + t_w_curr;

		Eigen::Matrix<T, 3, 1> norm(T(plane_unit_norm.x()), T(plane_unit_norm.y()), T(plane_unit_norm.z()));
		residual[0] = norm.dot(point_w) + T(negative_OA_dot_norm);
		return true;
	}

	static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d plane_unit_norm_,
									   const double negative_OA_dot_norm_)
	{
		return (new ceres::AutoDiffCostFunction<
				LidarPlaneNormFactor, 1, 4, 3>(
			new LidarPlaneNormFactor(curr_point_, plane_unit_norm_, negative_OA_dot_norm_)));
	}

	Eigen::Vector3d curr_point;
	Eigen::Vector3d plane_unit_norm;
	double negative_OA_dot_norm;
};

这里和线点的类是,首先是计算出当前的点的坐标,然后通过传进去的norm和negative_OA_dot_norm计算点到面的距离。先看create部分再看operator部分。

					TicToc t_solver;
					ceres::Solver::Options options;
					options.linear_solver_type = ceres::DENSE_QR;
					options.max_num_iterations = 4;
					options.minimizer_progress_to_stdout = false;
					options.check_gradients = false;
					options.gradient_check_relative_precision = 1e-4;
					ceres::Solver::Summary summary;
					ceres::Solve(options, &problem, &summary);

设置ceres的options然后求解。

void transformUpdate()
{
	q_wmap_wodom = q_w_curr * q_wodom_curr.inverse();
	t_wmap_wodom = t_w_curr - q_wmap_wodom * t_wodom_curr;
}

将map转换到odom坐标系下。

公式推导如上。

double parameters[7] = {0, 0, 0, 1, 0, 0, 0};
Eigen::Map<Eigen::Quaterniond> q_w_curr(parameters);
Eigen::Map<Eigen::Vector3d> t_w_curr(parameters + 4);

这里使用了Eigen的map方式。

后面的部分就是局部地图的更新。

			for (int i = 0; i < laserCloudCornerStackNum; i++)
			{
				pointAssociateToMap(&laserCloudCornerStack->points[i], &pointSel);

				int cubeI = int((pointSel.x + 25.0) / 50.0) + laserCloudCenWidth;
				int cubeJ = int((pointSel.y + 25.0) / 50.0) + laserCloudCenHeight;
				int cubeK = int((pointSel.z + 25.0) / 50.0) + laserCloudCenDepth;

				if (pointSel.x + 25.0 < 0)
					cubeI--;
				if (pointSel.y + 25.0 < 0)
					cubeJ--;
				if (pointSel.z + 25.0 < 0)
					cubeK--;

				if (cubeI >= 0 && cubeI < laserCloudWidth &&
					cubeJ >= 0 && cubeJ < laserCloudHeight &&
					cubeK >= 0 && cubeK < laserCloudDepth)
				{
					int cubeInd = cubeI + laserCloudWidth * cubeJ + laserCloudWidth * laserCloudHeight * cubeK;
					laserCloudCornerArray[cubeInd]->push_back(pointSel);
				}
			}

这里是对与线点的在cube中的位置的判断,确定范围后直接将pointSel放进去。

			for (int i = 0; i < laserCloudValidNum; i++)
			{
				int ind = laserCloudValidInd[i];

				pcl::PointCloud<PointType>::Ptr tmpCorner(new pcl::PointCloud<PointType>());
				downSizeFilterCorner.setInputCloud(laserCloudCornerArray[ind]);
				downSizeFilterCorner.filter(*tmpCorner);
				laserCloudCornerArray[ind] = tmpCorner;

				pcl::PointCloud<PointType>::Ptr tmpSurf(new pcl::PointCloud<PointType>());
				downSizeFilterSurf.setInputCloud(laserCloudSurfArray[ind]);
				downSizeFilterSurf.filter(*tmpSurf);
				laserCloudSurfArray[ind] = tmpSurf;
			}

将线点和面点进行降采样。

			if (frameCount % 5 == 0)
			{
				laserCloudSurround->clear();
				for (int i = 0; i < laserCloudSurroundNum; i++)
				{
					int ind = laserCloudSurroundInd[i];
					*laserCloudSurround += *laserCloudCornerArray[ind];
					*laserCloudSurround += *laserCloudSurfArray[ind];
				}

				sensor_msgs::PointCloud2 laserCloudSurround3;
				pcl::toROSMsg(*laserCloudSurround, laserCloudSurround3);
				laserCloudSurround3.header.stamp = ros::Time().fromSec(timeLaserOdometry);
				laserCloudSurround3.header.frame_id = "/camera_init";
				pubLaserCloudSurround.publish(laserCloudSurround3);
			}

			if (frameCount % 20 == 0)
			{
				pcl::PointCloud<PointType> laserCloudMap;
				for (int i = 0; i < 4851; i++)
				{
					laserCloudMap += *laserCloudCornerArray[i];
					laserCloudMap += *laserCloudSurfArray[i];
				}
				sensor_msgs::PointCloud2 laserCloudMsg;
				pcl::toROSMsg(laserCloudMap, laserCloudMsg);
				laserCloudMsg.header.stamp = ros::Time().fromSec(timeLaserOdometry);
				laserCloudMsg.header.frame_id = "/camera_init";
				pubLaserCloudMap.publish(laserCloudMsg);
			}

之后就是以不同频率进行发布。每5帧发布一个采样的局部地图,每20帧发布一个局部地图。

			int laserCloudFullResNum = laserCloudFullRes->points.size();
			for (int i = 0; i < laserCloudFullResNum; i++)
			{
				pointAssociateToMap(&laserCloudFullRes->points[i], &laserCloudFullRes->points[i]);
			}

			sensor_msgs::PointCloud2 laserCloudFullRes3;
			pcl::toROSMsg(*laserCloudFullRes, laserCloudFullRes3);
			laserCloudFullRes3.header.stamp = ros::Time().fromSec(timeLaserOdometry);
			laserCloudFullRes3.header.frame_id = "/camera_init";
			pubLaserCloudFullRes.publish(laserCloudFullRes3);

将全部点云数据发布。

			nav_msgs::Odometry odomAftMapped;
			odomAftMapped.header.frame_id = "/camera_init";
			odomAftMapped.child_frame_id = "/aft_mapped";
			odomAftMapped.header.stamp = ros::Time().fromSec(timeLaserOdometry);
			odomAftMapped.pose.pose.orientation.x = q_w_curr.x();
			odomAftMapped.pose.pose.orientation.y = q_w_curr.y();
			odomAftMapped.pose.pose.orientation.z = q_w_curr.z();
			odomAftMapped.pose.pose.orientation.w = q_w_curr.w();
			odomAftMapped.pose.pose.position.x = t_w_curr.x();
			odomAftMapped.pose.pose.position.y = t_w_curr.y();
			odomAftMapped.pose.pose.position.z = t_w_curr.z();
			pubOdomAftMapped.publish(odomAftMapped);

将经过后端优化后的里程计数据发布出去。

			geometry_msgs::PoseStamped laserAfterMappedPose;
			laserAfterMappedPose.header = odomAftMapped.header;
			laserAfterMappedPose.pose = odomAftMapped.pose.pose;
			laserAfterMappedPath.header.stamp = odomAftMapped.header.stamp;
			laserAfterMappedPath.header.frame_id = "/camera_init";
			laserAfterMappedPath.poses.push_back(laserAfterMappedPose);
			pubLaserAfterMappedPath.publish(laserAfterMappedPath);

将经过后端优化的path发布。

			static tf::TransformBroadcaster br;
			tf::Transform transform;
			tf::Quaternion q;
			transform.setOrigin(tf::Vector3(t_w_curr(0),
											t_w_curr(1),
											t_w_curr(2)));
			q.setW(q_w_curr.w());
			q.setX(q_w_curr.x());
			q.setY(q_w_curr.y());
			q.setZ(q_w_curr.z());
			transform.setRotation(q);
			br.sendTransform(tf::StampedTransform(transform, odomAftMapped.header.stamp, "/camera_init", "/aft_mapped"));

			frameCount++;
		}

最后发布tf。

四、总结

将a-loam源码粗略过了一边,但还是有很多不是很理解,包括栅格地图的更新机制。未来还是要再多琢磨琢磨。

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

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

相关文章

前端食堂技术周刊第 67 期:2022 State of JS、ESLint 2022、pnpm 2022、大淘宝 Web 端技术概览

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;东北大饭包 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 本期摘要 2022 State of JS 结果出炉ESLint 2022 年终总结pnpm 2022 年终总结大淘…

Win10注册表损坏进不了系统怎么U盘重装系统?

Win10注册表损坏进不了系统怎么U盘重装系统&#xff1f;有用户电脑提示注册表损坏&#xff0c;自己进行了系统的重新启动之后&#xff0c;遇到了电脑无法正常启动进行系统桌面的情况。那么遇到这样的情况&#xff0c;我们怎么去进行U盘重装系统呢&#xff1f;一起来看看具体的操…

ZIP压缩文件如何加密?忘记密码怎么办?

ZIP是常用的压缩文件格式之一&#xff0c;除了方便传输、节省空间&#xff0c;还能通过设置密码来保护文件。 设置ZIP文件的密码保护需要用到压缩软件&#xff0c;下面先说说最常用的WinRAR是如何设置ZIP文件的密码。 方法一&#xff1a; 我们可以压缩文件的同时设置密码&am…

core-js常见于qiankun中的多份polyfill冲突问题

问题 开门见山&#xff0c;你可能会在 qiankun 等微前端体系中&#xff0c;有多个子应用时&#xff0c;发生这样的加载崩溃问题&#xff1a; Cannot redefine property: DEG_PER_RAD Cannot redefine property: RAD_PER_DEG 实际上 DEG_PER_RAD 和 RAD_PER_DEG 都是 Math 上的…

Android面经_111道安卓基础问题(四大组件Activity、Service篇)

111道Android面试基础题目&#xff0c;巩固基础作用。 Android基础问题——四大组件之Activity、Service1、Activity1.1、请介绍activity的生命周期1.1.1、生命周期回调之onCreate()1.1.2、生命周期回调之onStart()1.1.3、生命周期回调之onResume()1.1.4、生命周期回调之onPaus…

【java】java多线程及线程池面试题

目录前言线程是什么&#xff1f;多线程是什么&#xff1f;多线程的作用和好处以及缺点守护线程和用户线程并发和并行的区别一.线程的状态和常用方法1.线程各种状态转化图2.线程相关常用方法有① wait()② sleep(long timeout)③ join()④ yield()⑤ notify()和notifyAll()3.wai…

全流量分析为企业提升SAP用户体验

前言 某汽车总部已部署NetInside流量分析系统&#xff0c;使用流量分析系统提供实时和历史原始流量&#xff0c;重点针对SAP系统性能进行分析&#xff0c;以供安全取证、应用事务分析、网络质量监测以及深层网络分析。 本次分析报告包含&#xff1a;SAP系统性能分析。 分析对…

【北京理工大学-Python 数据分析-3.2Pandas数据特征分析】

对一组数据的理解 数据摘要&#xff1a;通过以下方法&#xff1a;有损地提取数据特征的过程。 基本统计&#xff08;含排序&#xff09;分布/累计统计数据特征相关性周期性等数据分析 Pandas库的数据排序 .sort_index(axis0,asccendingTrue)。.sort_index()方法在指定轴上根…

Tomcat 三种简单网站部署方式

Tomcat 服务部署 1、隐式部署 为什么可以隐式部署&#xff0c;是因为 Tomcat 配置了默认主机 webapps&#xff0c;在 Engine 下你也可以配置其他主机&#xff08;要保证你配置的主机在 ect/host 下有对应关系&#xff09;&#xff0c;但是 appBase 要确保唯一。保证每台主机下…

Effective C++条款40:明智而审慎地使用多重继承(Use multiple inheritance judiciously)

Effective C条款40&#xff1a;明智而审慎地使用多重继承&#xff08;Use multiple inheritance judiciously&#xff09;条款40&#xff1a;明智而审慎地使用多重继承1、多重继承的两个阵营2、多重继承中&#xff0c;接口调用的歧义性3、菱形继承与虚(virtual)继承3.1 菱形继承…

注意力FM模型AFM

1. 概述 在CTR预估任务中&#xff0c;对模型特征的探索是一个重要的分支方向&#xff0c;尤其是特征的交叉&#xff0c;从早起的线性模型Logistic Regression开始&#xff0c;研究者在其中加入了人工的交叉特征&#xff0c;对最终的预估效果起到了正向的效果&#xff0c;但是人…

华为MPLS跨域C1方案实验配置

目录 配置接域内IGP路由协议与LDP协议 配置IPv4的BGP邻居 配置PE之间的Vpnv4邻居 配置PE与CE设备对接命令 ASBR上手工为PE地址分配标签 MPLS隧道——跨域解决方案C1、C2讲解_静下心来敲木鱼的博客-CSDN博客_route-policy rr permit node 10 if-match mpls-labelhttps://bl…

IB地理课选课指南,SL还是HL适合呢?

IB地理科的标准级别&#xff08;Standard Level&#xff0c; SL&#xff09;课程跟高级级别&#xff08;Higher Level&#xff0c;HL&#xff09;课程的最大不同处在于&#xff0c;考卷的数量跟题目的数量是不同的。可是&#xff0c;两者之间的教学内容和科目指引&#xff08;S…

二十八、Kubernetes中job详解

1、概述 在kubernetes中&#xff0c;有很多类型的pod控制器&#xff0c;每种都有自己的适合的场景&#xff0c;常见的有下面这些&#xff1a; ReplicationController&#xff1a;比较原始的pod控制器&#xff0c;已经被废弃&#xff0c;由ReplicaSet替代 ReplicaSet&#xff…

CentOS 7 升级安装 Python 3.9 版本

由于 yum install python3 默认安装的 Python 版本较低&#xff0c;现如今有更高版本的 Python 需求&#xff0c;就想用编译安装的方法安装一个较高版本的 Python&#xff0c;顺道记录一下安装过程。 注意&#xff1a;不要卸载自带的 python2&#xff0c;由于 yum 指令需要 pyt…

idea中代码git的版本穿梭Git Rest三种模式详解(soft,mixed,hard)

使用Git进行版本控制开发时难免会遇到回顾的情况&#xff0c;这里来解释下该如何正确的回滚 文章目录1.本地仓库回滚2.远程仓库回滚2.1错误案例2.2正确操作3.代码提交到错误的分支解决4.Git Rest三种模式详解&#xff08;soft,mixed,hard&#xff09;4.1操作演示reset --hard&a…

【论文简述】FlowFormer:A Transformer Architecture for Optical Flow(ECCV 2022)

一、论文简述 1. 第一作者&#xff1a;Zhaoyang Huang、Xiaoyu Shi 2. 发表年份&#xff1a;2022 3. 发表期刊&#xff1a;ECCV 4. 关键词&#xff1a;光流、代价体、Transformer、GRU 5. 探索动机&#xff1a;现有的方法对代价体的信息利用有限。 6. 工作目标&#xff1…

RabbitMQ 部署及配置详解(集群部署)

RabbitMQ 集群是一个或 多个节点&#xff0c;每个节点共享用户、虚拟主机、 队列、交换、绑定、运行时参数和其他分布式状态。一、RabbitMQ 集群可以通过多种方式形成&#xff1a;通过在配置文件中列出群集节点以声明方式以声明方式使用基于 DNS 的发现以声明方式使用 AWS &…

Java中的LinkedList

文章目录前言一、LinkedList的使用1.1 什么是LinkedList1.2 LinkedList的使用1.2.1 LinkedList的构造1.2.2 LinkedList的其他常用方法介绍1.2.3 LinkedList的遍历二、LinkedList的模拟实现三、ArrayList和LinkedList的区别总结前言 上一节中我们讲解了Java中的链表&#xff0c…

vue3.0中echarts实现中图地图的省份切换,并解决多次切换后地图卡死的情况

一、echarts安装及地图的准备 1、安装echarts npm install echarts2、下载china.js等json文件到项目中的文件夹 map的下载地址&#xff1a; 等审核 二、代码说明 <template><div class"center-body"><div class"map" id"map"…