背景:
pcl官方教程:
http://www.pointclouds.org/documentation/tutorials/progressive_morphological_filtering.php#progressive-morphological-filtering
论文:http://users.cis.fiu.edu/~chens/PDF/TGRS.pdf
注:
这个算法本身用于处理高空获取的激光雷达数据,把地面与非地面的物体分割,来获取地貌3d地图的。我跑到数据与这类数据有一个明显的区别,就是x,y的范围与z的范围相差不多,而他们的高空数据,z的变化范围相比其他两个轴较小。但是我同样想试试它是否能帮我鲁棒地去除地面的点来达到分割物体和地面的效果。
还有,我的数据本来z轴不是对应着不是向上的方向,我还调整了一下数据。
1.使用感受
参数多,不看论文细节,无法调参数。如果参数不调好,算法耗时很长。对使用者极度不友好。
2.算法细节
首先根据数据里x, y的范围值,2d栅格化点云所占的空间。每个格子拥有0个或多个点,只是他们的高度(论文里的evaluation)不同,对于这个格子,如果0个点,用插值找一个点,如果多个点,把最低的点留下来。然后,
遍历每一行()
对于每一行,存储起来这一行里所以格子里的所有点高度。
遍历每一个格子()
做一个“开”运算。开运算需要设置窗口大小,以及窗口内的点的高度。那么开运算是什么呢,后面会解释。
把开运算的结果(一个最低的高度值)设置为这一个格子的“窗口内洼点”。
如果这一个格子里的那个最低点高于这个“窗口内洼点”到一定程度(高度差距阈值),这个格子原本包含的所有点都
是非地面点。
end
end
为了使算法里的“窗口内洼点”参考更大范围的局部点云,算法慢慢增大窗口大小和高度阈值,并运行以上说的循环。只要有一个循环里,一个格子的所有点被视为非地面点,那么这些点就是非地面点。最后剩下的都是地面点。
那么,开运算是什么鬼?其实很简单,它先对一整行每一个格子做了一个Erosion操作,用一整行Erosion结果再对做一个Dilation操作,用两幅图说明你就明白。
3. 调参
因为不明的原因,算法在我数据上运行时间是半个小时。所以调参研究时间太长,这里不研究。当时为了我本人调参,我把ProgressiveMorphologicalFiltering.cpp的内容抽出来,写成my_pmf这个函数,方便我打印过程的数据。详见底部的代码。my_pmf只需要输入点云,参数都是放在代码上方,都是全局变量。最后生成object.pcd和ground.pcd, 你可以通过运行$ ./pcl_viewer object.pcd ground.pcd。pcl_viewer可执行文件一般被放在了linux系统的usr/bin。
可以参考:
https://github.com/PointCloudLibrary/pcl/blob/master/segmentation/include/pcl/segmentation/progressive_morphological_filter.h4.跑自己的数据集
原点云
地面点云:
物体点云:
分割效果还是很好的,就是运行时间长。
5. 总结
很多参数要设置,而且很难设置,参数需要对算法深度理解才能调好。运行时间长,实时性低(即使跑官方的代码和数据集,时间也较长)。算法对地面的分割效果好。
6. 代码
#include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/extract_indices.h> #include <pcl/segmentation/progressive_morphological_filter.h> #include <pcl/filters/morphological_filter.h> #include <pcl/pcl_base.h> using namespace std; #define max_window_size 0.05 #define slope 0.7f #define max_distance 0.1f #define initial_distance 0.01f #define cell_size 0.01f #define base 2.0f #define exponential true pcl::PointCloud<pcl::PointXYZ>::Ptr my_pmf (pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in) { // Compute the series of window sizes and height thresholds std::vector<float> height_thresholds; std::vector<float> window_sizes; std::vector<int> ground_indices; int iteration = 0; float window_size = 0.0f; float height_threshold = 0.0f; while (window_size < max_window_size) { // Determine the initial window size. if (exponential) window_size = cell_size * (2.0f * std::pow (base, iteration) + 1.0f); else window_size = cell_size * (2.0f * (iteration+1) * base + 1.0f); cout << "window_size " << window_size << endl; // Calculate the height threshold to be used in the next iteration. if (iteration == 0) height_threshold = initial_distance; else height_threshold = slope * (window_size - window_sizes[iteration-1]) * cell_size + initial_distance; cout << "height_threshold " << height_threshold << endl; // Enforce max distance on height threshold if (height_threshold > max_distance) height_threshold = max_distance; window_sizes.push_back (window_size); height_thresholds.push_back (height_threshold); iteration++; } // Ground indices are initially limited to those points in the input cloud we // wish to process for (int i=0;i<cloud_in->points.size();i++){ ground_indices.push_back(i); } // Progressively filter ground returns using morphological open for (size_t i = 0; i < window_sizes.size (); ++i) { cout<< "Iteration " << i << "height threshold = " << height_thresholds[i] << " window size = " << window_sizes[i] << endl; // Limit filtering to those points currently considered ground returns typename pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); pcl::copyPointCloud<pcl::PointXYZ> (*cloud_in, ground_indices, *cloud); // Create new cloud to hold the filtered results. Apply the morphological // opening operation at the current window size. typename pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_f (new pcl::PointCloud<pcl::PointXYZ>); pcl::applyMorphologicalOperator<pcl::PointXYZ> (cloud, window_sizes[i], pcl::MORPH_OPEN, *cloud_f); // Find indices of the points whose difference between the source and // filtered point clouds is less than the current height threshold. std::vector<int> pt_indices; //cout << "ground.size() = " << ground.size() << endl; for (size_t p_idx = 0; p_idx < ground_indices.size (); ++p_idx) { float diff = cloud->points[p_idx].z - cloud_f->points[p_idx].z; //cout << "diff " << diff << endl; if (diff < height_thresholds[i]) pt_indices.push_back (ground_indices[p_idx]); } // Ground is now limited to pt_indices ground_indices.swap (pt_indices); cout << "ground now has " << ground_indices.size () << " points" << endl; } typename pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out (new pcl::PointCloud<pcl::PointXYZ>); // Extract cloud_in with ground indices pcl::copyPointCloud<pcl::PointXYZ> (*cloud_in, ground_indices, *cloud_out); return cloud_out; } 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::PointIndicesPtr ground (new pcl::PointIndices); // Fill in the cloud data pcl::PCDReader reader; // Replace the path below with the path where you saved your file reader.read<pcl::PointXYZ> ("test_pcd_from_topview.pcd", *cloud); std::cerr << "Cloud before filtering: " << std::endl; std::cerr << *cloud << std::endl; // Run progressive morphological filter cloud_filtered = my_pmf(cloud); std::cerr << "Ground cloud after filtering: " << std::endl; std::cerr << *cloud_filtered << std::endl; pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("ground.pcd", *cloud_filtered, false); // Extract non-ground returns pcl::ExtractIndices<pcl::PointXYZ> extract; extract.setInputCloud (cloud); extract.setIndices (ground); //extract.filter (*cloud_filtered); extract.setNegative (true); extract.filter (*cloud_filtered); std::cerr << "Object cloud after filtering: " << std::endl; std::cerr << *cloud_filtered << std::endl; writer.write<pcl::PointXYZ> ("object.pcd", *cloud_filtered, false); return (0); }