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库。

运行结果如下:

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

更多推荐