IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    三维点云处理入门(18):点云网格化及PCL重建实例

    52txr发表于 2024-03-19 19:45:41
    love 0

    本文讨论了三维重建中的网格类型选择和点云贪婪三角化方法。首先介绍了六种不同类型的网格,包括三角网格、非结构化网格等,并分析了它们的特点和应用场景。接着详细阐述了点云贪婪三角化的工作原理及参数设置。最后简要介绍了用于创建平滑三维表面模型的Poisson重建技术。

    网格类型

    网格类型是指用于表示重建后三维模型的网格结构。这些网格类型在形状、大小和连接方式上有所不同,以适应不同的应用和处理需求。

    网格类型描述应用
    三角网格(Triangular Mesh)由三角形组成,每个三角形有三个顶点和三条边。适用于大多数三维表面,尤其是不规则或复杂的形状。
    四边形网格(Quadrilateral Mesh)由四边形组成,每个四边形通常是一个平面。常用于建筑建模和一些工程应用,可以高效进行细分和平滑处理。
    多边形网格(Polygonal Mesh)由多边形构成,边数可以多于四条。适用于表示更复杂的几何结构,如地质建模等专业应用。
    正则网格(Regular Grid)由规则排列的四边形或六边形组成,具有统一的大小和形状。常用于医学影像和某些类型的流体动力学模拟。
    非结构化网格(Unstructured Mesh)不遵循规则排列的三角形或多边形。适用于适应各种复杂的表面和体积形状,常用于复杂的科学和工程计算。
    体素网格(Voxel Grid)由体素(三维像素)组成的规则立方体网格。用于体积建模,如医学成像中的CT扫描重建,适用于3D打印。

    在点云三维重建的过程中,选择合适的网格类型取决于源数据的特点、所需的模型精度和最终应用目的。

    点云三维重建

    点云贪心三角化

    参考资料:【PCL学习:无序点云的快速三角化】

    将三维点通过法线投影到某一平面,然后对投影得到的点云作平面内的三角化,从而得到各点的连接关系。在平面区域三角化的过程中用到了基于 Delaunay 的 空间区域增长算法,该方法通过选取一个样本三角片作为初始曲面,不断扩张曲面边界,最后形成一张完整的三角网格曲面。最后根据投影点云的连接关系确定各原始三维点间的拓扑连接,所得三角格网即为重建得到的曲面模型。

    在PCL中,类GreedyProjectionTriangulation实现了将三维点投影到某一局部二维坐标平面的贪婪三角化算法,该算法需要点云平滑,并且密度变化连续。

    关键成员函数:

    1. SetMaximumNearestNeighbors(unsigned):

      • 功能:定义每个点可以搜索的最大邻域个数。
      • 典型值:50到100。
      • 用途:控制邻域大小,帮助适应点云密度的变化。
    2. SetMu(double):

      • 功能:规定被搜索点的最远距离。
      • 典型值:2.5到3(或每栅格1.5)。
      • 用途:适应点云密度变化,限制搜索范围。
    3. SetSearchRadius(double):

      • 功能:设置三角化后,每个三角形边的最大长度。
      • 用途:控制生成的三角形大小,避免过大的三角形。
    4. SetMinimumAngle(double) 和 SetMaximumAngle(double):

      • 功能:分别设置三角形的最小角和最大角度限制。
      • 典型值:最小角10度,最大角120度(单位:弧度)。
      • 用途:保证三角化后的三角形在合理的角度范围内。
    5. SetMaximumSurfaceAngle(double):

      • 功能:设置法线方向的最大偏离角度。
      • 典型值:45度(单位:弧度)。
      • 用途:用于处理边缘尖锐或表面两侧非常靠近的情况,通过比较法线方向来确定是否连接点。
    6. SetNormalConsistency(bool):

      • 功能:确保网格的法线一致性。
      • 缺省值:false。
      • 用途:当未设置时,无法保证所有估计的法线朝向一致。

    下面将以一个实例来感受一下这个用法:

    #include <pcl/point_types.h>
    #include <pcl/io/pcd_io.h>
    #include <pcl/kdtree/kdtree_flann.h>
    #include <pcl/features/normal_3d.h>
    #include <pcl/surface/gp3.h>
    #include <pcl/visualization/pcl_visualizer.h>
    #include <boost/thread/thread.hpp>
    
    int main(int argc, char** argv)
    {
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);                        //智能指针加载点云数据
        pcl::PCLPointCloud2::Ptr cloud_blob(new pcl::PCLPointCloud2);
        pcl::io::loadPCDFile("E:\\PCL1\\pcl1\\bun000_StructuredASCII.pcd", *cloud_blob);
        pcl::fromPCLPointCloud2(*cloud_blob, *cloud);
    
        pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n;                                                //创建法线估计对象
        pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);                        //智能指针存储法线数据
        pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);                //智能指针存储kd tree
        tree->setInputCloud(cloud);
        n.setInputCloud(cloud);
        n.setSearchMethod(tree);
        n.setKSearch(20);
        n.compute(*normals);                                                                                //执行法线估计
    
        pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointNormal>);    //将点云与法线进行字段拼接
        pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
    
        pcl::search::KdTree<pcl::PointNormal>::Ptr tree2(new pcl::search::KdTree<pcl::PointNormal>);        //创建搜索树
        tree2->setInputCloud(cloud_with_normals);
    
        pcl::GreedyProjectionTriangulation<pcl::PointNormal> gp3;                                            //初始化贪婪三角形的对象
        pcl::PolygonMesh triangles;
    
        gp3.setSearchRadius(0.1);                                                                            //设置两点之间的最近距离
    
        gp3.setMu(2.5);
        gp3.setMaximumNearestNeighbors(1000);                                                                //设置最大近邻点的个数
        gp3.setMaximumSurfaceAngle(M_PI / 4);                                                                //设置最大搜索角度
        gp3.setMinimumAngle(M_PI / 18);                                                                        //设置最小角度
        gp3.setMaximumAngle(2 * M_PI / 3);                                                                    //设置最大角度
        gp3.setNormalConsistency(false);                                                                    //是否考虑法线
    
        gp3.setInputCloud(cloud_with_normals);
        gp3.setSearchMethod(tree2);
        gp3.reconstruct(triangles);                                                                            //利用三角形进行重构
    
        std::vector<int>parts = gp3.getPartIDs();                                                            //贪婪三角形可视化
        std::vector<int>states = gp3.getPointStates();
        boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
        viewer->setBackgroundColor(0, 0, 0);
        viewer->addPolygonMesh(triangles, "my");
        viewer->addCoordinateSystem(1.0);
        viewer->initCameraParameters();
    
        while (!viewer->wasStopped())
        {
            viewer->spinOnce(100);
            boost::this_thread::sleep(boost::posix_time::microseconds(100000));
        }
        return(0);
    }

    报错“Not enough neighbors are considered: ffn or sfn out of range! Consider increasing nnn_... Setting R=4276 to be BOUNDARY! Not enough neighbors are considered: source of R=22352 is out of range! Consider increasing nnn_...”的修改意见:

    增加最大近邻数:增加gp3.setMaximumNearestNeighbors的值。

    调整搜索半径:增加gp3.setSearchRadius的值,以覆盖更大的区域,找到更多的邻居点。

    点云贪心三角化重建

    三角化的细节

    Poisson重建

    Poisson重建是一种用于从点云数据创建三维表面模型的技术。这个名字来源于它使用的一个数学工具,叫做泊松方程。想象一下,你有很多散落的点,这些点表示一个物体的外表面,但它们之间没有直接的连接。Poisson重建的目的就是找到一个平滑的表面,这个表面最好地穿过这些点,从而形成一个连续的三维模型。

    这个过程可以比作是解决一个谜题。你有很多线索(点云),而你需要找到一个解决方案(表面)来连接所有的线索。在Poisson重建中,每个点都有一个称为“法线”的小箭头,指示表面在该点处应该朝向哪个方向。这就像每个线索都附带了一个小提示,帮助解决谜题。

    使用泊松方程,这种方法可以计算出一个平滑的表面,不仅经过所有的点,而且尽可能地沿着这些小箭头的方向。最终的结果是一个非常平滑、自然的三维模型,这在处理复杂或不完整的点云数据时特别有用。

    在PCL中可以很简单的实现:

    #include <pcl/point_types.h>
    #include <pcl/io/pcd_io.h>
    #include <pcl/kdtree/kdtree_flann.h>
    #include <pcl/features/normal_3d_omp.h>
    #include <pcl/features/normal_3d.h>
    #include <pcl/surface/gp3.h>
    #include <pcl/surface/poisson.h>
    #include <pcl/visualization/pcl_visualizer.h>
    #include <boost/thread/thread.hpp>
    #include <fstream>
    #include <iostream>
    #include <string.h>
    #include <string>
    
    int main(int argc, char** argv)
    {
        std::string input_file = "E:\\PCL1\\pcl1\\bun000_StructuredASCII.pcd";
    
        // Load the point cloud
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
        if (pcl::io::loadPCDFile<pcl::PointXYZ>(input_file, *cloud) == -1)
        {
            PCL_ERROR("Could not read pcd file!\n");
            return -1;
        }
    
        // Calculate normals
        pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(new pcl::PointCloud<pcl::PointNormal>);
        pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n;
        pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
        pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
        tree->setInputCloud(cloud);
        n.setInputCloud(cloud);
        n.setSearchMethod(tree);
        n.setKSearch(20);
        n.compute(*normals);
    
        // Concatenate point cloud and normals
        pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
    
        // Create KD tree
        pcl::search::KdTree<pcl::PointNormal>::Ptr tree2(new pcl::search::KdTree<pcl::PointNormal>);
        tree2->setInputCloud(cloud_with_normals);
    
        // Create Poisson object and set parameters
        pcl::Poisson<pcl::PointNormal> pn;
        pn.setConfidence(false);
        pn.setDegree(2);
        pn.setDepth(8);
        pn.setIsoDivide(8);
        pn.setManifold(false);
        pn.setOutputPolygons(false);
        pn.setSamplesPerNode(3.0);
        pn.setScale(1.25);
        pn.setSolverDivide(8);
    
        // Set search method and input point cloud
        pn.setSearchMethod(tree2);
        pn.setInputCloud(cloud_with_normals);
    
        // Create polygon mesh to store result
        pcl::PolygonMesh mesh;
        // Perform reconstruction
        pn.performReconstruction(mesh);
    
        // Display result
        boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D viewer"));
        viewer->setBackgroundColor(0, 0, 0);
        viewer->addPolygonMesh(mesh, "my");
        // viewer->addCoordinateSystem(50.0);
        // viewer->initCameraParameters();
        while (!viewer->wasStopped())
        {
            viewer->spinOnce(100);
            boost::this_thread::sleep(boost::posix_time::microseconds(100000));
        }
    
        return 0;
    }

    运行效果:

    Poisson重建



沪ICP备19023445号-2号
友情链接