PCL学习笔记(三十一)-- 基于超体素的点云分割

一、简介

        人类视觉感知到的图像信息并不是从某一个孤立的像素点得到的,而是从由大量像素点组成而成的区域得到的,孤立的单一像素点并没有具体的实际意义,只有许多像素点组合在一起才对人类的视觉感知有意义。可见,像素并不是视觉感知的着重点。在这种需求下,二维图像处理领域,产生了“超像素”的概念。所谓超像素,是由许多像素点构成的小区域,这些像素点在位置上是相邻的,并且在某些特征(图像的亮度、颜色、纹理等特征)存在一定的相似性,这些小区域大都没有破坏图像的边界信息,而且还保留了对图像进行进一步分割的有效信息。现在超像素越来越被广泛地应用在计算机视觉领域中,并且作为图像分割和模式识别的初始阶段,最根本的原因是:一方面是使用了超像素后可以有效地减少图像局部信息的冗余,使图像处理的复杂度和运算量大大降低;另一方面是基于像素级的传统的图像处理方法,也不能准确定位目标区域的边界,只能给出一个大概的位置。

        超像素分割算法是将图像中的像素分割成符合目标物体边界的语义区域。基于图的算法,比如之所以MRF和CRF变得流行起来了,是因为该方法可以较好地融合高层语义只是和底层图像特征,消除高计算量的像素级图操作,而是转向中等级推理框架,该框架并不直接处理像素,而是使用像素群体,该像素群体被称作超像素。利用过分割的小区域,通过局部低层特征形成超像素。注意这里的像素和点云中的点对应,区别在于,点云一般是无序的,而像素都是有序的,不过都可以理解为对空间的一种离散采样表示。

        超体素有一些非常重要的特征,其中,超体素避免跨越物体边界是其最重要的特征。因为违反物体边界会降低后续分类的准确性。另外一种有用的特性是分割后超像素的规律分布,因为这回为后续合并优化分割步骤产生简单的图。

        pcl::SupervoxelClustering实现的方法是VCCS(Voxel Cloud Connectivity Segmentation)方法,它属于一种超像素方法,该方法可以产生3D点云数据的像素级分割,获得的分割结果被称为超体素。在处理物体边界方面,超体素要优于现有的基于二维图像的方法。同时,该方法保持较高的效率,利用空间八叉树结构,VCCS使用k-均值聚类的区域增长来直接对点云机型超体素分割。得到的超体素有两个重要特性:第一,在3D空间内,均匀分布,该特性,通过在点云空间中均匀设定种子达到;第二,除非体素空间上是相连的,否则超体素不能跨越边界。利用八叉树结构,可以判断叶节点是否相邻。在体素化的3D空间内,超体素保持相邻关系,是指这些体素之间,分享共用的面、边和顶点。

        利用八叉树的R_voxel近邻体素可以有效地维护超体素的邻接图(Adjacency Graph),其中,R_voxel制定了八叉树叶节点的分辨率。该邻接图既用于生成时的区域生长过程,同时也可以确定超体素之间的邻接关系。

        VCCS是一种区域生长算法,在空间分辨率为R_seed的三维空间网格下,算法对均匀分布在空格键的种子点进行增长形成超体素。为了提高效率,VCCS没有进行全局搜索,只考虑以种子为中心的R_seed半径以内的点。

        种子点的扩展由特征距离决定,该特征距离考虑了空间、颜色和法向量的特征空间计算。空间是通过种子分辨率归一化实现,颜色距离D_c是归一化RGB空间下的欧几里得距离,法向量距离D_n是测量种子于候选点之间表面法向量的角度。

