0、滤波算法概述
PCL点云库中的滤波算法是处理点云数据不可或缺的一部分,它们能够有效地去除噪声、提取特征或进行数据降维。例如,使用体素网格滤波(VoxelGrid)可以减少点云数据量,同时保留重要的形状特征。此外,统计滤波器(StatisticalOutlierRemoval)能够识别并移除离群点,这对于清理数据集中的异常值非常有用。在实际应用中,滤波器的选择和配置需要根据具体任务的需求来决定,比如在机器人导航中,可能需要使用半径滤波器(RadiusOutlierRemoval)来去除距离传感器过近或过远的点,以获得更准确的环境模型。通过这些滤波技术,可以显著提高点云数据的质量,为后续的处理和分析工作打下坚实的基础。在PCL点云滤波中,还有一种常见的滤波方法是条件滤波(ConditionalRemoval)。这种方法允许用户根据自定义的条件来移除点云中的某些点。比如,可以根据点的坐标范围、颜色范围或者法线方向等条件进行过滤,以提取出满足特定条件的点云子集。条件滤波的灵活性使得它适用于各种复杂的场景和需求。
除了上述提到的滤波方法外,PCL点云库还提供了许多其他类型的滤波算法,如双边滤波(BilateralFilter)和高斯滤波(GaussianFilter)等。双边滤波能够在保留边缘特征的同时平滑点云数据,非常适合于去除噪声但又不希望破坏点云中的细节结构。而高斯滤波则是一种更为简单的平滑滤波方法,它通过计算每个点周围邻域内的点的加权平均来更新该点的位置,从而达到平滑的效果。
在实际应用中,PCL点云滤波算法的选择通常需要综合考虑多个因素,包括滤波效果、计算效率以及具体的应用场景等。有时,为了获得更好的滤波效果,可能需要结合使用多种滤波方法。例如,可以先使用体素网格滤波进行数据下采样,然后再使用统计滤波器去除剩余的噪声点。这样的组合使用可以充分发挥各种滤波算法的优势,达到最佳的滤波效果。总之,PCL点云库中的滤波算法为我们提供了强大的工具来处理和分析点云数据。通过合理选择和使用这些滤波方法,我们可以有效地去除噪声、提取特征或进行数据降采样,为后续的点云处理和分析工作提供有力的支持。下面我们将从条件滤波和直通滤波开启PCL库中滤波算法之旅!
1、直通滤波算法(PassThrough Filter)
1.1、算法原理
PCL库中点云直通滤波算法是一种简单有效的点云数据处理方法,其核心思想是根据要滤波的字段(如,X轴,Y轴,Z轴)和设定阈值范围α∈(limitMin,limitMax),如x∈[0.1,0.5],通过这个阈值来决定哪些点是噪声点,哪些点是有效点。直观式子如下:
在实际操作中,算法会遍历点云数据集中的每一个点,根据预设的阈值条件来判断该点是否保留。通常,这个条件可能与点的强度、距离、角度或其他特征有关。通过这种方式,可以有效地去除那些不符合条件的噪声点,从而得到更加平滑和准确的点云数据,为后续的处理和分析提供更加可靠的输入。
1.2、主要成员函数和变量
PCL库中的pcl:PassThrough类实现了比较灵活,完全取决于用户设置的限定字段和对应条件的直通滤波算法。主要的成员变量和函数有如下:
1、主要成员变量
1)、用户所需要过滤字段名称
std::string filter_field_name_;
2)、限制的最小过滤值
double filter_limit_min_;
3)、限制的最大过滤值
double filter_limit_max_;
2、主要成员函数
1)、设置限定字段的名称字符串field_name,例如"X"
void setFilterFieldName(const std::string &field_ name)
2)、设置滤波限制条件, 最小值limit_ min和最大值limit_max
void setFilterLimits(const double &limit_min,const double &limit_max)
3)、设置返回滤波结果是限制条件外点还是内部点,limit_negative默认值为false,输出点云为在设定字段的设定范围内的点集,如果设置为true则刚好相反
inline void setFilterLimitsNegative (const bool limit_negative)
1.3、主要部分代码注解
template <typename PointT> void
pcl::PassThrough<PointT>::applyFilterIndices (std::vector<int> &indices)
{// The arrays to be usedindices.resize (indices_->size ());removed_indices_->resize (indices_->size ());int oii = 0, rii = 0; // oii = 输出索引, rii = 移除索引// 如果未指定滤波的字段,则仅仅是移除点云数据无效点if (filter_field_name_.empty ()){// 仅仅是移除点云数据无效点for (const auto ii : *indices_) // ii = input index{// // 滤掉无效点if (!std::isfinite (input_->points[ii].x) ||!std::isfinite (input_->points[ii].y) ||!std::isfinite (input_->points[ii].z)){if (extract_removed_indices_)(*removed_indices_)[rii++] = ii;continue;}indices[oii++] = ii;}}else{// 获取字段名称的索引,如字段“z",返回索引为:std::vector<pcl::PCLPointField> fields;int distance_idx = pcl::getFieldIndex<PointT> (filter_field_name_, fields);if (distance_idx == -1){PCL_WARN ("[pcl::%s::applyFilter] Unable to find field name in point type.\n", getClassName ().c_str ());indices.clear ();removed_indices_->clear ();return;}// 滤波算法主体,滤掉无效点和指定的字段限制值for (const auto ii : *indices_) // ii = input index{// 滤掉无效点if (!std::isfinite (input_->points[ii].x) ||!std::isfinite (input_->points[ii].y) ||!std::isfinite (input_->points[ii].z)){if (extract_removed_indices_)(*removed_indices_)[rii++] = ii;//保留移除点的索引continue;}// 获取指定字段的值,如x=0.3const std::uint8_t* pt_data = reinterpret_cast<const std::uint8_t*> (&input_->points[ii]);float field_value = 0;memcpy (&field_value, pt_data + fields[distance_idx].offset, sizeof (float));// 判断获取指定字段的值是否为无效点,如果是则移除掉if (!std::isfinite (field_value)){if (extract_removed_indices_)(*removed_indices_)[rii++] = ii;continue;}//negative_ 为false, 在字段限制值之外将会被移除,if (!negative_ && (field_value < filter_limit_min_ || field_value > filter_limit_max_)){if (extract_removed_indices_)(*removed_indices_)[rii++] = ii;continue;}// negative_ 为true,在字段限制值之外将会保留if (negative_ && field_value >= filter_limit_min_ && field_value <= filter_limit_max_){if (extract_removed_indices_)(*removed_indices_)[rii++] = ii;continue;}// 保留输出内点的索引indices[oii++] = ii;}}// Resize the output arraysindices.resize (oii);removed_indices_->resize (rii);
}
1.4、算法使用示例
/*****************************************************************//**
* \file PCLPassthroughmain.cpp
* \brief
*
* \author YZS
* \date December 2024
*********************************************************************/
#include<iostream>
#include <vector>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/auto_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/conditional_removal.h>
void Passthrough()
{// 随机种子初始化srand(time(NULL));pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);// 生成点云数据3000个cloud->width = 1000;cloud->height = 1;cloud->points.resize(cloud->width * cloud->height);for (size_t i = 0; i < cloud->points.size(); ++i) {cloud->points[i].x = 10.0f * rand() / (RAND_MAX + 1.0f);cloud->points[i].y = 10.0f * rand() / (RAND_MAX + 1.0f);cloud->points[i].z = 10.0f * rand() / (RAND_MAX + 1.0f);}//保存滤波的结果pcl::PointCloud<pcl::PointXYZ>::Ptr cloudFilter(new pcl::PointCloud<pcl::PointXYZ>);// 创建PassThrough滤波器对象pcl::PassThrough<pcl::PointXYZ> pass;//滤波器对象pass.setInputCloud(cloud); //设置需要滤波的点云pass.setFilterFieldName("y"); //设置需要滤波的字段pass.setFilterLimits(0.0, 5.0); //设置限定范围//pass.setFilterLimitsNegative (true);//是否反向过滤,默认为falsepass.filter(*cloudFilter); //执行滤波//结果可视化// PCLVisualizer对象pcl::visualization::PCLVisualizer viewer("FilterVIS");//创建左右窗口的ID v1和v2int v1(0);int v2(1);//设置V1窗口尺寸和背景颜色viewer.createViewPort(0.0, 0.0, 0.5, 1, v1);viewer.setBackgroundColor(0, 0, 0, v1);//设置V2窗口尺寸和背景颜色viewer.createViewPort(0.5, 0.0, 1, 1, v2);viewer.setBackgroundColor(0.1, 0.1, 0.1, v2);//设置cloud1的渲染颜色,点云的ID和指定可视化窗口v1pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud1_color(cloud, 255, 255, 255);viewer.addPointCloud(cloud, cloud1_color, "cloud1", v1);viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "cloud1");//设置cloud2的渲染颜色,点云的ID和指定可视化窗口v2pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud2_color(cloud, 250, 255, 255);viewer.addPointCloud(cloudFilter, cloud2_color, "cloud2", v2);viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "cloud2");// 可视化循环主体while (!viewer.wasStopped()){viewer.spinOnce();}
}
int main(int argc, char* argv[])
{Passthrough();std::cout << "Hello World!" << std::endl;std::system("pause");return 0;
}
结果:
2、条件滤波算法(ConditionalRemoval Filter )
2.1、算法原理
点云滤波条件滤波算法原理基于设定的条件来筛选点云数据。与直通滤波算法不同,条件滤波算法不限于单一的轴向阈值,而是可以基于点云的多种属性,如强度、颜色、法线方向等,来定义更为复杂的滤波规则。例如,可以设置一个强度阈值,仅保留强度在一定范围内的点,或者根据点的法线方向来过滤数据,只保留那些法线方向符合特定条件的点。因此,条件滤波允许用户定义一个或多个条件表达式,这些表达式会应用于点云数据集中的每一个点。算法会根据这些条件表达式来决定是否保留某个点。由于条件可以非常灵活,因此这种算法能够适应各种不同的应用场景和需求。在实际应用中,条件滤波算法可以实现更为精细的数据处理。例如,在自动驾驶车辆的激光雷达数据处理中,可以使用条件滤波来去除地面反射的点,保留车辆和障碍物的点云数据。这样的处理对于后续的物体检测和分类至关重要。
2.2、主要成员函数和变量
PCL库中的pcl:ConditionalRemoval类中提供了多种条件比较,如FieldComparison类(字段比较)、PackedRGBComparison类(RGB颜色比较)、PackedHSIComparison类(HSI色彩比较)和TfQuadraticXYZComparison类(二次项比较)等条件类型,这些条件类型最终通过ConditionOr类和ConditionAnd类来控制调用,用户可以根据自己的需求来设置滤波条件。滤波算法中主要的成员变量有如下:
1、主要成员变量
1)、是否要保持点云数据的原始结构,true,保持原始结构,被滤波的点用NAN替代,false不保存原始结果。
bool keep_organized_;
2)、滤波比较条件
ConditionBasePtr condition_;
滤波条件类:
class ConditionAnd 当所有比较条件值都为真时,ConditionAnd的值为真
class ConditionOr 当任何比较条件的值为真时,ConditionOr的值为真
3)、用户设置的过滤限定值
float user_filter_value_;
2、主要成员函数
1)、设置是否要保持点云数据的原始结构
inline void setKeepOrganized (bool val);
2)、设置用户提供的过滤限定值
inline void setUserFilterValue (float val);
3)、设置过滤器所用条件。每个参数条件都必须满足,以确保数据点不会因不满足条件而被过滤器排除。
void setCondition (ConditionBasePtr condition);
比较操作的类型如下:
enum CompareOp{GT, GE, LT, LE, EQ};
具体含义如下:
EQ EQUAL等于GT GREATER THAN大于 LT LESS THAN小于GE GREATER THAN OR EQUAL 大于等于LE LESS THAN OR EQUAL 小于等于
2.3、主要部分代码注解
template <typename PointT> void
pcl::ConditionalRemoval<PointT>::applyFilter (PointCloud &output)
{........// 将输入的头文件赋值给输出文件output.header = input_->header;if (!keep_organized_){//不保存原来数据结构 output.height = 1; output.is_dense = true;}else{//保存原来数据结构 output.height = this->input_->height;output.width = this->input_->width;output.is_dense = this->input_->is_dense;}output.points.resize (input_->points.size ());removed_indices_->resize (input_->points.size ());int nr_p = 0;int nr_removed_p = 0;//不保存原来数据结构实现if (!keep_organized_){for (std::size_t index: (*Filter<PointT>::indices_)){const PointT& point = input_->points[index];// 无效点判断if (!std::isfinite (point.x)|| !std::isfinite (point.y)|| !std::isfinite (point.z)){if (extract_removed_indices_){(*removed_indices_)[nr_removed_p] = index;nr_removed_p++;}continue;}//条件判断实现接口,if (condition_->evaluate (point)){//将满足条件的数据拷贝到输出点云中copyPoint (point, output.points[nr_p]);nr_p++;}else{//保存移除点云的索引if (extract_removed_indices_){(*removed_indices_)[nr_removed_p] = index;nr_removed_p++;}}}output.width = nr_p;output.points.resize (nr_p);}else //不保存原来数据结构实现{//获取输入点云数据的索引集合std::vector<int> indices = *Filter<PointT>::indices_;std::sort (indices.begin (), indices.end ()); //对索引集排序bool removed_p = false;std::size_t ci = 0;for (std::size_t cp = 0; cp < input_->points.size (); ++cp){//对索引集合内的点进行条件比较if (cp == static_cast<std::size_t> (indices[ci])){if (ci < indices.size () - 1){ci++;if (cp == static_cast<std::size_t> (indices[ci])) continue;}copyPoint (input_->points[cp], output.points[cp]);//条件判断实现接口,if (!condition_->evaluate (input_->points[cp])){//满足点的值设置为用户设定的值output.points[cp].getVector4fMap ().setConstant (user_filter_value_);removed_p = true;if (extract_removed_indices_){(*removed_indices_)[nr_removed_p] = static_cast<int> (cp);nr_removed_p++;}}}else{//将非索引集合以外的点的值设置为用户设定的值output.points[cp].getVector4fMap ().setConstant (user_filter_value_);removed_p = true;}}if (removed_p && !std::isfinite (user_filter_value_))output.is_dense = false;}........
}
2.4、算法使用示例
/*****************************************************************//**
* \file ConditionalRemovalmain.cpp
* \brief
*
* \author YZS
* \date December 2024
*********************************************************************/
#include<iostream>
#include <vector>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/auto_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/conditional_removal.h>using namespace std;void ConditionalRemoval()
{pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>());std::string fileName = "E:/PCLlearnData/9/fragment.pcd";pcl::io::load(fileName, *cloud);std::cout << "Cloud Size:" << cloud->points.size() << std::endl;// 创建过滤条件pcl::ConditionAnd<pcl::PointXYZRGB>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZRGB>());// x值 大于-0.05range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZRGB>::ConstPtr \(new pcl::FieldComparison<pcl::PointXYZRGB>("x", pcl::ComparisonOps::GT, -1.3)));// x值 小于-1.3range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZRGB>::ConstPtr \(new pcl::FieldComparison<pcl::PointXYZRGB>("x", pcl::ComparisonOps::LT, -0.05)));//保存滤波后的结果pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloudFilter(new pcl::PointCloud<pcl::PointXYZRGB>());pcl::ConditionalRemoval<pcl::PointXYZRGB> conFilter;// 条件滤波器类对象conFilter.setCondition(range_cond); // 设置滤波条件conFilter.setInputCloud(cloud);conFilter.setKeepOrganized(false);// 设置是否保持有序,若输入为有序点云,可以设置为trueconFilter.filter(*cloudFilter); // 执行滤波,并且保存结果到cloudFilter中std::cout << "filter Cloud Size:" << cloudFilter->points.size() << std::endl;//结果可视化
// PCLVisualizer对象pcl::visualization::PCLVisualizer viewer("FilterVIS");//创建左右窗口的ID v1和v2int v1(0);int v2(1);//设置V1窗口尺寸和背景颜色viewer.createViewPort(0.0, 0.0, 0.5, 1, v1);viewer.setBackgroundColor(0, 0, 0, v1);//设置V2窗口尺寸和背景颜色viewer.createViewPort(0.5, 0.0, 1, 1, v2);viewer.setBackgroundColor(0.1, 0.1, 0.1, v2);// 添加2d文字标签viewer.addText("v1", 10, 10, 20, 1, 0, 0, "Txtv1", v1);viewer.addText("v2", 10, 10, 20, 0, 1, 0, "Txtv2", v2);//设置cloud1的渲染属性,点云的ID和指定可视化窗口v1viewer.addPointCloud(cloud, "cloud1", v1);viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "cloud1");//设置cloud2的渲染属性,点云的ID和指定可视化窗口v2viewer.addPointCloud(cloudFilter, "cloud2", v2);viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "cloud2");// 可视化循环主体while (!viewer.wasStopped()){viewer.spinOnce();}
}
int main(int argc, char* argv[])
{ConditionalRemoval();std::cout << "Hello World!" << std::endl;std::system("pause");return 0;
}
结果:
至此完成第九节PCL库点云滤波算法之直通滤波(PassThrough)和条件滤波(ConditionalRemoval)学习,下一节我们将进入《PCL库中点云滤波之体素滤波(VoxelGrid)》的学习。