loam的学习记录1(scanRegistration节点)

LOAM激光雷达里程计详解
本文深入解析LOAM激光雷达里程计的工作原理,重点介绍其核心算法:点云预处理、曲率计算、瑕点排除及特征点分类。通过详细步骤解释如何从原始点云数据中提取有用信息,实现环境建图。

main函数
main函数中就是订阅了2个话题和发布了6个话题。
接下来我主要讲解两个回调函数laserCloudHandler和imuHandler。

回调函数laserCloudHandler(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg)

  1. 对接收到的点云进行预处理;
  2. 计算曲率;
  3. 排除瑕点。
  4. 对角点和平面点进行分类

1:首先说一下对接收到的点云进行预处理
该函数接受的参数是sensor_msgs::PointCloud2类型的智能指针。

pcl::PointCloud<pcl::PointXYZ> laserCloudIn;
pcl::fromROSMsg(*laserCloudMsg, laserCloudIn);

将sensor_msg类型转换成pcl的点云形式。

//lidar scan开始点的旋转角,atan2范围[-pi,+pi],计算旋转角时取负号是因为velodyne是顺时针旋转
float startOri = -atan2(laserCloudIn.points[0].y, laserCloudIn.points[0].x);
//lidar scan结束点的旋转角,加2*pi使点云旋转周期为2*pi
float endOri = -atan2(laserCloudIn.points[cloudSize - 1].y,
                 		laserCloudIn.points[cloudSize - 1].x) + 2 * M_PI;

计算点云的起始和终止角度。

std::vector<pcl::PointCloud<PointType> > laserCloudScans(N_SCANS);

laserCloudScans保存具有不同特征点的点云。

for (int i = 0; i < cloudSize; i++)

遍历所有的点云,计算每个点的角度。

在循环中:

//计算点的仰角(根据lidar文档垂直角计算公式),根据仰角排列激光线号,velodyne每两个scan之间间隔2度
float angle = atan(point.y / sqrt(point.x * point.x + point.z * point.z)) * 180 / M_PI;
int scanID;
//仰角四舍五入(加减0.5截断效果等于四舍五入)
int roundedAngle = int(angle + (angle<0.0?-0.5:+0.5)); 
if (roundedAngle > 0){
  	scanID = roundedAngle;
}
else {
  	scanID = roundedAngle + (N_SCANS - 1);
}

计算laserCloudScans所需要的下标scanID。

//-0.5 < relTime < 1.5(点旋转的角度与整个周期旋转角度的比率, 即点云中点的相对时间)
float relTime = (ori - startOri) / (endOri - startOri);
//点强度=线号+点相对时间(即一个整数+一个小数,整数部分是线号,小数部分是该点的相对时间),匀速扫描:根据当前扫描的角度和扫描周期计算相对扫描起始位置的时间
point.intensity = scanID + scanPeriod * relTime;
if (imuPointerLast >= 0)

如果接收到imu数据,使用imu矫正点云畸变。

laserCloudScans[scanID].push_back(point);//将每个补偿矫正的点放入对应线号的容器
pcl::PointCloud<PointType>::Ptr laserCloud(new pcl::PointCloud<PointType>());
for (int i = 0; i < N_SCANS; i++) {//将所有的点按照线号从小到大放入一个容器
	*laserCloud += laserCloudScans[i];
}

将所有的线扫的点云放入大的点云中。

2:计算曲率

for (int i = 5; i < cloudSize - 5; i++) {//使用每个点的前后五个点计算曲率,因此前五个与最后五个点跳过
    float diffX = laserCloud->points[i - 5].x + laserCloud->points[i - 4].x 
                + laserCloud->points[i - 3].x + laserCloud->points[i - 2].x 
                + laserCloud->points[i - 1].x - 10 * laserCloud->points[i].x 
                + laserCloud->points[i + 1].x + laserCloud->points[i + 2].x
                + laserCloud->points[i + 3].x + laserCloud->points[i + 4].x
                + laserCloud->points[i + 5].x;
    float diffY = laserCloud->points[i - 5].y + laserCloud->points[i - 4].y 
                + laserCloud->points[i - 3].y + laserCloud->points[i - 2].y 
                + laserCloud->points[i - 1].y - 10 * laserCloud->points[i].y 
                + laserCloud->points[i + 1].y + laserCloud->points[i + 2].y
                + laserCloud->points[i + 3].y + laserCloud->points[i + 4].y
                + laserCloud->points[i + 5].y;
    float diffZ = laserCloud->points[i - 5].z + laserCloud->points[i - 4].z 
                + laserCloud->points[i - 3].z + laserCloud->points[i - 2].z 
                + laserCloud->points[i - 1].z - 10 * laserCloud->points[i].z 
                + laserCloud->points[i + 1].z + laserCloud->points[i + 2].z
                + laserCloud->points[i + 3].z + laserCloud->points[i + 4].z
                + laserCloud->points[i + 5].z;
    //曲率计算
    cloudCurvature[i] = diffX * diffX + diffY * diffY + diffZ * diffZ;
	...
}

