点云分割是根据空间、几何和纹理等特征对点云进行划分,使得同一划分区域内的点云拥有相似的特征 。 点云的有效分割往往是许多应用的前提。例如,在逆向工程CAD/CAM 领域,对零件的不同扫描表面进行分割,然后才能更好地进行孔洞修复、曲面重建、特征描述和提取,进而进行基于 3D内容的检索、组合重用等。在激光遥感领域,同样需要对地面、物体首先进行分类处理,然后才能进行后期地物的识别、重建 。
总之,分割采用分而治之的思想,在点云处理中和滤波一样属于重要的基础操作,在PCL 中目前实现了进行分割的基础架构,为后期更多的扩展奠定了基础,现有实现的分割算法是鲁棒性比较好的Cluster聚类分割和RANSAC基于随机采样一致性的分割。
PCL分割库包含多种算法,这些算法用于将点云分割为不同簇。适合处理由多个隔离区域空间组成的点云。将点云分解成其组成部分,然后可以对其进行独立处理。 可以在集群提取教程中找到理论入门,以解释集群方法的工作原理。这两个图说明了平面模型分割(上)和圆柱模型分割(下)的结果。
在聚类方法中每个点都与一个特征向量相关联,特征向量又包含了若干个几何或者辐射度量值。然后,在特征空间中通过聚类的方法(如K-mean法最大似然方法和模糊聚类法)分割点云数据。聚类分割的基本原理为:考察m 个数据点,在m维空间内定义点与点之间某种性质的亲疏聚类,设 m个数据点组成类,然后将具有最小距离的两类合为一类,并重新计算类与类之间的距离,迭代直到任意两类之间的距离大于指定的闽值,或者类的个数小于指定的数目,完成分割。
基于随机采样一致性算法的分割是通过随机取样剔除局外点,构建一个仅由局内点数据组成的基本子集的过程。其基本思想为:在进行参数估计时,不是不加区分地对待所有可能的输人数据,而是首先针对具体问题设计出一个判断准则模型,利用此判断准则迭代地剔除那些与所估计的参数不一致的输入数据,然后通过正确的输入数据来估计模型参数。基于随机采样一致性的点云分割的过程为:首先从输入的点云数据集中随机选择一些点并计算用户给定模型的参数,对数据集中的所有点设置距离阙值,如果点到模型的距离在距离闽值范围内,则将该点归为局内点,否则为局外点,然后统计所有局内点的个数,判断是否大于设定的阙值,如果是,则用内点重新估计模型,作为模型输出,存储所有内点作为分割结果,如果不是,则与当前最大的局内点个数对比,如果大于则取代当前最大局内点个数,并存储当前的模型系数,然后进行迭代计算,直到分割出用户满意的模型
#include
#include
#include
#include
#include
#include
#include
#include
int
main ()
{
pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
pcl::PointCloud::Ptr inliers_points(new pcl::PointCloud);
// Fill in the cloud data
cloud->width = 15;
cloud->height = 1;
cloud->points.resize (cloud->width * cloud->height);
// Generate the data
for (auto& point: *cloud)
{
point.x = rand () / (RAND_MAX + 1.0f);
point.y = rand () / (RAND_MAX + 1.0f);
point.z = 1.0;
}
// Set a few outliers
(*cloud)[0].z = 2.0;
(*cloud)[3].z = -2.0;
(*cloud)[6].z = 4.0;
std::cerr << "Point cloud data: " << cloud->size () << " points" << std::endl;
for (const auto& point: *cloud)
std::cerr << " " << point.x << " "
<< point.y << " "
<< point.z << std::endl;
// 创建模型系数
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers (new pcl::PointIndices); // 创建索引
// Create the segmentation object
pcl::SACSegmentation seg; // 创建分割对象并设置参数
// Optional
seg.setOptimizeCoefficients (true);
// Mandatory
seg.setModelType (pcl::SACMODEL_PLANE);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setDistanceThreshold (0.01);
seg.setInputCloud (cloud);
seg.segment (*inliers, *coefficients); //获取点云索引和对应的系数
if (inliers->indices.size () == 0)
{
PCL_ERROR ("Could not estimate a planar model for the given dataset.\n");
return (-1);
}
std::cerr << "Model coefficients: " << coefficients->values[0] << " "
<< coefficients->values[1] << " "
<< coefficients->values[2] << " "
<< coefficients->values[3] << std::endl;
std::cerr << "Model inliers: " << inliers->indices.size () << std::endl;
for (const auto& idx : inliers->indices)
{
std::cerr << idx << " " << cloud->points[idx].x << " "
<< cloud->points[idx].y << " "
<< cloud->points[idx].z << std::endl;
auto& point = cloud->points[idx];
inliers_points->emplace_back(point);
}
// 创建 PCL 可视化对象
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
// 设置显示点云的颜色为红色
pcl::visualization::PointCloudColorHandlerCustom color1(cloud, 255, 0, 0);
pcl::visualization::PointCloudColorHandlerCustom color2(inliers_points, 0, 255, 0);
// 添加点云数据到可视化对象并设置颜色
viewer.addPointCloud(cloud, color1, "cloud");
viewer.addPointCloud(inliers_points, color2, "inliers_points");
// 设置点云数据的显示参数
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "cloud");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "inliers_points");
// 添加坐标轴到可视化对象
viewer.addCoordinateSystem(2.0); // 默认1.0为坐标轴的尺寸
// 显示点云数据
while (!viewer.wasStopped())
{
viewer.spinOnce();
}
return (0);
}
本小节举例说明了如何采用随机采样一致性估计从带有噪声的点云中提取一个圆柱体模型,整个程序处理流程如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef pcl::PointXYZ PointT;
int
main ()
{
// All the objects needed
pcl::PCDReader reader; // pcd读对象
pcl::PassThrough pass; // 直通滤波对象
pcl::NormalEstimation ne; // 法向量估计对象
pcl::SACSegmentationFromNormals seg; // 分割对象
pcl::PCDWriter writer; // pcd写对象
pcl::ExtractIndices extract; // 点云索引对象
pcl::ExtractIndices extract_normals; // 法向量索引对象
pcl::search::KdTree::Ptr tree (new pcl::search::KdTree ()); // kd树对象
// Datasets
pcl::PointCloud::Ptr cloud (new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_filtered (new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_normals (new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_filtered2 (new pcl::PointCloud);
pcl::PointCloud::Ptr cloud_normals2 (new pcl::PointCloud);
pcl::ModelCoefficients::Ptr coefficients_plane (new pcl::ModelCoefficients), coefficients_cylinder (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers_plane (new pcl::PointIndices), inliers_cylinder (new pcl::PointIndices);
// 读取点云数据
reader.read ("table_scene_mug_stereo_textured.pcd", *cloud);
std::cerr << "PointCloud has: " << cloud->size () << " data points." << std::endl;
// Build a passthrough filter to remove spurious NaNs and scene background
// 直通滤波出想要的点云,保留z轴0到1.5m的点云数据到cloud_filtered对象中,其他都删除
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");
pass.setFilterLimits (0, 1.5);
pass.filter (*cloud_filtered);
std::cerr << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std::endl;
// Estimate point normals
// 估计cloud_filtered的法向量
ne.setSearchMethod (tree);
ne.setInputCloud (cloud_filtered);
ne.setKSearch (50);
ne.compute (*cloud_normals);
// Create the segmentation object for the planar model and set all the parameters
// 为平面模型创建分割对象并设置所有参数
seg.setOptimizeCoefficients (true);
seg.setModelType (pcl::SACMODEL_NORMAL_PLANE);
seg.setNormalDistanceWeight (0.1);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setMaxIterations (100);
seg.setDistanceThreshold (0.03);
seg.setInputCloud (cloud_filtered);
seg.setInputNormals (cloud_normals);
// Obtain the plane inliers and coefficients
// 获取平面离群值和系数
seg.segment (*inliers_plane, *coefficients_plane);
std::cerr << "Plane coefficients: " << *coefficients_plane << std::endl;
// Extract the planar inliers from the input cloud
// 提取平面内的点云
extract.setInputCloud (cloud_filtered);
extract.setIndices (inliers_plane);
extract.setNegative (false);
// Write the planar inliers to disk
// 把平面点云保存到磁盘
pcl::PointCloud::Ptr cloud_plane (new pcl::PointCloud ());
extract.filter (*cloud_plane);
std::cerr << "PointCloud representing the planar component: " << cloud_plane->size () << " data points." << std::endl;
writer.write ("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false);
// Remove the planar inliers, extract the rest
// 删除平面点云,提取剩余的点云到cloud_filtered2
extract.setNegative (true);
extract.filter (*cloud_filtered2);
// 提取剩余的法向量
extract_normals.setNegative (true);
extract_normals.setInputCloud (cloud_normals);
extract_normals.setIndices (inliers_plane);
extract_normals.filter (*cloud_normals2);
// Create the segmentation object for cylinder segmentation and set all the parameters
// 创建分割圆柱体的对象,并设置参数
seg.setOptimizeCoefficients (true);
seg.setModelType (pcl::SACMODEL_CYLINDER);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setNormalDistanceWeight (0.1);
seg.setMaxIterations (10000);
seg.setDistanceThreshold (0.05);
seg.setRadiusLimits (0, 0.1);
seg.setInputCloud (cloud_filtered2);
seg.setInputNormals (cloud_normals2);
// Obtain the cylinder inliers and coefficients
seg.segment (*inliers_cylinder, *coefficients_cylinder);
std::cerr << "Cylinder coefficients: " << *coefficients_cylinder << std::endl;
// Write the cylinder inliers to disk
extract.setInputCloud (cloud_filtered2);
extract.setIndices (inliers_cylinder);
extract.setNegative (false);
pcl::PointCloud::Ptr cloud_cylinder (new pcl::PointCloud ());
extract.filter (*cloud_cylinder);
if (cloud_cylinder->points.empty ())
std::cerr << "Can't find the cylindrical component." << std::endl;
else
{
std::cerr << "PointCloud representing the cylindrical component: " << cloud_cylinder->size () << " data points." << std::endl;
writer.write ("table_scene_mug_stereo_textured_cylinder.pcd", *cloud_cylinder, false);
}
return (0);
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int
main ()
{
// Read in the cloud data
pcl::PCDReader reader;
pcl::PointCloud::Ptr cloud (new pcl::PointCloud), cloud_f (new pcl::PointCloud);
reader.read ("table_scene_lms400.pcd", *cloud);
std::cout << "PointCloud before filtering has: " << cloud->size () << " data points." << std::endl; //*
// Create the filtering object: downsample the dataset using a leaf size of 1cm
pcl::VoxelGrid vg;
pcl::PointCloud::Ptr cloud_filtered (new pcl::PointCloud);
vg.setInputCloud (cloud);
vg.setLeafSize (0.01f, 0.01f, 0.01f);
vg.filter (*cloud_filtered);
std::cout << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std::endl; //*
// Create the segmentation object for the planar model and set all the parameters
pcl::SACSegmentation seg;
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
pcl::PointCloud::Ptr cloud_plane (new pcl::PointCloud ());
pcl::PCDWriter writer;
seg.setOptimizeCoefficients (true);
seg.setModelType (pcl::SACMODEL_PLANE);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setMaxIterations (100);
seg.setDistanceThreshold (0.02);
int nr_points = (int) cloud_filtered->size ();
while (cloud_filtered->size () > 0.3 * nr_points)
{
// Segment the largest planar component from the remaining cloud
seg.setInputCloud (cloud_filtered);
seg.segment (*inliers, *coefficients);
if (inliers->indices.size () == 0)
{
std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
break;
}
// Extract the planar inliers from the input cloud
pcl::ExtractIndices extract;
extract.setInputCloud (cloud_filtered);
extract.setIndices (inliers);
extract.setNegative (false);
// Get the points associated with the planar surface
extract.filter (*cloud_plane);
std::cout << "PointCloud representing the planar component: " << cloud_plane->size () << " data points." << std::endl;
// Remove the planar inliers, extract the rest
extract.setNegative (true);
extract.filter (*cloud_f);
*cloud_filtered = *cloud_f;
}
// Creating the KdTree object for the search method of the extraction
pcl::search::KdTree::Ptr tree (new pcl::search::KdTree);
tree->setInputCloud (cloud_filtered);
std::vector cluster_indices;
pcl::EuclideanClusterExtraction ec;
ec.setClusterTolerance (0.02); // 2cm
ec.setMinClusterSize (100);
ec.setMaxClusterSize (25000);
ec.setSearchMethod (tree);
ec.setInputCloud (cloud_filtered);
ec.extract (cluster_indices);
int j = 0;
for (std::vector::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it)
{
pcl::PointCloud::Ptr cloud_cluster (new pcl::PointCloud);
for (const auto& idx : it->indices)
cloud_cluster->push_back ((*cloud_filtered)[idx]); //*
cloud_cluster->width = cloud_cluster->size ();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
std::cout << "PointCloud representing the Cluster: " << cloud_cluster->size () << " data points." << std::endl;
std::stringstream ss;
ss << "cloud_cluster_" << j << ".pcd";
writer.write (ss.str (), *cloud_cluster, false); //*
j++;
}
return (0);
}
pcl::EuclideanClusterExtraction ec;
因为点云是PointXYZ类型的,所以这里用PointXYZ创建一个欧氏聚类对象,并设置提取的参数和变量。
ec.setClusterTolerance(0.02); // 设置临近搜索的搜索半径(搜索容差)为2cm
设置一个合适的聚类搜索半径 ClusterTolerance,如果搜索半径取一个非常小的值,那么一个实际独立的对象就会被分割为多个聚类;如果将值设置得太高,那么多个对象就会被分割为一个聚类,所以需要进行测试找出最合适的ClusterTolerance.
ec.setMinClusterSize(100); // 每个簇(集群)的最小大小
ec.setMaxClusterSize(25000); // 每个簇(集群)的最大大小
本例用两个参数来限制找到的聚类 :