创建点云并发布ROS点云---个人学习篇
文章目录
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)算法是点云配准的核心算法,主要步骤:
- 最近点匹配:对于源点云中的每一个点,找到目标点云中与之最近的点
- 变换矩阵估计:通过SVD或最小二乘法估计刚性变换矩阵
- 点云更新:将估计的变换矩阵应用到源点云
- 迭代判断:检查是否满足终止条件
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可视化配置
- 启动launch文件:实现点云的发布和订阅,并用rviz显示
- 配置RVIZ:
- 点击左下方"Add"按钮
- 选择"PointCloud2"显示类型
- 重复两次操作,分别配置两个PointCloud2模块
- 设置Topic:
- 第一个PointCloud2的Topic设置为
/point_cloud_publisher_topic - 第二个PointCloud2的Topic设置为
/adjustd_cloud
- 第一个PointCloud2的Topic设置为
- 设置坐标系:
- 将"Fixed Frame"修改为代码中
output_msg.header.frame_id对应的值"point_cloud_frame_id"
- 将"Fixed Frame"修改为代码中
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提供了一系列点云配准算法,可以将多个点云数据进行配准,实现不同数据源的对齐。
配准流程:
- 粗配准:使用特征匹配或全局配准算法
- 精配准:使用ICP或其变种算法
- 多视角配准:多帧点云融合
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 数据预处理优化
- 下采样:使用体素网格滤波减少数据量
- 离群点去除:提前去除噪声点
- 感兴趣区域提取:只处理必要的区域
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中不可见
- 解决方案:
- 检查Topic名称是否正确
- 确认Fixed Frame设置正确
- 调整点云显示大小和颜色
9.4 性能问题
- 问题:处理大规模点云时速度慢
- 解决方案:
- 使用下采样减少数据量
- 使用KD树加速搜索
- 开启并行计算
9.5 内存泄漏问题
- 问题:长时间运行后内存占用过高
- 解决方案:
- 使用智能指针管理内存
- 及时释放不再使用的点云
- 使用内存池技术
10. 进阶学习资源
10.1 官方文档
10.2 参考书籍
- 《点云库PCL学习教程》
- 《ROS机器人程序设计》
- 《3D计算机视觉:原理、算法与应用》
10.3 在线资源
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)