PCL 法线微分分割(Don)

PCL专栏目录及须知-CSDN博客

注意:本方法效果一般,需要调参。

1.原理

对于点云中的每个点云点P,使用一大一小两个半径计算该点的两个法向量,然后用两个单位法向量的差异计算Don特征。

如下图:

图1为大半径计算得到的法向量;图2为小半径计算得到的法向量;图3为一大一小两个法向量计算得出的Don特征。

步骤:

(1)建KD树,计算点云平均密度(即点云点之间的平均邻近距离)。

(2)用户依据平均密度设置两个尺度的Don半径,然后分别计算两个Don半径下的法向量。

(3)通过公式:

计算两个尺度下Don法向量的差异。

(4)得到对应的法线微分差异,得出Don结果。

2.使用场景

(1)寻找特征点(例如边缘点,角点等;因为法线微分分割之后,变化差异大的位置即边缘位置

(2)分割点云,得到点云的各个部分【往往用于分割具有多个平面的点云,如建筑物,道路面等】(在剔除Don计算出的边缘点之后,对其他点进行聚类,然后得到点云的不同部分)

3.注意事项

代码中的pcl::PointNormal不要写为pcl::Normal(因为我就写了...后面滤波报错了)

4.关键函数

(1)生成点云法线(在代码最初,生成点云法线时,最好使用K邻近搜索)

setKSearch()

(2)计算平均半径,用于辅助分割(以下代码可以搜索与当前点云点最邻近的点注意,参数设置为2,因为索引=0的点,是当前点云点本身

std::vector<int> pointIdxNKNSearch(2);                                                          // 保存最邻近点的索引(该方法最低设置为2,其中第一个值为当前点)
std::vector<float> pointNKNSquaredDistance(2);                                                  // 保存最邻近点的距离的平方(同上)                     
tree->nearestKSearch(cloud->points[i], 2, pointIdxNKNSearch, pointNKNSquaredDistance);
double dis = sqrt(pointNKNSquaredDistance[1]);

(3)大小尺度半径(大小尺度半径通过这个函数设置,以基于这两个函数,设置两个不同的值)

setRadiusSearch()

(4)Don法线微分分割(设置大小半径的参数)

pcl::DifferenceOfNormalsEstimation<pcl::PointXYZRGB, pcl::PointNormal, pcl::PointNormal> feature;
feature.setNormalScaleLarge(ne_sm);                         // 设置小尺寸半径
feature.setNormalScaleSmall(ne_lg);                         // 设置大尺寸半径

(5)条件滤波相关(用于过滤边缘点【保留边缘点同理】)条件滤波

pcl::ConditionOr<pcl::PointNormal>::Ptr filtersOr_keypoint(new pcl::ConditionOr<pcl::PointNormal>());
filtersOr_keypoint->addComparison(pcl::FieldComparison<pcl::PointNormal>::ConstPtr(new pcl::FieldComparison<pcl::PointNormal>("curvature", pcl::ComparisonOps::GT, 0.3)));       // 条件选择器,曲率值大于0.3
pcl::ConditionalRemoval<pcl::PointNormal> filters_keypoint;
filters_keypoint.setInputCloud(donCloud);
filters_keypoint.setCondition(filtersOr_keypoint);                                            // 设置条件选择器(或条件)
pcl::PointCloud<pcl::PointNormal>::Ptr cloud_filtered_keypoint(new pcl::PointCloud<pcl::PointNormal>);
filters_keypoint.filter(*cloud_filtered_keypoint);

(6)聚类相关(Don之后聚类可将去掉边缘的点云分成几部分;聚类得到的结果为聚类前点云点的索引

 // 对去掉角点的内部点按照欧式聚类进行聚类,实现对物体不同面的分割功能
pcl::search::KdTree<pcl::PointNormal>::Ptr segtree(new pcl::search::KdTree<pcl::PointNormal>);
segtree->setInputCloud(cloud_filtered_inside);
std::vector<pcl::PointIndices> cluster_indices;                         // 聚类得到的索引集群(每个vector是一个集群)
pcl::EuclideanClusterExtraction<pcl::PointNormal> seg;                  // 欧式聚类
seg.setClusterTolerance(meanDis*5);                                     // 设置欧式聚类的阈值
seg.setMinClusterSize(50);                                              // 最小聚类点个数
seg.setMaxClusterSize(100000);                                          // 最大聚类点个数
seg.setSearchMethod(segtree);
seg.setInputCloud(cloud_filtered_inside);
seg.extract(cluster_indices);                                           // 得到聚类结果

5.代码

#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/search/kdtree.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/filters/conditional_removal.h> //条件滤波
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/segmentation/impl/extract_clusters.hpp>
#include <pcl/features/don.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
#include <random>

int main()
{
    /****************Don法线微分分割********************/
    // 原始点云
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
    pcl::io::loadPCDFile("D:/code/csdn/data/two_tree.pcd", *cloud);   // 加载原始点云数据

    // 生成点云法线
    pcl::NormalEstimationOMP<pcl::PointXYZRGB, pcl::PointNormal> normEst;                              // 基于邻域的法线估计器(OMP型,可多线程处理)
    pcl::PointCloud<pcl::PointNormal>::Ptr normals(new pcl::PointCloud<pcl::PointNormal>);             // 法向量保存
    pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZRGB>());      // KD树
    normEst.setNumberOfThreads(4);
    normEst.setInputCloud(cloud);
    normEst.setSearchMethod(tree);
    normEst.setKSearch(50);                                                                            // 使用K领域搜索
     normEst.setRadiusSearch(0.3);                                                                   // 进行法向估计的领域半径(该半径范围内的点云拟合一个平面,生成点云法向量)
    normEst.compute(*normals);

    // 计算平均半径,用于辅助分割
    double totalDis = 0;
    for (int i = 0; i < cloud->size(); i++)                                                             // 搜索最邻近的两个点
    {
        std::vector<int> pointIdxNKNSearch(2);                                                          // 保存最邻近点的索引(该方法最低设置为2,其中第一个值为当前点)
        std::vector<float> pointNKNSquaredDistance(2);                                                  // 保存最邻近点的距离的平方(同上)                     
        tree->nearestKSearch(cloud->points[i], 2, pointIdxNKNSearch, pointNKNSquaredDistance);
        totalDis += sqrt(pointNKNSquaredDistance[1]);
    }
    double meanDis = totalDis / cloud->size();                                                          // 平均距离
    double scale_sm = meanDis * 2.0;                                                                    // 小尺度辅助半径
    double scale_lg = meanDis * 8.0;                                                                    // 大尺度辅助半径

    // 小尺度don半径
    pcl::NormalEstimationOMP<pcl::PointXYZRGB, pcl::PointNormal> normEst_sm;                                 // 基于邻域的法线估计器(OMP型,可多线程处理)
    pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree_sm(new pcl::search::KdTree<pcl::PointXYZRGB>());
    pcl::PointCloud<pcl::PointNormal>::Ptr ne_sm(new pcl::PointCloud<pcl::PointNormal>);
    normEst_sm.setNumberOfThreads(4);
    normEst_sm.setInputCloud(cloud);
    normEst_sm.setSearchMethod(tree_sm);
    normEst_sm.setRadiusSearch(scale_sm);
    normEst_sm.compute(*ne_sm);

    // 大尺度don半径
    pcl::NormalEstimationOMP<pcl::PointXYZRGB, pcl::PointNormal> normEst_lg;                                 // 基于邻域的法线估计器(OMP型,可多线程处理)
    pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree_lg(new pcl::search::KdTree<pcl::PointXYZRGB>());
    pcl::PointCloud<pcl::PointNormal>::Ptr ne_lg(new pcl::PointCloud<pcl::PointNormal>);
    normEst_lg.setNumberOfThreads(4);
    normEst_lg.setInputCloud(cloud);
    normEst_lg.setSearchMethod(tree_lg);
    normEst_lg.setRadiusSearch(scale_lg);
    normEst_lg.compute(*ne_lg);

    // Don法线微分分割
    pcl::DifferenceOfNormalsEstimation<pcl::PointXYZRGB, pcl::PointNormal, pcl::PointNormal> feature;
    feature.setInputCloud(cloud);
    feature.setNormalScaleLarge(ne_sm);                         // 设置小尺寸半径
    feature.setNormalScaleSmall(ne_lg);                         // 设置大尺寸半径

    pcl::PointCloud<pcl::PointNormal>::Ptr donCloud(new pcl::PointCloud<pcl::PointNormal>);
    pcl::copyPointCloud<pcl::PointXYZRGB, pcl::PointNormal>(*cloud, *donCloud);                  // 准备结果点云
    feature.computeFeature(*donCloud);

    // 应用一(寻找特征点)
    // 按照曲率进行条件滤波,删除小于给定曲率阈值的点云,得到特征点(曲率变化大的点,即角点)
    pcl::ConditionOr<pcl::PointNormal>::Ptr filtersOr_keypoint(new pcl::ConditionOr<pcl::PointNormal>());
    filtersOr_keypoint->addComparison(pcl::FieldComparison<pcl::PointNormal>::ConstPtr(new pcl::FieldComparison<pcl::PointNormal>("curvature", pcl::ComparisonOps::GT, 0.3)));       // 条件选择器,曲率值大于0.3
    pcl::ConditionalRemoval<pcl::PointNormal> filters_keypoint;
    filters_keypoint.setInputCloud(donCloud);
    filters_keypoint.setCondition(filtersOr_keypoint);                                            // 设置条件选择器(或条件)
    pcl::PointCloud<pcl::PointNormal>::Ptr cloud_filtered_keypoint(new pcl::PointCloud<pcl::PointNormal>);
    filters_keypoint.filter(*cloud_filtered_keypoint);

    // 应用二(分割点云)
    // 按照曲率进行条件滤波,删除大于给定曲率阈值的点云,得到内部点
    pcl::ConditionOr<pcl::PointNormal>::Ptr filtersOr_inside(new pcl::ConditionOr<pcl::PointNormal>());
    filtersOr_inside->addComparison(pcl::FieldComparison<pcl::PointNormal>::ConstPtr(new pcl::FieldComparison<pcl::PointNormal>("curvature", pcl::ComparisonOps::LT, 0.3)));       // 条件选择器,曲率值小于0.3
    pcl::ConditionalRemoval<pcl::PointNormal> filters_inside;
    filters_inside.setInputCloud(donCloud);
    filters_inside.setCondition(filtersOr_inside);                                            // 设置条件选择器(或条件)
    pcl::PointCloud<pcl::PointNormal>::Ptr cloud_filtered_inside(new pcl::PointCloud<pcl::PointNormal>);
    filters_inside.filter(*cloud_filtered_inside);
    // 对去掉角点的内部点按照欧式聚类进行聚类,实现对物体不同面的分割功能
    pcl::search::KdTree<pcl::PointNormal>::Ptr segtree(new pcl::search::KdTree<pcl::PointNormal>);
    segtree->setInputCloud(cloud_filtered_inside);
    std::vector<pcl::PointIndices> cluster_indices;                         // 聚类得到的索引集群(每个vector是一个集群)
    pcl::EuclideanClusterExtraction<pcl::PointNormal> seg;                  // 欧式聚类
    seg.setClusterTolerance(meanDis*5);                                     // 设置欧式聚类的阈值
    seg.setMinClusterSize(50);                                              // 最小聚类点个数
    seg.setMaxClusterSize(100000);                                          // 最大聚类点个数
    seg.setSearchMethod(segtree);
    seg.setInputCloud(cloud_filtered_inside);
    seg.extract(cluster_indices);                                           // 得到聚类结果

    /****************展示********************/
    // 整体曲率展示
    boost::shared_ptr<pcl::visualization::PCLVisualizer> pointView(new pcl::visualization::PCLVisualizer("Curvature"));
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointNormal> handler(donCloud, "curvature");
    pointView->setBackgroundColor(1, 1, 1);
    pointView->addPointCloud(donCloud, handler);
    pointView->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3);
    pointView->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_OPACITY, 0.5);
    /*
    while (!pointView->wasStopped())
    {
        pointView->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }
    */

    // 内部点展示
    boost::shared_ptr<pcl::visualization::PCLVisualizer> insideView(new pcl::visualization::PCLVisualizer("KeyPoint"));
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointNormal> handler_inside(cloud_filtered_inside, "curvature");
    insideView->setBackgroundColor(1, 1, 1);
    insideView->addPointCloud(cloud_filtered_inside, handler_inside);
    insideView->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3);
    insideView->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_OPACITY, 0.5);
    /*
    while (!insideView->wasStopped())
    {
        insideView->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }
    */

    // 聚类展示
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr clusters_cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
    clusters_cloud->resize(cloud_filtered_inside->points.size());
    for (size_t i = 0; i < cloud_filtered_inside->points.size(); i++)                   // 设置初始值,默认为白色
    {
        clusters_cloud->points[i].x = cloud_filtered_inside->points[i].x;
        clusters_cloud->points[i].y = cloud_filtered_inside->points[i].y;
        clusters_cloud->points[i].z = cloud_filtered_inside->points[i].z;
        clusters_cloud->points[i].r = 255;
        clusters_cloud->points[i].g = 255;
        clusters_cloud->points[i].b = 255;
    }

    for (size_t i = 0; i < cluster_indices.size(); i++)
    {
        std::random_device rand;                                // 生成随机颜色,每个聚类颜色一致
        std::mt19937 gen(rand());
        std::uniform_int_distribution<>dis(1, 254);
        int r = dis(gen);
        int g = dis(gen);
        int b = dis(gen);

        for (size_t j = 0; j < cluster_indices[i].indices.size(); j++)
        {
            clusters_cloud->points[cluster_indices[i].indices[j]].x = cloud_filtered_inside->points[cluster_indices[i].indices[j]].x;
            clusters_cloud->points[cluster_indices[i].indices[j]].y = cloud_filtered_inside->points[cluster_indices[i].indices[j]].y;
            clusters_cloud->points[cluster_indices[i].indices[j]].z = cloud_filtered_inside->points[cluster_indices[i].indices[j]].z;
            clusters_cloud->points[cluster_indices[i].indices[j]].r = r;
            clusters_cloud->points[cluster_indices[i].indices[j]].g = g;
            clusters_cloud->points[cluster_indices[i].indices[j]].b = b;
        }
    }

    boost::shared_ptr<pcl::visualization::PCLVisualizer> view_raw(new pcl::visualization::PCLVisualizer("cluster"));
    view_raw->addPointCloud<pcl::PointXYZRGB>(clusters_cloud, "cluster cloud");
    view_raw->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cluster cloud");
    while (!view_raw->wasStopped())
    {
        view_raw->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }

    return 0;
}

6.结果展示

(1)整体曲率展示

(2)去除边缘点之后的内部点展示

(3)聚类之后的内部点展示(将内部点分为几部分,用于分割平面,每个颜色是一个平面)

PCL(Point Cloud Library)是一个开源的点云处理库,广泛用于计算机视觉和机器人学领域。其中的法线滤波器(Normal Filtering)是一种常见的预处理步骤,用于估计每个表面点的法线方向,从而提高形状和纹理的描述。 在PCL中,法线滤波可以通过`pcl::NormalEstimation`类结合`pcl::MeanShiftNormalEstimation`或者其他方法来实现。这里是一个简单的C++示例,演示如何使用`pcl::MeanShiftNormalEstimation`进行法线滤波: ```cpp #include <pcl/point_cloud.h> #include <pcl/features/normal_3d.h> #include <pcl/io/pcd_io.h> #include <pcl/console/print.h> int main() { // 读取点云数据 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); if (pcl::io::loadPCDFile<pcl::PointXYZ> ("input.pcd", *cloud) == -1) { PCL_ERROR ("Cloud read error.\n"); return (-1); } // 创建法线计算器并设置参数 pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne; ne.setInputCloud(cloud); ne.setKSearch(50); // 设置搜索邻居的范围 // 使用Mean Shift算法算法线 pcl::MeanShiftNormalEstimation<pcl::PointXYZ, pcl::Normal> msne; msne.setInputCloud(cloud); msne.setRadiusSearch(0.03); // 设置搜索半径 msne.setKernelSigma(0.01); // 设置核函数的带宽 // 计算法线并保存到新的点云 pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>); msne.compute(*cloud_normals); // 将法线附加到原始点云 pcl::concatenateFields(*cloud, *cloud_normals, *cloud); // 输出处理后的点云 pcl::io::savePCDFile("output_with_normals.pcd", *cloud); return 0; } ``` 在这个例子中,我们首先读取点云数据,然后创建`NormalEstimation`对象来执行法线估计。接着,我们使用`MeanShiftNormalEstimation`来优化这个过程,并将结果保存为一个新的`pcl::PointCloud<pcl::Normal>`。最后,我们将计算出的法线信息添加回原始点云并保存为新文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值