一、常用概念
1,过滤器Filters
消除噪音
2,特征Features
集合点属性:曲面的曲率估计和查询点的法线
通过k-neighborhood计算得到这两个属性作为特征
查找方法:KD-tress、八叉树等
3,关键点Keypoints
可以利用明确标准检测出来的稳定、独特的点,往往较少
4,注册Registration
将点云数据在统一的世界坐标系下进行组合起来
5,Kd-tree
使用FLANN以达到快速进行邻区搜索
FLANN快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)
6,Octree
八叉树通常用于邻区搜索、K邻区搜索、指定半径内搜索等
7,分割Segmentation
一个点云切分成多个片段簇
二、PointT类型
1,PointXYZ,成员变量float x,y,z
每个点云都包含三个信息
points[i]表示第i个点云
points[i].x、points[i].y、points[i].z进行访问
2,PointXYZI,成员变量float x,y,z,intensity
3,PointXYZRGBA,成员变量float x,y,z、uint32_t rgba
4,PointXYZRGB,成员变量float x,y,z,rgb
5,PointXY,成员变量float x,y
6,InterestPoint,成员变量float x,y,z,strength,其中strength表示关键点的强度测量值
7,Normal,成员变量float normal[3], curvature,其中normal结构体表示给定点所在样本曲面上的法线方向以及对应的曲率的测量值
其中法线为normal,曲率为curvature
访问法向量的第一个坐标:points[i].normal[0]
8,PointNormal,成员变量float x,y,z,normal[3],curvature
三、IO模块和类
四、从PCD文件中读写点云数据
1,自定义点云数据并保存
#include <pcl/io/pcd_io.h> // PCD读写类相关的头文件
#include <pcl/point_types.h> // PCL中支持的点类型的头文件
#include <iostream> // 标准C++库中的输入输出的头文件
int main(int argc,char* argv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
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 + 10.f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 10.f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 10.f);
}
// 把点云进行保存
pcl::io::savePCDFileASCII("test_pcd.pcd", *cloud);
// 输出点云的信息
for (size_t i = 0; i < cloud->size(); i++) {
std::cerr << " " << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << std::endl;
}
return 0;
}
2,加载自定义点云文件
#include <pcl/io/pcd_io.h> // PCD读写类相关的头文件
#include <pcl/point_types.h> // PCL中支持的点类型的头文件
#include <iostream> // 标准C++库中的输入输出的头文件
int main(int argc,char* argv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
// 加载点云
if (pcl::io::loadPCDFile<pcl::PointXYZ>("test_pcd.pcd",*cloud) == -1)
{
PCL_ERROR("Can't read file test_pcd.pcd \n");
return -1;
}
std::cerr << "Loaded " << cloud->width * cloud->height << " data points" << 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;
}
3,三个点云进行合并
①点云连接
A+B=C
A,B,C三个点云类型均为pcl::PointXYZ,得到的点云C前几行是A后几行是B
*cloud_c = *cloud_a + *cloud_b; // 把cloud_a和cloud_b连接一起创建cloud_c 后输出
#include <pcl/io/pcd_io.h> // PCD读写类相关的头文件
#include <pcl/point_types.h> // PCL中支持的点类型的头文件
#include <iostream> // 标准C++库中的输入输出的头文件
int main(int agrc,char*agrv[])
{
// 声明三个点云pcl::PointXYZ类型
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_a = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_b = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_c = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
// 创建点云为无需点云
cloud_a->width = 5;
cloud_a->height = cloud_b->height = 1;
cloud_a->points.resize(cloud_a->height * cloud_a->width);
// 点云连接 a有5个点,b有3个点,合并之后c有8个点
cloud_b->width = 3;
cloud_b->points.resize(cloud_b->height * cloud_b->width);
for (size_t i = 0; i < cloud_a->points.size(); ++i)
{
cloud_a->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_a->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_a->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
for (size_t i = 0; i < cloud_b->points.size(); ++i)
{
cloud_b->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_b->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_b->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
std::cerr << "Cloud A(XYZ): " << std::endl;
for (size_t i = 0; i < cloud_a->size(); i++) {
std::cerr << " " << cloud_a->points[i].x << " " << cloud_a->points[i].y << " " << cloud_a->points[i].z << std::endl;
}
std::cerr << "Cloud B(XYZ): " << std::endl;
for (size_t i = 0; i < cloud_b->size(); i++) {
std::cerr << " " << cloud_b->points[i].x << " " << cloud_b->points[i].y << " " << cloud_b->points[i].z << std::endl;
}
*cloud_c = *cloud_a + *cloud_b;//把cloud_a和cloud_b连接一起创建cloud_c 后输出
std::cerr << "Cloud C(XYZ): " << std::endl;
for (size_t i = 0; i < cloud_c->points.size(); ++i){
std::cerr << " " << cloud_c->points[i].x << " " << cloud_c->points[i].y << " " << cloud_c->points[i].z << " " << std::endl;
}
return 0;
}
②字段间连接
A+B=C
A是pcl::PointXYZ类型,B是pcl::Normal类型,C是pcl::PointNormal类型
将A和B进行字段间连接,也就是C是XYZNormal,A是XYZ,B是Normal,C就是将A和B填到对应的C的位置即可
pcl::concatenateFields(*cloud_a, *cloud_b, *cloud_c);
#include <pcl/io/pcd_io.h> // PCD读写类相关的头文件
#include <pcl/point_types.h> // PCL中支持的点类型的头文件
#include <iostream> // 标准C++库中的输入输出的头文件
int main(int agrc,char*agrv[])
{
// 声明三个点云pcl::PointXYZ类型
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_a = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
// 声明存储连接时需要的点云类型为pcl::Normal
pcl::PointCloud<pcl::Normal>::Ptr cloud_b = std::make_shared<pcl::PointCloud<pcl::Normal>>();
// 声明最终合并的点云类型为pcl::PointNormal
pcl::PointCloud<pcl::PointNormal>::Ptr cloud_c = std::make_shared<pcl::PointCloud<pcl::PointNormal>>();
// 创建点云为无序点云
cloud_a->width = 5;
cloud_a->height = cloud_b->height = 1;
cloud_a->points.resize(cloud_a->height * cloud_a->width);
// 因为cloud_a是5,因为要字段间连接,故需要将cloud_b转换成cloud_bb,且cloud_bb也应该与cloud_a保持一致都是5
cloud_b->width = 5;
cloud_b->points.resize(cloud_b->height * cloud_b->width);
for (size_t i = 0; i < cloud_a->points.size(); ++i)
{
cloud_a->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_a->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_a->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
for (size_t i = 0; i < cloud_b->points.size(); ++i)
{
cloud_b->points[i].normal_x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_b->points[i].normal_y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud_b->points[i].normal_z = 1024 * rand() / (RAND_MAX + 1.0f);
}
std::cerr << "Cloud A(XYZ): " << std::endl;
for (size_t i = 0; i < cloud_a->size(); i++) {
std::cerr << " " << cloud_a->points[i].x << " " << cloud_a->points[i].y << " " << cloud_a->points[i].z << std::endl;
}
std::cerr << "Cloud B(normal): " << std::endl;
for (size_t i = 0; i < cloud_b->size(); i++) {
std::cerr << " " << cloud_b->points[i].normal_x << " " << cloud_b->points[i].normal_y << " " << cloud_b->points[i].normal_z << std::endl;
}
pcl::concatenateFields(*cloud_a, *cloud_b, *cloud_c); // 把cloud_a和cloud_b字段间连接一起创建cloud_c后输出
std::cerr << "Cloud C(XYZnormal): " << std::endl;
for (size_t i = 0; i < cloud_c->points.size(); i++){
std::cerr << " " << cloud_c->points[i].x << " " << cloud_c->points[i].y << " " << cloud_c->points[i].z << " "
<< cloud_c->points[i].normal_x << " " << cloud_c->points[i].normal_y << " " << cloud_c->points[i].normal_z << std::endl;
}
return 0;
}
五,kd-tree搜索
1,KNN最小近邻搜索
kdtree.setInputCloud(cloud);
kdtree.nearestKSearch(beyond, K, pointIdxKNN, pointKNNSquareDistance)
查找在cloud点云中,找离beyond这个点最近的K个点的坐标位置,查找出来的点是K个
#include <pcl/point_cloud.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <iostream>
#include <vector>
#include <ctime>
int main(int agrc, char* agrv[])
{
srand(time(NULL)); // 使用系统时间初始化随机种子
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
// 随机生成点云
cloud->width = 100;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
// 点云数据填充
for (size_t i = 0; i < cloud->points.size(); i++)
{
cloud->points[i].x = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
cloud->points[i].y = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
cloud->points[i].z = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
}
// 创建KdTreeFlann对象,并把创建的点云作为输入,创建一个searchPoint变量作为查询点
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
// 要搜索的点云
kdtree.setInputCloud(cloud);
// 设置查询的点云并赋值
pcl::PointXYZ beyond;
beyond.x = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
beyond.y = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
beyond.z = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
// 上述表示,在点云cloud中搜索beyond这个点云
// k-neighborhood
int K = 10; // 查找beyond这个点附近的K个点
std::vector<int> pointIdxKNN(K); // 存放查询点附近的点的索引
std::vector<float> pointKNNSquareDistance(K); // 存放附件点对应距离的平方
std::cerr << "K nearest neighbor at( " << beyond.x << " " << beyond.y << " " << beyond.z << " ) with K= " << K << std::endl;
// 在cloud这个点云中找beyond这个点最接近的周围K个点的索引pointIdxKNN和距离距离平方和pointKNNSquareDistance
if (kdtree.nearestKSearch(beyond, K, pointIdxKNN, pointKNNSquareDistance) > 0)
{
for (size_t i = 0; i < pointIdxKNN.size(); i++)
{
// 输出所有近邻点的坐标
std::cerr << " " << cloud->points[pointIdxKNN[i]].x
<< " " << cloud->points[pointIdxKNN[i]].y
<< " " << cloud->points[pointIdxKNN[i]].z
<< " (squared distance : " << pointKNNSquareDistance[i] << " )"
<< std::endl;
}
}
return 0;
}
2,半径R内搜索
kdtree.setInputCloud(cloud);
kdtree.radiusSearch(beyond, radius, pointIdxRadiusKNN, pointRadiusKNNSquareDistance)
查找在cloud点云中,以beyond这个点为圆心,radius为半径找在这个范围内的所有点的坐标位置
#include <pcl/point_cloud.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <iostream>
#include <vector>
#include <ctime>
int main(int agrc,char* agrv[])
{
srand(time(NULL)); // 使用系统时间初始化随机种子
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
// 随机生成点云
cloud->width = 100;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
// 点云数据填充
for(size_t i=0;i<cloud->points.size();i++)
{
cloud->points[i].x = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
cloud->points[i].y = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
cloud->points[i].z = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
}
// 创建KdTreeFlann对象,并把创建的点云作为输入,创建一个searchPoint变量作为查询点
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
// 要搜索的点云
kdtree.setInputCloud(cloud);
// 设置查询的点云并赋值
pcl::PointXYZ beyond;
beyond.x = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
beyond.y = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
beyond.z = 1024.0f * rand() / (RAND_MAX)+(float)1.0f;
// 上述表示,在点云cloud中搜索beyond这个点云
int K = 10; // 查找beyond这个点附近的K个点
// 最小半径搜索
std::vector<int> pointIdxRadiusKNN(K); // 存放查询点附近的点的索引
std::vector<float> pointRadiusKNNSquareDistance(K); // 存放附件点对应距离的平方
float radius = 256.0f * rand() / (RAND_MAX + 1.0f); // 随机生成的半径
std::cerr << "K within radius neighbor at( " << beyond.x << " " << beyond.y << " " << beyond.z << " ) with radius= " << radius << std::endl;
// 在cloud这个点云中以beyond这个点为圆心半径为radius内的点的索引pointIdxRadiusKNN和距离距离平方和pointRadiusKNNSquareDistance
if (kdtree.radiusSearch(beyond, radius, pointIdxRadiusKNN, pointRadiusKNNSquareDistance) > 0)
{
for (size_t i = 0; i < pointIdxRadiusKNN.size(); i++)
{
// 输出所有近邻点的坐标
std::cerr << " " << cloud->points[pointIdxRadiusKNN[i]].x
<< " " << cloud->points[pointIdxRadiusKNN[i]].y
<< " " << cloud->points[pointIdxRadiusKNN[i]].z
<< " (squared distance : " << pointRadiusKNNSquareDistance[i] <<" )"
<< std::endl;
}
}
return 0;
}
因为点和半径都是随机的,不一定每次运行就能找到
有可能这个半径内没有点,所以有可能没有点的输出
多尝试几次
六、Octree八叉树搜索
数学原理有点多,后期再补充学习
七、Visualizer点云可视化
显示自己的点云数据
#include <pcl\visualization\cloud_viewer.h>
#include <iostream>
#include <pcl\io\io.h>
#include <pcl\io\pcd_io.h>
int main(int agrc,char*agrv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::io::loadPCDFile("1_part.pcd",*cloud);
pcl::visualization::PCLVisualizer visulaizer;
visulaizer.setBackgroundColor(0, 0, 0); // RGB设置背景颜色
visulaizer.addPointCloud(cloud);
visulaizer.addCoordinateSystem(1.0);
visulaizer.initCameraParameters(); // 显示坐标系 红x绿y蓝z
// visulaizer.resetCamera(); // 不显示坐标系
visulaizer.spin();
return 0;
}
八、Filter点云滤波
1,直通滤波
说白了就是可以根据指定某个轴给定范围进行保留
pcl::PassThrough<pcl::PointXYZ> pass; // 定义直通滤波对象
pass.setInputCloud(cloud); // 对加载的原始点云进行处理
pass.setFilterFieldName("z"); // 直通滤波Z轴
pass.setFilterLimits(700, 920); // Z轴的范围为700-920
pass.filter(*result_z); // 滤波之后的结果为result_z点云
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/io/pcd_io.h>
#include <pcl\visualization\pcl_visualizer.h>
int main(int agrc ,char* agrv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::PointCloud<pcl::PointXYZ>::Ptr result_z = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::PointCloud<pcl::PointXYZ>::Ptr result_y = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::PointCloud<pcl::PointXYZ>::Ptr result_x = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::io::loadPCDFile("1_model_xyz.pcd", *cloud);
pcl::PassThrough<pcl::PointXYZ> pass; // 定义直通滤波对象
pass.setInputCloud(cloud); // 对加载的原始点云进行处理
pass.setFilterFieldName("z"); // 直通滤波Z轴
pass.setFilterLimits(700, 920); // Z轴的范围为700-920
pass.filter(*result_z); // 滤波之后的结果为result_z点云
// 对Y轴和X轴进行直通滤波的时候是基于得到的结果进行再次操作
pass.setInputCloud(result_z);
pass.setFilterFieldName("y");
pass.setFilterLimits(-200, 100);
pass.filter(*result_y);
pass.setInputCloud(result_y);
pass.setFilterFieldName("x");
pass.setFilterLimits(-200, 200);
pass.filter(*result_x);
/*
std::cerr << "Cloud after filtering: " << std::endl;
for (size_t i = 0; i < result_x->points.size(); ++i)
std::cerr << " " << result_x->points[i].x << " "
<< result_x->points[i].y << " "
<< result_x->points[i].z << std::endl;
*/
pcl::visualization::PCLVisualizer visualizer;
visualizer.addPointCloud(result_x);
visualizer.initCameraParameters();
visualizer.resetCamera();
visualizer.spin();
return 0;
}
原点云
通过直通滤波进行XYZ各个方向进行过滤,得到只包含红色目标的物体
先对Z轴进行直通滤波,然后依次对Y轴和X轴进行直通滤波操作
2,VoxelGrid滤波器
体素化网格方法实现下采样,减少点云数据
原理:创建一个三位体素栅格,每个体素内的所有点的重心来近似表示体素中的其他点,这个体素内的所有点都可以用重心点表示,可以减少数据量
使用VoxelGrid滤波器加载的数据必须是pcl::PCLPointCloud2格式
pcl::VoxelGrid<pcl::PCLPointCloud2> sor; // 定义VoxelGrid对象
sor.setInputCloud(cloud); //加载原始点云数据进行处理
sor.setLeafSize(1.0f, 1.0f, 1.0f); // 设置滤波时创建的体素体积为1m的立方体,这样相当于1立方米内的体素栅格内的点归一成重心点,可以下采样减少点云数据量
sor.filter(*cloud_filtered); // 输出结果,类型也必须是pcl::PCLPointCloud2
pcl::fromPCLPointCloud2(*cloud_filtered, *src);//可以将pcl::PCLPointCloud2格式转换为pcl::PointXYZ格式,通过可视化进行显示
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/io/pcd_io.h>
#include <pcl\visualization\pcl_visualizer.h>
int main(int agrc, char* agrv[])
{
pcl::PCLPointCloud2::Ptr cloud = std::make_shared<pcl::PCLPointCloud2>();;
pcl::PCLPointCloud2::Ptr cloud_filtered = std::make_shared<pcl::PCLPointCloud2>();
pcl::io::loadPCDFile("1_model_xyz.pcd", *cloud);
pcl::VoxelGrid<pcl::PCLPointCloud2> sor;
sor.setInputCloud(cloud);
sor.setLeafSize(1.0f, 1.0f, 1.0f); // 设置滤波时创建的体素体积为1m的立方体,这样相当于1立方米内的体素栅格内的点归一成重心点,可以下采样减少点云数据量
sor.filter(*cloud_filtered);
pcl::PointCloud<pcl::PointXYZ>::Ptr src = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::fromPCLPointCloud2(*cloud_filtered, *src);
std::cerr << "voxelgrid filter is: "<< src->points.size() << std::endl; //
pcl::visualization::PCLVisualizer visualizer;
visualizer.addPointCloud(src);
visualizer.initCameraParameters();
visualizer.resetCamera();
visualizer.spin();
return 0;
}
原始点云大小为:1063964
下采样之后的点云大小为:925246
3,statisticalOutlierRemoval滤波器
statisticalOutlierRemoval滤波器移除离群点,emmm说真的,没看懂,先放个案例在这吧
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; // 定义滤波器对象
sor.setInputCloud(cloud); // 放入待处理的点云
sor.setMeanK(200); //设置考虑临界点点数
sor.setStddevMulThresh(1.0); // 考虑离群点的阈值
sor.filter(*cloud_result); // 得出结果点云
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/io/pcd_io.h>
#include <pcl\visualization\pcl_visualizer.h>
int main(int agrc, char* agrv[])
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_result = std::make_shared<pcl::PointCloud<pcl::PointXYZ>>();
pcl::io::loadPCDFile("1_model_xyz.pcd", *cloud);
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud); //设置待滤波的点云
sor.setMeanK(200); //设置在进行统计时考虑查询点临*点数
sor.setStddevMulThresh(1.0); //设置判断是否为离群点的阀值
sor.filter(*cloud_result);
std::cerr << "StatisticalOutlierRemoval filter is: " << cloud_result->points.size() << std::endl; //
pcl::visualization::PCLVisualizer visualizer;
visualizer.addPointCloud(cloud);
visualizer.initCameraParameters();
visualizer.resetCamera();
visualizer.spin();
return 0;
}