PCL实现点云合并
一、理论部分
简单的点云合并步骤为:读点云 --> 点云合并 --> 写点云。
1. 读写点云
(1)读PCD文件
应用pcl::io::loadPCDFilepcl::PointT(pcd文件,点云对象)函数实现PCD文件的读取。
#include <iostream>
#include <pcl/io/pcd_io.h> //pcd流头文件
#include <pcl/point_types.h> //点类型即现成的pointT
int main (int argc, char** argv)
{
//创建点云指针
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
//直接读取pcd文件到cloud中,并判断是否读取正确
if (pcl::io::loadPCDFile<pcl::PointXYZ> ("test_pcd.pcd", *cloud) == -1) //* load the file
{
PCL_ERROR ("Couldn't read file test_pcd.pcd \n");
return (-1);
}
//控制台输出点云宽高(无序点云为总数)
std::cout << "Loaded "
<< cloud->width * cloud->height
<< " data points from test_pcd.pcd with the following fields: "
<< std::endl;
//遍历点云输出坐标
for (const auto& point: *cloud)
std::cout << " " << point.x
<< " " << point.y
<< " " << point.z << std::endl;
return (0);
}
(2)写PCD文件
应用pcl::io::savePCDFileASCII (pcd文件, 点云对象)函数将点云写入PCD文件。
注意:写入的代码点云对象不是指针所以与读取代码略有不同,具体栈和堆创建区别自行查找C++基础。
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
int main (int argc, char** argv)
{
//创建栈上的点云对象
pcl::PointCloud<pcl::PointXYZ> cloud;
//为点云基础属性赋值;
cloud.width = 5;//总数
cloud.height = 1;
cloud.is_dense = false;//是否有限,即是否包含NAN或者INF
cloud.points.resize (cloud.width * cloud.height);//更新点云内部存储结构大小
//随机为每个点赋值
for (auto& point: cloud)
{
point.x = 1024 * rand () / (RAND_MAX + 1.0f);
point.y = 1024 * rand () / (RAND_MAX + 1.0f);
point.z = 1024 * rand () / (RAND_MAX + 1.0f);
}
//将cloud存入pcd文件中
pcl::io::savePCDFileASCII ("test_pcd.pcd", cloud);
std::cerr << "Saved " << cloud.size () << " data points to test_pcd.pcd." << std::endl;
//控制台输出
for (const auto& point: cloud)
std::cerr << " " << point.x << " " << point.y << " " << point.z << std::endl;
return (0);
}
2. 点云合并
点云合并的方式有两种,分别为横向合并和纵向合并。
(1)官方文档
由于官方文档代码过长,大部分都是随机创建数据,只提取关键部分;
点云合并的官方文档
(提示:官方文档中合并的代码整个流程即对A和B两个点云的位置XYZ纵向合并 和对A点云的位置XYZ字段与B点云的法向量XYZ字段横向合并)
小结:
对于纵向合并,直接A+B即可
横向字段合并,采用pcl::concatenateFields (cloud_a, n_cloud_b, p_n_cloud_c);
(2)横向合并
横向合并(Horizontal Concatenation)则可能是将两个点云的属性合并,每个点的属性数量增加。例如,A点云有XYZ坐标,B点云有法向量Nx, Ny, Nz,横向合并后每个点将包含XYZ和Nx, Ny, Nz,前提是两者的点数相同,即一一对应。
适用场景:
①属性融合:将不同传感器数据整合到同一组点上(如坐标+颜色+法向量)。
②点级关联:需要为每个点附加额外属性(如将法向量与坐标绑定)。
③数据对齐:两个点云的点一一对应(如来自同一视角的不同属性提取结果)。
限制:
①点数一致:两个点云必须有相同的点数(N=M),否则无法逐点合并。
②属性互补:合并后的属性需有实际意义(如XYZ+法向量可用于表面重建)。
(3)纵向合并
纵向合并(Vertical Concatenation)通常指的是将两个点云的点数叠加,形成一个新的点云,点数等于两者之和,但属性维度不变。例如,A有100个点,每个点有XYZ坐标,B有50个点,每个点也有XYZ坐标,纵向合并后得到150个点,每个点仍保持XYZ坐标。
适用场景:
①场景拼接:合并两个不同位置或视角采集的点云(如两片不重叠的墙面点云),形成更完整的场景。
②数据增强:将多个独立采集的点云合并为一个更大的数据集。
③简单叠加:无需点间属性关联,只需将点数量累加。
限制:
①属性一致性:两个点云的属性必须完全一致(如都只有XYZ坐标)。
②无对应关系:合并后的点之间没有逻辑关联,仅简单叠加。
二、Demo
注意:横向合并需要确保两个点云的点数相同,否则会出错。代码中需要添加检查点数的步骤。而纵向合并则没有这个限制,但需要确保点的类型一致。
1. 总结步骤:
- 横向合并:创建两个点数相同的点云,合并属性到新的点云结构中。
- 纵向合并:合并两个点云的所有点到同一个点云中。
- 添加必要的检查和注释,确保代码健壮性。
- 提供保存和输出的示例,方便验证结果。
2. 纵向合并(Vertical Concatenation)
将两个点云的 点数量(行) 叠加,生成点数更多的点云。要求两个点云的属性一致(如均为XYZ坐标)。
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
int main() {
// 创建两个点云(假设均为XYZ坐标)
pcl::PointCloud<pcl::PointXYZ> cloud_A, cloud_B, cloud_merged;
// 填充点云A(示例数据)
for (int i = 0; i < 100; ++i) {
cloud_A.push_back(pcl::PointXYZ(i, i, i));
}
// 填充点云B(示例数据)
for (int i = 0; i < 50; ++i) {
cloud_B.push_back(pcl::PointXYZ(i * 2, i * 2, i * 2));
}
// 纵向合并:将B的点追加到A的末尾
cloud_merged = cloud_A;
cloud_merged += cloud_B; // 等同于 cloud_merged.insert(cloud_merged.end(), cloud_B.begin(), cloud_B.end());
// 输出合并后的点数
std::cout << "纵向合并后的点数: " << cloud_merged.size() << std::endl;
// 保存结果
pcl::io::savePCDFile("vertical_merged.pcd", cloud_merged);
return 0;
}
3. 横向合并(Horizontal Concatenation)
将两个点云的 属性(列) 叠加,生成属性更丰富的点云。要求两个点云点数相同(如将XYZ坐标与法向量合并)。
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
// 自定义点类型:包含XYZ坐标和法向量
struct MyPoint {
float x, y, z;
float normal_x, normal_y, normal_z;
};
POINT_CLOUD_REGISTER_POINT_STRUCT(
MyPoint,
(float, x, x)
(float, y, y)
(float, z, z)
(float, normal_x, normal_x)
(float, normal_y, normal_y)
(float, normal_z, normal_z)
);
int main() {
// 创建两个点云:A为XYZ坐标,B为法向量
pcl::PointCloud<pcl::PointXYZ> cloud_A;
pcl::PointCloud<pcl::Normal> cloud_B;
pcl::PointCloud<MyPoint> cloud_merged;
// 填充点云A(XYZ坐标)
for (int i = 0; i < 100; ++i) {
cloud_A.push_back(pcl::PointXYZ(i, i, i));
}
// 填充点云B(法向量)
for (int i = 0; i < 100; ++i) {
pcl::Normal normal;
normal.normal_x = 0.1f;
normal.normal_y = 0.2f;
normal.normal_z = 0.3f;
cloud_B.push_back(normal);
}
// 检查点数是否一致
if (cloud_A.size() != cloud_B.size()) {
std::cerr << "错误:点数不一致,无法横向合并!" << std::endl;
return -1;
}
// 横向合并:将XYZ坐标与法向量合并为自定义点类型
cloud_merged.resize(cloud_A.size());
for (size_t i = 0; i < cloud_A.size(); ++i) {
MyPoint point;
point.x = cloud_A[i].x;
point.y = cloud_A[i].y;
point.z = cloud_A[i].z;
point.normal_x = cloud_B[i].normal_x;
point.normal_y = cloud_B[i].normal_y;
point.normal_z = cloud_B[i].normal_z;
cloud_merged[i] = point;
}
// 输出合并后的属性维度
std::cout << "横向合并后的点数: " << cloud_merged.size() << std::endl;
// 保存结果
pcl::io::savePCDFile("horizontal_merged.pcd", cloud_merged);
return 0;
}
4.代码说明
(1)纵向合并:
使用 += 运算符或 insert 方法直接将两个点云的点叠加。
适用于简单拼接场景,如合并两个独立的扫描数据。
(2)横向合并:
自定义点类型:定义包含XYZ坐标和法向量的结构体 MyPoint。
数据对齐:确保两个点云的点数相同,否则合并失败。
逐点赋值:将XYZ坐标和法向量依次填充到新点云中。
适用于融合多属性数据,如坐标+法向量+颜色。
(3)输入数据:
示例中直接生成数据,实际应用可替换为从PCD文件加载:
pcl::io::loadPCDFile(“cloud_A.pcd”, cloud_A);
(4)输出验证:
使用CloudCompare打开生成的 vertical_merged.pcd 和 horizontal_merged.pcd 验证结果。
三、说明
文章理论部分,参考引用了一篇文章,文章链接如下,感兴趣的可以去看看,本文添加了合并的理论知识和两种合并方法的Demo。
链接:https://blog.youkuaiyun.com/qq_41795143/article/details/112245395