Bootstrap

PCL学习一:点云与PCL基础

参考引用

PCL点云库学习笔记(文章链接汇总)

1. 点云概述

  • 点云(Point Cloud)是三维空间中,表达目标空间分布和目标表面特性的点的集合
  • 点云通常可以从深度相机或激光雷达中直接获取,也可以从 CAD 等软件中生成
  • 点云是用于表示多维点集合的数据结构,通常用于表示三维数据:在 3D 点云中,这些点通常代表采样表面的 X,Y 和 Z 几何坐标

    当存在颜色信息时,点云变为 4D(下图一为 3D,下图二为 4D)

在这里插入图片描述

在这里插入图片描述

2. PCL 概述

  • PCL(Point Cloud Library)是用于 2D/3D 图像和点云处理的大型开源跨平台的 C++ 编程库,PCL 框架实现了大量点云相关的通用算法和高效的数据结构,PCL 是 BSD 授权方式,可以免费进行商业和学术应用。如果说 OpenCV 是 2D 信息获取与处理的结晶,那么 PCL 就在 3D 信息获取与处理上也具有相同的地位,相比图像数据,点云数据多了一个维度,因此能够更全面的刻画三维场景中的对象
  • 支持多种操作系统,可以在 Windows、Linux、MacOS X、Android、部分嵌入式实时系统上运行
  • 应用领域:机器人、CAD/CAM、逆向工程、遥感测量、VR/AR、人机交互
  • PCL 内容涉及了点云的获取、滤波、分割、配准、检索、特征提取、特征估计,表面重建、识别、模型拟合、追踪、曲面重建、可视化等,下图一是 PCL 架构图,下图二是点云常见处理

    PCL 中的所有模块和算法都是通过 Boost 共享指针来传送数据,因而避免了多次复制系统中已存在的数据

在这里插入图片描述

在这里插入图片描述

3. 点云输入输出(I/O)

3.1 PCD(Point Cloud Data)文件解析
# .PCD v.7 - Point Cloud Data file format
VERSION .7			# 版本号
FIELDS x y z rgb		#  指定一个点可以有的每一个维度和字段的名字
SIZE 4 4 4 4			# 用字节数指定每一个维度的大小。例如:
TYPE F FFF			# 用一个字符指定每一个维度的类型
COUNT 1 1 1 1			# 指定每一个维度包含的元素数目,默认情况下,如果没有COUNT,所有维度的数目被设置成1
WIDTH 640    			# 像图像一样的有序结构,有640行和480列,
HEIGHT 480    			# 这样该数据集中共有640*480=307200个点
VIEWPOINT 0 0 0 1 0 0 0		# 指定数据集中点云的获取视点 视点信息被指定为平移(tx ty tz)+ 四元数(qw qx qy qz)
POINTS 307200			# 指定点云中点的总数。从0.7版本开始,该字段就有点多余了
DATA ascii			# 指定存储点云数据的数据类型。支持两种数据类型:ascii和二进制
0.93773 0.33763 0 4.2108e+06
0.90805 0.35641 0 4.2108e+06
  • TYPE:用一个字符指定每一个维度的类型
    • I 表示有符号类型 int8(char)、int16(short)和 int32(int)
    • U 表示无符号类型 uint8(unsigned char)、uint16(unsigned short)和 uint32(unsigned int)
    • F 表示浮点类型
  • FIELDS:指定一个点可以有的每一个维度和字段的名字
    • FIELDS x y z
    • FIELDS x y z rgb
    • FIELDS x y z normal_x normal_y normal_z
  • PCD 文件的文件头部分必须按上面的顺序精确指定
