文章目录

1. PCL点云库概述

1.1 PCL简介

   PCL是在吸收了前人点云相关研究基础上建立起来的大型跨平台开源C++编程库,它实现了大量点云相关的通用算法和高效数据结构,涉及到点云获取、滤波、分割、配准、检索、特征提取、识别、追踪、曲面重建、可视化等。如果说OpenCV是2D信息获取与处理的结晶,那么PCL就在3D信息获取与处理上具有相同的地位。

1.2 PCL特点

  • 跨平台支持:可在Windows、Linux、Android、Mac OS X、部分嵌入式实时系统上运行
  • 模块化设计:基于现代C++模板库,结构清晰,易于扩展
  • 高性能计算:利用OpenMP、GPU、CUDA等先进高性能计算技术
  • 丰富算法:实现点云相关的获取、滤波、分割、配准、检索、特征提取等
  • 开源许可:BSD授权方式,可以免费进行商业和学术应用

1.3 PCL与其他库对比

特性 PCL Open3D CGAL
主要语言 C++ Python/C++ C++
算法丰富度 非常丰富 中等 丰富
可视化能力 中等 优秀 基础
性能优化 优秀 良好 优秀
学习曲线 较陡 平缓 陡峭
适用场景 传统点云处理 3D数据处理与可视化 计算几何

2. 点云基本概念

2.1 点云结构公共字段

   PCL包含一个重要的数据结构,被设计成一个模板类,把点的类型当做模板类的参数。pcl::PointCloud<PointT>类似于vector,但具有更丰富的功能。

   主要字段说明:

字段名 类型 描述
header pcl::PCLHeader 记录了点云的获取时间、序列号等信息
points std::vector<PointT,...> 储存所有点的容器,vector定义中的PointT对应模板参数
width uint32_t 指定点云组织成图像时的宽度
height uint32_t 指定点云组成图像时的高度
is_dense bool 指定点云中是否有无效值(inf/NaN)
sensor_origin_ Eigen::Vector4f 传感器相对于原点平移所得的位姿
sensor_orientation_ Eigen::Quaternionf 定义传感器旋转所得的位姿

   width和height的详细解释:

  • 对于无组织点云数据集width表示该点云所有点的个数,height设置为1
  • 对于有组织的点云数据集width指一行有多少个点,height等于行数

2.2 点云类型详解

   PointT是pcl::PointCloud类的模板参数,定义了点云的类型。PCL提供了多种点类型以满足不同应用需求:

点类型 包含信息 适用场景
pcl::PointXYZ 位置(x, y, z) 最基本的点云类型,仅包含坐标信息
pcl::PointXYZI 位置+强度 激光雷达数据,包含反射强度信息
pcl::PointXYZRGB 位置+颜色 彩色点云,包含RGB颜色信息
pcl::PointXYZRGBA 位置+颜色+透明度 带透明度的彩色点云
pcl::Normal 法线+曲率 表示曲面上给定点处的法线以及测量的曲率
pcl::PointNormal 位置+法线+曲率 包含位置和法线信息的点
pcl::PointXYZRGBNormal 位置+颜色+法线+曲率 完整的点云信息
pcl::PointXYZINormal 位置+强度+法线+曲率 带强度的法线点云
pcl::InterestPoint 位置+兴趣值 特征点检测
pcl::PointWithRange 位置+范围 包含深度信息的点

   这些点类型的具体信息包含在文件#include <pcl/point_types.h>中。

3. ROS的PCL接口

3.1 ROS点云消息类型

   ROS定义了不同的消息类型来处理点云数据:

消息类型 描述
std_msgs::Header 包含发送的时间、序列号、坐标系等信息
sensor_msgs::PointCloud2 ROS标准点云消息,用于转换pcl::PointCloud类型
pcl_msgs::PointIndices 储存点云的索引
pcl_msgs::PolygonMesh 保存了描绘网格、顶点和多边形
pcl_msgs::Vertices 将一组顶点的索引保存在数组中
pcl_msgs::ModelCoefficients 储存一个模型的不同系数,如描述一个平面需要四个参数

3.2 点云格式转换

   PCL和ROS之间存在三种主要的点云格式:

sensor_msgs::PointCloud
sensor_msgs::PointCloud2
pcl::PointCloud<T>

在这里插入图片描述

   它们之间的转换关系如下:

PointCloud ⟺ PointCloud2 ⟺ pcl::PointCloud<T>

   转换实现代码:

3.2.1 PointCloud2转PointCloud

