测试程序
table_scene_mug_stereo_textured.pcd
差异法线特征文件don.pcd
过滤掉差异法线强度小于threshold的点 don_filtered.pcd
don_cluster_0.pcd
don_cluster_1.pcd
properties
[pcl::NormalEstimationOMP::setNumberOfThreads] Parallelization is requested, but OpenMP is not available! Continuing without parallelization.
Calculating normals for scale...0.05
Calculating normals for scale...0.3
Calculating DoN...
Filtering out DoN mag <= 0.2...
Filtered Pointcloud: 34135 data points.
Clustering using EuclideanClusterExtraction with tolerance <= 0.5...
PointCloud representing the Cluster: 18278 data points.
PointCloud representing the Cluster: 15857 data points.
测试程序:参数---table_scene_mug_stereo_textured.pcd 0.05 0.3 0.2 0.5
基于法线差分割
在本教程中,我们将学习如何使用差异法特征,实现在 pcl::DifferenceOfNormalsEstimation
类中,对无组织点云的基于尺度的分割。
该算法对给定的输入点云执行基于尺度的分割,找到属于给定尺度参数的点。
理论入门
使用法线差进行分割
源码解析
这段代码是一个利用PCL(Point Cloud Library,点云库)进行点云数据处理的C++程序。程序功能是读取一个点云数据文件,并使用差异法线(Difference of Normals, DoN)技术来分割和分析点云。具体来说:
-
接收命令行参数,参数包括输入文件名、两个用于计算DoN的不同尺度(小尺度scale1和大尺度scale2)、阈值threshold和聚类半径segradius。
-
加载点云数据文件(PCD格式),并创建用于存储计算结果的点云结构。
-
根据点云是不是有序的,创建一个搜索树来辅助后续的邻域搜索。
-
计算指定尺度下的点云法线,分别是小尺度和大尺度的法线。
-
创建差异法线的估计器对象,设置输入的大、小尺度法线,并计算差异法线特征。
-
将计算得到的差异法线特征写入文件(don.pcd)。
-
使用阈值条件移除操作,过滤掉差异法线强度小于threshold的点,得到过滤后的点云。
-
写入过滤后的点云到文件(don_filtered.pcd)。
-
对过滤后的点云运用欧几里得聚类提取算法,根据给定的容忍度segradius进行聚类。
-
遍历分群结果,为每个群集保存点云数据到文件(don_cluster_#.pcd,#为群集编号)。
通过上述步骤,这段代码演示了如何在点云数据中应用差异法线滤镜和基于欧几里得距离的聚类方法来检测和分割结构。
这段代码主要技术点详细解释:
-
pcl::NormalEstimationOMP
用于多线程计算点云中各点的法线。 -
DifferenceOfNormalsEstimation
类计算差异法线(DoN)特征。 -
ConditionalRemoval
过滤点云中不满足特定条件的点。 -
EuclideanClusterExtraction
算法基于空间接近度对点云数据进行聚类。
综上,该代码的核心功能是使用DoN技术对点云数据进行特征增强和结构分割的过程。
cpp
/**
* @file don_segmentation.cpp
* Difference of Normals Example for PCL Segmentation Tutorials.
* 使用PCL进行分割教程的差异法线示例。
*
* @author Yani Ioannou
* @date 2012-09-24
*/
#include <string>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/search/organized.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/features/don.h>
using namespace pcl;
int
main (int argc, char *argv[])
{
///The smallest scale to use in the DoN filter.
///DoN滤波器中使用的最小规模。
double scale1;
///The largest scale to use in the DoN filter.
///DoN滤波器中使用的最大规模。
double scale2;
///The minimum DoN magnitude to threshold by
///按阈值的最小DoN幅度
double threshold;
///segment scene into clusters with given distance tolerance using euclidean clustering
///使用欧式聚类按给定距离容忍度将场景分割成簇
double segradius;
if (argc < 6)
{
std::cerr << "usage: " << argv[0] << " inputfile smallscale largescale threshold segradius" << std::endl;
exit (EXIT_FAILURE);
}
/// the file to read from.
/// 要读取的文件。
std::string infile = argv[1];
/// small scale
/// 小尺度
std::istringstream (argv[2]) >> scale1;
/// large scale
/// 大尺度
std::istringstream (argv[3]) >> scale2;
std::istringstream (argv[4]) >> threshold; // threshold for DoN magnitude
// DoN幅度的阈值
std::istringstream (argv[5]) >> segradius; // threshold for radius segmentation
// 半径分割的阈值
// Load cloud in blob format
// 以blob格式加载云
pcl::PCLPointCloud2 blob;
pcl::io::loadPCDFile (infile.c_str (), blob);
pcl::PointCloud<PointXYZRGB>::Ptr cloud (new pcl::PointCloud<PointXYZRGB>);
pcl::fromPCLPointCloud2 (blob, *cloud);
// Create a search tree, use KDTreee for non-organized data.
// 创建一个搜索树,对于非组织化数据使用KDTree。
pcl::search::Search<PointXYZRGB>::Ptr tree;
if (cloud->isOrganized ())
{
tree.reset (new pcl::search::OrganizedNeighbor<PointXYZRGB> ());
}
else
{
tree.reset (new pcl::search::KdTree<PointXYZRGB> (false));
}
// Set the input pointcloud for the search tree
// 为搜索树设置输入点云
tree->setInputCloud (cloud);
if (scale1 >= scale2)
{
std::cerr << "Error: Large scale must be > small scale!" << std::endl;
exit (EXIT_FAILURE);
}
// Compute normals using both small and large scales at each point
// 在每个点上使用小尺度和大尺度计算法线
pcl::NormalEstimationOMP<PointXYZRGB, PointNormal> ne;
ne.setInputCloud (cloud);
ne.setSearchMethod (tree);
/**
* NOTE: setting viewpoint is very important, so that we can ensure
* normals are all pointed in the same direction!
* 注意:设置视点非常重要,以确保所有法线都指向同一方向!
*/
ne.setViewPoint (std::numeric_limits<float>::max (), std::numeric_limits<float>::max (), std::numeric_limits<float>::max ());
// calculate normals with the small scale
// 使用小尺度计算法线
std::cout << "Calculating normals for scale..." << scale1 << std::endl;
pcl::PointCloud<PointNormal>::Ptr normals_small_scale (new pcl::PointCloud<PointNormal>);
ne.setRadiusSearch (scale1);
ne.compute (*normals_small_scale);
// calculate normals with the large scale
// 使用大尺度计算法线
std::cout << "Calculating normals for scale..." << scale2 << std::endl;
pcl::PointCloud<PointNormal>::Ptr normals_large_scale (new pcl::PointCloud<PointNormal>);
ne.setRadiusSearch (scale2);
ne.compute (*normals_large_scale);
// 创建DoN结果的输出点云
PointCloud<PointNormal>::Ptr doncloud (new pcl::PointCloud<PointNormal>);
copyPointCloud (*cloud, *doncloud); // 拷贝原始点云到DoN点云
std::cout << "Calculating DoN... " << std::endl;
// 创建DoN算子
pcl::DifferenceOfNormalsEstimation<PointXYZRGB, PointNormal, PointNormal> don;
don.setInputCloud (cloud); // 设置输入点云
don.setNormalScaleLarge (normals_large_scale); // 设置大尺度的法线
don.setNormalScaleSmall (normals_small_scale); // 设置小尺度的法线
// 如果DoN算子初始化失败,则退出程序
if (!don.initCompute ())
{
std::cerr << "Error: Could not initialize DoN feature operator" << std::endl;
exit (EXIT_FAILURE);
}
// 计算DoN特征
don.computeFeature (*doncloud);
// 保存DoN特征数据到PCD文件
pcl::PCDWriter writer;
writer.write<pcl::PointNormal> ("don.pcd", *doncloud, false);
// 根据幅度过滤点云数据
std::cout << "Filtering out DoN mag <= " << threshold << "..." << std::endl;
// 构建用于过滤的条件
pcl::ConditionOr<PointNormal>::Ptr range_cond (
new pcl::ConditionOr<PointNormal> ()
);
// 添加过滤条件,只保留曲率大于阈值的点
range_cond->addComparison (pcl::FieldComparison<PointNormal>::ConstPtr (
new pcl::FieldComparison<PointNormal> ("curvature", pcl::ComparisonOps::GT, threshold))
);
// 创建条件移除过滤器
pcl::ConditionalRemoval<PointNormal> condrem;
condrem.setCondition (range_cond); // 设置过滤条件
condrem.setInputCloud (doncloud); // 设置输入点云
pcl::PointCloud<PointNormal>::Ptr doncloud_filtered (new pcl::PointCloud<PointNormal>);
// 应用过滤器
condrem.filter (*doncloud_filtered);
// 更新为过滤后的点云
doncloud = doncloud_filtered;
// 保存过滤后的点云
std::cout << "Filtered Pointcloud: " << doncloud->size () << " data points." << std::endl;
writer.write<pcl::PointNormal> ("don_filtered.pcd", *doncloud, false);
// 使用欧氏聚类法进行聚类,容忍度小于等于segradius
std::cout << "Clustering using EuclideanClusterExtraction with tolerance <= " << segradius << "..." << std::endl;
// 创建欧氏聚类使用的搜索树
pcl::search::KdTree<PointNormal>::Ptr segtree (new pcl::search::KdTree<PointNormal>);
segtree->setInputCloud (doncloud); // 设置输入点云
std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<PointNormal> ec;
// 设置聚类容忍度、最小和最大聚类尺寸以及搜索方法
ec.setClusterTolerance (segradius);
ec.setMinClusterSize (50);
ec.setMaxClusterSize (100000);
ec.setSearchMethod (segtree);
ec.setInputCloud (doncloud);
// 进行聚类
ec.extract (cluster_indices);
int j = 0;
// 遍历所有聚类
for (const auto& cluster : cluster_indices)
{
// 创建每个聚类的点云
pcl::PointCloud<PointNormal>::Ptr cloud_cluster_don (new pcl::PointCloud<PointNormal>);
for (const auto& idx : cluster.indices)
{
// 添加点到聚类点云
cloud_cluster_don->points.push_back ((*doncloud)[idx]);
}
// 设置聚类点云的属性
cloud_cluster_don->width = cloud_cluster_don->size ();
cloud_cluster_don->height = 1;
cloud_cluster_don->is_dense = true;
// 保存聚类,每个聚类一个PCD文件
std::cout << "PointCloud representing the Cluster: " << cloud_cluster_don->size () << " data points." << std::endl;
std::stringstream ss;
ss << "don_cluster_" << j << ".pcd";
writer.write<pcl::PointNormal> (ss.str (), *cloud_cluster_don, false);
j++; // 聚类编号增加
}
}
本段代码实现了利用差分法线(Difference of Normals, DoN)方法对点云数据进行分割与提取的功能。首先通过加载点云数据,然后计算给定不同尺度的法线,以获取每个点在小尺度和大尺度下的法线变化。通过这些变化,计算DoN特征,然后依据设定的阈值过滤掉变化较小的区域,最后使用欧式聚类方法对剩下的点云数据进行聚类分割,形成有意义的集群,并将结果保存为文件。这个过程对于理解和分析复杂场景的三维结构非常有用。
cpp
pcl::fromPCLPointCloud2 (blob, *cloud);
go
尺度(小尺度scale1和大尺度scale2)、
阈值threshold和聚类半径segradius 一般取值范围是多少
css
pcl::search::Search<PointXYZRGB>::Ptr tree
cpp
pcl::search::OrganizedNeighbor<PointXYZRGB> ()
pcl::search::KdTree<PointXYZRGB> (false)
css
ne.setViewPoint (std::numeric_limits<float>::max (),
std::numeric_limits<float>::max (),
std::numeric_limits<float>::max ());
在PCL(Point Cloud Library)中,setViewPoint
函数通常用于设置点云中点的法线计算时的视点。默认情况下,点云的法线是相对于原点(0,0,0)计算的,这意味着法线的方向是基于假设观察者位于坐标系统的原点处。但在某些应用场景下,我们可能需要从不同的视点来计算或解释这些法线。这就是setViewPoint
函数的用途。
这段代码:
将视点设置为浮点数的最大值。std::numeric_limits<float>::max()
是C++标准库中定义的一个值,表示类型float
所能表示的最大正数。将视点设置到这样一个极端的位置,实质上等同于将视点设置到了"无限远"的地方。
从实际效果来看,当视点被设置到一个非常远的位置时,所有计算得到的法线大致上会被认为是相向的或平行的,因为从无限远处看,所有的点相对于观察者的位置变化可以忽略不计。这在一些特定的应用场景下可能是有用的,比如当我们不希望视点的位置影响法线方向时。
总之,这一操作通过设置一个极远的视点,用于确保计算出的法线不会因为视点的具体位置而发生变化,从而在某种程度上使法线计算具有"视点无关"的性质。这种处理方式可能对于全局特征计算或者当点云数据代表一个封闭物体而观察者假定在外部时特别有用。
cpp
pcl::DifferenceOfNormalsEstimation<PointXYZRGB, PointNormal, PointNormal> don;
markdown
opyPointCloud (*cloud, *doncloud); 这个是如何拷贝的,两个点云类型不同
踩坑笔记
css
错误 LNK2001 无法解析的外部符号 "public: void __cdecl pcl::EuclideanClusterExtraction<struct pcl::PointNormal>::extract(class std::vector<struct pcl::PointIndices,class std::allocator<struct pcl::PointIndices> > &)" (?extract@?$EuclideanClusterExtraction@UPointNormal@pcl@@@pcl@@QEAAXAEAV?$vector@UPointIndices@pcl@@V?$allocator@UPointIndices@pcl@@@std@@@std@@@Z) test V:\learn\PCL\pcl\examples\test\don_segmentation.obj 1
C/C++ 预处理器增加:PCL_NO_PRECOMPILE
参考网址
[1209.1759] 无组织点云中多尺度算子的法线差异 --- [1209.1759] Difference of Normals as a Multi-Scale Operator in Unorganized Point Clouds (arxiv.org)
https://ar5iv.labs.arxiv.org/html/1209.1759?_immersive_translate_auto_translate=1