3.2 基本类型与衍生类型
  • 根据激光测量原理得到的点云,包含三维坐标信息(xyz)和激光反射强度信息(intensity),其中:激光反射强度与仪器的激光发射能量、波长,目标的表面材质、粗糙程度、入射角相关
  • 根据摄影测量原理得到的点云,包括三维坐标(xyz)和颜色信息(rgb)
  • 结合两个原理的多传感器融合技术(多见于手持式三维扫描仪),能够同时得到这三种信息
  • PCL 基本类型 PointCloud:是一个 C++ 的模板类,包含以下字段

    • WIDTH(int):指定点云数据集的宽度
      • 对于无序点云的数据集,width 代表了所有点的总数
      • 对于有序点云的数据集,width 代表了一行中的总点数
    • HEIGHT(int):制定点云数据集的高度
      • 对于无序点云的数据集,值为 1
      • 对于有序点云的数据集,表示总行数
    • POINTS(std::vector<PointT>):包含所有 PointT 类型的点的数据列表
  • 衍生类型

    • PointXYZ:float x,y,z
    • PointXYZI:float x,y,z,intensity
    • PointXYZRGB:float x,y,z,rgb
    • PointXYZRGBA:float x,y,z,uint32_t rgba
    • Normal:float normal[3],curvature 法线方向,对应的曲率的测量值
    • PointNormal:float x,y,z,normal[3],curvature 采样点,法线和曲率
    • Histogram:float histogram[N] 用于存储一般用途的n维直方图

    为了涵盖能想到的所有可能的情况,PCL 中定义了大量的 point 类型,如果你需要的类型已经存在于 PCL,那么就不需要重复定义了

  • 点云数据转换

    • sensor_msgs::PointCloud2(ROS 点云数据) 转 PointXYZI(PointT)
    /*
    ConstPtr:const pointer(常量指针),可理解为传递常量数据的共享指针
        1. 为什么要用指针而不直接传数据,因为数据太大了,值传递需要进行对数据的复制
           而指针引用不需要,因为涉及到右值传参和类型推断,不加引用的话类型可能发生变化
        2. ::constPtr 可以理解为指向常量的共享指针,::Ptr 可以理解为指向一个(可修改的)共享指针
        3. ::constPtr& 可以理解为对一个常量消息的共享指针的引用,::Ptr& 可以理解为对一个(可修改的)消息的共享指针的引用
        4. 使用共享指针,它是一种智能指针,可以自动管理他指向的内存的存储时间,不必手动释放内存
    */
    void elevatedCloudCallback(const sensor_msgs::PointCloud2::ConstPtr &input_cloud) {
        pcl::PointCloud<PointT>::Ptr output_cloud(new pcl::PointCloud<PointT>());
        pcl::fromROSMsg(*input_cloud, *output_cloud);
        //pcl::toROSMsg(pcl::PointCloud<pcl::PointXYZ>, sensor_msgs::PointCloud2); // 逆过程
    }
    
3.3 其他文件格式
  • PLY 是一种多边形文件格式,由 Stanford 大学的 Turk 等人设计开发
    • 常用的点云数据文件,ply 文件不仅可以存储点数据,而且可以存储网格数据
  • STL 是 3D Systems 公司创建的模型文件格式,主要应用于 CAD、CAM 领域
  • OBJ 是从几何学上定义的文件格式,首先由 Wavefront Technologies 开发
  • X3D 是符合 ISO 标准的基于 XML 的文件格式,表示 3D 计算机图形数据