#include "sensor_msgs/point_cloud_conversion.h"

static inline bool convertPointCloud2ToPointCloud(
    const sensor_msgs::PointCloud2 &input, 
    sensor_msgs::PointCloud &output);

3.2.2 PointCloud 转 PointCloud2

#include "sensor_msgs/point_cloud_conversion.h"

static inline bool convertPointCloudToPointCloud2(
    const sensor_msgs::PointCloud &input, 
    sensor_msgs::PointCloud2 &output);

3.2.3 pcl::PointCloud 转 PointCloud2

#include "pcl_conversions/pcl_conversions.h"

template<typename T>
void toROSMsg(const pcl::PointCloud<T> &pcl_cloud, 
              sensor_msgs::PointCloud2 &cloud)
{
    pcl::PCLPointCloud2 pcl_pc2;
    pcl::toPCLPointCloud2(pcl_cloud, pcl_pc2);
    pcl_conversions::moveFromPCL(pcl_pc2, cloud);
}

3.2.4 PointCloud2 转 pcl::PointCloud

#include "pcl_conversions/pcl_conversions.h"

template<typename T>
void fromROSMsg(const sensor_msgs::PointCloud2 &cloud, 
                pcl::PointCloud<T> &pcl_cloud)
{
    pcl::PCLPointCloud2 pcl_pc2;
    pcl_conversions::toPCL(cloud, pcl_pc2);
    pcl::fromPCLPointCloud2(pcl_pc2, pcl_cloud);
}

4. PCL核心算法详解

4.1 点云滤波算法

4.1.1 体素网格滤波 (Voxel Grid Filter)

#include <pcl/filters/voxel_grid.h>

pcl::VoxelGrid<pcl::PointXYZ> voxel_grid;
voxel_grid.setInputCloud(cloud);
voxel_grid.setLeafSize(0.01f, 0.01f, 0.01f); // 设置体素大小
voxel_grid.filter(*cloud_filtered);

   原理:将三维空间划分为小的立方体(体素),对每个体素内的点进行下采样。

4.1.2 统计离群点去除(Statistical Outlier Removal)

#include <pcl/filters/statistical_outlier_removal.h>

pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50); // 设置邻域点数
sor.setStddevMulThresh(1.0); // 设置标准差倍数
sor.filter(*cloud_filtered);

4.1.3 直通滤波(PassThrough Filter)

#include <pcl/filters/passthrough.h>

pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z"); // 设置过滤字段
pass.setFilterLimits(0.0, 1.0); // 设置范围
pass.filter(*cloud_filtered);

4.2 点云特征提取

4.2.1 法线估计

#include <pcl/features/normal_3d.h>

pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud(cloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
ne.setSearchMethod(tree);
ne.setRadiusSearch(0.03); // 设置搜索半径
ne.compute(*normals);

4.2.2 特征点检测(ISS, SIFT, Harris)

// ISS特征点检测
#include <pcl/keypoints/iss_3d.h>

pcl::ISSKeypoint3D<pcl::PointXYZ, pcl::PointXYZ> iss_detector;
iss_detector.setInputCloud(cloud);
iss_detector.setSalientRadius(6 * resolution);
iss_detector.setNonMaxRadius(4 * resolution);
iss_detector.compute(*keypoints);

4.3 点云配准算法

4.3.1 ICP算法原理

   ICP(Iterative Closest Point)算法是点云配准的核心算法,主要步骤:

  1. 最近点匹配:对于源点云中的每一个点,找到目标点云中与之最近的点
  2. 变换矩阵估计:通过SVD或最小二乘法估计刚性变换矩阵
  3. 点云更新:将估计的变换矩阵应用到源点云
  4. 迭代判断:检查是否满足终止条件

4.3.2 ICP算法实现

#include <pcl/registration/icp.h>

pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
icp.setInputSource(source_cloud);
icp.setInputTarget(target_cloud);
icp.setMaximumIterations(50); // 最大迭代次数
icp.setTransformationEpsilon(1e-8); // 变换矩阵容差
icp.setEuclideanFitnessEpsilon(1); // 欧氏距离容差

pcl::PointCloud<pcl::PointXYZ> final_cloud;
icp.align(final_cloud);

if (icp.hasConverged()) {
    std::cout << "ICP converged. Score: " << icp.getFitnessScore() << std::endl;
    std::cout << "Transformation matrix:\n" << icp.getFinalTransformation() << std::endl;
}

4.3.3 改进的ICP算法

  • Point-to-Plane ICP:点到平面的距离,收敛更快
  • Generalized ICP:考虑局部表面特性
  • NDT(Normal Distributions Transform):基于概率分布的方法

4.4 点云分割算法

4.4.1 平面分割(RANSAC)

#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>

pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setDistanceThreshold(0.01);

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

4.4.2 欧几里得聚类分割

#include <pcl/segmentation/extract_clusters.h>

pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud);

std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(0.02); // 聚类容差
ec.setMinClusterSize(100); // 最小聚类点数
ec.setMaxClusterSize(25000); // 最大聚类点数
ec.setSearchMethod(tree);
ec.setInputCloud(cloud);
ec.extract(cluster_indices);

5. 创建点云并发布ROS点云话题

5.1 创建功能包

catkin_create_pkg point_cloud_pkg std_msgs rospy roscpp sensor_msgs pcl_ros pcl_conversions std_srvs message_generation

5.2 发布ROS点云话题

   create_point_cloud_pub.cpp

#include <iostream>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>

int main(int argc, char** argv)
{
    ros::init(argc, argv, "point_cloud_node");
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<sensor_msgs::PointCloud2>("/point_cloud_publisher_topic", 1000);
    ros::Rate rate(10);
    
    unsigned int num_points = 10;
    
    // 建立pcl点云
    pcl::PointCloud<pcl::PointXYZRGB> cloud;
    
    // 点云初始化
    cloud.points.resize(num_points);
    
    // 建立ros点云
    sensor_msgs::PointCloud2 output_msg;
    
    while(ros::ok()){
        // 调用ros获取时间的接口,获取系统时间,作为时间戳stamp
        output_msg.header.stamp = ros::Time::now();
        
        // 创建num_points个绿色的点
        for(int i = 0; i < num_points; i++)
        {
            cloud.points[i].x = i;
            cloud.points[i].y = i;
            cloud.points[i].z = i;
            cloud.points[i].r = 0;
            cloud.points[i].g = 255;
            cloud.points[i].b = 0;
        }
        
        // 将pcl点云转换到ros消息对象
        pcl::toROSMsg(cloud, output_msg);
        
        // 发布的点云坐标系
        output_msg.header.frame_id = "point_cloud_frame_id";
        
        pub.publish(output_msg);
        rate.sleep();
    }
    
    ros::spin();
    return 0;
}

5.3 订阅ROS点云话题

   pub_sub_pcl_topic_pkg.cpp

#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/common/transforms.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/voxel_grid.h>

class pcl_sub
{
private:
    ros::NodeHandle n;
    ros::Subscriber subCloud;
    ros::Publisher pubCloud;
    
    // 接收到的点云消息
    sensor_msgs::PointCloud2 msg;   
    // 等待发布的点云消息             
    sensor_msgs::PointCloud2 adjust_msg; 
    // 建立了一个pcl的点云,用于完成点云的中间处理过程         
    pcl::PointCloud<pcl::PointXYZRGB> adjust_pcl; 

public:
    // 构造函数
    pcl_sub(): n("~")
    {   
        // 接收点云数据,进入回调函数 getcloud()
        // /point_cloud_topic 为订阅的点云话题名
        // 1 为待处理信息队列大小,一次只处理一个消息
        // &pcl_sub::getcloud 调用的函数指针,即回调函数
        // this 回调函数所在的类
        subCloud = n.subscribe<sensor_msgs::PointCloud2>("/point_cloud_publisher_topic", 1, &pcl_sub::getcloud, this); 
        
        // 发布位姿变换后的点云/adjusted_cloud
        pubCloud = n.advertise<sensor_msgs::PointCloud2>("/adjustd_cloud", 1);                                     
    }
    
    // 回调函数
    void getcloud(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg)
    {   
        // 放在这里是因为,每次都需要重新初始化
        pcl::PointCloud<pcl::PointXYZRGB>::Ptr adjust_pcl_ptr(new pcl::PointCloud<pcl::PointXYZRGB>); 
        
        // 把msg消息转化为点云
        pcl::fromROSMsg(*laserCloudMsg, *adjust_pcl_ptr);                                             
        
        // 定义一个旋转矩阵(虽然称为3d,实质上是4x4的矩阵:旋转R+平移t)
        Eigen::Isometry3d T = Eigen::Isometry3d::Identity();
        Eigen::AngleAxisd rotationVector(M_PI/4, Eigen::Vector3d(0, 0, 1));
        Eigen::Matrix3d rotationMatrix = Eigen::Matrix3d::Identity();
        rotationMatrix = rotationVector.toRotationMatrix();
        
        // 旋转部分赋值
        T.linear() = rotationMatrix;
        // 平移部分赋值
        T.translation() = Eigen::Vector3d(0, 0, 0);
        
        // 执行变换,并将结果保存在新创建的 transformed_cloud 中
        pcl::PointCloud<pcl::PointXYZRGB>::Ptr transformed_cloud(new pcl::PointCloud<pcl::PointXYZRGB>());
        pcl::transformPointCloud(*adjust_pcl_ptr, *transformed_cloud, T.matrix());
        
        adjust_pcl = *transformed_cloud;
        
        // 把接收到的点云位置不变,颜色全部变为红色
        for (int i = 0; i < adjust_pcl.points.size(); i++)
        {
            adjust_pcl.points[i].r = 255;
            adjust_pcl.points[i].g = 0;
            adjust_pcl.points[i].b = 0;
        }
        
        // 将点云转化为消息才能发布
        pcl::toROSMsg(adjust_pcl, adjust_msg); 
        
        // adjust_msg.header.frame_id = "point_cloud_frame_id";
        
        // 发布调整之后的点云数据,主题为/adjustd_cloud
        pubCloud.publish(adjust_msg);          
    }
    
    // 析构函数
    ~pcl_sub() {}
};

int main(int argc, char **argv)
{
    // 初始化了一个节点,名字为colored
    ros::init(argc, argv, "point_cloud_transform"); 
    
    // 创建一个对象,回调函数放在了构造函数中
    // 所以创建对象的时候会顺序调用构造函数、回调函数
    // 然后点云的接收、转换、发布就完成了
    pcl_sub ps;
    
    ros::spin();
    return 0;
}

5.4 CMakeLists.txt配置

cmake_minimum_required(VERSION 3.0.2)
project(point_cloud_pkg)

find_package(catkin REQUIRED COMPONENTS
  message_generation
  pcl_conversions
  pcl_ros
  roscpp
  rospy
  sensor_msgs
  std_msgs
  std_srvs
)

find_package(PCL REQUIRED COMPONENTS common io)
find_package(catkin REQUIRED COMPONENTS)

catkin_package()

include_directories(
  
  
)

link_directories(
  
  
)

add_definitions()

# 添加可执行文件
add_executable(point_cloud_publisher src/create_point_cloud_pub.cpp)
target_link_libraries(point_cloud_publisher
  
  
)

add_executable(point_cloud_transform src/pub_sub_pcl_topic_pkg.cpp)
target_link_libraries(point_cloud_transform
  
  
)

5.5 Launch文件配置

   point_cloud.launch

<launch>
    <!-- 启动RVIZ可视化工具 -->
    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find point_cloud_pkg)/rviz/point_cloud.rviz"/>
    
    <!-- 启动点云发布节点 -->
    <node pkg="point_cloud_pkg" type="point_cloud_publisher" name="point_cloud_node" output="screen"/>
    
    <!-- 启动点云变换节点 -->
    <node pkg="point_cloud_pkg" type="point_cloud_transform" name="point_cloud_transform" output="screen"/>
    
    <!-- 
        pkg功能包名:看package.xml中的名字;
        type可执行文件名:看cmakelists.txt里面add_executable对应名字
        name节点名:launch中的name会替代cpp中的ros::init()中的名字
    -->
</launch>

5.6 RVIZ可视化配置

  1. 启动launch文件:实现点云的发布和订阅,并用rviz显示
  2. 配置RVIZ
    • 点击左下方"Add"按钮
    • 选择"PointCloud2"显示类型
    • 重复两次操作,分别配置两个PointCloud2模块
  3. 设置Topic
    • 第一个PointCloud2的Topic设置为/point_cloud_publisher_topic
    • 第二个PointCloud2的Topic设置为/adjustd_cloud
  4. 设置坐标系
    • 将"Fixed Frame"修改为代码中output_msg.header.frame_id对应的值"point_cloud_frame_id"

5.7 RVIZ配置保存与加载

   保存配置

  • 使用Ctrl+Shift+S将当前配置保存
  • 可以保存到功能包下的rviz文件夹中(如没有则新建)

   加载配置
   在launch文件中,将rviz对应的node的args指定为需要加载的rviz配置文件:

<node pkg="rviz" type="rviz" name="rviz" args="-d $(find point_cloud_pkg)/rviz/point_cloud.rviz"/>

   注意$(find point_cloud_pkg)这里是小括号

