Removing outliers using a custom non-destructive condition

This document demonstrates how to use the FunctionFilter class to remove points from a PointCloud that do not satisfy a custom criteria. This is a cleaner and faster approach compared to ConditionalRemoval filter or a custom Condition class.

Note

Advanced users can use the FunctorFilter class that can provide a small but measurable speedup when used with a lambda.

The code

First, create a file, let’s say, sphere_removal.cpp in you favorite editor, and place the following inside it:

 1#include <pcl/common/generate.h>
 2#include <pcl/filters/experimental/functor_filter.h>
 3#include <pcl/point_types.h>
 4
 5#include <iostream>
 6
 7int
 8main()
 9{
10  using XYZCloud = pcl::PointCloud<pcl::PointXYZ>;
11  const auto cloud = pcl::make_shared<XYZCloud>();
12  const auto filtered_cloud = pcl::make_shared<XYZCloud>();
13
14  // Create a random generator to fill in the cloud
15  pcl::common::CloudGenerator<pcl::PointXYZ, pcl::common::UniformGenerator<float>>
16      generator{{-2.0, 2, 1234}};
17  generator.fill(10, 1, *cloud);
18
19  std::cerr << "Cloud before filtering: " << std::endl;
20  for (const auto& pt : *cloud)
21    std::cerr << "    " << pt.x << " " << pt.y << " " << pt.z << std::endl;
22
23  // Setup a condition to reject points inside a filter
24  const Eigen::Vector3f center{0, 0, 2};
25  const float radius = 2;
26
27  pcl::experimental::FilterFunction<pcl::PointXYZ> filter;
28  filter = [=](const XYZCloud& cloud, pcl::index_t idx) {
29    return ((cloud[idx].getVector3fMap() - center).norm() >= radius);
30  };
31
32  // build the filter
33  pcl::experimental::FunctionFilter<pcl::PointXYZ> func_filter(filter);
34  func_filter.setInputCloud(cloud);
35
36  // apply filter
37  func_filter.filter(*filtered_cloud);
38
39  // display pointcloud after filtering
40  std::cerr << "Cloud after filtering: " << std::endl;
41  for (const auto& pt : *filtered_cloud)
42    std::cerr << "    " << pt.x << " " << pt.y << " " << pt.z << std::endl;
43
44  return (0);
45}

The explanation

Now, let’s break down the code piece by piece.

In the following lines, we define the PointCloud structures, fill in the input cloud, and display its content to screen.

  using XYZCloud = pcl::PointCloud<pcl::PointXYZ>;
  const auto cloud = pcl::make_shared<XYZCloud>();
  const auto filtered_cloud = pcl::make_shared<XYZCloud>();

  // Create a random generator to fill in the cloud
  pcl::common::CloudGenerator<pcl::PointXYZ, pcl::common::UniformGenerator<float>>
      generator{{-2.0, 2, 1234}};
  generator.fill(10, 1, *cloud);

  std::cerr << "Cloud before filtering: " << std::endl;
  for (const auto& pt : *cloud)
    std::cerr << "    " << pt.x << " " << pt.y << " " << pt.z << std::endl;

Then, we create the condition which a given point must satisfy so that it remains in our PointCloud. To do this we create a std::function which accepts a PointCloud by const reference and an index, and returns true only if the point lies inside a sphere. This is then used to build the filter

  // Setup a condition to reject points inside a filter
  const Eigen::Vector3f center{0, 0, 2};
  const float radius = 2;

  pcl::experimental::FilterFunction<pcl::PointXYZ> filter;
  filter = [=](const XYZCloud& cloud, pcl::index_t idx) {
    return ((cloud[idx].getVector3fMap() - center).norm() >= radius);
  };

  // build the filter
  pcl::experimental::FunctionFilter<pcl::PointXYZ> func_filter(filter);
  func_filter.setInputCloud(cloud);

This last bit of code just applies the filter to our original PointCloud, and removes all of the points that do not satisfy the conditions we specified. Then it outputs all of the points remaining in the PointCloud.

  // apply filter
  func_filter.filter(*filtered_cloud);

  // display pointcloud after filtering
  std::cerr << "Cloud after filtering: " << std::endl;
  for (const auto& pt : *filtered_cloud)
    std::cerr << "    " << pt.x << " " << pt.y << " " << pt.z << std::endl;

Compiling and running the program

Add the following lines to your CMakeLists.txt file:

1cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
2
3project(function_filter)
4
5find_package(PCL 1.11.1.99 REQUIRED)
6
7add_executable (sphere_removal sphere_removal.cpp)
8target_link_libraries (sphere_removal ${PCL_LIBRARIES})
9add_definitions(${PCL_DEFINITIONS})

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

$ ./sphere_removal

You will see something similar to:

Cloud before filtering:
    -1.23392 1.81505 -0.968005
    -0.00934529 1.36497 0.158734
    0.488435 1.96851 -0.0534078
    1.27135 1.16404 -1.00462
    -0.249089 -0.0815883 1.13229
    0.448447 1.48914 1.78378
    1.14143 1.77363 1.68965
    1.08544 -1.01664 -1.13041
    1.1199 0.9951 -1.13308
    1.44268 -1.44434 -0.391739
Cloud after filtering:
    -1.23392 1.81505 -0.968005
    -0.00934529 1.36497 0.158734
    0.488435 1.96851 -0.0534078
    1.27135 1.16404 -1.00462
    1.14143 1.77363 1.68965
    1.08544 -1.01664 -1.13041
    1.1199 0.9951 -1.13308
    1.44268 -1.44434 -0.391739