ICP算法加速优化--多线程和GPU

本文介绍了作者对ICP算法的C++实现进行了重构,移除第三方库并优化了多线程和GPU加速。实验结果显示,多核CPU和GPU在处理大规模点云时有显著优势,但在少量点云时GPU性能受限于数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LZ之前的文章ICP算法实现(C++) 用C++实现了基础的ICP算法,由于该算法是一种迭代的优化算法,里面含有大量循环操作以及矩阵运算,可以通过使用多线程或者GPU硬件来进行加速,具体分别可以通过OpenMP和CUDA编程实现。这里给出的代码是根据github地址:https://github.com/alex-van-vliet/icp的代码改写的。原作者的代码质量还是不错的,有许多值得借签和学习的地方。但是考虑到使用的第三方库太多不便于配置和使用,LZ把这份代码重构了一下。原作者在代码里造了很多轮子,比如自己实现了Point3D、matrix以及vp-tree(也是一种搜索树,比PCL库中的kd-tree出现时间略晚)的数据结构,但是SVD分解还是调用了Eigen库(可能他也觉得底层实现太麻烦了吧~),LZ把这里面的矩阵结构统一用Eigen库实现了。另外去掉了一些不方便编译的第三方库,并简化了CmakeLists的内容以及程序的结构。如果只编译libcpu库只需依赖Eigen,编译libgpu库的话需要CUDA。

该工程的运行效果如下所示:
lib_icp.exe line1.pcd line2.pcd --cpu 100 1e-6 32 (CPU单核,0ms)
lib_icp.exe line1.pcd line2.pcd --cpu 100 1e-6 32 (CPU多核,2ms)
lib_icp.exe line1.pcd line2.pcd --gpu 100 1e-6 1024 (GPU,81ms)
lib_icp.exe bunny1.pcd bunny2.pcd --cpu 100 1e-6 32 (CPU单核,1407ms)
lib_icp.exe bunny1.pcd bunny2.pcd --cpu 100 1e-6 32 (CPU多核,246ms)
lib_icp.exe bunny1.pcd bunny2.pcd --gpu 100 1e-6 1024 (GPU,156ms)
lib_icp.exe horse1.pcd horse2.pcd --cpu 100 1e-6 32 (CPU单核,12585ms)
lib_icp.exe horse1.pcd horse2.pcd --cpu 100 1e-6 32 (CPU多核,1603ms)
lib_icp.exe horse1.pcd horse2.pcd --gpu 100 1e-6 1024 (GPU,363ms)
Release模式下编译,测试平台为Windows10系统,内存32G,CPU是i7-12700(20线程),GPU是NVIDIA GeForce RTX 3070 Laptop GPU(显存8G,每个block最大线程数为1024),计算的运行时间为三次求平均值。修改后的代码在linux系统也能编译,本人在WSL(Ubuntu20.04)的docker中测试过和Windows本地差别不大,但是调用CUDA版本代码计算结果有误。line1.pcd和line2.pcd点数为10,bunny1.pcd和bunny2.pcd点数为35947,horse1.pcd和horse2.pcd点数为193940;另外LZ调用PCL库计算这三组点云ICP配准耗时分别为0ms,758ms,30550ms。可以看出多核和GPU的加速作用随着点云点数增加优势还是非常明显的,但是点数非常少的话,GPU由于需要和CPU进行数据传输,此时运算速度会不太理想。

自己写了测试main函数,可以直接读取.pcd格式的点云文件(读取二进制储存的pcd文件更为高效,原作者代码是读.txt文件),并添加了时间计算部分和可视化部分,需要另外配置PCL库。

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/common/transforms.h>

#ifdef _WIN32
	#include <ctime>
	#include <pcl/visualization/pcl_visualizer.h>
#endif

#ifdef __linux__
	#include <sys/time.h>
#endif

#include "libcpu/icp.h"
#include "libgpu/icp.h"


