本教程类似于教会你加减乘除(点云基础、分割、滤波、配准),然后自己做一道包含加减乘除的综合题(实践操作)。
此教程用最简单的例程,给大家直观感受。就像做一道物理大题,我们总用理想情况,便于理解学习。因此,所有的示例都围绕斯坦福的小兔子展开。
我不会带你们看官方文档,而是用自己的想法和语言来表达,目的是让一头雾水的人会去应用。毕竟我知道,做这个的大多数人,也只是为了应用,如果去深究原理,必然也不会来到这里。很多东西,我们只会应用就会“高人一等”,原理重要吗?重要!但对于大多数人来说,没有必要。司机并不一定知道汽车内部是什么结构,怎样运行的,他只需要会开车。没错,所以我们只能是司机,但对于我们来说,足够了。在我看来学会了应用,才会去学习更深层次的东西。当你不知原因打印出一个"hello world!",那种神秘和兴奋,会引导你去想弄清楚,这到底是为什么。
首先说明我的环境:win10 +visual studio 2013 +PCL1.8.0
如果你使用其他版本,只要配置好了,也没有什么问题。
如果你还没有配置好环境,请看以下文章。
此外需要你有一定的c/c++,或是其他任意一门语言基础。
真的只需要基础就可以,这方面课程也比较多,请先自行去学习。
示例斯坦福兔子下载
ヾ( ̄ー ̄)X
滤波无非就是把数据中的噪声(无用的干扰信息)进行过滤去除。好比在景区拍照,你永远都会拍到别的游客!自拍除外。
其实记住一句话,滤波的核心就是调参。前人已经为我们设计了很多算法,也有人为我们把代码写入库内。 好人一生平安! 我们做的仅仅是简简单单的调参。
这就好像给一个二次函数寻找零点。
f ( x ) = x 2 − 2 f(x) = x^2-2 \quad \quad f(x)=x2−2
我们目的就是找到使f(x)最接近0的那个值,但前提是我们都是蠢b,只会代数。
其实面对一个真实数据,以我们目前的水平,就是蠢b。我们能做的只能是调参。
在实际问题中,可能各种滤波都不尽人意。而且,我们参数如果设置的不合理,会出现各种奇怪的错误。
本篇可能体会不到,下一篇才有感受!
你能不能处理好你的真实数据,修行就看个人了。
言归正传,在我看来,我们有两种方式去掉它,一种是简单粗暴的剪裁,另一种是筛点。
我们也会看到这种方法叫做离群点去除,群不就是你想拍的东西吗?离群的自然是你不想拍的。
//引用头文件,好人一生平安!
#include
#include
#include
#include
#include
#include
int main()
{
//加载原点云
pcl::PointCloud<pcl::PointXYZ>::Ptr my_cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr my_cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader my_reader;
my_reader.read<pcl::PointXYZ>("你路径下的rabbit.pcd",*my_cloud);
//创建条件滤波器
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());
//设置过滤范围,根据x y z轴
//GT大于,GE大于或等于,NE是不等于,EQ是等于,LT小于,LE小于或等于
//下列说明取值范围 -7≤x<1 0≤y<10
//那么问题来了,我怎么知道点云的坐标分布?具体看代码之后的文章
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, 0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 10.0)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, -7)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 1)));
//执行点云移除处理,并将剩下的点添加到filtered(过滤后的点云)
pcl::ConditionalRemoval<pcl::PointXYZ> condrem(range_cond, false);
condrem.setInputCloud(my_cloud);
condrem.setKeepOrganized(false);
condrem.filter(*my_cloud_filtered);
//打印一下剩下的点云个数而已
std::cout << "输入的点云个数: " << my_cloud->points.size() << std::endl;
std::cout << "输出的点云个数: " << my_cloud_filtered->points.size() << std::endl;
//创建显示窗口并命名3D viewer
pcl::visualization::PCLVisualizer viewer("3D viewer");
//这里不要深究,可以同时一个视窗展示2个点
//并且跟鼠标拖动改变的是同一个视角,方面观察。
//自己可以根据需求改变点云颜色和背景颜色
int v1(0);
//xmin, ymin, xmax, ymax,取值范围0-1
viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);
//背景颜色 黑色
viewer.setBackgroundColor(0, 0, 0, v1);
//点云颜色 绿色
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green0(my_cloud, 0, 225, 0);
viewer.addPointCloud(my_cloud, green0, "cloud", v1);
//同上
int v2(0);
viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
viewer.setBackgroundColor(0.3, 0.3, 0.3, v2);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green1(my_cloud_filtered, 0, 225, 0);
viewer.addPointCloud(my_cloud_filtered, green1, "cloud_filtered", v2);
//添加点云坐标系
viewer.addCoordinateSystem();
//循环显示
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
return (0);
}
请先读完代码注释!那就没什么好说的了。
所以重点是我们要知道点云的XYZ坐标分布!
我们可以使用CloudCompare软件来查看,本人并不怎么会使用,如果有大神还请指教修改。
仔细看左下角写明了点云总体所占坐标体积,好吧,我只会凭感觉来,还没专门学过这个软件。
红绿蓝RGB三色的三维指示坐标轴XYZ
颜色 | 坐标轴 |
---|---|
红色R | X |
绿色G | Y |
蓝色B | Z |
右手坐标系
好吧,其实,在cloudcompare里我们完全可以手动切割。
兔子:好像身体被掏空
难道拍照有自带美颜功能,你就不给自己再P图了吗?
好像是,也好像不是。如果你有一堆点云数据需要处理呢,这种手动效果很好,但却很慢。
我从头到尾都会说:“自己要根据所要解决的问题进行选择”。
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
//加载读取点云文件
if (pcl::io::loadPCDFile("E:\\Desktop\\code\\点云滤波\\rabbit.pcd", *cloud) == -1)
{
cout << "COULD NOT READ FILE \n";
system("pause");
return (-1);
}
cout << "points size is:" << cloud->size() << endl;
// 定义滤波器
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(0.0, 1.0);
//pass.setFilterLimitsNegative (true); 为true则取反 不写默认为false
pass.filter(*cloud_filtered);
//显示窗口
pcl::visualization::PCLVisualizer viewer("3D viewer");
int v1(0);
viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);//xmin, ymin, xmax, ymax,取值范围0-1
viewer.setBackgroundColor(0, 0, 0, v1);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green0(cloud, 0, 225, 0);
viewer.addPointCloud(cloud, green0, "cloud", v1);
int v2(0);
viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
viewer.setBackgroundColor(0.3, 0.3, 0.3, v2);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green1(cloud_filtered, 0, 225, 0);
viewer.addPointCloud(cloud_filtered, green1, "cloud_filtered", v2);
cout << "filtered points size is:" << cloud_filtered->size() << endl;
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
return 0;
}
直通滤波器在切割上貌似只能在一个轴上,除非再多创建几个滤波器。
至于跟条件滤波有什么区别?鄙人拙见,没什么区别。如果你只想过滤一个轴上的点,想更简洁更快,必然选择直通滤波。反之,还是条件滤波。
网上一般过滤z轴(我愿称之为深度轴),也就是一些远近点的过滤。
但如果我们三维重建一个物体,毫无疑问选择条件滤波,因为在x、y轴(我愿称之为平面轴),也有很多我们不需要的点,具体项目具体分析,适合自己解决问题的就是最好的。
pass.setFilterLimitsNegative (false);
我想说的是,你有没有发现两种滤波代码有什么不同,又有什么不同。
你会发现,两种代码的不同之处,只有头文件和定义滤波器部分罢了。
自然而然,我们用什么方法滤波,就要引用别人写好的头文件。
这就像更换天文望远镜的目镜一样,其他的代码根本不用动哈。
你甚至可以再定义多个滤波器,想怎么滤,就怎么滤。
pcl::方法<pcl::PointXYZ> 方法对象名;
//传入原来的点云cloud
方法对象名.setInputCloud(cloud);
...
...
...此处省略不同方法的参数的设置
//生成过滤后的点云cloud_filtered
方法对象名.filter(*cloud_filtered);
然后套娃就好,
你可以在下面把cloud_filtered传入另一种过滤方法对象,
传出时命名cloud_filtered_again 怎么命名随你啦
还是那句话,怎么过滤好,要根据自己的情况来。
什么,你还发现了读取点云的代码不一样?无非是两种方法罢了,都是可以替换的,是不是这种更简单?要学会变通!
文章开头我说过,滤波就是调参,我们改动的也不过只是滤波器内的参数,像这种剪裁方式的滤波还是比较好控制的。
另一种,我称之为点云滤波之筛点,比如:高斯滤波、半径滤波、体素滤波等等,下一篇再介绍。
根据点赞来决定下一篇何时发布。