曲率用该点的前后各5个点来近似表达

3:排除瑕点

for (int i = 5; i < cloudSize - 6; i++)

该循环中排除瑕点,循环中的代码如下:

float diffX = laserCloud->points[i + 1].x - laserCloud->points[i].x;
float diffY = laserCloud->points[i + 1].y - laserCloud->points[i].y;
float diffZ = laserCloud->points[i + 1].z - laserCloud->points[i].z;
//计算有效曲率点与后一个点之间的距离平方和
float diff = diffX * diffX + diffY * diffY + diffZ * diffZ;

if (diff > 0.1) {//前提:两个点之间距离要大于0.1
	//点的深度
	float depth1 = sqrt(laserCloud->points[i].x * laserCloud->points[i].x + 
	laserCloud->points[i].y * laserCloud->points[i].y +
	laserCloud->points[i].z * laserCloud->points[i].z);
	
	//后一个点的深度
	float depth2 = sqrt(laserCloud->points[i + 1].x * laserCloud->points[i + 1].x + 
	laserCloud->points[i + 1].y * laserCloud->points[i + 1].y +
	laserCloud->points[i + 1].z * laserCloud->points[i + 1].z);
	
	if (depth1 > depth2) {
	  diffX = laserCloud->points[i + 1].x - laserCloud->points[i].x * depth2 / depth1;
	  diffY = laserCloud->points[i + 1].y - laserCloud->points[i].y * depth2 / depth1;
	  diffZ = laserCloud->points[i + 1].z - laserCloud->points[i].z * depth2 / depth1;
	
	  //边长比也即是弧度值,若小于0.1,说明夹角比较小,斜面比较陡峭,点深度变化比较剧烈,点处在近似与激光束平行的斜面上
	  if (sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) / depth2 < 0.1) {//排除容易被斜面挡住的点
	      //该点及前面五个点(大致都在斜面上)全部置为筛选过
	    cloudNeighborPicked[i - 5] = 1;
	    cloudNeighborPicked[i - 4] = 1;
	    cloudNeighborPicked[i - 3] = 1;
	    cloudNeighborPicked[i - 2] = 1;
	    cloudNeighborPicked[i - 1] = 1;
	    cloudNeighborPicked[i] = 1;
	  }
	} else {
	  diffX = laserCloud->points[i + 1].x * depth1 / depth2 - laserCloud->points[i].x;
	  diffY = laserCloud->points[i + 1].y * depth1 / depth2 - laserCloud->points[i].y;
	  diffZ = laserCloud->points[i + 1].z * depth1 / depth2 - laserCloud->points[i].z;
	
	  if (sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) / depth1 < 0.1) {
	    cloudNeighborPicked[i + 1] = 1;
	    cloudNeighborPicked[i + 2] = 1;
	    cloudNeighborPicked[i + 3] = 1;
	    cloudNeighborPicked[i + 4] = 1;
	    cloudNeighborPicked[i + 5] = 1;
	    cloudNeighborPicked[i + 6] = 1;
	  }
	}
}

这是对论文中瑕点b的判定标准。
为了表达的更加清楚,我将雷达扫描的第i个点记为A,第i+1个点记为B,diff>0.1 表明AB两点有一定的距离,depth1为A点的深度,depth2为B点的深度。
在depth1 > depth2的情况下,对laserCloud->points[i].x * depth2 / depth1的解读我拆成两部分:laserCloud->points[i].x/ depth1表示先将OA(X轴方向)除以depth1即把OA变换为OA方向上的单位向量,再乘以depth2表示长度变成与OB长度相等,重新变换长度后我将该点记为A’。则diffX = laserCloud->points[i + 1].x - laserCloud->points[i].x * depth2 / depth1表示A‘与B在X轴方向的距离。
sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ)表示A’B的距离(记为dAB),dAB/depth2 < 0.1 表示角A’OB很小(小于一定的阈值),将该点及前面五个点剔除。
depth1 < depth2的情况与之类似,我这里就不赘述了。
作图直观地感受下:
在这里插入图片描述

float diffX2 = laserCloud->points[i].x - laserCloud->points[i - 1].x;
float diffY2 = laserCloud->points[i].y - laserCloud->points[i - 1].y;
float diffZ2 = laserCloud->points[i].z - laserCloud->points[i - 1].z;
//与前一个点的距离平方和
float diff2 = diffX2 * diffX2 + diffY2 * diffY2 + diffZ2 * diffZ2;

//点深度的平方和
float dis = laserCloud->points[i].x * laserCloud->points[i].x
          + laserCloud->points[i].y * laserCloud->points[i].y
          + laserCloud->points[i].z * laserCloud->points[i].z;