int main(int argc, char* argv[])
{
	std::cout << "\033[31mUsage:	.exe <source .pcd> <target .pcd> --cpu|gpu <iterations> <error> <capacity>\n"
		<< "for example:	./lib_icp.exe bunny1.pcd bunny2.pcd  --gpu 100 1e-6 1024\033[0m" << std::endl;

	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_src(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::io::loadPCDFile(argv[1], *cloud_src);

	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_tgt(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::io::loadPCDFile(argv[2], *cloud_tgt);

	bool use_gpu = (std::string(argv[3]) == "--gpu");
	if (use_gpu)	std::cout << "use gpu" << std::endl;
	else	std::cout << "use cpu" << std::endl;

	size_t iterations = std::atoi(argv[4]);
	std::cout << "max iterations:" << iterations << std::endl;

	float error = std::atof(argv[5]);
	std::cout << "error:" << error << std::endl;

	int capacity = std::atoi(argv[6]);
	std::cout << "capacity:" << capacity << std::endl;

	std::vector<Eigen::Vector3f> p(cloud_src->size());
	std::vector<Eigen::Vector3f> q(cloud_tgt->size());
	for (size_t i = 0; i < cloud_src->size(); i++)
	{
		p[i] = cloud_src->points[i].getVector3fMap();
	}
	for (size_t i = 0; i < cloud_tgt->size(); i++)
	{
		q[i] = cloud_tgt->points[i].getVector3fMap();
	}

#ifdef _WIN32
	clock_t start = clock();
#endif

#ifdef __linux__
	struct timeval start, end;
	gettimeofday(&start, 0);
#endif

	auto [transformation, new_p] = (use_gpu ? libgpu::icp(q, p, iterations, error, capacity): libcpu::icp(q, p, iterations, error, capacity));


#ifdef _WIN32
	clock_t end = clock();
	std::cout << "time cost:" << end - start << "ms" << std::endl;
#endif

#ifdef __linux__
	gettimeofday(&end, 0);
	std::cout << "time cost:" << (1000000.0 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec) / 1000.0 << "ms" << std::endl;
#endif
	
	std::cout << "transformation matrix: \n" << transformation << std::endl;

	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_icp(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::transformPointCloud(*cloud_src, *cloud_icp, transformation);
	pcl::io::savePCDFile("cloud_icp.pcd", *cloud_icp);

#ifdef _WIN32
	pcl::visualization::PCLVisualizer viewer("registration Viewer");
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> src_h(cloud_src, 0, 255, 0); 	//原始点云绿色
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> tgt_h(cloud_tgt, 255, 0, 0); 	//目标点云红色
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> final_h(cloud_icp, 0, 0, 255); 	//匹配好的点云蓝色

	viewer.setBackgroundColor(0, 0, 0);
	viewer.addPointCloud(cloud_src, src_h, "source cloud");
	viewer.addPointCloud(cloud_tgt, tgt_h, "target cloud");
	viewer.addPointCloud(cloud_icp, final_h, "result cloud");
	while (!viewer.wasStopped())
	{
		viewer.spinOnce(100);
	}
#endif

	return 0;
}

工程结构如下:
在这里插入图片描述

运行结果截图:
在这里插入图片描述在这里插入图片描述
附:调用PCL库ICP配准API的测试代码:

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>


int main(int argc, char* argv[])
{
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_src(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_tgt(new pcl::PointCloud<pcl::PointXYZ>);

	pcl::io::loadPCDFile(argv[1], *cloud_src);
	pcl::io::loadPCDFile(argv[2], *cloud_tgt);

	clock_t start = clock();

	pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_icp_registration(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::search::KdTree<pcl::PointXYZ>::Ptr tree1(new pcl::search::KdTree<pcl::PointXYZ>);
	pcl::search::KdTree<pcl::PointXYZ>::Ptr tree2(new pcl::search::KdTree<pcl::PointXYZ>);
	tree1->setInputCloud(cloud_src);
	tree2->setInputCloud(cloud_tgt);
	icp.setSearchMethodSource(tree1);
	icp.setSearchMethodTarget(tree2);
	icp.setInputSource(cloud_src);
	icp.setInputTarget(cloud_tgt);
	icp.setEuclideanFitnessEpsilon(1e-6);
	icp.setMaximumIterations(100);
	Eigen::Matrix4f transformation = Eigen::MatrixXf::Identity(4, 4);
	icp.align(*cloud_icp_registration, transformation);

	clock_t end = clock();
	std::cout << end - start << "ms" << std::endl;

	Eigen::Matrix4f transformation_icp = icp.getFinalTransformation();
	std::cout << transformation_icp << std::endl;
	std::cout << "ICP has converged:" << icp.hasConverged() << " score: " << icp.getFitnessScore() << std::endl;

	pcl::transformPointCloud(*cloud_src, *cloud_icp_registration, transformation_icp);
	pcl::io::savePCDFileBinary("icp.pcd", *cloud_icp_registration);

	return 0;
}

代码和数据集下载地址如下:
工程下载链接

### 关于 ICP 算法的改进方法或优化方案 ICP(Iterative Closest Point)算法是一种广泛应用于点云配准的经典算法。然而,由于其计算复杂度较高以及对初始姿态敏感等问题,许多研究者提出了多种改进方法来提升其性能。以下是几种常见的优化技术: #### 1. **加速最近邻搜索** 传统的 ICP 算法通过暴力搜索找到目标点云中最接近源点云的对应点,这种方法效率较低。为了提高速度,可以采用空间划分数据结构,例如 KD-Tree 或 Octree 来快速查找最近邻居[^5]。 ```python from scipy.spatial import cKDTree def find_nearest_neighbors(source_points, target_points): tree = cKDTree(target_points) distances, indices = tree.query(source_points) return distances, indices ``` #### 2. **减少迭代次数** 可以通过设置合理的收敛条件或引入全局优化策略来减少迭代次数。例如,使用随机采样一致性 (RANSAC) 方法筛选出更可靠的匹配点对,从而加快收敛过程[^6]。 #### 3. **增加鲁棒性处理异常值** 原始 ICP 对噪声离群点非常敏感。一种常见做法是在每次更新变换矩阵之前剔除明显偏离正常范围的数据点;另一种方式则是利用 M-Estimators 替代最小二乘估计器以降低极端误差的影响[^7]。 #### 4. **多分辨率框架** 构建多层次表示形式——先从粗略尺度开始逐步细化至精细级别完成最终调整。这样不仅可以显著缩短总耗时还能获得更加精确的结果[^8]。 #### 5. **并行化实现** 随着硬件设备的发展尤其是GPU的强大算力支持下,并行版本成为可能并且展现出巨大潜力。比如CUDA编程模型允许我们把大量简单重复的任务分配给不同的线程同时执行进而极大地提高了程序运行速率[^9]。 ```cpp __global__ void compute_distances(float* d_source, float* d_target, int n, float* d_result){ unsigned idx = threadIdx.x + blockIdx.x * blockDim.x; if(idx < n){ // Compute distance here... } } ``` ---
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给算法爸爸上香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值