3.4 点云序列化和反序列化
  • 序列化

    • 将对象的状态信息转换为可以存储或传输的形式(文件、缓冲、或经由网络中发送)的过程。例如,点云在 C++ 中以类对象的形式存放,而点云存放为文件的时候是一个点 PCD、PLY 等文件,将代码中暂存的点云对象转成点 PCD 文件这一操作就叫做序列化
    // 随机生成了 5 个点,并将之以 ASCII 形式保存(序列化)在 test_pcd.pcd 文件中:save_pcd.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);
    
        std::cout << rand() << std::endl;
        std::cout << rand() / (RAND_MAX + 1.0f) << std::endl;
        std::cout << 1024 * rand() / (RAND_MAX + 1.0f) << std::endl;
    
        // 随机生成5个点
        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);
        // pcl::io::savePCDFileBinary("test_pcd_binary.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);
    }
    
    // 输出
    # .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.3974061 -0.47310591 0.29260206
    -0.73189831 0.66710472 0.44130373
    -0.73476553 0.85458088 -0.036173344
    -0.46070004 -0.2774682 -0.91676188
    0.1837492 0.96880913 0.5120554
    
    // 若保存指令改为 pcl::io::savePCDFileBinary("test_pcd_binary.pcd", cloud); 则输出如下
    // 以 binary 的格式进行序列化可以节省空间并提高读写效率
    # .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 binary
    �x˾�:��ϕ>];�`�*?���><���Z?*����P���j��(<>�x??
    
  • 反序列化

    • 和序列化相反,反序列化则是将一些已经序列化过的数据进行反序列化得到原来的对象。例如,一个点 PCD 文件,将它读取成一个点云对象这一操作就是反序列化
    // 对一个点云进行反序列化操作,将之保存到 PointCloud 对象中:load_pcd.cpp
    #include <iostream>
    #include <pcl/io/pcd_io.h>
    #include <pcl/point_types.h>
    
    int main(int argc, char **argv) {
        // 准备pcl::PointXYZ类型的点云
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
        // 将pcd中的数据加载到cloud中
        if (pcl::io::loadPCDFile<pcl::PointXYZ>("./data/bunny.pcd", *cloud) == -1) {         //* load the file
        
            PCL_ERROR ("Couldn't read file bunny.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);
    }
    
3.5 可视化工具:pcl_viewer
  • 基本使用

    • 进入:pcl_viewer xxxxx.pcd
    • 帮助:在界面中输入 h,可以在控制台看到帮助信息
    • 退出:界面中输入 q
    • 放大缩小:鼠标滚轮 或 Alt + [+/-]
    • 平移:Shift + 鼠标拖拽
    • 旋转:Ctrl + 鼠标拖拽
  • 其他技巧

    • 修改点颜色:数字 1,2,3,4,5…9,重复按 1 可切换不同颜色方便观察
    • 放大缩小点:放大 Ctrl + Shift + 加号,缩小 Ctrl + 减号
    • 保存截图:j
    • 显示颜色尺寸:u
    • 显示比例尺寸:g
    • 在控制列出所有几何和颜色信息:l
  • 鼠标选点打印坐标

    • 选点模式进入:pcl_viewer -use_point_picking bunny.pcd
    • 选择指定点:shift + 鼠标左键

4. common

  • pcl_common 中主要是包含了 PCL 库常用的公共数据结构和方法,比如 PointCloud 的类和许多用于表示点、曲面、法向量和特征描述等点的类型,用于计算距离、均值、协方差、角度转换以及几何变化的函数
4.1 common 模块中的头文件
  • angles.h 定义了标准的 C 接口的角度计算函数
  • centriod.h 定义了中心点的估算以及协方差矩阵的计算
  • commo.h 标准的 C 以及 C++ 类,是其他 common 函数的父类
  • distance.h 定义标准的 C 接口用于计算距离
  • file_io.h 定义了一些文件帮助写或者读方面的功能
  • random.h 定义一些随机点云生成的函数
  • geometry.h 定义一些基本的几何功能的函数
  • intersection.h 定义线与线相交的函数
  • norm.h 定义了标准的 C 方法计算矩阵的正则化
  • time.h 定义了时间计算的函数
  • Point_types.h 定义了所有 PCL 实现的点云的数据结构的类型
4.2 common 模块中的基本函数
// 从弧度到角度
pcl::rad2deg(fllat alpha) 

// 从角度到弧度
pcl::deg2rad(float aipha)

// 正则化角度在(-PI,PI)之间
pcl::normAngle(float alpha)

// 计算给定一群点的3D中心点,并且返回一个三维向量
pcl::compute3DCentroid (const pcl::PointCloud< PointT > &cloud, Eigen::Matrix< Scalar, 4, 1 > &centroid)

// 计算给定的三维点云的协方差矩阵
pcl::computeCovarianceMatrix (const pcl::PointCloud< PointT > &cloud, const Eigen::Matrix< Scalar, 4, 1 > &centroid, Eigen::Matrix< Scalar, 3, 3 > &covariance_matrix)

// 计算正则化的3*3的协方差矩阵以及给定点云数据的中心点
pcl::computeMeanAndCovarianceMatrix (const pcl::PointCloud< PointT > &cloud, Eigen::Matrix< Scalar, 3, 3 > &covariance_matrix, Eigen::Matrix< Scalar, 4, 1 > &centroid

// 利用一组点的指数对其进行一般的、通用的nD中心估计
pcl::demeanPointCloud (const pcl::PointCloud< PointT > &cloud_in, const Eigen::Matrix< Scalar, 4, 1 > &centroid, pcl::PointCloud< PointT > &cloud_out)
pcl::computeNDCentroid (const pcl::PointCloud< PointT > &cloud, Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > &centroid)

// 计算两个向量之间的角度
pcl::getAngle3D (const Eigen::Vector4f &v1, const Eigen::Vector4f &v2, const bool in_degree=false)

// 同时计算给定点云数据的均值和标准方差
pcl::getMeanStd (const std::vector< float > &values, double &mean, double &stddev)

// 在给定边界的情况下,获取一组位于框中的点
pcl::getPointsInBox (const pcl::PointCloud< PointT > &cloud, Eigen::Vector4f &min_pt, Eigen::Vector4f &max_pt, std::vector< int > &indices)

// 给定点云数据中点与点之间的最大距离的值
pcl::getMaxDistance (const pcl::PointCloud< PointT > &cloud, const Eigen::Vector4f &pivot_pt, Eigen::Vector4f &max_pt)

// 获取给定点云中的在XYZ轴上的最大和最小值
pcl::getMinMax3D (const pcl::PointCloud< PointT > &cloud, PointT &min_pt, PointT &max_pt)

// 计算由三个点pa、pb和pc构成的三角形的外接圆半径
pcl::getCircumcircleRadius (const PointT &pa, const PointT &pb, const PointT &pc)

// 获取点直方图上的最小值和最大值
pcl::getMinMax (const PointT &histogram, int len, float &min_p, float &max_p)

// 根据给定的多边形的点云计算多边形的面积
pcl::calculatePolygonArea (const pcl::PointCloud< PointT > &polygon)

// 从Point_in把字段数据赋值到Point_out
pcl::copyPoint (const PointInT &point_in, PointOutT &point_out)

// 获取两条三维直线之间的最短三维线段
pcl::lineToLineSegment (const Eigen::VectorXf &line_a, const Eigen::VectorXf &line_b, Eigen::Vector4f &pt1_seg, Eigen::Vector4f &pt2_seg)

// 获取点到线的平方距离(由点和方向表示)
pcl::sqrPointToLineDistance (const Eigen::Vector4f &pt, const Eigen::Vector4f &line_pt, const Eigen::Vector4f &line_dir)

// 在给定的一组点中获得最大分段,并返回最小和最大点
pcl::getMaxSegment (const pcl::PointCloud< PointT > &cloud, PointT &pmin, PointT &pmax)

// 确定最小特征值及其对应的特征向量
pcl::eigen22 (const Matrix &mat, typename Matrix::Scalar &eigenvalue, Vector &eigenvector)

// 确定对称半正定输入矩阵给定特征值对应的特征向量
pcl::computeCorrespondingEigenVector (const Matrix &mat, const typename Matrix::Scalar &eigenvalue, Vector &eigenvector)

// 确定对称半正定输入矩阵最小特征值的特征向量和特征值
pcl::eigen33 (const Matrix &mat, typename Matrix::Scalar &eigenvalue, Vector &eigenvector)

// 计算2x2矩阵的逆
pcl::invert2x2 (const Matrix &matrix, Matrix &inverse)

//计算3x3对称矩阵的逆
pcl::invert3x3SymMatrix (const Matrix &matrix, Matrix &inverse)

// 计算3x3矩阵的行列式
pcl::determinant3x3Matrix (const Matrix &matrix)

// 获得唯一 的3D旋转,将Z轴旋转成(0,0,1)Y轴旋转成(0,1,0)并且两个轴是正交的
pcl::getTransFromUnitVectorsZY (const Eigen::Vector3f &z_axis, const Eigen::Vector3f &y_direction, Eigen::Affine3f &transformation)

// 得到将origin转化为(0,0,0)的变换,并将Z轴旋转成(0,0,1)和Y方向(0,1,0)
pcl::getTransformationFromTwoUnitVectorsAndOrigin (const Eigen::Vector3f &y_direction, const Eigen::Vector3f &z_axis, const Eigen::Vector3f &origin, Eigen::Affine3f &transformation)

// 从给定的变换矩阵中提取欧拉角
pcl::getEulerAngles (const Eigen::Transform< Scalar, 3, Eigen::Affine > &t, Scalar &roll, Scalar &pitch, Scalar &yaw)

// 给定的转换中,提取XYZ以及欧拉角
pcl::getTranslationAndEulerAngles (const Eigen::Transform< Scalar, 3, Eigen::Affine > &t, Scalar &x, Scalar &y, Scalar &z, Scalar &roll, Scalar &pitch, Scalar &yaw)

// 从给定的平移和欧拉角创建转换矩阵
pcl::getTransformation (float x, float y, float z, float roll, float pitch, float yaw)

// 保存或者写矩阵到一个输出流中
pcl::saveBinary (const Eigen::MatrixBase< Derived > &matrix, std::ostream &file)

// 从输入流中读取矩阵
pcl::loadBinary (Eigen::MatrixBase< Derived > const &matrix, std::istream &file)

// 获取空间中两条三维直线作为三维点的交点
pcl::lineWithLineIntersection (const Eigen::VectorXf &line_a, const Eigen::VectorXf &line_b, Eigen::Vector4f &point, double sqr_eps=1e-4)

// 获取指定字段的索引(即维度/通道)
pcl::getFieldIndex (const pcl::PCLPointCloud2 &cloud, const std::string &field_name)

// 获取给定点云中所有可用字段的列表
pcl::getFieldsList (const pcl::PointCloud< PointT > &cloud)

// 获取特定字段数据类型的大小(字节)
pcl::getFieldSize (const int datatype)

// 连接 pcl::PCLPointCloud2类型的点云字段
pcl::concatenatePointCloud (const pcl::PCLPointCloud2 &cloud1, const pcl::PCLPointCloud2 &cloud2, pcl::PCLPointCloud2 &cloud_out)

5. PCL 安装(Ubuntu 18.04为例)

  • 依赖库安装

    • 将以下内容保存为 install_pcl_dependences.sh ,在终端输入 sudo sh install_pcl_dependences.sh 即可进行安装,在下载安装依赖库过程中会提示是否安装,都输入 y
    # install_pcl_dependences.sh
    sudo apt-get update
    sudo apt-get install git build-essential linux-libc-dev
    sudo apt-get install cmake cmake-gui 
    sudo apt-get install libusb-1.0-0-dev libusb-dev libudev-dev
    sudo apt-get install mpi-default-dev openmpi-bin openmpi-common
    sudo apt-get install libpcap-dev
    sudo apt-get install libflann1.9 libflann-dev
    sudo apt-get install libeigen3-dev
    sudo apt-get install libboost-all-dev
    sudo apt-get install vtk6 libvtk6.3 libvtk6-dev libvtk6.3-qt libvtk6-qt-dev 
    sudo apt-get install libqhull* libgtest-dev
    sudo apt-get install freeglut3-dev pkg-config
    sudo apt-get install libxmu-dev libxi-dev 
    sudo apt-get install mono-complete
    sudo apt-get install libopenni-dev libopenni2-dev
    
  • 源码克隆

    $ git clone https://github.com/PointCloudLibrary/pcl.git 
    
  • 编译

    $ cd pcl
    # 切换到指定版本v1.9.1再编译
    $ git checkout pcl-1.9.1
    # 创建目录
    $ mkdir release
    # 进入目录
    $ cd release
    # 1 配置cmake
    $ cmake -DCMAKE_BUILD_TYPE=None \
            -DCMAKE_INSTALL_PREFIX=/usr/local \
            -DBUILD_GPU=ON \
            -DBUILD_apps=ON \
            -DBUILD_examples=ON ..
    
    # 2.进行编译,也可以 `make -j11` 11 为内核数,按自己的 cpu 内核填写,不写数字默认使用全部核心编译
    $ make 
    
  • 安装

    $ sudo make install
    
  • 测试

    $ pcl_viewer ../test/pcl_logo.pcd
    

在这里插入图片描述

可能存在的错误

  • 报错提示

    fatal error: pcl/io/pcd_io.h: No such file or directory  #include <pcl/io/pcd_io.h>
    
  • 解决办法

    $ sudo ln -s /usr/include/eigen3/Eigen/ /usr/include/Eigen
    $ sudo ln -s /usr/include/pcl-1.8/pcl/ /usr/include/pcl
    
;