点云聚类算法存档

这篇文章上次修改于 3 个月前,可能部分内容已经不适用,如有疑问可询问作者。

点云聚类算法存档

欧式聚类

在PCL库的基本过程如下:

首先通过pcl::VoxelGrid (filters)先对点云数据进行下采样滤波;

然后通过pcl::SACSegmentation seg; (segmentation)创建Nodelet样本细分类别;

然后通过 pcl::ExtractIndices extract;(filters)提取索引;

最后通过pcl::EuclideanClusterExtraction ec; 生成欧式聚类对象 (segmentation)。

C++
#include "cluster.h"
#include <pcl/search/kdtree.h>
#include <cmath>
#include <algorithm>

pcl::PointCloud<pcl::PointXYZ>::Ptr Cluster::voxelDownsample(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, float leaf_size) {
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::VoxelGrid<pcl::PointXYZ> sor;
    sor.setInputCloud(cloud);
    sor.setLeafSize(leaf_size, leaf_size, leaf_size);
    sor.filter(*cloud_filtered);
    return cloud_filtered;
}

std::vector<pcl::PointIndices> Cluster::euclideanCluster(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, 
                                                         float cluster_tolerance, 
                                                         int min_cluster_size, 
                                                         int max_cluster_size) {
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
    tree->setInputCloud(cloud);

    std::vector<pcl::PointIndices> cluster_indices;
    pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
    ec.setClusterTolerance(cluster_tolerance); 
    ec.setMinClusterSize(min_cluster_size);
    ec.setMaxClusterSize(max_cluster_size);
    ec.setSearchMethod(tree);
    ec.setInputCloud(cloud);
    ec.extract(cluster_indices);

    return cluster_indices;
}
Copy

距离图聚类

C++
std::vector<pcl::PointIndices> Cluster::depthCluster(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud,
                                                     double angle_threshold, 
                                                     int search_step) {
    // 初始化数据结构
    std::array<double, DEPTH_HEIGHT*DEPTH_WIDTH*4> range_image = {};
    std::vector<std::vector<std::pair<int,double>>> pointlist(DEPTH_HEIGHT*DEPTH_WIDTH);
    std::vector<int> cluster_points(cloud->size(), -1);
    
    // 步骤1:将3D点云投影到2D深度图像
    projectToImage(cloud, range_image, pointlist);
    
    // 步骤2:执行深度聚类
    int current_label = 0;
    auto labels = depthClusterCore(range_image, current_label, angle_threshold, search_step);
    
    // 步骤3:将2D聚类结果映射回3D点云
    for(int i = 0; i < pointlist.size(); i++) {
        if(!pointlist[i].empty()) {
            // 为最近的点分配聚类标签
            cluster_points[pointlist[i][pointlist[i].size()-1].first] = labels[i];
            int dist = pointlist[i][pointlist[i].size()-1].second;
            
            // 为深度相近的其他点分配相同标签
            for(int j = pointlist[i].size()-2; j > -1; j--) {
                if(pointlist[i][j].second - dist < 0) {
                    cluster_points[pointlist[i][j].first] = labels[i];
                }
                dist = pointlist[i][j].second;
            }
        }
    }
    
    // 步骤4:将结果转换为PCL格式
    std::vector<pcl::PointIndices> cluster_indices;
    std::map<int, std::vector<int>> label_to_indices;
    
    for(int i = 0; i < cluster_points.size(); i++) {
        if(cluster_points[i] > 0) {
            label_to_indices[cluster_points[i]].push_back(i);
        }
    }
    
    for(auto& pair : label_to_indices) {
        if(pair.second.size() > 10) { // 过滤小聚类
            pcl::PointIndices indices;
            indices.indices = pair.second;
            cluster_indices.push_back(indices);
        }
    }
    
    return cluster_indices;
}
Copy

FEC

虽然其readme形同虚设,但是常规配置即可

https://github.com/YizhenLAO/FEC

核心的头文件如下:

C++
struct PointIndex_NumberTag
{
    float nPointIndex;
    float nNumberTag;
};

bool NumberTag(const PointIndex_NumberTag& p0, const PointIndex_NumberTag& p1)
{
    return p0.nNumberTag < p1.nNumberTag;
}

std::vector<pcl::PointIndices> FEC(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, int min_component_size, double tolorance, int max_n) {

    unsigned long i, j;
    if (cloud->size() < min_component_size)
    {
        PCL_ERROR("Could not find any cluster");
    }

    pcl::KdTreeFLANN<pcl::PointXYZ>cloud_kdtreeflann;
    cloud_kdtreeflann.setInputCloud(cloud);

    int cloud_size = cloud->size();
    std::vector<int> marked_indices;
    marked_indices.resize(cloud_size);


    memset(marked_indices.data(), 0, sizeof(int) * cloud_size);
    std::vector<int> pointIdx;
    std::vector<float> pointquaredDistance;

    int tag_num = 1, temp_tag_num = -1;

    for (i = 0; i < cloud_size; i++)
    {
        // Clustering process
        if (marked_indices[i] == 0) // reset to initial value if this point has not been manipulated
        {
            pointIdx.clear();
            pointquaredDistance.clear();
            cloud_kdtreeflann.radiusSearch(cloud->points[i], tolorance, pointIdx, pointquaredDistance, max_n);
            /**
            * All neighbors closest to a specified point with a query within a given radius
            * para.tolorance is the radius of the sphere that surrounds all neighbors
            * pointIdx is the resulting index of neighboring points
            * pointquaredDistance is the final square distance to adjacent points
            * pointIdx.size() is the maximum number of neighbors returned by limit
            */
            int min_tag_num = tag_num;
            for (j = 0; j < pointIdx.size(); j++)
            {
                /**
                 * find the minimum label value contained in the field points, and tag it to this cluster label.
                 */
                if ((marked_indices[pointIdx[j]] > 0) && (marked_indices[pointIdx[j]] < min_tag_num))
                {
                    min_tag_num = marked_indices[pointIdx[j]];
                }
            }
            for (j = 0; j < pointIdx.size(); j++)
            {
                temp_tag_num = marked_indices[pointIdx[j]];

                /*
                 * Each domain point, as well as all points in the same cluster, is uniformly assigned this label
                 */
                if (temp_tag_num > min_tag_num)
                {
                    for (int k = 0; k < cloud_size; k++)
                    {
                        if (marked_indices[k] == temp_tag_num)
                        {
                            marked_indices[k] = min_tag_num;
                        }
                    }
                }
                marked_indices[pointIdx[j]] = min_tag_num;
            }
            tag_num++;
        }
    }

    std::vector<PointIndex_NumberTag> indices_tags;
    std::vector<pcl::PointIndices> cluster_indices;
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    indices_tags.resize(cloud_size);

    PointIndex_NumberTag temp_index_tag;


    for (i = 0; i < cloud_size; i++)
    {
        /**
        * Put each point index and the corresponding tag value into the indices_tags
        */
        temp_index_tag.nPointIndex = i;
        temp_index_tag.nNumberTag = marked_indices[i];

        indices_tags[i] = temp_index_tag;
    }

    sort(indices_tags.begin(), indices_tags.end(), NumberTag);

    unsigned long begin_index = 0;
    for (i = 0; i < indices_tags.size(); i++)
    {
        // Relabel each cluster
        if (indices_tags[i].nNumberTag != indices_tags[begin_index].nNumberTag)
        {
            if ((i - begin_index) >= min_component_size)
            {
                unsigned long m = 0;
                inliers->indices.resize(i - begin_index);
                for (j = begin_index; j < i; j++)
                    inliers->indices[m++] = indices_tags[j].nPointIndex;
                cluster_indices.push_back(*inliers);
            }
            begin_index = i;
        }
    }

    if ((i - begin_index) >= min_component_size)
    {
        for (j = begin_index; j < i; j++)
        {
            unsigned long m = 0;
            inliers->indices.resize(i - begin_index);
            for (j = begin_index; j < i; j++)
            {
                inliers->indices[m++] = indices_tags[j].nPointIndex;
            }
            cluster_indices.push_back(*inliers);
        }
    }
    return cluster_indices;

}
Copy

算法解析

同样使用欧几里得(L2)距离度量来测量无组织点云的接近度。但是和EC不同的是并不反复使用簇级方案。对每个点只使用了一次KD树的查询。查询次数为O(N)。

EC:每次找到一个点,查找所有临近点,构成一个点簇。对点簇内继续递归查找直到簇扩展完成。

FEC:对点进行遍历,对没有标记的点进行查询,自动合并到临近簇。


输入:未聚类的未排序的点云P,判断为同类的阈值 $ d_th $ ,领域点的最大数量 $ Th_max $。

输出:P.lab 带有聚类标签的点云。

初始化:P.lab设置为0;SegLab设置为1;建立P的KD树。

for p.lab in P: # 该逐点遍历方案和EC相反

  if p.lab = 0:

    Pnn = FindNeighbor(p, $ d_th $);

    if 非0的Pnn.lab 存在:

      minSegLab = min(非0的Pnn.lab,SegLab);

    else:

      minSegLab = SegLab;

    end

    for pj in Pnn:

      if pj.lab > Pnn:

        for pk.lab in P:

          if pk.lab = minSegLab:  pk.lab = minSegLab;

        end

      end

    end

    SegLab++;

  end

end

return point labels
Copy

相关文章

  • 地面点滤除算法存档