三维重构(9):PCL点云分割

点云分割是根据空间、几何和纹理特征对点云进行划分,使得同一划分内的点云拥有相似的特征。PCL实现的分割算法是鲁棒性比较好的聚类分割 和基于随机采样一致性的分割。

聚类分割

点云库PCL学习教程:在聚类方法中每个点都与一个特征向量相关联,特征向量又包含了若干个几何或者辐射度量值。然后在特征空间中通过聚类的方法(如K-means,最大似然或模糊聚类)分割点云数据。聚类分割基本原理:考察 m m m个数据点,在 m m m维空间内定义点与点之间某种性质的亲疏聚类。设 m m m个点组成 n n n类,然后将具有最小距离的两类合为一类,并重新计算类与类之间的距离,迭代到任意两类之间的距离大于指定阈值,或者类的个数小于指定的数目,即完成分割。
使用聚类确实容易得到点符合哪些分布。加上点云的维度本身并不会特别高,所以计算起来应该挺容易的。

随机采样一致性(RANSAC)分割

点云库PCL学习教程:RANSAC算法分割是通过随机取样剔除局外点,构建一个仅由局内点数据组成的基本子集的过程。基本思想为:在进行参数估计时,不是不加区分地对待所有可能的输入数据,而是首先针对具体问题设计出一个判断准则模型,利用此判断准则迭代地剔除那些与所估计的参数不一致的输入数据,然后通过正确的输入数据来估计模型参数。
分割过程为:首先从输入的点云数据中随机选择一些点并计算用户给定模型的参数,对数据集中的所有点设置距离阈值,如果点到模型的距离在阈值之内,则点归为局内点,否则为局外点,然后统计所有局内点的个数,是否大于设定的阈值,若是,则用内点重新估计模型,作为模型输出并存储所有内点作为分割结果;若不是则与当前最大的局内点个数对比,如果大于则取代当前最大局内点个数,并存储当前的模型系数,然后进行迭代直到分个数用户满意的模型。

说实话,这个随机采样一致性是我想起来刚学习3D重构时看的一个SFM的算法。opencv中就包含一致性检验去除模型外的点。

PCL分割实例

PCL中Segmentation库提供了点云分割的基础数据结构和部分通用分割算法。库里的函数主要基于上述两种算法。这些算法适用于将多个空间分布独立的区域组成的点云分割成各自独立的点云子集,或者提取出特定模型的点云数据,以方便后续处理

实现平面模型分割

头文件:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

关于分割的代码

pcl::PointCloud cloud;
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
	pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
	//创建分割对象
	pcl::SACSegmentation seg;
	//可选设置
	seg.setOptimizeCoefficients(true);
	//必须设置
	seg.setModelType(pcl::SACMODEL_PLANE);//平面模型
	seg.setMethodType(pcl::SAC_RANSAC);//迭代方式
	seg.setDistanceThreshold(0.01);	//模型内外点阈值
	seg.setInputCloud(cloud.makeShared());	//输入点云
	seg.segment(*inliers, *coefficients);//执行分割操作,分割结果的点云的索引存储在inliers中

一般情况下,分割的代码需要根据分割结果决定是否迭代
然后将索引代表的点提取出来

pcl::ExtractIndices extract;//如定义:extract indices
    extract.setInputCloud (cloud_filtered);
    extract.setIndices (inliers);
    extract.setNegative (false);
    // Write the planar inliers to disk
    extract.filter (*cloud_plane);//将平面提取出来

三维重构(9):PCL点云分割_第1张图片
提取出的平面如图:
三维重构(9):PCL点云分割_第2张图片
可运行代码(有的代码会根据inliers的个数设定迭代条件,我这里没有添加迭代):

#include
#include
#include
#include
#include
#include
//滤波头文件
#include 
#include 
#include 
#include 
//分割头文件
#include 
#include 
#include 
#include 
#include 
#include 

int user_data;
using std::cout;


void viewerOneOff(pcl::visualization::PCLVisualizer& viewer) {
	viewer.setBackgroundColor(1.0, 0.5, 1.0);
}

