Identifying ground returns using ProgressiveMorphologicalFilter segmentation
Implements the Progressive Morphological Filter for segmentation of ground points.
Background
A complete description of the algorithm can be found in the article “A Progressive Morphological Filter for Removing Nonground Measurements from Airborne LIDAR Data” by K. Zhang, S. Chen, D. Whitman, M. Shyu, J. Yan, and C. Zhang.
The code
First, download the dataset samp11-utm.pcd and save it somewhere to disk.
Then, create a file, let’s say, bare_earth.cpp
in your favorite editor, and
place the following inside it:
1#include <iostream>
2#include <pcl/io/pcd_io.h>
3#include <pcl/point_types.h>
4#include <pcl/filters/extract_indices.h>
5#include <pcl/segmentation/progressive_morphological_filter.h>
6
7int
8main ()
9{
10 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
11 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
12 pcl::PointIndicesPtr ground (new pcl::PointIndices);
13
14 // Fill in the cloud data
15 pcl::PCDReader reader;
16 // Replace the path below with the path where you saved your file
17 reader.read<pcl::PointXYZ> ("samp11-utm.pcd", *cloud);
18
19 std::cerr << "Cloud before filtering: " << std::endl;
20 std::cerr << *cloud << std::endl;
21
22 // Create the filtering object
23 pcl::ProgressiveMorphologicalFilter<pcl::PointXYZ> pmf;
24 pmf.setInputCloud (cloud);
25 pmf.setMaxWindowSize (20);
26 pmf.setSlope (1.0f);
27 pmf.setInitialDistance (0.5f);
28 pmf.setMaxDistance (3.0f);
29 pmf.extract (ground->indices);
30
31 // Create the filtering object
32 pcl::ExtractIndices<pcl::PointXYZ> extract;
33 extract.setInputCloud (cloud);
34 extract.setIndices (ground);
35 extract.filter (*cloud_filtered);
36
37 std::cerr << "Ground cloud after filtering: " << std::endl;
38 std::cerr << *cloud_filtered << std::endl;
39
40 pcl::PCDWriter writer;
41 writer.write<pcl::PointXYZ> ("samp11-utm_ground.pcd", *cloud_filtered, false);
42
43 // Extract non-ground returns
44 extract.setNegative (true);
45 extract.filter (*cloud_filtered);
46
47 std::cerr << "Object cloud after filtering: " << std::endl;
48 std::cerr << *cloud_filtered << std::endl;
49
50 writer.write<pcl::PointXYZ> ("samp11-utm_object.pcd", *cloud_filtered, false);
51
52 return (0);
53}
The explanation
Now, let’s break down the code piece by piece.
The following lines of code will read the point cloud data from disk.
// Fill in the cloud data
pcl::PCDReader reader;
// Replace the path below with the path where you saved your file
reader.read<pcl::PointXYZ> ("samp11-utm.pcd", *cloud);
Then, a pcl::ProgressiveMorphologicalFilter filter is created. The output (the indices of ground returns) is computed and stored in ground.
// Create the filtering object
pcl::ProgressiveMorphologicalFilter<pcl::PointXYZ> pmf;
pmf.setInputCloud (cloud);
pmf.setMaxWindowSize (20);
pmf.setSlope (1.0f);
pmf.setInitialDistance (0.5f);
pmf.setMaxDistance (3.0f);
pmf.extract (ground->indices);
To extract the ground points, the ground indices are passed into a pcl::ExtractIndices filter.
// Create the filtering object
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud (cloud);
extract.setIndices (ground);
extract.filter (*cloud_filtered);
The ground returns are written to disk for later inspection.
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ> ("samp11-utm_ground.pcd", *cloud_filtered, false);
Then, the filter is called with the same parameters, but with the output negated, to obtain the non-ground (object) returns.
// Extract non-ground returns
extract.setNegative (true);
extract.filter (*cloud_filtered);
And the data is written back to disk.
writer.write<pcl::PointXYZ> ("samp11-utm_object.pcd", *cloud_filtered, false);
Compiling and running the program
Add the following lines to your CMakeLists.txt file:
1cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
2
3project(bare_earth)
4
5find_package(PCL 1.7.2 REQUIRED)
6
7include_directories(${PCL_INCLUDE_DIRS})
8link_directories(${PCL_LIBRARY_DIRS})
9add_definitions(${PCL_DEFINITIONS})
10
11add_executable (bare_earth bare_earth.cpp)
12target_link_libraries (bare_earth ${PCL_LIBRARIES})
After you have made the executable, you can run it. Simply do:
$ ./bare_earth
You will see something similar to:
Cloud before filtering:
points[]: 38010
width: 38010
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]
Ground cloud after filtering:
points[]: 18667
width: 18667
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]
Object cloud after filtering:
points[]: 19343
width: 19343
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]
You can also look at your outputs samp11-utm_inliers.pcd and samp11-utm_outliers.pcd:
$ ./pcl_viewer samp11-utm_ground.pcd samp11-utm_object.pcd
You are now able to see both the ground and object returns in one viewer. You should see something similar to this: