参考引用
- Point Cloud Library
- 黑马机器人 | PCL-3D点云
1. 点云滤波概述
1.1 背景
- 在获取点云数据时,由于设备精度、操作者经验、环境因素等带来的影响,以及电磁波衍射特性、被测物体表面性质变化和数据拼接配准操作过程的影响,点云数据中将不可避免地出现一些噪声点
- 实际应用中除了这些测量随机误差产生的噪声点之外,由于受到外界干扰如:视线遮挡、障碍物等因素的影响,点云数据中往往存在着一些离主体点云较远的离群点,不同的获取设备点云噪声结构也有不同
1.2 解决方法
- 通过滤波实现孔洞修复、最小信息损失的海量点云数据压缩处理等
- 在点云处理流程中滤波处理作为预处理的第一步,往往对后续处理流程影响很大,只有在滤波预处理中将噪声点、离群点、孔洞、数据压缩等按照后续需求处理,才能够更好地进行配准、特征提取、曲面重建、可视化等后续流程
- PCL 中点云滤波模块提供了很多灵活实用的滤波处理算法,例如:双边滤波、高斯滤波、条件滤波、直通滤波、基于随机采样一致性滤波 RANSAC 等
1.3 应用场景
- 点云数据密度不规则需要平滑处理
- 去除因为遮挡等问题造成的离群点
- 数据量较大,需要进行下采样(Downsample)
- 去除噪声数据
PCL 点云格式分为有序点云和无序点云
- 一般深度相机采集到的点云的数据是有序点云,针对有序点云提供了双边滤波、高斯滤波、中值滤波等
- 激光雷达采集的点云的数据是无序点云,针对无序点云提供了体素栅格、随机采样等
1.4 示例
- 下图显示了一个去除噪声数据的示例
- 由于测量误差,某些数据集会出现大量阴影点。这使局部点云 3D 特征的估算变得复杂。通过对每个点的邻域进行统计分析,并修剪掉不符合特定条件的异常值,进而过滤掉某些异常值
- PCL 中实现稀疏离群值的消除,需要计算数据集中的点与邻居距离的分布。即对于每个点,都会计算从它到所有相邻点的平均距离。通过假设结果分布是具有均值和标准差的高斯分布,可以将那些平均距离在(由全局距离均值和标准差定义的区间)之外的所有点视为离群值,并将之从数据集中进行修剪
2. 高斯滤波
-
使用高斯卷积核对图片进行平滑(模糊)处理,是一种常见的线性图片过滤技术。每一个输出图片中的像素点都是其输入图片中周围邻居像素值的加权求和结果。其核心就是一个核函数的卷积操作,对图片进行低通滤波。高斯模糊(Gaussian blur / GB)图片滤波器定义如下
G B [ I ] p = ∑ q ∈ S G σ ( ∥ p − q ∥ ) I q GB[I]_{\mathbf{p}}=\sum\limits_{\mathbf{q}\in\mathcal{S}}G_{\sigma}(\|\mathbf{p}-\mathbf{q}\|)I_{\mathbf{q}} GB[I]p=q∈S∑Gσ(∥p−q∥)Iq- 这里
G
σ
(
x
)
G_σ(x)
Gσ(x) 表示二维的高斯卷积核
G σ ( x ) = 1 2 π σ 2 exp ( − x 2 2 σ 2 ) G_\sigma(x)=\frac{1}{2\pi\sigma^2}\exp\left(-\frac{x^2}{2\sigma^2}\right) Gσ(x)=2πσ21exp(−2σ2x2)
- 这里
G
σ
(
x
)
G_σ(x)
Gσ(x) 表示二维的高斯卷积核
-
高斯滤波是求相邻位置强度的加权平均值,其权值随到中心位置 p 的空间距离减小而减小。点 q 中心像素 p 的权重通过高斯分布 G σ ( ∥ p − q ∥ ) G_{\sigma}(\|\mathbf{p}-\mathbf{q}\|) Gσ(∥p−q∥) 描述,这里的 σ 参数定义邻域的大小,也就是卷积核窗体大小, ∥ p − q ∥ \|\mathbf{p-q}\| ∥p−q∥ 为两个向量差的范数,即其有向线段的长度。这种影响的强度只取决于像素之间的空间距离,而不是它们的绝对位置值。例如,一个亮像素对相邻的暗像素有很大的影响,尽管这两个像素值差异很大
-
下图是在不同标准差 σ 时的高斯线性滤波,越大的 σ 边缘模糊的更厉害,因为其平均值是通过更大的范围计算出来的
3. 双边滤波
双边滤波算法,是通过取邻近采样点的加权平均来修正当前采样点的位置,从而达到滤波效果。同时也会有选择地剔除部分与当前采样点 “差异” 太大的相邻采样点,从而达到保持原特征的目的
-
双边滤波可以保留边缘信息,其实质也是计算邻居像素的加权平均和,非常类似于高斯卷积。不同之处在于双边滤波器在平滑的同时考虑到与邻边像素颜色值的差异,进而保留边缘信息。双边滤波器的关键思想是一个像素对另一个像素影响程度,不应该只和位置距离有关,还应该具有相似的像素颜色值,因此双边滤波器是一种非线性滤波器
-
相等距离情况下,颜色值接近的像素点权重应当高一些,颜色值差异大的像素点权重应当小一些。于是,双边滤波 bilateral filter(BF)的定义如下
B F [ I ] p = 1 W p ∑ q ∈ S G σ s ( ∥ p − q ∥ ) G σ r ( ∣ I p − I q ∣ ) I q BF[I]_{\mathbf{p}}=\frac{1}{W_{\mathbf{p}}}\sum\limits_{\mathbf{q}\in\mathcal{S}}G_{\sigma_s}(\|\mathbf{p}-\mathbf{q}\|)G_{\sigma_r}\left(|I_{\mathbf{p}}-I_{\mathbf{q}}|\right)I_{\mathbf{q}} BF[I]p=Wp1q∈S∑Gσs(∥p−q∥)Gσr(∣Ip−Iq∣)Iq- 这里通过归一化因子
W
p
W_p
Wp 保证像素的权重和为 1.0
W p = ∑ q ∈ S G σ s ( ∥ p − q ∥ ) G σ r ( ∣ I p − I q ∣ ) W_{\mathbf{p}}=\sum_{\mathbf{q}\in\mathcal{S}}G_{\sigma_\mathbf{s}}(\|\mathbf{p}-\mathbf{q}\|)G_{\sigma_\mathbf{r}}(|I_\mathbf{p}-I_\mathbf{q}|) Wp=q∈S∑Gσs(∥p−q∥)Gσr(∣Ip−Iq∣)
- 这里通过归一化因子
W
p
W_p
Wp 保证像素的权重和为 1.0
-
双边滤波里的两个权重域的概念
- 空间域(spatial domain S)和像素范围域(range domain R)
- 双边滤波的核函数是空间域核与像素范围域核的综合结果
- 空间域(spatial domain S)和像素范围域(range domain R)
-
综合结论
- 在图像的平坦区域,像素值变化很小,对应的像素范围域权重接近于 1,此时空间域权重起主要作用,相当于进行高斯模糊
- 在图像的边缘区域,像素值变化很大,像素范围域权重变大,从而保持了边缘的信息
-
两个权重对图像的影响
由于点云本身是稀疏且不连贯的,所以通过双边滤波对点云的 RGB 图做上采样后,将点云对应到 RGB 图,可以得到边缘更完整清晰的 3D 点云。BF 为双边滤波,MED 为中值滤波,AVE 为均值滤波
4. PassThrough Filter(直通滤波器)
-
直通滤波算作最为简单、粗暴的一种滤波方式,就是直接对点云的 X、Y、Z 轴的点云坐标约束来进行滤波,可以约束只在 Z 轴,或者 XYZ 三个坐标轴共同约束来达到点云滤波效果
-
passthrough.cpp
#include <iostream> #include <pcl/point_types.h> #include <pcl/filters/passthrough.h> #include <pcl/visualization/cloud_viewer.h> typedef pcl::PointXYZ PointT; int main(int argc, char **argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>); // 随机填充点云数据 cloud->width = 5; cloud->height = 1; cloud->points.resize(cloud->width * cloud->height); for (size_t i = 0; i < cloud->points.size(); ++i) { cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f); cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f); cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f); } std::cerr << "Cloud before filtering: " << std::endl; for (size_t i = 0; i < cloud->points.size(); ++i) { std::cerr << " " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl; } // 从外部导入 pcd 点云文件 //pcl::PCDReader reader; //reader.read("xxx.pcd", *cloud); // 创建滤波对象:将点云中 Z 坐标在(0,1)范围外的点过滤掉 pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud(cloud); // 1. 设置输入点云 pass.setFilterFieldName("z"); // 2. 设置过滤时所需要点云类型的Z字段 pass.setFilterLimits(0.0, 1.0); // 3. 设置在过滤字段的范围 // pass.setFilterLimitsNegative(true); // 设置保留范围内还是过滤掉范围内,是否保存滤波的限制范围内的点云,默认为false,保存限制范围内点云 pass.filter(*cloud_filtered); // 4. 执行过滤,并将结果输出到cloud_filtered std::cerr << "Cloud after filtering: " << std::endl; for (size_t i = 0; i < cloud_filtered->points.size(); ++i) std::cerr << " " << cloud_filtered->points[i].x << " " << cloud_filtered->points[i].y << " " << cloud_filtered->points[i].z << std::endl; // 保存滤波后结果 //pcl::PCDWriter writer; //writer.write("xxx_filtered.pcd", *cloud_filtered, false); // 点云可视化 pcl::visualization::CloudViewer viewer("Cloud Viewer"); // 这里会一直阻塞直到点云被渲染 viewer.showCloud(cloud); while (!viewer.wasStopped()) { } return (0); }
// 不仅限于对单一坐标轴的过滤,其实主要就是再一次进行目标坐标轴过滤即可 // 通过重复使用直通滤波就可以进行三维区间的滤波 // filter range X-axis pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud(cloud); pass.setFilterFieldName("x"); pass.setFilterLimits(-5.0, 5.0); // pass.setFilterLimitsNegative(true); pass.filter(*cloud_filtered2); // filter range Y-axis pass.setInputCloud(cloud_filtered2); pass.setFilterFieldName("y"); pass.setFilterLimits(-5.0, 5.0); pass.filter(*cloud_filtered3); // filter range Z-axis pass.setInputCloud(cloud_filtered3); pass.setFilterFieldName("z"); pass.setFilterLimits(-0.5, 3.0); pass.filter(*cloud_filtered);
-
配置文件 CMakeLists.txt
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(passthrough) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable(passthrough passthrough.cpp) target_link_libraries(passthrough ${PCL_LIBRARIES})
-
编译并执行
$ mkdir build $ cd build $ cmake .. $ make $ ./passthrough
// 输出结果:下图中绿色表示为滤波后剩余的点,红色表示为已被滤波器去除的点 // 如果使用 pass.setFilterLimitsNegative (true);,则以下结果取反 Cloud before filtering: 0.352222 -0.151883 -0.106395 -0.397406 -0.473106 0.292602 -0.731898 0.667105 0.441304 -0.734766 0.854581 -0.0361733 -0.4607 -0.277468 -0.916762 Cloud after filtering: -0.397406 -0.473106 0.292602 -0.731898 0.667105 0.441304
5. VoxelGrid filter(体素网格滤波器)
-
通过体素网格滤波器实现点云降采样,减少点数量的同时保证点云的形状特征,可以提高配准、曲面重建、形状识别等算法的速度,并保证准确性
-
PCL 是实现的 VoxelGrid 类通过输入的点云数据创建一个三维体素栅格,容纳后每个体素内用体素中所有点的重心来近似显示体素中其他点,这样该体素内所有点都用一个重心点最终表示,对于所有体素处理后得到的过滤后的点云,这种方法比用体素中心逼近的方法更慢,但是对于采样点对应曲面的表示更为准确
-
voxel_grid.cpp
#include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/voxel_grid.h> int main (int argc, char** argv) { pcl::PCLPointCloud2::Ptr cloud(new pcl::PCLPointCloud2()); pcl::PCLPointCloud2::Ptr cloud_filtered(new pcl::PCLPointCloud2()); // 从文件读取点云图 pcl::PCDReader reader; reader.read ("../data/table_scene_lms400.pcd", *cloud); // 改为自己的 pcd 文件路径 std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height << " data points (" << pcl::getFieldsList(*cloud) << ")." << std::endl; // 创建一个长宽高分别是 1cm 的体素过滤器,cloud作为输入数据,cloud_filtered作为输出数据 float leftSize = 0.01f; pcl::VoxelGrid<pcl::PCLPointCloud2> sor; sor.setInputCloud(cloud); sor.setLeafSize(leftSize, leftSize, leftSize); sor.filter(*cloud_filtered); std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points (" << pcl::getFieldsList (*cloud_filtered) << ")." << std::endl; // 将结果输出到文件 pcl::PCDWriter writer; writer.write ("../data/table_scene_lms400_downsampled.pcd", *cloud_filtered); return (0); }
-
配置文件 CMakeLists.txt
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(voxel_grid) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable(voxel_grid voxel_grid.cpp) target_link_libraries(voxel_grid ${PCL_LIBRARIES})
-
编译并执行
$ mkdir build $ cd build $ cmake .. $ make $ ./voxel_grid ../data/table_scene_lms400.pcd
// 输出结果 PointCloud before filtering: 460400 data points (x y z intensity distance sid). PointCloud after filtering: 41049 data points (x y z intensity distance sid).
# 对比查看滤波前后的 pcd 文件(见下图) $ pcl_viewer -multiview 1 ../data/table_scene_lms400.pcd ../data/table_scene_lms400_downsampled.pcd
6. 离群点移除
- 激光扫描通常会生成不同点密度的点云数据集。此外,测量误差会导致稀疏的异常值,从而进一步破坏结果。这会使局部点云特征(例如表面法线或曲率变化)的估计复杂化,从而导致错误的值,进而可能导致点云配准失败。通过对每个点的邻域进行统计分析,并对不符合特定条件的部分进行修整,可以解决其中一些不规则现象
- 稀疏离群值的消除基于输入数据集中点到邻居距离的分布的计算。对于每个点,计算从它到所有相邻点的平均距离。通过假设结果分布是具有均值和标准差的高斯分布,可以将其平均距离在由全局距离均值和标准差定义的区间之外的所有点视为离群值并从数据集中进行修剪
6.1 StatisticalOutlierRemoval(统计学离群点移除过滤器)
-
实现步骤
- 查找每一个点的所有邻域点
- 计算每个点到其邻居的距离
d
i
j
d_{ij}
dij
- 其中 i = [ 1 , . . . , m ] i = [1,...,m] i=[1,...,m] 表示共 m 个点, j = [ 1 , . . . , k ] j=[1,...,k] j=[1,...,k] 表示每个点有k个邻居
- 根据高斯分布
d
∼
N
(
μ
,
σ
)
d\sim N(\mu,\sigma)
d∼N(μ,σ) 模型化距离参数,计算所有点与邻居的
μ
\mu
μ(距离的均值)与
σ
\sigma
σ(距离的标准差)
μ = 1 n k ∑ i = 1 m ∑ j = 1 k d i j , σ = 1 n k ∑ i = 1 m ∑ j = 1 k ( d i j − μ ) 2 \mu=\frac{1}{nk}\sum_{i=1}^m\sum_{j=1}^k d_{ij},\\ \sigma=\sqrt{\frac{1}{nk}\sum_{i=1}^m\sum_{j=1}^k\left(d_{ij}-\mu\right)^2} μ=nk1i=1∑mj=1∑kdij,σ=nk1i=1∑mj=1∑k(dij−μ)2 - 为每一个点,计算其与邻居的距离均值 ∑ j = 1 k d i j \sum_{j=1}^{k}d_{i j} ∑j=1kdij
- 遍历所有点,如果其距离的均值大于高斯分布的指定置信度,则移除
-
statistical_removal.cpp
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>
int main (int argc, char** argv) {
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
// 从文件读取点云
pcl::PCDReader reader;
reader.read<pcl::PointXYZ> ("../data/table_scene_lms400.pcd", *cloud);
std::cerr << "Cloud before filtering: " << std::endl;
std::cerr << *cloud << std::endl;
// 创建过滤器,每个点分析计算时考虑的最近邻居个数为 50 个
// 设置标准差阈值为 1,意味着所有距离查询点的平均距离的标准偏差 均大于 1 个标准偏差的所有点 都将被标记为离群值并删除
// 计算输出并将其存储在 cloud_filtered 中
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
// 设置平均距离估计的最近邻居的数量 K
sor.setMeanK(50);
// 设置标准差阈值系数
sor.setStddevMulThresh(1.0);
// 执行过滤
sor.filter(*cloud_filtered);
std::cerr << "Cloud after filtering: " << std::endl;
std::cerr << *cloud_filtered << std::endl;
// 将留下来的点保存到后缀为_inliers.pcd的文件
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ> ("../data/table_scene_lms400_inliers.pcd", *cloud_filtered, false);
// 使用个相同的过滤器,但是对输出结果取反,则得到那些被过滤掉的点,保存到_outliers.pcd文件
sor.setNegative(true);
sor.filter(*cloud_filtered);
writer.write<pcl::PointXYZ> ("../data/table_scene_lms400_outliers.pcd", *cloud_filtered, false);
return (0);
}
-
配置文件 CMakeLists.txt
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(statistical_removal) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable (statistical_removal statistical_removal.cpp) target_link_libraries (statistical_removal ${PCL_LIBRARIES})
-
编译并执行
$ mkdir build $ cd build $ cmake .. $ make $ ./statistical_removal ../data/table_scene_lms400.pcd
// 输出结果 Cloud before filtering: header: seq: 0 stamp: 0 frame_id: points[]: 460400 width: 460400 height: 1 is_dense: 1 sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1] Cloud after filtering: header: seq: 0 stamp: 0 frame_id: points[]: 451410 width: 451410 height: 1 is_dense: 1 sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]
# 下图一:黄绿色部分为移除的离群点,粉红色部分为保留的点 $ pcl_viewer ../data/table_scene_lms400_inliers.pcd ../data/table_scene_lms400_outliers.pcd
# 下图二:左图为已处理离群点后的点云,右图为被移除的离群点云 $ pcl_viewer -multiview 1 ../data/table_scene_lms400_inliers.pcd ../data/table_scene_lms400_outliers.pcd
6.2 多滤波器方案
-
ConditionalRemoval 滤波器
- 条件滤波,设置不同维度滤波规则进行滤波,删除给定输入点云中不满足一个或多个给定条件的所有索引
- 该滤波器删除点云中不符合用户指定的一个或者多个条件的数据点
-
RadiusOutlinerRemoval 滤波器
- 半径离群值滤波,删除其输入点云中在特定范围内至少没有一定数量的邻居的所有索引,在点云数据中,设定每个点一定范围内周围至少有足够多的近邻,不满足就会被删除
- 在点云数据中,用户指定每个点的一定范围内周围至少要有足够多的近邻。例如下图,如果指定至少要有 1 个邻居,只有黄色的点会被删除,如果指定至少要有 2 个邻居,黄色和绿色的点都将被删除
-
remove_outliers.cpp
#include <iostream>
#include <pcl/point_cloud.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/visualization/pcl_visualizer.h>
typedef pcl::PointXYZ PointType;
// 指针所指向的点云数据是一个常量,使用该指针访问和操作所指向的点云数据时,不能对其进行修改
// 这种指针类型通常用于传递指向点云数据的指针参数,以保证传递过程中数据不发生修改
void showPointClouds(const pcl::PointCloud<PointType>::Ptr &cloud, const pcl::PointCloud<PointType>::Ptr &cloud2) {
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
viewer->setBackgroundColor(0.05, 0.05, 0.05, 0);
// 添加一个普通点云
pcl::visualization::PointCloudColorHandlerCustom<PointType> single_color(cloud, 0, 255, 0);
viewer->addPointCloud<PointType>(cloud, single_color, "sample cloud");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");
// 添加第二个点云
pcl::visualization::PointCloudColorHandlerCustom<PointType> single_color2(cloud, 255, 0, 0);
viewer->addPointCloud<PointType>(cloud2, single_color2, "sample cloud 2");
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 4, "sample cloud 2");
viewer->addCoordinateSystem(1.0);
while (!viewer->wasStopped()) {
viewer->spinOnce();
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
exit(0);
}
pcl::PointCloud<PointType>::Ptr cloud(new pcl::PointCloud<PointType>);
pcl::PointCloud<PointType>::Ptr cloud_filtered(new pcl::PointCloud<PointType>);
cloud->width = 100;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i) {
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
// pcl::PCDReader reader;
// reader.read<PointType> ("../data/table_scene_lms400.pcd", *cloud);
/*
字符串比较函数 strcmp() 接受两个字符串作为输入,并根据字典(母)序将它们进行比较
1. 如果第一个字符串小于第二个字符串,则函数返回一个负整数
2. 如果两个字符串相等,则返回 0
3. 如果第一个字符串大于第二个字符串,则返回一个正整数
*/
if (strcmp(argv[1], "-r") == 0) {
pcl::RadiusOutlierRemoval<PointType> outrem;
outrem.setInputCloud(cloud); // 设置输入点云
outrem.setRadiusSearch(0.4); // 设置搜索半径为 0.4 个单位
outrem.setMinNeighborsInRadius(2); // 设置在上述半径内至少需要 2 个邻居点才能保留点云中的某个点
outrem.filter(*cloud_filtered); // 对输入点云进行滤波,并将输出结果保存 cloud_filtered
} else if (strcmp(argv[1], "-c") == 0) {
// 用于保存多个滤波条件的逻辑与关系(and),仅保留 z 高度在 [0.0, 0.8] 之间的点云子集
pcl::ConditionAnd<PointType>::Ptr range_cond(new pcl::ConditionAnd<PointType>());
// 第一个比较条件:用于比较点云中每个点的 z 字段是否大于 0.0
// 第一个参数表示比较对象为点云中点的 z 字段
// 第二个参数 pcl::ComparisonOps::GT 表示比较操作是大于(greater than)操作
// 第三个参数表示要比较的值为 0.0
range_cond->addComparison(pcl::FieldComparison<PointType>::ConstPtr(
new pcl::FieldComparison<PointType>("z", pcl::ComparisonOps::GT, 0.0)));
// 第二个比较条件:用于比较点云中每个点的 z 字段是否小于 0.8
// ConstPtr 表示该指针所指向的对象是一个常量,即其指向的点云数据不能被修改
range_cond->addComparison(pcl::FieldComparison<PointType>::ConstPtr(
new pcl::FieldComparison<PointType>("z", pcl::ComparisonOps::LT, 0.8)));
pcl::ConditionalRemoval<PointType> condrem;
condrem.setCondition(range_cond);
condrem.setInputCloud(cloud);
condrem.setKeepOrganized(true); // 在过滤操作中保留点云的有效性和结构
condrem.filter(*cloud_filtered);
} else {
std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
exit(0);
}
std::cerr << "Cloud before filtering: " << std::endl;
for (size_t i = 0; i < cloud->points.size(); ++i) {
std::cerr << " " << cloud->points[i].x << " "
<< cloud->points[i].y << " "
<< cloud->points[i].z << std::endl;
}
std::cerr << "Cloud after filtering: " << std::endl;
for (size_t i = 0; i < cloud_filtered->points.size(); ++i) {
std::cerr << " " << cloud_filtered->points[i].x << " "
<< cloud_filtered->points[i].y << " "
<< cloud_filtered->points[i].z << std::endl;
}
showPointClouds(cloud, cloud_filtered);
// pcl::PCDWriter writer;
// writer.write<PointType> ("../data/table_scene_lms400_outliers.pcd", *cloud_filtered, false);
return 0;
}
-
配置文件 CMakeLists.txt
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(remove_outliers) find_package(PCL 1.2 REQUIRED) include_directories(${PCL_INCLUDE_DIRS}) link_directories(${PCL_LIBRARY_DIRS}) add_definitions(${PCL_DEFINITIONS}) add_executable (remove_outliers remove_outliers.cpp) target_link_libraries (remove_outliers ${PCL_LIBRARIES})
-
编译并执行
$ mkdir build $ cd build $ cmake .. $ make $ ./remove_outliers -c #(条件滤波:下图一) $ ./remove_outliers -r #(半径离群值滤波:下图二)
相关工具使用
- 对一个点云进行降采样
# 三个轴向上的体素大小,即 X 轴、Y 轴和 Z 轴的体素大小均为 0.03 $ pcl_voxel_grid input.pcd output.pcd -leaf 0.03,0.03,0.03