6. PCL点云处理实践应用

6.1 三维重建

   PCL提供了一系列算法用于点云的三维重建,可以从传感器采集的点云数据中恢复出三维场景的表面信息。例如,通过点云数据可以重建出房屋、建筑物、汽车等物体的三维模型。

   关键技术

  • 泊松表面重建 (Poisson Surface Reconstruction)
  • 贪婪投影三角化 (Greedy Projection Triangulation)
  • 移动最小二乘法 (Moving Least Squares)

6.2 物体识别与分割

   PCL中的特征提取和分类算法可以用于物体的识别与分割。通过分析点云数据的几何特征和表面法线等信息,可以实现对不同物体的自动识别和分割。

   应用场景

  • 机器人自动抓取
  • 自动驾驶障碍物检测
  • 工业零件分拣

6.3 点云配准

   PCL提供了一系列点云配准算法,可以将多个点云数据进行配准,实现不同数据源的对齐。

配准流程

  1. 粗配准:使用特征匹配或全局配准算法
  2. 精配准:使用ICP或其变种算法
  3. 多视角配准:多帧点云融合

6.4 运动估计

   基于PCL的点云处理技术,可以实现对物体的运动估计。通过跟踪不同时间采集的点云数据,可以计算物体的运动轨迹和速度等信息。

6.5 环境感知

   PCL提供了一些环境感知的算法,可以从点云数据中提取出道路、建筑物、障碍物等环境信息。

7. PCL性能优化技巧

7.1 内存优化

// 使用智能指针管理点云内存
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);

// 选择合适的点类型
// PointXYZ: 12字节,PointXYZRGB: 16字节,PointXYZRGBA: 20字节

7.2 计算优化

// 使用KD树加速最近邻搜索
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
tree->setInputCloud(cloud);

// 并行化处理
#pragma omp parallel for
for (size_t i = 0; i < cloud->size(); ++i) {
    // 并行处理代码
}

7.3 数据预处理优化

  1. 下采样:使用体素网格滤波减少数据量
  2. 离群点去除:提前去除噪声点
  3. 感兴趣区域提取:只处理必要的区域

7.4 算法选择优化

  • 小规模数据:使用精确算法
  • 大规模数据:使用近似算法
  • 实时应用:使用增量式算法

8. 点云可视化技巧

8.1 PCL可视化

#include <pcl/visualization/cloud_viewer.h>

pcl::visualization::CloudViewer viewer("Point Cloud Viewer");
viewer.showCloud(cloud);

while (!viewer.wasStopped()) {
    // 保持可视化窗口
}

8.2 高级可视化设置

pcl::visualization::PCLVisualizer viewer("3D Viewer");
viewer.setBackgroundColor(0, 0, 0);
viewer.addPointCloud<pcl::PointXYZRGB>(cloud, "sample cloud");
viewer.setPointCloudRenderingProperties(
    pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
viewer.addCoordinateSystem(1.0);
viewer.initCameraParameters();

8.3 多视图可视化

// 创建多个视图
int v1(0);
viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);
viewer.addPointCloud(cloud1, "cloud1", v1);

int v2(0);
viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
viewer.addPointCloud(cloud2, "cloud2", v2);

9. 常见问题与解决方案

9.1 编译错误处理

  • 问题:找不到PCL库
  • 解决方案:确保CMakeLists.txt中正确配置了PCL依赖
find_package(PCL REQUIRED COMPONENTS common io)
include_directories()
target_link_libraries(your_target )

9.2 点云转换失败

  • 问题:点云格式转换时出现数据丢失
  • 解决方案:检查点云字段是否匹配,确保转换前后数据类型一致

9.3 RVIZ显示问题

  • 问题:点云在RVIZ中不可见
  • 解决方案
    1. 检查Topic名称是否正确
    2. 确认Fixed Frame设置正确
    3. 调整点云显示大小和颜色

9.4 性能问题

  • 问题:处理大规模点云时速度慢
  • 解决方案
    1. 使用下采样减少数据量
    2. 使用KD树加速搜索
    3. 开启并行计算

9.5 内存泄漏问题

  • 问题:长时间运行后内存占用过高
  • 解决方案
    1. 使用智能指针管理内存
    2. 及时释放不再使用的点云
    3. 使用内存池技术

10. 进阶学习资源

10.1 官方文档

10.2 参考书籍

  • 《点云库PCL学习教程》
  • 《ROS机器人程序设计》
  • 《3D计算机视觉:原理、算法与应用》

10.3 在线资源

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