基于Win系统下PCL库入门到实践:自定义PCL数据类型(附代码链接)
PCL 默认内置了很多点类型,比如:
pcl::PointXYZ(只包含坐标 x,y,z)
pcl::PointXYZRGB(包含坐标 + 颜色)
pcl::PointNormal(包含坐标 + 法向量)
pcl::PointXYZI(包含坐标 + 强度)
这些已经覆盖了常见场景,但在实际项目中,很多数据并不能完全用现成类型表达。
医学三维数据 → 可能需要 组织类别标签。多光谱/高光谱点云 → 每个点可能带有几十个通道的信息。如果用标准的 PointXYZ,就没办法存这些额外字段。自定义点类型就能让数据结构和实际业务需求对应起来。
PCL 的大部分算法(滤波、分割、配准等)都是 模板函数,支持任何符合要求的点类型。注册了自定义类型,PCL 的 I/O 模块(PLY、PCD 等)也能自动识别和读写。自定义点类型是把“实际数据”和“PCL 的算法体系”结合起来的桥梁,是让 PCL 真正服务于任务的关键。
自定义的点云数据类型可以加上实际任务中所需要的元素。以具体代码为例,逐步分析如何自定义点云数据类型,并用其生成对应的点云文件。
为了程序整体的可读性,可以新建Mypoint.h文件,将定义数据类型的内容放在该文件里。
1.定义数据
struct EIGEN_ALIGN16 MyPoint
{
PCL_ADD_POINT4D
PCL_ADD_RGB
double time_stamp;
//加自己定义的属性
inline MyPoint()
{
data[0] = 0;data[1] = 0;
data[2] = 0; data[3] = 0;
rgba = 0;
time_stamp = 0;
}
inline MyPoint(float _x,float _y,float _z,uint32_t _rgba,double _time_stamp)
{
x = _x;
y = _y;
z = _z;
rgba = _rgba;
time_stamp = _time_stamp;
}
friend std::ostream &operator <<(std::ostream & os,MyPoint &p)
{
os << "(" << p.x << "," <<p.y << "," << p.z << "," << p.rgba << "," << p.time_stamp << ")";
return os;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
PCL_ADD_POINT4D是我们点云数据加上包括x,y,z除外一个 float data[4] 数组,用来和 Eigen::Vector4f 对接,也能一次存储 (x, y, z, 1.0f)。
PCL_ADD_RGB就是添加rgb元素。不添加这个,后续rgba就会报错。
double time_stamp;用于记录点生成的时间。也是我们自己加的一个属性。
inline MyPoint ()主要是赋值的作用,保证你创建 MyPoint p; 时,所有字段都有初始化值,而不是随机内存里的垃圾值。这样做的好处:在PointCloud<MyPoint> 扩容或 resize() 时,新分配的点都是干净的 0 值,不会导致渲染或保存文件时崩溃。
初始化值,同样也是要赋值的,确保提供一个方便的构造方式,一次性给 x, y, z, rgba, time_stamp 赋值。
输出运算符重载: friend std::ostream &operator <<(std::ostream & os,MyPoint &p),可以直接用 std::cout << point; 打印点的内容。
输出格式形如:(1.0, 2.0, 3.0, 16711680, 123456.78)
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
在 PCL 里,点类型通常会用到 Eigen::Vector4f、Eigen::Matrix4f 之类的数据结构。这些结构要求 16 字节对齐(alignment),这样才能让 SSE/AVX 指令(SIMD 向量化)快速访问数据。
到这一步基本上自定义的点类型就完成了。如果需要其他属性,可以在上述代码//位置接着加上即可,不过要记得在初始化以及赋值里面完成对应的操作即可。
2.注册到点云里
紧接着一步就是将自定义的点云登记在点云库中:POINT_CLOUD_REGISTER_POINT_STRUCT
翻译过来也就是点云库点云结构体登记。第一个参数给的是结构体的变量名称,往下都是我们自定义属性的数据类型声明,各个属性要定义什么数据类型还是较为重要,这涉及到数据的精确度,往往,在实际任务中决定对应属性的精度。
POINT_CLOUD_REGISTER_POINT_STRUCT(MyPoint,
(float,x,x)
(float,y,y)
(float,z,z)
(uint32_t,rgb,rgb)
(double,time_stamp,time_stamp));
生成的.h文件示意图:

完整代码:
#ifndef MYPOINT_H
#define MYPOINT_H
#include <pcl/point_types.h>
struct EIGEN_ALIGN16 MyPoint
{
PCL_ADD_POINT4D
PCL_ADD_RGB
double time_stamp;
inline MyPoint()
{
data[0] = 0;data[1] = 0;
data[2] = 0;data[3] = 0;
rgba = 0;
time_stamp = 0;
}
inline MyPoint(float _x,float _y,float _z,uint32_t _rgba,double _time_stamp)
{
x = _x;
y = _y;
z = _z;
rgba = _rgba;
time_stamp = _time_stamp;
}
friend std::ostream &operator <<(std::ostream & os,MyPoint &p)
{
os << "(" << p.x << "," <<p.y << "," << p.z << "," << p.rgba << "," << p.time_stamp << ")";
return os;
}
// uint8_t a; // 占位,保证和 PointXYZRGB 内存对齐
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
POINT_CLOUD_REGISTER_POINT_STRUCT(MyPoint,
(float,x,x)
(float,y,y)
(float,z,z)
(uint32_t,rgb,rgb)
(double,time_stamp,time_stamp));
#endif // MYPOINT_H
3.调用自定义数据类型
先直接给出完整的代码:
#include <MyPoint.h>
#include <chrono>
#include <thread>
#include <pcl/point_types.h>
#include <pcl/impl/point_types.hpp>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/io/pcd_io.h>
double getTimeStamp()
{
auto timeNow = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch());
return timeNow.count()/1000;
}
int main ()
{
srand(time(NULL));
pcl::PointCloud<MyPoint>::Ptr cloud(new pcl::PointCloud<MyPoint>);
cloud->width = 100;
cloud->height = 100;
cloud->resize(100*100);
cloud->is_dense = false;
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
cloud->points[i*100+j].x = 1024*rand()/(RAND_MAX + 1.0f);
cloud->points[i*100+j].y = 1024*rand()/(RAND_MAX + 1.0f);
cloud->points[i*100+j].z = 1024*rand()/(RAND_MAX + 1.0f);
cloud->points[i*100+j].r = rand()%256;
cloud->points[i*100+j].g = rand()%256;
cloud->points[i*100+j].b = rand()%256;
cloud->points[i*100+j].time_stamp = getTimeStamp();
}
}
std::cout << "✅ Loaded points: " <<cloud->size() << std::endl;
std::cout << "✅ points: " <<cloud->points.at(100) << std::endl;
if(pcl::io::savePCDFile<MyPoint>("C:\\Users\\admin\\Desktop\\code\\pcl_type2.pcd",*cloud) == 0)
{
std::cout << "✅ save successed " << std::endl;
}
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloudRGB(new pcl::PointCloud<pcl::PointXYZRGB>);
cloudRGB->width = cloud->width;
cloudRGB->height = cloud->height;
cloudRGB->is_dense = cloud->is_dense;
cloudRGB->resize(cloud->size());
for (size_t i = 0; i < cloud->size(); i++) {
cloudRGB->points[i].x = cloud->points[i].x;
cloudRGB->points[i].y = cloud->points[i].y;
cloudRGB->points[i].z = cloud->points[i].z;
cloudRGB->points[i].r = cloud->points[i].r;
cloudRGB->points[i].g = cloud->points[i].g;
cloudRGB->points[i].b = cloud->points[i].b;
}
// 创建 CloudViewer
pcl::visualization::CloudViewer viewer("Viewer");
//pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr CloudConst = cloudRGB;//非必需
viewer.showCloud(cloudRGB, "cloud");
viewer.runOnVisualizationThread([&](pcl::visualization::PCLVisualizer &vis)
{
vis.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud");
vis.setBackgroundColor(0.1,0.3,0.5);
});
// 保持显示
while (!viewer.wasStopped())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
首先getTimeStamp()函数是用来获取系统的时间。
double getTimeStamp()
{
auto timeNow = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch());
return timeNow.count()/1000;
}
注册好的自定义数据类型就直接可以实例化一个点云容器即可。
pcl::PointCloud<MyPoint>::Ptr cloud(new pcl::PointCloud<MyPoint>);
上述自定义的点云结构体中有一个打印的函数,具体实现如下:找到<<后会调用定义的方法去打印对应的内容。一会可以在结果显示。
std::cout << "✅ points: " <<cloud->points.at(100) << std::endl;
由于自定义的点云数据往往不能直接通过可视化显示,后面这一段主要是生成新的点云去读取。然后显示。操作步骤不难,就不再赘述。这里大家可以将其保存为las文件,再读出来可视化,也方便联系上一文章的laslib库。
运行结果如下:

到这里就差不多掌握了自定义点云数据类型了,真正的需要还是看具体的任务要求。
更多推荐



所有评论(0)