目录
(1)PassThrough 直通滤波器
(2)VoxelGrid 基于体素网格下采样的滤波器
(3)StatisticalOutlierRemoval、ConditionalRemoval、RadiusOutlierRemoval 移除离群点滤波器
(4)ProjectInliers 点云投影到参数化模型滤波器
(5)ExtractIndices 提取点云子集滤波器
(6)CropHull 任意多边形内部点云提取滤波器
(1)PassThrough 直通滤波器
直通滤波器用于对点云数据某一个维度的数据进行过滤,比较简单,例如,下面的代码的功能是,只保留点云数据的所有z轴值在 [ 0.0, 1.0 ] 范围的点:
qDebug() << "PassThrough Filter Demo";
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 4;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 1.0;
index = 1;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 0.4;
index = 2;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 1.1;
index = 3;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 0.0;
qDebug() << "Points Count : " << clound->points.size();
for(int i = 0; i < clound->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound->points[i].x << ","
<< clound->points[i].y << ","
<< clound->points[i].z << " ) ";
}
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_out(new pcl::PointCloud<pcl::PointXYZ>());
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(clound);
pass.setFilterFieldName("z");
pass.setFilterLimits(0.0, 1.0); //闭区间[0.0, 1.0],保留该闭区间内的值
qDebug() << "Pass Limits : z - [0.0, 1.0]";
pass.filter(*clound_out);
qDebug() << "Points Filtered Count : " << clound_out->points.size();
for(int i = 0; i < clound_out->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound_out->points[i].x << ","
<< clound_out->points[i].y << ","
<< clound_out->points[i].z << " ) ";
}
该代码输出结果为:
(2)VoxelGrid 基于体素网格下采样的滤波器
VoxelGrid 滤波器是基于体素化网格方法对点云进行下采样,从而达到减少点的数量,减少点云数据,并同时保持点云的形状特征的滤波器。该滤波器在提高配准、曲面重建、形状识别等算法中非常实用。
在我的多篇PCL文章的一篇关于八叉树《PCL中的八叉树》的文章中,提到过,点云构建八叉树时,就是将点云空间按正方体进行切割,从而形成一个个小正方体组成的点云空间,这些个小正方体就是体素。该VoxelGrid滤波器也是采用类似方法,将点云控件切割成一个个不同的小立方体。但是,跟八叉树不同的是,VoxelGrid滤波器是将点云空间切割成一个个小的长方体,而不是八叉树采用的正方体。因此,VoxelGrid滤波器中的体素是长方体,其长宽高都是可以设置的。
另外,《PCL中的八叉树》的文章中提到对点云数据进行八叉树压缩时,会从每个含有点的体素中提取一个下采样点,这个点的提取方式可选,有2种方式,1种是根据体素内所有点计算得到,另1种是取体素的中心点。而在VoxelGrid滤波器进行滤波时,不是取体素的中心点,而是根据体素中所有点的重心来近似显示体素中其它点,即根据体素内所有点计算得到下采样点。
以下代码演示了 VoxelGrid 滤波器的使用方法:
qDebug() << "VoxelGrid Filter Demo";
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 6;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
clound->points[index].x = -1;
clound->points[index].y = -1;
clound->points[index].z = -1;
index = 1;
clound->points[index].x = 0.5;
clound->points[index].y = 0.5;
clound->points[index].z = 0.5;
index = 2;
clound->points[index].x = 1.5;
clound->points[index].y = 1.5;
clound->points[index].z = 1.5;
index = 3;
clound->points[index].x = 1.8;
clound->points[index].y = 1.8;
clound->points[index].z = 1.8;
index = 4;
clound->points[index].x = 1.9;
clound->points[index].y = 1.9;
clound->points[index].z = 1.9;
index = 5;
clound->points[index].x = 1.2;
clound->points[index].y = 1.2;
clound->points[index].z = 1.2;
qDebug() << "Points Count : " << clound->points.size();
for(int i = 0; i < clound->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound->points[i].x << ","
<< clound->points[i].y << ","
<< clound->points[i].z << " ) ";
}
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_out(new pcl::PointCloud<pcl::PointXYZ>());
pcl::VoxelGrid<pcl::PointXYZ> voxel_grid;
voxel_grid.setInputCloud(clound);
voxel_grid.setLeafSize(1.0f, 1.0f, 1.0f);//注:体素不一定是正方体,体素可以是长宽高不同的长方体
qDebug() << "Voxel Cuboid : (1.0, 1.0, 1.0)";
voxel_grid.filter(*clound_out);
qDebug() << "Points Filtered Count : " << clound_out->points.size();
for(int i = 0; i < clound_out->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound_out->points[i].x << ","
<< clound_out->points[i].y << ","
<< clound_out->points[i].z << " ) ";
}
以上代码的输出如下:
如上图所示,根据《PCL中的八叉树》的文章中提到的体素化形成的方法,结合本文提到的 VoxelGrid 滤波器的体素特点,得知,该滤波器会形成3个这样中心点,边长为1的,包含有点的正方体体素:(-0.5, -0.5, -0.5)、(0.5, 0.5, 0.5)和(1.5, 1.5, 1.5)。
中心点为(-0.5, -0.5, -0.5)的体素:只包含 Point 1;
中心点为(0.5, 0.5, 0.5)的体素:只包含 Point 2;
中心点为(1.5, 1.5, 1.5)的体素:包含 Point 3、4、5、6。
又由于 VoxelGrid 滤波器是根据体素中所有点的重心来近似显示体素中其它点来下采样,因此,中心点为(1.5, 1.5, 1.5)的体素下采样后得到点(1.6, 1.6, 1.6)。
(3)StatisticalOutlierRemoval、ConditionalRemoval 、RadiusOutlierRemoval 移除离群点滤波器
激光扫描通常会产生密度不均匀的点云数据集。另外,测量中的误差会产生稀疏的离群点。因此,点云算法通常要从一个点云数据集中移除测量噪声点(也就是离群点)。
(3.1) StatisticalOutlierRemoval 移除离群点滤波器
StatisticalOutlierRemoval 滤波器是一个基于统计学的离群点移除滤波器。
该滤波器的原理是,对点云中的每个点,计算它到它的所有临近点的平均距离。假设得到的结果是一个高斯分布,其形状由均值和标准差决定,平均距离在标准范围(由全局距离平均值和方差定义)之外的点,可以被定义为离群点并可以从数据集中除掉。
该滤波器滤波时,需要指定的参数有2个:
(1)查询点临近点数 K :通过 StatisticalOutlierRemoval::setMeanK(int) 设置;
这个参数跟我的PCL系列文章中的《PCL中 的 kd-tree》和《PCL中的八叉树》两篇文章中讲过的K近邻搜索中的 K 值含义一样,就是计算时只取查询点的 K 个临近点的距离为参数。
(2)判断是否为离群点的阈值:通过 StatisticalOutlierRemoval::setStddevMulThresh(double) 设置。
这个阈值就是查询点到 K 个临近点的距离平均值与总平均距离之差,与方差的比值。
根据以上分析,下面我分别用2种方式滤波,一种是模拟 StatisticalOutlierRemoval 滤波器的实现原理自行滤波,另一种是直接调用 StatisticalOutlierRemoval 滤波器进行滤波,看两者的结果是不是一样。
以下,先模拟 StatisticalOutlierRemoval 滤波器进行滤波,代码如下:
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 67;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
for(int x = 0; x < 8; ++x)
{
for(int y = 0; y < 8; ++y)
{
index = x * 8 + y;
clound->points[index].x = x;
clound->points[index].y = y;
clound->points[index].z = 0;
}
}
index = 64;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 4;
index = 65;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 4;
index = 66;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 5;
//取每个点到其临近的63个点的距离的高斯分布的标准差
int K = 63;
//计算每个点到其临近点的平均距离
std::vector<float> diatance_avgs;
for(int i = 0; i < clound->points.size(); ++i)
{
//获取临近的63个点的索引和距离
std::vector<int> indies;
std::vector<float> distances;
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(clound);
kdtree.nearestKSearch(i, K, indies, distances);
//计算平均距离
float diatance_avg = 0;
for(int n = 0; n < distances.size(); ++n)
diatance_avg += distances.at(n);
diatance_avg /= distances.size();
/*qDebug() << " Point : " << (i + 1) << " ( "
<< clound->points[i].x << ","
<< clound->points[i].y << ","
<< clound->points[i].z << " ) "
<< " ; diatance_avg : " << QString("%1").arg(diatance_avg, 8, 'f', 6, '0');
*/
diatance_avgs.push_back(diatance_avg);
}
//计算平均距离
float diatance_avg_total = 0;
for(int n = 0; n < diatance_avgs.size(); ++n)
diatance_avg_total += diatance_avgs.at(n);
diatance_avg_total /= diatance_avgs.size();
//计算高斯分布的标准差
float delta = 0;
for(int n = 0; n < diatance_avgs.size(); ++n)
delta += std::pow(diatance_avgs.at(n) - diatance_avg_total, 2);
delta /= diatance_avgs.size();
delta = std::sqrt(delta);
qDebug() << QString::fromUtf8("总平均距离 : %1 ; 方差 : %2")
.arg(diatance_avg_total, 8, 'f', 6, '0')
.arg(delta, 8, 'f', 6, '0');
//判断离群点的阈值
float mul_thres = 2.5f;//3.6f;//4.9f;//5.9f;//点的距离超出平均距离的倍数
for(int n = 0; n < diatance_avgs.size(); ++n)
{
float diff = std::abs(diatance_avgs.at(n) - diatance_avg_total);
float mul = 0;
if(delta > 0)
mul = diff / delta;
qDebug() << QString::fromUtf8(" Point : %1 (%2, %3, %4) ; 平均距离 : %5 ; 倍数 : %6 ; 是否离群点 : %7")
.arg(n + 1)
.arg(clound->points[n].x)
.arg(clound->points[n].y)
.arg(clound->points[n].z)
.arg(diatance_avgs.at(n), 8, 'f', 6, '0')
.arg(mul, 4, 'f', 2, '0')
.arg(mul >= mul_thres ? "Y" : "N");
}
从结果看到,模拟代码成功找到了3个离群点(Point 65、66和67)。
下面,再调用 StatisticalOutlierRemoval 滤波器,代码如下:
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 67;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
for(int x = 0; x < 8; ++x)
{
for(int y = 0; y < 8; ++y)
{
index = x * 8 + y;
clound->points[index].x = x;
clound->points[index].y = y;
clound->points[index].z = 0;
}
}
index = 64;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 4;
index = 65;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 4;
index = 66;
clound->points[index].x = rand();
clound->points[index].y = rand();
clound->points[index].z = 5;
for(int n = 0; n < clound->points.size(); ++n)
{
qDebug() << QString::fromUtf8(" Point : %1 (%2, %3, %4)")
.arg(n + 1)
.arg(clound->points[n].x)
.arg(clound->points[n].y)
.arg(clound->points[n].z);
}
//取每个点到其临近的63个点的距离的高斯分布的标准差
int K = 63;
//判断离群点的阈值
float mul_thres = 2.5f;//3.6f;//4.9f;//5.9f;//点的距离超出平均距离的倍数
//使用pcl::StatisticalOutlinerRemoval
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(clound);
sor.setMeanK(K);
sor.setStddevMulThresh(mul_thres);
sor.setNegative(true);//输出离群点
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_filtered(new pcl::PointCloud<pcl::PointXYZ>());
sor.filter(*clound_filtered);
for(int n = 0; n < clound_filtered->points.size(); ++n)
{
qDebug() << QString::fromUtf8("PCL 离群点 - Point : ") << (n + 1)
<< " ( " << clound_filtered->points[n].x << ","
<< clound_filtered->points[n].y << ","
<< clound_filtered->points[n].z << " ) ";
}
以上代码的输出如下:
可见,直接调用 StatisticalOutlierRemoval 滤波器滤波得到的结果,和模拟 StatisticalOutlierRemoval 滤波器滤波得到的结果一致,找到的离群点都是(Point 65、66和67)。
(3.2) ConditionalRemoval 移除离群点滤波器
这个滤波器用于删除点云中不符合指定的一个或多个条件的数据点。
这个滤波器跟 PassThrough 直通滤波器类似,可以设置值保留某个或多个字段的值满足条件的数据点。
该滤波器进行滤波时,可以设置是否删除不满足条件的数据点:
通过 ConditionalRemoval::setKeepOrganized(bool) 函数;
如果设置不删除不满足条件的数据点,默认情况下,点云中不满足条件的数据点的每个字段的值都被设置为 NAN,可以该默认值可以设置为其它值:
通过 ConditionalRemoval::setUserFilterValue(float) 函数;
值 NAN 可以通过 stl 的函数 std::isnan(float) 判断。
如下例演示了怎样使用 ConditionalRemoval 滤波器:
qDebug() << "ConditionalRemoval Filter Demo";
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 5;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
clound->points[index].x = 1;
clound->points[index].y = 0;
clound->points[index].z = 0;
index = 1;
clound->points[index].x = 0;
clound->points[index].y = 1;
clound->points[index].z = 0;
index = 2;
clound->points[index].x = 0;
clound->points[index].y = 0;
clound->points[index].z = 1;
index = 3;
clound->points[index].x = 5;
clound->points[index].y = 0;
clound->points[index].z = 0;
index = 4;
clound->points[index].x = 0;
clound->points[index].y = 5;
clound->points[index].z = 0;
qDebug() << "Points Count : " << clound->points.size();
for(int i = 0; i < clound->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound->points[i].x << ","
<< clound->points[i].y << ","
<< clound->points[i].z << " ) ";
}
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_out(new pcl::PointCloud<pcl::PointXYZ>());
pcl::ConditionalRemoval<pcl::PointXYZ> condition_removal;
condition_removal.setInputCloud(clound);
pcl::ConditionAnd<pcl::PointXYZ>::Ptr cond_add(new pcl::ConditionAnd<pcl::PointXYZ>());
pcl::FieldComparison<pcl::PointXYZ>::ConstPtr x_cond(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 4));
pcl::FieldComparison<pcl::PointXYZ>::ConstPtr y_cond(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 4));
cond_add->addComparison(x_cond);
cond_add->addComparison(y_cond);
condition_removal.setCondition(cond_add);
condition_removal.setKeepOrganized(false);
//condition_removal.setUserFilterValue(555);
condition_removal.filter(*clound_out);
qDebug() << "Points Filtered Count : " << clound_out->points.size();
for(int i = 0; i < clound_out->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound_out->points[i].x << ","
<< clound_out->points[i].y << ","
<< clound_out->points[i].z << " ) ";
}
if(clound_out->points.size() > 3)
{
qDebug() << "Points Filtered 1 .x is NAN : " << std::isnan(clound_out->points[0].x);
qDebug() << "Points Filtered 4 .x is NAN : " << std::isnan(clound_out->points[3].x);
}
以上代码的行54和行55,如果这样写:
condition_removal.setKeepOrganized(false);
//condition_removal.setUserFilterValue(555);
则,输出结果为:
如果这样写:
condition_removal.setKeepOrganized(true);
//condition_removal.setUserFilterValue(555);
则,输出结果为:
如果这样写:
condition_removal.setKeepOrganized(true);
condition_removal.setUserFilterValue(555);
则,输出结果为:
(3.3)RadiusOutlierRemoval 移除离群点滤波器
RadiusOutlierRemoval 滤波器有2个参数:
(1)搜索半径
这个参数跟我的PCL系列文章中的《PCL中 的 kd-tree》和《PCL中的八叉树》两篇文章中讲过的半径搜索中的半径的含义一样,就是计算时只取位于离查询点的距离位于搜索半径内的点。
(2)最少近临点数
这个参数指,只保留位于查询点的搜索半径内的点的数目不低于该值的查询点。
如下代码演示了该滤波器的使用方法,以下代码中,设定了一个6个点的点云,前4个点在搜索半径为2的区域内,都有3个~4个紧邻点,后2个点在搜索半径为2的区域内,都只有1个紧邻点。
代码设置搜索半径为2:
radius_removal.setRadiusSearch(2.0);
设置最少近邻点数为2:
radius_removal.setMinNeighborsInRadius(2);
因此, 程序的输出结果应该是经过 RadiusOutlierRemoval 滤波器过滤,只剩下前4个点:
qDebug() << "RadiusOutlierRemoval Filter Demo";
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 5;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
clound->points[index].x = 1;
clound->points[index].y = 0;
clound->points[index].z = 0;
index = 1;
clound->points[index].x = 0;
clound->points[index].y = 1;
clound->points[index].z = 0;
index = 2;
clound->points[index].x = 0;
clound->points[index].y = 0;
clound->points[index].z = 1;
index = 3;
clound->points[index].x = 2.9;
clound->points[index].y = 0;
clound->points[index].z = 0;
index = 4;
clound->points[index].x = 0;
clound->points[index].y = 2.9;
clound->points[index].z = 0;
qDebug() << "Points Count : " << clound->points.size();
for(int i = 0; i < clound->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound->points[i].x << ","
<< clound->points[i].y << ","
<< clound->points[i].z << " ) ";
}
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_out(new pcl::PointCloud<pcl::PointXYZ>());
pcl::RadiusOutlierRemoval<pcl::PointXYZ> radius_removal;
radius_removal.setInputCloud(clound);
radius_removal.setRadiusSearch(2.0);
radius_removal.setMinNeighborsInRadius(2);
radius_removal.filter(*clound_out);
qDebug() << "Points Filtered Count : " << clound_out->points.size();
for(int i = 0; i < clound_out->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound_out->points[i].x << ","
<< clound_out->points[i].y << ","
<< clound_out->points[i].z << " ) ";
}
程序输出和预期一致,如下所示:
(4)ProjectInliers 点云投影到参数化模型滤波器
点云数据投影到一个参数化模型上(例如平面或者球面)的操作,可以想象成将一个3D物体压扁成一张画纸,然后,将这张画纸贴到一个3D物体的表面上,就是这种效果。
PCL中可以通过 ProjectInliers 滤波器将点云数据投影到一个参数化模型上。
以下,使用互联网上的一个 rabbit.pcd 点云文件(一个兔子模型的点云数据文件)为例子,演示怎样通过调用 ProjectInliers 滤波器,将该3D的兔子点云数据集投影到一个平面上。
一个3D平面的函数是 ax+by+cz+d=0 ,因此,只需要确定 a、b、c、d 这4个参数,就可以确定一个平面。
ProjectInliers 滤波器设置投影模型类型的函数是:
ProjectInliers:setModelTyoe(int);
通过该函数可以设置投影滤波器使用的模型类型,然后,使用 pcl::ModelCoefficients 类对象,可以设置平面模型的 a、b、c、d 这4个参数。
rabbit.pcd 点云文件的兔子模型如下所示:
代码如下:
qDebug() << QString::fromUtf8("ProjectInliers Filter Demo");
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
pcl::PCDReader pcd;
pcd.read<pcl::PointXYZ>("C:\\work\\rabbit.pcd", *clound);
//平面 ax+by+cz+d=0 的系数a b c d
pcl::ModelCoefficients::Ptr coef(new pcl::ModelCoefficients());
coef->values.resize(4);
coef->values[0] = 1;
coef->values[1] = 1;
coef->values[2] = 1;
coef->values[3] = -1;
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_out(new pcl::PointCloud<pcl::PointXYZ>());
//ProjectInliers滤波器
pcl::ProjectInliers<pcl::PointXYZ> prot;
prot.setModelType(pcl::SACMODEL_PLANE); //设置投影模型为平面模型
prot.setInputCloud(clound);
prot.setModelCoefficients(coef);
prot.filter(*clound_out);
//显示投影后的点云
pcl::visualization::CloudViewer * viewer = new pcl::visualization::CloudViewer("ss1");
viewer->showCloud(clound, "ss1");
while(!viewer->wasStopped())
{}
程序的输出如下,3D的兔子模型,被压缩成了一个平面的兔子贴纸:
(5)ExtractIndices 提取点云子集滤波器
ExtractIndices 滤波器的功能比较简单,就是从点云数据集中,根据给定的点序号,提取点云子集。
以下代码演示了如何使用 ExtractIndices 滤波器:
qDebug() << QString::fromUtf8("ExtractIndices Filter Demo");
pcl::PointCloud<pcl::PointXYZ>::Ptr clound(new pcl::PointCloud<pcl::PointXYZ>());
clound->width = 3;
clound->height = 1;
clound->resize(clound->width *clound->height);
int index = 0;
clound->points[index].x = 1;
clound->points[index].y = 0;
clound->points[index].z = 0;
index = 1;
clound->points[index].x = 0;
clound->points[index].y = 1;
clound->points[index].z = 0;
index = 2;
clound->points[index].x = 0;
clound->points[index].y = 0;
clound->points[index].z = 1;
qDebug() << "Points Count : " << clound->points.size();
for(int i = 0; i < clound->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound->points[i].x << ","
<< clound->points[i].y << ","
<< clound->points[i].z << " ) "
<< QString::fromUtf8(" ; 序号 : ") << i;
}
//给定要提取或者要排除的点序号
pcl::PointIndices::Ptr indices(new pcl::PointIndices());
indices->indices.push_back(2); //序号为2(序号从0开始)的点
qDebug() << QString::fromUtf8("指定序号数 : ") << indices->indices.size();
for(int i = 0; i < indices->indices.size(); ++i)
{
qDebug() << QString::fromUtf8(" 序号 : ") << indices->indices.at(i);
}
//ProjectInliers滤波器
pcl::PointCloud<pcl::PointXYZ>::Ptr clound_out(new pcl::PointCloud<pcl::PointXYZ>());
pcl::ExtractIndices<pcl::PointXYZ> flt;
flt.setInputCloud(clound);
flt.setIndices(indices);
flt.setNegative(true);//true-提取指定序号的点;false-提取除了指定序号之外的点
flt.filter(*clound_out);
qDebug() << "Points Filtered Count : " << clound_out->points.size();
for(int i = 0; i < clound_out->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< clound_out->points[i].x << ","
<< clound_out->points[i].y << ","
<< clound_out->points[i].z << " ) ";
}
flt.setNegative(true);时,保留的是指定除了指定序号点之外的点,程序输出如下:
flt.setNegative(false);时,保留的是指定序号的点,程序输出如下:
(6)CropHull 任意多边形内部点云提取滤波器
下面的代码中,需要使用 ConvexHull 类计算多边形顶点,以获得进行多边形点云滤波需要的多边形点云。
ConvexHull 类需要编译 PCL 时开启第三方库 libqhull ,如果之前编译 PCL 1.12.1 时没有开启 libhull ,则需要重新开启编译 PCL 1.12.1 。
首先,用 CMake 打开 PCL 1.12.1 的源码,
手动添加一项:
QHULL_INCLUDE_DIR,类型是 Path,值是 C:/PCL1.12.1/3rdParty/Qhull/include ;
设置以下 8 项:
(1)PCL_QHULL_REQUIRED_TYPE,值设置为 STATIC;
(2)WITH_QHULL,勾选;
(3)QHULL_LIBRARY_DEBUG,值设置为 C:/PCL1.12.1/3rdParty/Qhull/lib/qhull_rd.lib;
(4)QHULL_LIBRARY_DEBUG_STATIC,值设置为 C:/PCL1.12.1/3rdParty/Qhull/lib/qhullstatic_rd.lib;
(5)QHULL_LIBRARY_SHARED,值设置为 C:/PCL1.12.1/3rdParty/Qhull/lib/qhull_r.lib;
(6)QHULL_LIBRARY_STATIC,值设置为 C:/PCL1.12.1/3rdParty/Qhull/lib/qhullstatic_r.lib;
(7)BUILD_surface,勾选;
(8)BUILD_surface_on_nurbs,勾选。
然后,重新 Configure 、Generate。
重新编译 PCL 1.21.1 ,则可使用 ConvexHull 类:
工程包含目录添加:
INCLUDEPATH += C:\PCL1.12.1\3rdParty\Qhull\include\libqhullr
引用库文件添加:
win32:LIBS += $$quote(C:\PCL1.12.1\lib\pcl_surfaced.lib)
win32:LIBS += $$quote(C:\PCL1.12.1\lib\pcl_searchd.lib)
代码包含文件添加:
#include <pcl/filters/crop_hull.h>
#include <pcl/surface/convex_hull.h>
同时,在生成的.exe程序所在目录要添加 libhull 库的动态链接库:
qhull_r.dll 和 qhull_rd.dll。
以下代码,用一个xy平面(z=0)的正方形作为滤波多边形,对一个3D点云进行滤波:
#include <QCoreApplication>
#include <QDateTime>
#include <QThread>
#include <iostream>
#include <chrono>
#include <pcl/point_cloud.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/compression/compression_profiles.h>
#include <pcl/compression/octree_pointcloud_compression.h>
#include <pcl/octree/octree_search.h>
#include <pcl/octree/octree_pointcloud_changedetector.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/project_inliers.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/crop_hull.h>
#include <pcl/surface/convex_hull.h>
#include <pcl/ModelCoefficients.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << QString::fromUtf8("CropHull Filter Demo");
pcl::PointCloud<pcl::PointXYZ>::Ptr boundinbox(new pcl::PointCloud<pcl::PointXYZ>());
boundinbox->push_back(pcl::PointXYZ(0.1, 0.1, 0));
boundinbox->push_back(pcl::PointXYZ(0.1, -0.1, 0));
boundinbox->push_back(pcl::PointXYZ(-0.1, 0.1, 0));
boundinbox->push_back(pcl::PointXYZ(-0.1, -0.1, 0));
boundinbox->push_back(pcl::PointXYZ(0.05, 0.05, 0));
qDebug() << QString::fromUtf8("2D多边形点云 Count : ") << boundinbox->points.size();
for(int i = 0; i < boundinbox->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< boundinbox->points[i].x << ","
<< boundinbox->points[i].y << ","
<< boundinbox->points[i].z << " ) ";
}
pcl::ConvexHull<pcl::PointXYZ> hull;
hull.setInputCloud(boundinbox);
hull.setDimension(2);//设置凸包维度
std::vector<pcl::Vertices> polygons;
pcl::PointCloud<pcl::PointXYZ>::Ptr surface_hull(new pcl::PointCloud<pcl::PointXYZ>());
hull.reconstruct(*surface_hull, polygons);
qDebug() << QString::fromUtf8("检测到2D多边形个数 : ") << polygons.size();
for(int jk = 0; jk < polygons.size(); ++jk)
{
qDebug() << QString::fromUtf8("第%1个2D多边形顶点个数: ").arg(jk + 1) << polygons[jk].vertices.size();
for(int i = 0; i < polygons[jk].vertices.size(); ++i)
{
int pt_index = polygons[jk].vertices[i];
qDebug() << " Point : " << (i + 1) << " ( "
<< surface_hull->points[pt_index].x << ","
<< surface_hull->points[pt_index].y << ","
<< surface_hull->points[pt_index].z << " ) ";
}
}
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
cloud->push_back(pcl::PointXYZ(0.05, 0.05, 0));
cloud->push_back(pcl::PointXYZ(0.07, -0.08, 0));
cloud->push_back(pcl::PointXYZ(-0.09, 0.01, 0));
cloud->push_back(pcl::PointXYZ(0.05, 0.05, 1));
cloud->push_back(pcl::PointXYZ(0.08, -0.09, 200));
cloud->push_back(pcl::PointXYZ(1.1, 1.1, 0));
qDebug() << QString::fromUtf8("原始点云 Count : ") << cloud->points.size();
for(int i = 0; i < cloud->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< cloud->points[i].x << ","
<< cloud->points[i].y << ","
<< cloud->points[i].z << " ) ";
}
pcl::CropHull<pcl::PointXYZ> crop_filter;
crop_filter.setDim(2);//必须和多边形的维度一致 hull.setDimension(2);//设置凸包维度
crop_filter.setInputCloud(cloud);
crop_filter.setHullCloud(surface_hull);
crop_filter.setHullIndices(polygons);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());
crop_filter.filter(*cloud_filtered);
qDebug() << QString::fromUtf8("多边形滤后的点云 Count : ") << cloud_filtered->points.size();
for(int i = 0; i < cloud_filtered->points.size(); ++i)
{
qDebug() << " Point : " << (i + 1) << " ( "
<< cloud_filtered->points[i].x << ","
<< cloud_filtered->points[i].y << ","
<< cloud_filtered->points[i].z << " ) ";
}
return a.exec();
}
以上代码的执行结果如下:
可见,经过2D多边形滤波后,不管z轴坐标是多少,点云中所有xy坐标满足的都被保留。可见,2D多边形对3D点云进行多边形滤波,实际是进行“多边形柱体”滤波。
您的打赏是我写作的动力!