Construct a concave or convex hull polygon for a plane model

In this tutorial we will learn how to calculate a simple 2D hull polygon (concave or convex) for a set of points supported by a plane.

The code

First, download the dataset table_scene_mug_stereo_textured.pcd and save it somewhere to disk.

Then, create a file, let’s say, concave_hull_2d.cpp or convex_hull_2d.cpp in your favorite editor and place the following inside:

 1#include <pcl/ModelCoefficients.h>
 2#include <pcl/io/pcd_io.h>
 3#include <pcl/point_types.h>
 4#include <pcl/sample_consensus/method_types.h>
 5#include <pcl/sample_consensus/model_types.h>
 6#include <pcl/filters/passthrough.h>
 7#include <pcl/filters/project_inliers.h>
 8#include <pcl/segmentation/sac_segmentation.h>
 9#include <pcl/surface/concave_hull.h>
10
11int
12main ()
13{
14  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), 
15                                      cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>), 
16                                      cloud_projected (new pcl::PointCloud<pcl::PointXYZ>);
17  pcl::PCDReader reader;
18
19  reader.read ("table_scene_mug_stereo_textured.pcd", *cloud);
20  // Build a filter to remove spurious NaNs and scene background
21  pcl::PassThrough<pcl::PointXYZ> pass;
22  pass.setInputCloud (cloud);
23  pass.setFilterFieldName ("z");
24  pass.setFilterLimits (0, 1.1);
25  pass.filter (*cloud_filtered);
26  std::cerr << "PointCloud after filtering has: "
27            << cloud_filtered->size () << " data points." << std::endl;
28
29  pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
30  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
31  // Create the segmentation object
32  pcl::SACSegmentation<pcl::PointXYZ> seg;
33  // Optional
34  seg.setOptimizeCoefficients (true);
35  // Mandatory
36  seg.setModelType (pcl::SACMODEL_PLANE);
37  seg.setMethodType (pcl::SAC_RANSAC);
38  seg.setDistanceThreshold (0.01);
39
40  seg.setInputCloud (cloud_filtered);
41  seg.segment (*inliers, *coefficients);
42  std::cerr << "PointCloud after segmentation has: "
43            << inliers->indices.size () << " inliers." << std::endl;
44
45  // Project the model inliers
46  pcl::ProjectInliers<pcl::PointXYZ> proj;
47  proj.setModelType (pcl::SACMODEL_PLANE);
48  // proj.setIndices (inliers);
49  proj.setInputCloud (cloud_filtered);
50  proj.setModelCoefficients (coefficients);
51  proj.filter (*cloud_projected);
52  std::cerr << "PointCloud after projection has: "
53            << cloud_projected->size () << " data points." << std::endl;
54
55  // Create a Concave Hull representation of the projected inliers
56  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_hull (new pcl::PointCloud<pcl::PointXYZ>);
57  pcl::ConcaveHull<pcl::PointXYZ> chull;
58  chull.setInputCloud (cloud_projected);
59  chull.setAlpha (0.1);
60  chull.reconstruct (*cloud_hull);
61
62  std::cerr << "Concave hull has: " << cloud_hull->size ()
63            << " data points." << std::endl;
64
65  pcl::PCDWriter writer;
66  writer.write ("table_scene_mug_stereo_textured_hull.pcd", *cloud_hull, false);
67
68  return (0);
69}

Note

This tutorial is written for assuming you are looking for the CONCAVE hull. If you would like the CONVEX hull for a plane model, just replace concave with convex at EVERY point in this tutorial, including the source file, file names and the CMakeLists.txt file. You will also need to comment out setAlpha(), as this is not applicable to convex hulls.

The explanation

In the following lines of code, a segmentation object is created and some parameters are set. We use the SACMODEL_PLANE to segment this PointCloud, and the method used to find this model is SAC_RANSAC. The actual segmentation takes place when seg.segment (*inliers, *coefficients); is called. This function stores all of the inlying points (on the plane) to inliers, and it stores the coefficients to the plane (a * x + b * y + c * z = d) in coefficients.

  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
  // Create the segmentation object
  pcl::SACSegmentation<pcl::PointXYZ> seg;
  // Optional
  seg.setOptimizeCoefficients (true);
  // Mandatory
  seg.setModelType (pcl::SACMODEL_PLANE);
  seg.setMethodType (pcl::SAC_RANSAC);
  seg.setDistanceThreshold (0.01);

  seg.setInputCloud (cloud_filtered);
  seg.segment (*inliers, *coefficients);

The next bit of code projects the inliers onto the plane model and creates another cloud. One way that we could do this is by just extracting the inliers that we found before, but in this case we are going to use the coefficients we found before. We set the model type we are looking for and then set the coefficients, and from that the object knows which points to project from cloud_filtered to cloud_projected.

  pcl::ProjectInliers<pcl::PointXYZ> proj;
  proj.setModelType (pcl::SACMODEL_PLANE);
  // proj.setIndices (inliers);
  proj.setInputCloud (cloud_filtered);
  proj.setModelCoefficients (coefficients);
  proj.filter (*cloud_projected);

The real interesting part is in the lines below, where the ConcaveHull object gets created and the reconstruction is performed:

  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_hull (new pcl::PointCloud<pcl::PointXYZ>);
  pcl::ConcaveHull<pcl::PointXYZ> chull;
  chull.setInputCloud (cloud_projected);
  chull.setAlpha (0.1);
  chull.reconstruct (*cloud_hull);

Compiling and running the program

Add the following lines to your CMakeLists.txt file:

 1cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 2
 3project(concave_hull_2d)
 4
 5find_package(PCL 1.2 REQUIRED)
 6
 7include_directories(${PCL_INCLUDE_DIRS})
 8link_directories(${PCL_LIBRARY_DIRS})
 9add_definitions(${PCL_DEFINITIONS})
10
11add_executable (concave_hull_2d concave_hull_2d.cpp)
12target_link_libraries (concave_hull_2d ${PCL_LIBRARIES})

After you have made the executable, you can run it. Simply do:

$ ./concave_hull_2d

You will see something similar to:

PointCloud after filtering has: 139656 data points.
PointCloud after segmentation has: 123727 inliers.
PointCloud after projection has: 139656 data points.
Concave hull has: 457 data points.