//与前后点的平方和都大于深度平方和的万分之二,这些点视为离群点,包括陡斜面上的点,强烈凸凹点和空旷区域中的某些点,置为筛选过,弃用
if (diff > 0.0002 * dis && diff2 > 0.0002 * dis) {
	cloudNeighborPicked[i] = 1;
}

这是对论文中瑕点b的判定标准。我这里就直接上图说明了:
在这里插入图片描述

4:对角点和平面点进行分类

for (int i = 0; i < N_SCANS; i++) {
    pcl::PointCloud<PointType>::Ptr surfPointsLessFlatScan(new pcl::PointCloud<PointType>);
    //将每个scan的曲率点分成6等份处理,确保周围都有点被选作特征点
    for (int j = 0; j < 6; j++) {
    	...
	}
}
//按曲率从小到大冒泡排序
for (int k = sp + 1; k <= ep; k++) {
	for (int l = k; l >= sp + 1; l--) {
		//如果后面曲率点大于前面,则交换
		if (cloudCurvature[cloudSortInd[l]] < cloudCurvature[cloudSortInd[l - 1]]) {
			int temp = cloudSortInd[l - 1];
			cloudSortInd[l - 1] = cloudSortInd[l];
			cloudSortInd[l] = temp;
		}
	}
}
for (int k = ep; k >= sp; k--){
	int ind = cloudSortInd[k];
	...
}

挑选每个分段的曲率很大和比较大的点(找角点)

//如果曲率大的点,曲率的确比较大,并且未被筛选过滤掉
if (cloudNeighborPicked[ind] == 0 &&
     cloudCurvature[ind] > 0.1)
if (largestPickedNum <= 2) {//挑选曲率最大的前2个点放入sharp点集合
	cloudLabel[ind] = 2;//2代表点曲率很大
	cornerPointsSharp.push_back(laserCloud->points[ind]);
	cornerPointsLessSharp.push_back(laserCloud->points[ind]);
} else if (largestPickedNum <= 20) {//挑选曲率最大的前20个点放入less sharp点集合
	cloudLabel[ind] = 1;//1代表点曲率比较尖锐
	cornerPointsLessSharp.push_back(laserCloud->points[ind]);
} else {
	break;
}
cloudNeighborPicked[ind] = 1;//筛选标志置位
//将曲率比较大的点的前后各5个连续距离比较近的点筛选出去,防止特征点聚集,使得特征点在每个方向上尽量分布均匀
for (int l = 1; l <= 5; l++) {
	float diffX = laserCloud->points[ind + l].x 
	           - laserCloud->points[ind + l - 1].x;
	float diffY = laserCloud->points[ind + l].y 
	           - laserCloud->points[ind + l - 1].y;
	float diffZ = laserCloud->points[ind + l].z 
	           - laserCloud->points[ind + l - 1].z;
	if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05) {
	 	break;
	}
	
	cloudNeighborPicked[ind + l] = 1;
}
for (int l = -1; l >= -5; l--) {
	float diffX = laserCloud->points[ind + l].x 
	            - laserCloud->points[ind + l + 1].x;
	float diffY = laserCloud->points[ind + l].y 
	            - laserCloud->points[ind + l + 1].y;
	float diffZ = laserCloud->points[ind + l].z 
	            - laserCloud->points[ind + l + 1].z;
	if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05) {
  		break;
	}
	
	cloudNeighborPicked[ind + l] = 1;
}

找平面点同理。

sensor_msgs::PointCloud2 laserCloudOutMsg;
pcl::toROSMsg(*laserCloud, laserCloudOutMsg);
laserCloudOutMsg.header.stamp = laserCloudMsg->header.stamp;
laserCloudOutMsg.header.frame_id = "/camera";
pubLaserCloud.publish(laserCloudOutMsg);

将pcl点云信息转换成ros的sensor_msg形式,pubLaserCloud发布点云的信息。其余的点云信息、imu信息的发布同理。

回调函数imuHandler

float accX = imuIn->linear_acceleration.y - sin(roll) * cos(pitch) * 9.81;
float accY = imuIn->linear_acceleration.z - cos(roll) * cos(pitch) * 9.81;
float accZ = imuIn->linear_acceleration.x + sin(pitch) * 9.81;

将IMU坐标系下的加速度转换到世界坐标系下,同时减去重力的影响。

imuPointerLast = (imuPointerLast + 1) % imuQueLength;

循环移位效果,形成环形数组

imuTime[imuPointerLast] = imuIn->header.stamp.toSec();
imuRoll[imuPointerLast] = roll;
imuPitch[imuPointerLast] = pitch;
imuYaw[imuPointerLast] = yaw;
imuAccX[imuPointerLast] = accX;
imuAccY[imuPointerLast] = accY;
imuAccZ[imuPointerLast] = accZ;

对IMU信息(时间戳、欧拉角、加速度)赋值。

AccumulateIMUShift();

AccumulateIMUShift函数对imu的速度位移积分。

参考:
LOAM:3D激光里程计及环境建图的方法和实现(一)
LOAM_velodyne学习(一)

<think>好的,用户遇到了编译错误,找不到opencv/cv.h文件,需要正确包含OpenCV的头文件。首先,我要确认这个问题的背景。用户可能是在编译A-LOAM项目时遇到了问题,而错误提示是找不到cv.h。根据引用中的信息,用户可能正在使用较新版本的OpenCV(比如4.2.0),而A-LOAM原本可能是基于旧版本OpenCV编写的。 接下来,我需要回忆OpenCV版本的变化。在OpenCV 4.0之后,很多C接口的头文件被移除了,特别是像cv.h这样的头文件,现在应该使用C++的命名空间和头文件结构。因此,用户可能需要将旧的头文件引用改为新的格式。 查看引用[4]和[5],用户之前处理过类似的问题,比如将CV_LOAD_IMAGE_GRAYSCALE改为cv::IMREAD_GRAYSCALE,这说明项目中的代码确实需要适配OpenCV4。同样地,头文件的包含路径可能也需要调整。例如,旧代码中的#include <opencv/cv.h>应该替换为更现代的包含方式,比如#include <opencv2/opencv.hpp>,或者具体的模块头文件,如opencv2/imgproc.hpp等。 另外,用户可能没有正确配置CMakeLists.txt中的OpenCV路径。根据引用[2]和[3],用户需要确保find_package(OpenCV)正确指向了OpenCV4的安装位置,可能需要设置CMAKE_PREFIX_PATH来帮助CMake找到正确的版本。例如,引用[1]中提到设置路径为/usr/include/opencv4,这可能是必要的。 总结解决方案步骤: 1. 替换旧的头文件包含方式,使用OpenCV2的命名空间。 2. 更新CMakeLists.txt中的OpenCV版本查找,确保路径正确。 3. 检查其他可能存在的旧OpenCV宏或函数,进行相应的替换。 最后,需要验证这些步骤是否解决了编译错误,并考虑用户是否还有其他相关的问题,比如链接库是否正确,或者是否需要调整C++标准版本,如引用[1]和[3]提到的C++14。</think>### 编译错误 `opencv/cv.h` 的解决方法 #### 问题分析 `opencv/cv.h` 是 OpenCV 早期版本(如 OpenCV 2.x 或 3.x)的旧头文件路径。从 OpenCV 4.0 开始,部分 C 语言接口的模块被重构或移除,导致旧代码直接包含 `opencv/cv.h` 时会报错[^2][^4]。 #### 解决步骤 1. **更新头文件包含路径** OpenCV 4.x 的 C++ 接口头文件位于 `opencv2` 命名空间下。将代码中: ```cpp #include <opencv/cv.h> ``` 替换为: ```cpp #include <opencv2/opencv.hpp> // 包含所有核心模块 // 或按需包含具体模块,例如: #include <opencv2/imgproc/imgproc.hpp> // 图像处理模块 ``` 2. **修改 CMakeLists.txt 配置** 确保 CMake 正确找到 OpenCV 4.x 的安装路径。在 `CMakeLists.txt` 中添加: ```cmake set(CMAKE_PREFIX_PATH "/usr/include/opencv4") # 指定 OpenCV4 头文件路径 find_package(OpenCV 4.0 REQUIRED) # 强制要求 OpenCV 4.0+ 版本 include_directories(${OpenCV_INCLUDE_DIRS}) # 包含头文件路径 ``` 3. **适配旧版宏和函数** OpenCV 4.x 移除了部分旧宏(如 `CV_LOAD_IMAGE_GRAYSCALE`),需替换为新的枚举值[^4]: ```cpp // 旧代码 cv::Mat image = cv::imread("path.jpg", CV_LOAD_IMAGE_GRAYSCALE); // 新代码 cv::Mat image = cv::imread("path.jpg", cv::IMREAD_GRAYSCALE); ``` #### 验证示例 假设 `scanRegistration.cpp` 中有以下旧代码: ```cpp #include <opencv/cv.h> cv::Sobel(image, grad, CV_32F, 1, 0); ``` 修改后: ```cpp #include <opencv2/opencv.hpp> cv::Sobel(image, grad, cv::CV_32F, 1, 0); // 注意命名空间 `cv::` ``` #### 其他可能问题 - **PCL 版本兼容性**:若同时出现 PCL 相关错误,需确保 CMake 中 C++ 标准设置为 C++14(参考引用[1][^3]): ```cmake set(CMAKE_CXX_STANDARD 14) ``` ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值