D = \sqrt{w_{c}D_{c}^2 + \frac{w_{s}D_{s}^2}{3R^{2}_{seed}} + w_{n}D_{n}^2}

        上式是超体素聚类分割中所用的距离公式,其中w_c,w_s,和w_n分别是颜色,空间和法向量的权重,这些参数可以由用户在调用PCL中该方法实现时进行设置。

        超体素迭代产生,迭代过程利用了局部k均值聚类同时考虑连通性和流向。大体过程如下:从离聚类中心的最近的体素开始,想着邻近体素流动,利用上述的距离公式对邻近体素进行评价,计算这些邻近体素与超体素中心的特征距离。如果距离是最小的,标记该体素属于该超体素,并在邻接图中,添加该体素的相邻体素到这个标签的搜索序列。接着迭代处理下一个超体素,从超体素中心的每次向外迭代定义为是所有超体素的同一时间层次。以种子体素为中心向外进行迭代,直到到达每个体素的搜索体积的边缘(或者没有其他的邻近点可以遍历)。

二、代码分析

    1)输入点云,并定义相关参数:

	PointCloudT::Ptr cloud = pcl::make_shared <PointCloudT> ();
	pcl::console::print_highlight ("Loading point cloud...\n");
	if (pcl::io::loadPCDFile<PointT> (argv[1], *cloud))
	{
		pcl::console::print_error ("Error loading cloud file!\n");
		return (1);
	}
	cout<<"point size of input: "<<cloud->size()<<endl;

	bool disable_transform = pcl::console::find_switch (argc, argv, "--NT");
	//体素大小,空间八叉树的分辨率,类kinect或xtion获取的数据0.008左右合适
	float voxel_resolution = 0.008f;
	bool voxel_res_specified = pcl::console::find_switch (argc, argv, "-v");
	if (voxel_res_specified)
		pcl::console::parse (argc, argv, "-v", voxel_resolution);

	//种子的分辨率,一般可设置为体素分辨率的50倍以上
	float seed_resolution = 0.1f;
	bool seed_res_specified = pcl::console::find_switch (argc, argv, "-s");
	if (seed_res_specified)
		pcl::console::parse (argc, argv, "-s", seed_resolution);

	//针对分割场景,如果分割场景中各个物体之间的颜色特征差异明显,可以设置较大
	float color_importance = 0.2f;
	if (pcl::console::find_switch (argc, argv, "-c"))
		pcl::console::parse (argc, argv, "-c", color_importance);

	//设置较大且其他影响较小时,基本按照空间分辨率来决定体素分割
	float spatial_importance = 0.4f;
	if (pcl::console::find_switch (argc, argv, "-z"))
		pcl::console::parse (argc, argv, "-z", spatial_importance);

	//针对分割场景,如果分割场景中各个物体连通处的法线特征差异明显,可以设置较大,但在实际使用中,需要针对数据的结构适当考虑,发现估计的准确性等因素
	float normal_importance = 1.0f;
	if (pcl::console::find_switch (argc, argv, "-n"))
		pcl::console::parse (argc, argv, "-n", normal_importance);

    2)使用SupervoxelClustering类来负责聚类过程:

	pcl::SupervoxelClustering<PointT> super (voxel_resolution, seed_resolution);
	if (disable_transform)
		super.setUseSingleCameraTransform (false);
	super.setInputCloud (cloud);
	super.setColorImportance (color_importance);
	super.setSpatialImportance (spatial_importance);
	super.setNormalImportance (normal_importance);

    3)然后初始化用于提取超体素的相关数据结构,并运行算法。该数据结构supervoxel_clusters是以标签为键值存储超体素共享指针的映射容器。超提速Supervoxels是一个数据结构,其包含有数据域:normal_根据超体素中的体素集合计算的法向量;centorid_超体素的质心;voxels_在超体素中的体素中心对应的点云;normals_对于超体素的法向量:

	std::map <uint32_t, pcl::Supervoxel<PointT>::Ptr > supervoxel_clusters;
	//该单映射容器以标签为键值存储所有超体素
	pcl::console::print_highlight ("Extracting supervoxels!\n");
	super.extract (supervoxel_clusters);
	pcl::console::print_info ("Found %d supervoxels\n", supervoxel_clusters.size ());

    4)利用SupervoxelClustering的以西而输出接口来对相关数据结果进行可视化,voxel_centroid_cloud包含根据八叉树结构得到体素质心(就是对原始点云的基于体素的下采样)。根据超体素分割结果对voxel_centroid_cloud进行添加标签,并赋予随机颜色存储在colored_voxel_cloud,sv_normal_cloud是包含超体素法向量的点云数据:

	boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("点云库PCL学习教程第二版-超体素分割"));
	viewer->setBackgroundColor (1,1,1);

	PointCloudT::Ptr voxel_centroid_cloud = super.getVoxelCentroidCloud ();
	cout<<"voxel centroids: "<<voxel_centroid_cloud->size()<<endl;

    5)下面为了可视化超体素之间形成的图结构,调用getSupervoxelAdjacency读取处多重映射容器supervoxel_adjacency构造的邻接图:

	pcl::console::print_highlight ("Getting supervoxel adjacency\n");
	std::multimap<uint32_t, uint32_t> supervoxel_adjacency;
	super.getSupervoxelAdjacency (supervoxel_adjacency);

    6)然后,通过迭代多重映射,遍历supervoxel——adjacency中的每个超体素,并遍历当前超体素supervoxel_label的相邻超体素,同时以其相邻超体素的中心为点集构造点云adjacent_supervoxel_centers,用于后续可视化:

	//遍历多重映射容器构造邻接图
	std::multimap<uint32_t,uint32_t>::iterator label_itr = supervoxel_adjacency.begin ();
	for ( ; label_itr != supervoxel_adjacency.end (); )
	{
		//获取标签值
		uint32_t supervoxel_label = label_itr->first;
		//根据标签索引到该超体素
		pcl::Supervoxel<PointT>::Ptr supervoxel = supervoxel_clusters.at (supervoxel_label);

		//遍历该超体素相邻超体素并以其相邻超体素中心为点集构造点云,用于后续可视化,这里的相邻超体素在多重映射容器中具有相同的键值
		PointCloudT adjacent_supervoxel_centers;
		std::multimap<uint32_t,uint32_t>::iterator adjacent_itr = supervoxel_adjacency.equal_range (supervoxel_label).first;
		for ( ; adjacent_itr!=supervoxel_adjacency.equal_range (supervoxel_label).second; ++adjacent_itr)
		{
			pcl::Supervoxel<PointT>::Ptr neighbor_supervoxel = supervoxel_clusters.at (adjacent_itr->second);
			adjacent_supervoxel_centers.push_back (neighbor_supervoxel->centroid_);
		}
		//
		std::stringstream ss;
		ss << "supervoxel_" << supervoxel_label;
		//cout<<ss.str()<<endl;
		//绘制该超体素与其相邻超体素子图
		addSupervoxelConnectionsToViewer (supervoxel->centroid_, adjacent_supervoxel_centers, ss.str (), viewer);
		//使迭代器指向下一个标签。
		label_itr = supervoxel_adjacency.upper_bound (supervoxel_label);
	}

    7)整体代码如下:

#include <pcl/console/parse.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/segmentation/supervoxel_clustering.h>

// Types
typedef pcl::PointXYZRGBA PointT;
typedef pcl::PointCloud<PointT> PointCloudT;
typedef pcl::PointNormal PointNT;
typedef pcl::PointCloud<PointNT> PointNCloudT;
typedef pcl::PointXYZL PointLT;
typedef pcl::PointCloud<PointLT> PointLCloudT;

void addSupervoxelConnectionsToViewer (PointT &supervoxel_center,
	PointCloudT &adjacent_supervoxel_centers,
	std::string supervoxel_name,
	boost::shared_ptr<pcl::visualization::PCLVisualizer> & viewer);


int
	main (int argc, char ** argv)
{
	if (argc < 2)
	{
		pcl::console::print_error ("Syntax is: %s <pcd-file> \n "
			"--NT Dsables the single cloud transform \n"
			"-v <voxel resolution>\n-s <seed resolution>\n"
			"-c <color weight> \n-z <spatial weight> \n"
			"-n <normal_weight>\n", argv[0]);
		return (1);
	}


	PointCloudT::Ptr cloud = pcl::make_shared <PointCloudT> ();
	pcl::console::print_highlight ("Loading point cloud...\n");
	if (pcl::io::loadPCDFile<PointT> (argv[1], *cloud))
	{
		pcl::console::print_error ("Error loading cloud file!\n");
		return (1);
	}
	cout<<"point size of input: "<<cloud->size()<<endl;

	bool disable_transform = pcl::console::find_switch (argc, argv, "--NT");
	//体素大小,空间八叉树的分辨率,类kinect或xtion获取的数据0.008左右合适
	float voxel_resolution = 0.008f;
	bool voxel_res_specified = pcl::console::find_switch (argc, argv, "-v");
	if (voxel_res_specified)
		pcl::console::parse (argc, argv, "-v", voxel_resolution);

	//种子的分辨率,一般可设置为体素分辨率的50倍以上
	float seed_resolution = 0.1f;
	bool seed_res_specified = pcl::console::find_switch (argc, argv, "-s");
	if (seed_res_specified)
		pcl::console::parse (argc, argv, "-s", seed_resolution);

	//针对分割场景,如果分割场景中各个物体之间的颜色特征差异明显,可以设置较大
	float color_importance = 0.2f;
	if (pcl::console::find_switch (argc, argv, "-c"))
		pcl::console::parse (argc, argv, "-c", color_importance);

	//设置较大且其他影响较小时,基本按照空间分辨率来决定体素分割
	float spatial_importance = 0.4f;
	if (pcl::console::find_switch (argc, argv, "-z"))
		pcl::console::parse (argc, argv, "-z", spatial_importance);

	//针对分割场景,如果分割场景中各个物体连通处的法线特征差异明显,可以设置较大,但在实际使用中,需要针对数据的结构适当考虑,发现估计的准确性等因素
	float normal_importance = 1.0f;
	if (pcl::console::find_switch (argc, argv, "-n"))
		pcl::console::parse (argc, argv, "-n", normal_importance);

	//  //
	// This is how to use supervoxels
	//  //

	pcl::SupervoxelClustering<PointT> super (voxel_resolution, seed_resolution);
	if (disable_transform)
		super.setUseSingleCameraTransform (false);
	super.setInputCloud (cloud);
	super.setColorImportance (color_importance);
	super.setSpatialImportance (spatial_importance);
	super.setNormalImportance (normal_importance);

	std::map <uint32_t, pcl::Supervoxel<PointT>::Ptr > supervoxel_clusters;
	//该单映射容器以标签为键值存储所有超体素
	pcl::console::print_highlight ("Extracting supervoxels!\n");
	super.extract (supervoxel_clusters);
	pcl::console::print_info ("Found %d supervoxels\n", supervoxel_clusters.size ());

	boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("点云库PCL学习教程第二版-超体素分割"));
	viewer->setBackgroundColor (1,1,1);

	PointCloudT::Ptr voxel_centroid_cloud = super.getVoxelCentroidCloud ();
	cout<<"voxel centroids: "<<voxel_centroid_cloud->size()<<endl;
	if(0)
	{//对于体素中心的可视化和保存,基本就是对原始数据的空间均匀下采样
		viewer->addPointCloud<PointT>(voxel_centroid_cloud,"voxel centroids");
		pcl::io::savePCDFile("voxel_centroids.pcd",*voxel_centroid_cloud);
		viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,4, "voxel centroids");
		viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.5, "voxel centroids");
	}


	PointLCloudT::Ptr labeled_voxel_cloud = super.getLabeledVoxelCloud ();
	if(1)
	{//超体素分割结果显示与保存
		pcl::io::savePCDFile("labeled_voxels.pcd",*labeled_voxel_cloud);
		viewer->addPointCloud (labeled_voxel_cloud, "labeled voxels");
		cout<<"labeled voxels: "<<labeled_voxel_cloud->size()<<endl;
		viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,3, "labeled voxels");
		// viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.8, "labeled voxels");
	}

	PointNCloudT::Ptr sv_normal_cloud = super.makeSupervoxelNormalCloud (supervoxel_clusters);
	//
	if(0)//超体素对应的法线特征可视化
		viewer->addPointCloudNormals<pcl::PointNormal> (sv_normal_cloud,1,0.05f, "supervoxel_normals");

	pcl::console::print_highlight ("Getting supervoxel adjacency\n");
	std::multimap<uint32_t, uint32_t> supervoxel_adjacency;
	super.getSupervoxelAdjacency (supervoxel_adjacency);
	cout<<"size of supervoxel_adjacency: "<<supervoxel_adjacency.size()<<endl;

	//遍历多重映射容器构造邻接图
	std::multimap<uint32_t,uint32_t>::iterator label_itr = supervoxel_adjacency.begin ();
	for ( ; label_itr != supervoxel_adjacency.end (); )
	{
		//获取标签值
		uint32_t supervoxel_label = label_itr->first;
		//根据标签索引到该超体素
		pcl::Supervoxel<PointT>::Ptr supervoxel = supervoxel_clusters.at (supervoxel_label);

		//遍历该超体素相邻超体素并以其相邻超体素中心为点集构造点云,用于后续可视化,这里的相邻超体素在多重映射容器中具有相同的键值
		PointCloudT adjacent_supervoxel_centers;
		std::multimap<uint32_t,uint32_t>::iterator adjacent_itr = supervoxel_adjacency.equal_range (supervoxel_label).first;
		for ( ; adjacent_itr!=supervoxel_adjacency.equal_range (supervoxel_label).second; ++adjacent_itr)
		{
			pcl::Supervoxel<PointT>::Ptr neighbor_supervoxel = supervoxel_clusters.at (adjacent_itr->second);
			adjacent_supervoxel_centers.push_back (neighbor_supervoxel->centroid_);
		}
		//
		std::stringstream ss;
		ss << "supervoxel_" << supervoxel_label;
		//cout<<ss.str()<<endl;
		//绘制该超体素与其相邻超体素子图
		addSupervoxelConnectionsToViewer (supervoxel->centroid_, adjacent_supervoxel_centers, ss.str (), viewer);
		//使迭代器指向下一个标签。
		label_itr = supervoxel_adjacency.upper_bound (supervoxel_label);
	}

	while (!viewer->wasStopped ())
	{
		viewer->spinOnce();
	}
	return (0);
}

void
	addSupervoxelConnectionsToViewer (PointT &supervoxel_center,
	PointCloudT &adjacent_supervoxel_centers,
	std::string supervoxel_name,
	boost::shared_ptr<pcl::visualization::PCLVisualizer> & viewer)
{

	int i=0;
	//Iterate through all adjacent points, and add a center point to adjacent point pair
	PointCloudT::iterator adjacent_itr = adjacent_supervoxel_centers.begin ();
	for ( ; adjacent_itr != adjacent_supervoxel_centers.end (); ++adjacent_itr)
	{
		std::stringstream ss;
		ss<<supervoxel_name<<i;
		viewer->addLine(supervoxel_center,*adjacent_itr,ss.str());

		viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH,3,ss.str());
		viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR,0,255,0,ss.str());
		ss<<supervoxel_name<<i;
		viewer->addSphere(supervoxel_center,0.008,0,0,255,ss.str());
		viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_SHADING,pcl::visualization::PCL_VISUALIZER_SHADING_GOURAUD,ss.str());
		//viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_OPACITY,0.9,ss.str());
		i++;
	}

}


三、编译结果

  • 12
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值