Point Cloud Library (PCL)

Table of Contents

1. PCL 简介

The Point Cloud Library (or PCL) is a large scale, open project for 2D/3D image and point cloud processing. The PCL framework contains numerous state-of-the art algorithms including filtering, feature estimation, surface reconstruction, registration, model fitting and segmentation. These algorithms can be used, for example, to filter outliers from noisy data, stitch 3D point clouds together, segment relevant parts of a scene, extract keypoints and compute descriptors to recognize objects in the world based on their geometric appearance, and create surfaces from point clouds and visualize them -- to name a few.

参考:
What is PCL?: http://pointclouds.org/about/
PCL Tutorials: http://pointclouds.org/documentation/tutorials/index.php

1.1. 什么是 Point Cloud

A point cloud is a data structure used to represent a collection of multi-dimensional points and is commonly used to represent three-dimensional data. In a 3D point cloud, the points usually represent the X, Y, and Z geometric coordinates of an underlying sampled surface. When color information is present the point cloud becomes 4D.

Point clouds can be acquired from hardware sensors such as stereo cameras, 3D scanners, or time-of-flight cameras, or generated from a computer program synthetically. PCL supports natively the OpenNI 3D interfaces, and can thus acquire and process data from devices such as the Microsoft Kinect, or the Asus XTionPRO.

1.2. PCL dependencies

In order to compile every component of the PCL library we need to download and compile a series of 3rd party library dependencies:

Table 1: PCL dependencies
PCL 3rd party library dependency PCL 中用它做什么 是否必需
Boost version >= 1.46.1 (http://www.boost.org/) used for shared pointers, and threading. mandatory
Eigen version >= 3.0.0 (http://eigen.tuxfamily.org/) used as the matrix backend for SSE optimized math. mandatory
FLANN version >= 1.6.8 (http://www.cs.ubc.ca/research/flann/) used in kdtree for fast approximate nearest neighbors search. mandatory
Visualization ToolKit (VTK) version >= 5.6.1 (http://www.vtk.org/) used in visualization for 3D point cloud rendering and visualization. mandatory
googletest version >= 1.6.0 (http://code.google.com/p/googletest/) used to build test units. optional
QHULL version >= 2011.1 (http://www.qhull.org/) used for convex/concave hull decompositions in surface. optional
OpenNI version >= 1.1.0.25 (http://www.openni.org/) used to grab point clouds from OpenNI compliant devices. optional
Qt version >= 4.6 (http://qt.digia.com/) used for developing applications with a graphical user interface (GUI) optional

1.3. PCL 安装

Ubuntu 中可以按下面方式安装提前编译好的 PCL:

sudo add-apt-repository ppa:v-launchpad-jochen-sprickerhof-de/pcl
sudo apt-get update
sudo apt-get install libpcl-all

其它系统中,PCL 的安装方式可参考:http://pointclouds.org/downloads/

2. PCL 基本使用

PCL 中提供了一系列用来处理 Point Cloud 的数据结构和算法。

2.1. PCL 中基本数据结构

PCL 中最重要的数据结构是 pcl::PointCloud ,用来表示 Point Cloud 数据的集合(可看作是 C++中的容器) ,它是一个模板类(需要指定具体的 Point Cloud 类型)。例如,要创建 4 个随机的 pcl::PointXYZ 类型(后文将介绍其它类型)的 Point Cloud,可以这样:

pcl::PointCloud<pcl::PointXYZ> cloud;
cloud.push_back (pcl::PointXYZ (rand (), rand (), rand ()));
cloud.push_back (pcl::PointXYZ (rand (), rand (), rand ()));
cloud.push_back (pcl::PointXYZ (rand (), rand (), rand ()));
cloud.push_back (pcl::PointXYZ (rand (), rand (), rand ()));

pcl::PointCloud 中重要的 public field 有:

Table 2: PointCloud 中重要的 public field
PointCloud 中 public field 说明
width specifies the width of the point cloud dataset in the number of points. WIDTH has two meanings: it can specify the total number of points in the cloud (equal with POINTS see below) for unorganized datasets; it can specify the width (total number of points in a row) of an organized point cloud dataset. Mandatory.
height specifies the height of the point cloud dataset in the number of points. HEIGHT has two meanings: it can specify the height (total number of rows) of an organized point cloud dataset; it is set to 1 for unorganized datasets (thus used to check whether a dataset is organized or not). Mandatory.
points the data array where all points of type PointT are stored. Mandatory.
is_dense specifies if all the data in points is finite (true), or whether it might contain Inf/NaN values (false). Mandatory.
sensor_origin_ specifies the sensor acquisition pose (origin/translation). Optional.
sensor_orientation_ specifies the sensor acquisition pose (rotation). Optional.

2.1.1. Point Cloud 的类型

前面介绍过 pcl::PointCloud 是 Point Cloud 数据的容器,它是一个模板类,应用时需要指定具体的类型。

PCL 中定义了一些常用的 Point Cloud 类型。

Table 3: 常用的 Point Cloud 类型
Point Cloud 类型 说明
pcl::PointXYZ This is the simplest type of point and probably one of the most used; it stores only 3D xyz information.
pcl::PointXYZI This type of point is very similar to the previous one, but it also includes a field for the intensity of the point. Intensity can be useful when obtaining points based on a certain level of return from a sensor. There are two other standard identical point types to this one: the first one is pcl::InterestPoint, which has a field to store strength instead of intensity, and pcl::PointWithRange, which has a field to store the range instead of either intensity or strength.
pcl::PointXYZRGBA This type of point stores 3D information as well as color (RGB = Red, Green, Blue) and transparency (A = Alpha).
pcl::PointXYZRGB This type is similar to the previous point type, but it differs in that it lacks the transparency field.
pcl::Normal This is one of the most used types of points; it represents the surface normal at a given point and a measure of its curvature.
pcl::PointNormal This type is exactly the same as the previous one; it contains the surface normal and curvature information at a given point, but it also includes the 3D xyz coordinates of the point. Variants of this point are PointXYZRGBNormal and the PointXYZINormal, which, as the names suggest, include color (former) and intensity (latter).

2.2. PCL 中的算法

PCL 中实现了很多处理 3D 数据的算法。使用算法的基本流程如下:

pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr result(new pcl::PointCloud<pcl::PointXYZ>);

pcl::Algorithm<pcl::PointXYZ> algorithm;          // 应用时,把 pcl::Algorithm 替换为具体的算法名字。
algorithm.setInputCloud(cloud);
algorithm.setParameter(1.0);
algorithm.setAnotherParameter(0.33);
algorithm.process(*result);

2.3. 第一个实例:PassThrough filter

In this tutorial we will learn how to perform a simple filtering along a specified dimension – that is, cut off values that are either inside or outside a given user range.

这个例子摘自:http://pointclouds.org/documentation/tutorials/passthrough.php#passthrough

// file passthrough.cpp
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>

int main (int argc, char** argv)
{
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);   // creates a PointCloud<PointXYZ> boost shared pointer and initializes it
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);

  // Fill in the cloud data
  cloud->width  = 5;
  cloud->height = 1;
  cloud->points.resize (cloud->width * cloud->height);

  for (size_t i = 0; i < cloud->points.size (); ++i)             // 随机生成5个数据。
  {
    cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f);
    cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f);
    cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0f);
  }

  std::cerr << "Cloud before filtering: " << std::endl;
  for (size_t i = 0; i < cloud->points.size (); ++i)
    std::cerr << "    " << cloud->points[i].x << " "
                        << cloud->points[i].y << " "
                        << cloud->points[i].z << std::endl;

  // Create the filtering object
  pcl::PassThrough<pcl::PointXYZ> pass;
  pass.setInputCloud (cloud);
  pass.setFilterFieldName ("z");                                 // 过滤数据,只选取 z 坐标在 (0.0, 1.0) 范围内的数据。
  pass.setFilterLimits (0.0, 1.0);
  //pass.setFilterLimitsNegative (true);
  pass.filter (*cloud_filtered);

  std::cerr << "Cloud after filtering: " << std::endl;
  for (size_t i = 0; i < cloud_filtered->points.size (); ++i)
    std::cerr << "    " << cloud_filtered->points[i].x << " "
                        << cloud_filtered->points[i].y << " "
                        << cloud_filtered->points[i].z << std::endl;

  return (0);
}

2.3.1. 编译运行程序

新建 CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project(passthrough)

find_package(PCL 1.2 REQUIRED)

include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable (passthrough passthrough.cpp)
target_link_libraries (passthrough ${PCL_LIBRARIES})

编译运行步骤如下:

$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./passthrough
Cloud before filtering: 
    0.352222 -0.151883 -0.106395
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304
    -0.734766 0.854581 -0.0361733
    -0.4607 -0.277468 -0.916762
Cloud after filtering: 
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304

从运行的结果可以看到,仅 z 坐标在 (0.0, 1.0) 范围内的数据被留下了,这和程序中的设置是一致的。

3. Point Cloud Data (PCD) file format

可以把 Point Cloud 数据保存为文件,PCL 定义了存储 Point Cloud 数据的文件格式,称为 PCD (Point Cloud Data)格式。PCD 文件的实例如下:

# .PCD v.7 - Point Cloud Data file format
VERSION .7
FIELDS x y z rgb
SIZE 4 4 4 4
TYPE F F F F
COUNT 1 1 1 1
WIDTH 5
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 5
DATA ascii
0.93773 0.33763 0 4.2108e+06
0.90805 0.35641 0 4.2108e+06
0.81915 0.32 0 4.2108e+06
0.97192 0.278 0 4.2108e+06
0.944 0.29474 0 4.2108e+06

文件头中各个字段含义可参考:http://pointclouds.org/documentation/tutorials/pcd_file_format.php#pcd-file-format

3.1. 写数据到 PCD 文件

// file pcd_write.cpp
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>

int main (int argc, char** argv)
{
  pcl::PointCloud<pcl::PointXYZ> cloud;

  // Fill in the cloud data
  cloud.width    = 5;
  cloud.height   = 1;
  cloud.is_dense = false;
  cloud.points.resize (cloud.width * cloud.height);

  for (size_t i = 0; i < cloud.points.size (); ++i)
  {
    cloud.points[i].x = 1024 * rand () / (RAND_MAX + 1.0f);
    cloud.points[i].y = 1024 * rand () / (RAND_MAX + 1.0f);
    cloud.points[i].z = 1024 * rand () / (RAND_MAX + 1.0f);
  }

  pcl::io::savePCDFileASCII ("test_pcd.pcd", cloud);
  std::cerr << "Saved " << cloud.points.size () << " data points to test_pcd.pcd." << std::endl;

  for (size_t i = 0; i < cloud.points.size (); ++i)
    std::cerr << "    " << cloud.points[i].x << " " << cloud.points[i].y << " " << cloud.points[i].z << std::endl;

  return (0);
}

新建 CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project(pcd_write)

find_package(PCL 1.2 REQUIRED)

include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable (pcd_write pcd_write.cpp)
target_link_libraries (pcd_write ${PCL_LIBRARIES})

编译完成后,运行程序可生成 PCD 文件 test_pcd.pcd:

$ ./pcd_write
Saved 5 data points to test_pcd.pcd.
    0.352222 -0.151883 -0.106395
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304
    -0.734766 0.854581 -0.0361733
    -0.4607 -0.277468 -0.916762
$ cat test_pcd.pcd
# .PCD v0.7 - Point Cloud Data file format
VERSION 0.7
FIELDS x y z
SIZE 4 4 4
TYPE F F F
COUNT 1 1 1
WIDTH 5
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 5
DATA ascii
0.35222197 -0.15188313 -0.10639524
-0.3974061 -0.47310591 0.29260206
-0.73189831 0.66710472 0.44130373
-0.73476553 0.85458088 -0.036173344
-0.46070004 -0.2774682 -0.91676188

3.2. 从 PCD 文件加载数据

用 pcl::io::loadPCDFile 可以从 PCD 文件加载数据。实例如下:

// pcd_read.cpp
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>

int main (int argc, char** argv)
{
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);

  if (pcl::io::loadPCDFile<pcl::PointXYZ> ("test_pcd.pcd", *cloud) == -1)      // load the file
  {
    PCL_ERROR ("Couldn't read file test_pcd.pcd \n");
    return (-1);
  }
  std::cout << "Loaded "
            << cloud->width * cloud->height
            << " data points from test_pcd.pcd with the following fields: "
            << std::endl;
  for (size_t i = 0; i < cloud->points.size (); ++i)
    std::cout << "    " << cloud->points[i].x
              << " "    << cloud->points[i].y
              << " "    << cloud->points[i].z << std::endl;

  return (0);
}

假设这个例子中需要的文件 test_pcd.pcd 是从前节的例子中产生的,则这个例子的输出会为:

Loaded 5 data points from test_pcd.pcd with the following fields: x y z
  0.35222 -0.15188 -0.1064
  -0.39741 -0.47311 0.2926
  -0.7319 0.6671 0.4413
  -0.73477 0.85458 -0.036173
  -0.4607 -0.27747 -0.91676

Author: cig01

Created: <2016-06-03 Fri>

Last updated: <2017-12-08 Fri>

Creator: Emacs 27.1 (Org mode 9.4)