int main()
{
	pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
	pcl::PointCloud cloud_2;
	/*pcl::PCLPointCloud2::Ptr cloud(new pcl::PCLPointCloud2());
	pcl::PCLPointCloud2::Ptr cloud_2(new pcl::PCLPointCloud2());
	*/
	pcl::PointCloud::Ptr
		cloud_filtered(new pcl::PointCloud);
	
	char strfilepath[256] = "F:\\Code_projects\\particles\\Clouds\\my_cloud2.pcd";
	//char strfilepath[256] = "..\Clouds\my_cloud2.pcd";

	if (-1 == pcl::io::loadPCDFile(strfilepath, *cloud)) {
		cout << "error input!" << endl;
		return -1;
	}
//	pcl::fromPCLPointCloud2(*cloud, *cloud_filtered);

	pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
	pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
	//创建分割对象
	pcl::SACSegmentation seg;
	//可选设置
	seg.setOptimizeCoefficients(true);
	//必须设置
	seg.setModelType(pcl::SACMODEL_PLANE);
	seg.setMethodType(pcl::SAC_RANSAC);
	seg.setDistanceThreshold(0.1);
	seg.setInputCloud(cloud);
	seg.segment(*inliers, *coefficients);
	if (inliers->indices.size() == 0)
	{
		PCL_ERROR("Could not estimate a planar model for the given dataset.");
		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;
	
	
	//分割indices
	pcl::ExtractIndices extract;
	extract.setInputCloud(cloud);
	extract.setIndices(inliers);
	extract.setNegative(false);

	// Write the planar inliers to disk
	extract.filter(*cloud_filtered);
	
	pcl::visualization::CloudViewer viewer("Cloud Viewer");
	//showCloud函数是同步的,在此处等待直到渲染显示为止
	viewer.showCloud(cloud_filtered);
	//该注册函数在可视化时只调用一次
	viewer.runOnVisualizationThreadOnce(viewerOneOff);
	//该注册函数在渲染输出时每次都调用
	
	while (!viewer.wasStopped())
	{
		//在此处可以添加其他处理
		user_data++;
	}

	
	
	system("pause");
	return 0;
}

实现圆柱体模型分割

#include
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef pcl::PointXYZ PointT;
int user_data;
using std::cout;


void viewerOneOff(pcl::visualization::PCLVisualizer& viewer) {
	viewer.setBackgroundColor(1.0, 0.5, 1.0);
}
int
main(int argc, char** argv)
{
	// All the objects needed
	pcl::PCDReader reader;
	pcl::PassThrough pass;
	pcl::NormalEstimation ne;
	pcl::SACSegmentationFromNormals seg;
	pcl::PCDWriter writer;
	pcl::ExtractIndices extract;
	pcl::ExtractIndices extract_normals;
	pcl::search::KdTree::Ptr tree(new pcl::search::KdTree());

	// 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);

	// Read in the cloud data
	char strfilepath[256] = "F:\\Code_projects\\particles\\Clouds\\table_scene_mug_stereo_textured.pcd";
	reader.read(strfilepath, *cloud);
	std::cerr << "PointCloud has: " << cloud->points.size() << " data points." << std::endl;

	// Build a passthrough filter to remove spurious NaNs
	pass.setInputCloud(cloud);
	pass.setFilterFieldName("z");
	pass.setFilterLimits(0, 1.5);
	pass.filter(*cloud_filtered);
	std::cerr << "PointCloud after filtering has: " << cloud_filtered->points.size() << " data points." << std::endl;

	// Estimate point normals
	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->points.size() << " data points." << std::endl;
	writer.write("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false);

	// Remove the planar inliers, extract the rest
	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
	{
		pcl::visualization::CloudViewer viewer("Cloud Viewer");
			//showCloud函数是同步的,在此处等待直到渲染显示为止
			viewer.showCloud(cloud_cylinder);
			//该注册函数在可视化时只调用一次
			viewer.runOnVisualizationThreadOnce(viewerOneOff);
			//该注册函数在渲染输出时每次都调用
			
			while (!viewer.wasStopped())
			{
				//在此处可以添加其他处理
				user_data++;
			}
	}
	return (0);
}

原始点云:
三维重构(9):PCL点云分割_第3张图片
分割得到的圆柱:
三维重构(9):PCL点云分割_第4张图片

实现欧氏聚类提取



#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int
main(int argc, char** argv)
{
	// Read in the cloud data
	pcl::PCDReader reader;
	pcl::PointCloud::Ptr cloud(new pcl::PointCloud), cloud_f(new pcl::PointCloud);
	char strfilepath[256] = "F:\\Code_projects\\particles\\Clouds\\table_scene_lms400.pcd";

	reader.read(strfilepath, *cloud);
	std::cout << "PointCloud before filtering has: " << cloud->points.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->points.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 i = 0, nr_points = (int)cloud_filtered->points.size();
	while (cloud_filtered->points.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);

		// Write the planar inliers to disk
		extract.filter(*cloud_plane);
		std::cout << "PointCloud representing the planar component: " << cloud_plane->points.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 (std::vector::const_iterator pit = it->indices.begin(); pit != it->indices.end(); pit++)
			cloud_cluster->points.push_back(cloud_filtered->points[*pit]); //*
		cloud_cluster->width = cloud_cluster->points.size();
		cloud_cluster->height = 1;
		cloud_cluster->is_dense = true;

		std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size() << " data points." << std::endl;
		std::stringstream ss;
		ss << "cloud_cluster_" << j << ".pcd";
		writer.write(ss.str(), *cloud_cluster, false); //*
		j++;
	}

	return (0);
}

分类结果分别展示:
三维重构(9):PCL点云分割_第5张图片

三维重构(9):PCL点云分割_第6张图片
三维重构(9):PCL点云分割_第7张图片
……
没有详细学习demo细节,从上述分类结果来看,聚类结果不如直接指定模型进行分割来得明确。

Summary

主要学习了点云分割的概念:将具有相似性质(如空间,纹理等)的点分为相同类别,区别于其他类别。
PCL分割的方法主要有两种:聚类分割和随机采样分割
从分类结果来看,聚类分割可以将点云分为若干类别,而随机采样分割需要指定相应的模型,所以随机采样分割具有更好的针对性。
具体算法的理解还需要更深一步的探索。

知识点和代码均参考:点云库PCL学习教程

你可能感兴趣的:(3D)