Point Cloud Library项目的编译使用
官方教程源地址: https://pointclouds.org/documentation/tutorials/using_pcl_pcl_config.html
官方教程中提供了各平台比较通用的项目编译方法,这里作简单的翻译补充。
创建cpp文件和CMakeLists.txt
在项目文件夹中,我们创建一个调用了pcl库保存pcd文件的模块的示例c++源码文件pcd_write.cpp
,文件内容我们先不管。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
int
main ()
{
pcl::PointCloud<pcl::PointXYZ> cloud;
// Fill in the cloud data
cloud.width = 5;
cloud.height = 1;
cloud.is_dense = false;
cloud.resize (cloud.width * cloud.height);
for (auto& point: cloud)
{
point.x = 1024 * rand () / (RAND_MAX + 1.0f);
point.y = 1024 * rand () / (RAND_MAX + 1.0f);
point.z = 1024 * rand () / (RAND_MAX + 1.0f);
}
pcl::io::savePCDFileASCII ("test_pcd.pcd", cloud);
std::cerr << "Saved " << cloud.size () << " data points to test_pcd.pcd." << std::endl;
for (const auto& point: cloud)
std::cerr << " " << point.x << " " << point.y << " " << point.z << std::endl;
return (0);
}
|
这个文件要先被编译成可执行文件,才能够运行使用。这里,我们使用CMake这个跨平台编译工具来对cpp文件进行编译。为此需要创建一个CMakeLists.txt
来告诉CMake怎么编译。
不理解上面这段话没问题,在pcd_write.cpp
同一个目录下创建一个CMakeLists.txt
即可。
CMakeLists.txt
内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| ## 这一行声明了需要的cmake最低版本号,不用改
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
## 这一行是项目名称,一般没什么影响
project(your_project_name)
## 这一行表示需要PCL最低1.2版本的所有组件,不清楚不要修改
find_package(PCL 1.2 REQUIRED)
## 下面这三行不用改
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
## 这一句告诉CMake从单个源文件pcd_write.cpp建立一个名叫main的可执行文件
## 源文件文件名需要根据实际修改
## 可执行文件名称可根据个人喜好修改
add_executable (main pcd_write.cpp)
## ^^^^ ^^^^^^^^^^^^^
## 这一句表示将PCL库文件与可执行文件main链接
## 这一句中可执行文件的名称要与前面一致
target_link_libraries (main ${PCL_LIBRARIES})
## ^^^^
|
正常使用中,我们只需要注意源文件名和可执行文件名保持正确,位置已在文件中标出
编译文件
正确创建完成上面这两个文件后,项目目录结构如下所示,可以开始对cpp源文件进行编译了!
1
2
3
| project_folder
|-- pcd_write.cpp
|-- CMakeLists.txt
|
我们先在项目文件夹中创建一个名为build
的空文件夹,我们将在里面完成编译,文件夹叫build
是约定俗成的,一般不修改
现在项目目录结构是这样的
1
2
3
4
| project_folder
|-- pcd_write.cpp
|-- CMakeLists.txt
|-- build
|
我们从文件进入build
文件夹,右键打开终端。或者在终端里手动输入命令切换到build
目录下。
执行下面的指令,即可完成编译
编译成功后,终端中应该可以看到类似这样的信息
1
2
3
4
| Scanning dependencies of target main
[ 50%] Building CXX object CMakeFiles/main.dir/pcd_write.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
|
运行文件
现在我们可以输入以下指令运行可执行文件main
,观察源cpp文件的效果
使用示例文件得到的终端运行显示结果应该类似于这样,运行成功
1
2
3
4
5
6
| 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
|
我们可以看到在CMakeLists.txt中设置的名为main
的可执行文件放在build
目录下。现在项目目录结构如下
1
2
3
4
5
6
7
| project_folder
|-- pcd_write.cpp
|-- CMakeLists.txt
|-- build
|-- main
|-- 一些其它文件
...
|
总结
以上就是编译运行一个使用了PCL的项目的步骤。在不同项目中基本只要注意CMakeLists.txt
填写正确的源文件名
和可执行文件名
。
Point Cloud Library基础使用
pcd点云文件的读取
PCL的pcd点云文件读写模块放在pcl/io/pcd_io.h
中,记得include
读取pcd文件的关键代码为
1
2
| pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("pcd文件的路径", *cloud);
|
第一行的代码创建一个点云类型为pcl::PointXYZ
的名为cloud
的空点云
第二行的代码从pcd文件中读取点云数据,存入cloud
中
完整的文件如下
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| #include <iostream> //标准C++库中的输入输出类相关头文件。
#include <pcl/io/pcd_io.h> //pcd 读写类相关的头文件。
#include <pcl/point_types.h> //PCL中支持的点类型头文件。
int main(int argc, char **argv)
{
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
if (pcl::io::loadPCDFile(argv[1], *cloud) == -1)
{
cout << "error input!" << endl;
return -1;
}
// 输出点云信息
std::cout << "原始点云中包含点数量:" << cloud->points.size() << std::endl;
std::cout << "height:" << cloud->height << "\nwidth:" << cloud->width << std::endl;
// 输出各点信息
for (const auto &point : *cloud)
std::cout << " " << point.x
<< " " << point.y
<< " " << point.z
<< std::endl;
return 0;
}
|
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
| cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(pcd_read)
find_package(PCL 1.2 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable (pcd_read main.cpp)
target_link_libraries (pcd_read ${PCL_LIBRARIES})
|
在源代码中,下面这一行这样写是为了程序运行时从命令行中获取参数
1
| int main(int argc, char **argv)
|
argc
将包含参数数量,可执行文件本身占一个参数数量。
argv
为参数数组,里面包含了所有的参数。
当终端中执行命令行如下时,程序中argc == 3
,argv[0] == "./main"
,argv[1] == "value1"
读取pcd文件的源代码如下,可以直接在终端执行时通过修改命令行读取不同的pcd文件,避免文件路径写死在程序中,为了读不同的文件浪费时间重复编译。
1
| pcl::io::loadPCDFile(argv[1], *cloud);
|
编译完成后,终端执行命令行如下,就能够读取pcd文件了,文件路径根据实际修改
1
| ./pcd_read ~/pcl/pcdXYZ_files/cloud00000.pcd
|
保存点云到pcd文件
PCL的pcd点云文件写入模块同样放在pcl/io/pcd_io.h
中,记得include
PCL将点云写入pcd文件的代码如下,点云数据默认以二进制的格式写入pcd文件中,加快读写速度
1
| pcl::io::savePCDFile("path_to_save_pcd_file", *cloud);
|
如果希望pcd文件中的点云数据以人类可读的格式保存,则使用如下代码
1
| pcl::io::savePCDFileASCII("path_to_save_pcd_file", *cloud);
|
完整示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include <iostream> //标准C++库中的输入输出类相关头文件。
#include <pcl/io/pcd_io.h> //pcd 读写类相关的头文件。
#include <pcl/point_types.h> //PCL中支持的点类型头文件。
int main(int argc, char **argv)
{
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
// pcd文件路径根据实际修改或直接写成从参数里读
if (pcl::io::loadPCDFile("input.pcd", *cloud) == -1)
{
std::cout << "error input!" << std::endl;
return -1;
}
// pcd文件路径根据实际修改或直接写成从参数里读
pcl::io::savePCDFileASCII("output.pcd", *cloud);
return 0;
}
|
显示点云
包含PCL完整可视化类的头文件为pcl/visualization/pcl_visualizer.h
首先,我们通过下面的代码建立一个名为viewer
的可视化类对象,该对象弹出的显示的窗口名为Cloud Viewer
1
| pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("Cloud Viewer"));
|
我们现在有一个如下定义的一个名为cloud
的点云对象
1
| pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
然后通过以下代码,我们就可以将点云cloud
显示在一个名为Cloud Viewer
的窗口中,该点云在viewer
中的id为cloud1
1
| viewer->addPointCloud(cloud, "cloud1");
|
如果想显示多个点云,要注意id不能重复,否则后面重复id的点云将不会显示
1
2
| viewer->addPointCloud(cloud1, "cloud1");
viewer->addPointCloud(cloud2, "cloud2");
|
点云显示在窗口中后,程序会继续运行,运行完成后会直接退出,同时关闭点云显示窗口,这显然不是我们想要的
使用PCL时可以通过下面的两种方法暂停程序,保持点云显示,直到在点云显示窗口中按q
退出
1
2
3
4
5
6
7
8
9
10
11
12
| // 两种方法二选一
// 方法1,阻塞式,完全停止程序
viewer->spin();
// 方法2,非阻塞式,可以在里面添加其它操作
while(viewer->wasStopped())
{
// 可如下添加其他操作
viewer->spinOnce(100);
// boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
|
有的代码用的是system("pause");
来暂停程序,该方法只在Windows中有效,通用性差,不灵活,不建议使用
完整代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| #include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
// 这里是为了方便统一调整点云类型
typedef pcl::PointXYZ PointT;
int main(int argc, char **argv)
{
// 定义点云对象
pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>);
// 从命令行参数中直接读取pcd文件路径,写入cloud
pcl::io::loadPCDFile(argv[1], *cloud);
// 定义可视化对象
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("Cloud Viewer"));
// --- 显示点云数据 ----
// "cloud1" 为显示id,默认cloud,显示多个点云时用默认会报警告。
viewer->addPointCloud(cloud, "cloud1");
// 阻塞式暂停程序
viewer->spin();
/*
// 非阻塞式,二选一
while (!viewer.wasStopped())
{
viewer->spinOnce(100);
}
*/
return 0;
}
|
Point Cloud Library学习指南
中文学习资料
PCL学习可以先看看双愚这位作者写的学习指南和学习笔记,是中文的,很详细清晰,很适合学习理解
https://zhuanlan.zhihu.com/p/268524083 里面对Point Cloud Library整体框架、入门学习作了比较详细的介绍和总结
https://www.yuque.com/huangzhongqing/pcl 这里面是对PCL的学习总结,基本覆盖了正常操作需要的所有应用,对每个部分都有着较为详细的解释说明,而且包含了示例文件。我在学习时参考了很多
官方教程和文档
官方也提供了PCL的使用指南,覆盖了PCL各个模块的使用,不过因为是全英文,刚学习时可能会比较难理解,但是官方教程保证了准确性和规范性,如果英语能力较好还是建议对照着读一读
https://pointclouds.org/documentation/tutorials/index.html
当你想知道某个模块的完整功能使用说明,或是知道某个模块需要引入哪个头文件,或是某个方法的来源,都可以在官方提供的PCL的API的完整说明文档中找到答案。建议对模块有问题的时候,先到里面看一看
https://pointclouds.org/documentation/index.html
PCL问题解决方法查找
遇到问题我们通常先直接搜索引擎找答案,不过PCL开发相关的中文文章和资料占比其实是比较小的,有时如果想更好地找到问题的解决方法,要用英语关键词去搜一下,看看英语的文章资料
百度
百度搜索pcl+中文关键词,通常可以解决常见问题,搜到的大部分都是中文内容,不过有时会发现好几篇文章内容都是一模一样的
百度搜索pcl+英语关键词,可以查到官网页面,有时可以查到其它英语的文章
Google
用Google搜索pcl+中文关键词,有时可以查到百度没查到但是写得不错的中文文章
用Google搜索pcl+英语关键词,可以找到比较多的国外的文章以及论坛讨论
Github
Github上会有一些PCL的示例代码和笔记,以及PCL的应用项目,有时候一些问题在Github里找找,可能可以发现别人已经搭好的项目源代码
可以直接在Github里搜关键词,也可以在Google里搜关键词+github