简介
介绍
PCL(Point Cloud Library)收集了点云相关研究的一个开源的C++库,实现了大量点云相关的通用算法和高效数据结构,设计点云获取、滤波、分割、配准、检索、特征提取、识别、追踪、曲面重建、可视化等。同时具有高可移植性,在ROS、Android、Ubuntu、等主流linux平台都可以使用
版本
结构
对于3D点云处理来说,PCL完全是一个模块化的现代化C++模块库,主要基于Boost、Eigen、FLANN、VTKCUDA、OpenNI、QHull等第三方库。
OpenMP、GPU、CUDA:高性能计算技术,通过并行化实现高性能
FLANN:K近邻搜索操作的架构基于FLANN(Fast Library for Approximate Nearest Neighbors),是目前技术中最快的
Boost:目前PCL所有的模块和算法都是通过Boost共享指针来传送数据,从而避免多次复制系统中已存在的数据需要
调用流程
PCL中一个处理管道的基本接口程序:
创建处理对象,例如:过滤、特征估计、分割等
使用setInputCloud通过输入点云数据,处理模块
设置算法相关参数
调用计算得到输出
模块介绍
模块
说明
filters
实现采样、去除离群点、特征提取、拟合估计等数据过滤
features
实现多种三维特征,如曲面发现、曲率、边界点估计、矩不变量、主曲率、PFH、FPFH特征、旋转图像、积分图像、NARF描述子、RIFT、相对标准差、数据前度的筛选等
IO
实现数据的输入和输出操作,例如点云数据文件PCD文件的读写
segmentation
实现聚类提取,如通过采样一致性方法对一系列参数模型进行模型拟合点云分割提取
surface
表面重建技术,如网格重建、凸包重建、移动最小二乘法平滑等
register
实现点云配准方法,ICP
keypoints
实现不同的关键点的提取方法,这可以用来作为预处理步骤,决定在哪里提取特征描述符
range
实现支持不同点云数据继承的范围图像
环境
windows
组件搭建
说明
kinect v1 SDK
link
openni
openni1 ,openni2 ,openni1.x的组件安装之后,我的kinect无法连接,只能使用openni2
VTK
link ,不能使用太高的版本,否则visualization这个组件无法生成项目
glut
Glut库太老了,x64编译时一直报错,需要使用freeglut替代
缺失的组件在windows下都可以通过vcpkg工具进行查找和自动编译需要注意x86和x64的问题,vcpkg安装的库默认x86,如果系统是x64的:vcpkg install xxx --triplet x64-windows
vcpkg
说明
vcpkg search
搜索缺失包名
vcpkg install
进行安装,将lib、include路径配置到cmake
下载过慢问题
可以单独下载文件,重命名后再放入/vcpkg/download
PCL安装
说明
下载PCL
下载链接
cmake编译
必须选择BUILD_CUDA才能编译kinfu,如果使用openni2,需要在with中勾选该选项
样例数据
测试
.\pcl_viewerd.exe ism_test_cat.pcd -bc 255,255,255
PCL开发
编写自定义PCL类
假设我们将新的算法bilateral作为滤波库的一部分,首先在目录中建立三个文件
include/pcl/filters/bilateral.h:包含定义和声明
include/pcl/filters/impl/bilateral.hpp:包含模板类的具体实现
src/bilateral.cpp:包含具体的不同点类型的模板类实例化
我们定义新的类,命名为BilateralFilter。滤波器接口规定每个算法必须有两个声明和实现可供使用,一个操作PointCloud<T>,一个操作Point-Cloud2。这里实现PointCloud<T>
bilateral.h
类的声明,实现Filter类的的各种setters和getters虚函数。
#ifndef PCL_FILTERS_BILATERAL_H_ #define PCL_FILTERS_BILATERAL_H_ #include <pcl/filters/filter.h> namespace pcl{ template <typename PointT> class BilateralFilter :public Filter<PointT>{ using Filter<PointT>::input_; typedef typename Filter<PointT>::PointCloud PointCloud; typedef typename pcl::KdTree<PointT>::Ptr KdTreePtr; public : BilateralFilter():sigma_s_(0 ),sigma_r_(std ::numeric_limits<double >::max()){} void setSigmaS (const double sigma_s) { sigma_s_=sigma_s } double getSigmaS(){ return sigma_s_; } void setSigmaR (const double sigma_r) { sigma_r_=sigma_r } double getSigmaR(){ return sigma_r_; } void applyFilter (PointCloud &output) ; double computePointWeight (const int pid,const std ::vector <int >&indices,const std ::vector <float >&distances) ; void setSearchMethod (const KdTreePtr &tree) {tree_=tree;} private : double sigma_s_; double sigma_r_; KdTreePtr tree_; }; } #endif
bilateral.hpp
完成声明函数的具体实现,applyFilter和computePointWeight
applyFilter:利用输入数据构建的kd树,把所有输入数据复制到输出,然后计算新的点的强度,复制到点云数据output,
computePointWeight:通过传递一个要计算的强度重量的point索引,索引指示的点是由欧式空间的邻域组成。
完成类声明PCL_INSTANTIATE用于实例化模板类
#ifndef PCL_FILTERS_BILATERAL_IMPL_H_ #define PCL_FILTERS_BILATERAL_IMPL_H_ #include <pcl/filters/bilateral.h> template <typename PointT>double pcl::BilateralFilter<PointT>::computePointWeight(const int pid,const std ::vector <int >&indices,const std ::vector <float >&distances){ double BF=0 ,W=0 ; for (size_t nid=0 ;n_id<indices.size();++n_id){ double id=indices[n_id]; double dist=std ::sqrt (distances[n_id]); double intensity_dist = abs (input_->points[pid].intensity-input_->points[id].intensity); double weight = kernel(dist,sigma_s_)*kernel(intensity_dist,sigma_r_); BF += weight*input_->points[id].intensity; W += weight; } return (BF/W); } template <typename PointT>void pcl::BilateralFilter<PointT>::applyFilter(PointCloud &output){ if (sigma_s_==0 ){ PCL_ERROR("[pcl::BilateralFilter::applyFilter] Need a sigma_s value given before continuing.\n" );return ; } if (!tree_){ if (input_->isOrganized()) tree_.reset(new pcl::OrganizedDataIndex<PointT>()); else tree_.reset(new pcl::KdTreeFLANN<PointT>(false )); } tree_->setInputCloud(input_); std ::vector <int >k_indices; std ::vector <float >k_distances; output = *input_; for (size_t point_id=0 ;point_id<input_->points.size();++point_id){ tree_->radiusSearch(point_id,sigma_s_*2 ,k_indices,k_distances); output.points[point_id].intensity=computePointWeight(point_id,k_indices,k_distances); } } #define PCL_INSTANTIATE_BilateralFilter(T) template class PCL_EXPORTS pcl::BilateralFilter<T>; #endif
bilateral.cpp
为BilateralFilter类实例化并预编译实例化若干模板,增加编译时间,但是能够提高使用时的速度
#include <pcl/point_types.h> #include <pcl/impl/instantiate.hpp> #include <pcl/filters/bilateral.h> #include <pcl/filters/impl/bilateral.hpp> using namespace pcl;PCL_INSTANTIATE(BilateralFilter,PCL_XYZ_POINT_TYPES);
需要实现模板类PCL_INSTANTIATE和pcl::filter的纯虚函数,才能成功编译
CMakeLists.txt
将新建的文件添加到编译文件中,就可以实现编译连接的过程
set(srcs src/bilateral.cpp ) set(incs include/pcl/$(SUBSYS_NAME)/bilateral.h ) set(impl_incs include/pcl/$(SUBSYS_NAME)/impl/bilateral.hpp )
PCL自定义点类型
PCL定义的点类型PointT
在point_types.hpp中有完成目录,如果存在用户就不需要重复定义了
PointT
成员
说明
PointXY
float x,y
点数据类型xy坐标
PointXYZ
float x,y,z
点数据类型xyz坐标
PointXYZI
float x,y,z,intensity
xyz坐标,indensity:强度值
PointXYZRGB
float x,y,z,rgb
xyz坐标,RGB
PointXYZRGBA
float x,y,z;uint32_t rgba
xyz坐标,RGBA
InterestPoint
float x,y,z,strength
xyz坐标,strength关键点的强度测量值
Normal
float normal[3],curvature
法向量以及曲率的测量值
PointNormal
float x,y,z,normal[3],curvature
xyz,法向量及曲率
PointXYZRGBNormal
float x,y,z,rgb,normal[3],curvature
xyzrgb,法向量及曲率
PointXYZINormal
float x,y,z,intensity,normal[3],curvature
xyz,强度值,法向量及曲率
PointWithRange
float x,y,z,range
xyz,range包含视点到采样点的距离测量值
PointWithViewpoint
float x,y,z,vp_x,vp_y,vp_z
xyz,vp_x,vp_y,vp_z
MomentInvariants
float j1,j2,j3
包含采样曲面上面片的3个不变矩的point类型,描述面片上质量的分布情况
PrincipalRadiiRSD
float r_min,r_max
包含曲面块上两个RSD半径的point类型
Boundary
uint8_t boundary_point
存储一个点是否位于曲面边界上
PrincipalCurvature
float principal_curvature[3],pc1,pc2
包含给定点主曲率的简单point类型
PFHSignature125
float pfh[125]
包含给定点PFH(点特征直方图)的point类型
FPFHSignature33
float fpfh[33]
包含给定点FPFH(快速点特征直方图)的point类型
VFHSignature308
float vfh[308]
包含给定点VFH(快速点特征直方图)的point类型
Narf36
float xyz,roll,pitch,yaw,descriptor[36]
包含给定点NARF(归一化对齐半径特征)的point类型
BorderDescription
int x,y;BorderTraits traits
给定点边界类型
IntensityGradient
float gradie[3]
给定点强度的梯度
Histogram
float histogram[N]
n维直方图
PointWithScale
float x,y,z,scale
x,y,z,scale几何操作尺度
PointSurfel
float x,y,z,normal[3],rgba,radius,confidence,curvature
复杂point信息
struct {float x,y;};union { float data[4 ]; struct {float x,y,z;}; } union { struct { float intensity; } float data_c[4 ]; }; union { struct { uint32_t rgba; } float data_c[4 ]; } union { struct { float rgb; } float data_c[4 ]; } union { struct { float strength; } float data_c[4 ]; } union { float data_n[4 ]; float normal[3 ]; struct { float normal_x,normal_y,normal_z; } }; union { struct { float curvature; } float data_c[4 ]; } union { float data[4 ]; struct { float x,y,z; } }; union { float data_n[4 ]; float normal[3 ]; struct { float normal_x,normal_y,normal_z; } }; union { struct { float curvature; } float data_c[4 ]; }
新增PointT类型
定义新的类型,需要包含PCL特定的类/算法模板头文件的实现,这样才能在其共同使用。
#include <pcl/filters/bilateral.h> #include <pcl/filters/impl/bilateral.hpp> struct MyPointType { float test; }
如果是库的一部分,可以被他人使用,需要将自己的类型显式实例化。
#include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> struct MyPointType { PCL_ADD_POINT4D; float test; EIGEN_MAKE_ALIGNED_OPERAROE_NEW }EIGEN_ALIGN16; POINT_CLOUD_REGISTER_POINT_STRUCT(MyPointType,(float ,x,x),(float ,y,y),(float ,z,z),(float ,test,test)) int main (int argc,char **argv) { pcl::PointCloud<MyPointType>cloud; cloud.points.resize(2 ); cloud.width=2 ; cloud.height=1 ; cloud.points[0 ].test=1 ; cloud.points[1 ].test=1 ; cloud.points[0 ].x=cloud.points[0 ].y=cloud.points[0 ].z=0 ; cloud.points[1 ].x=cloud.points[1 ].y=cloud.points[1 ].z=3 ; pcl::io::savePCDFile("test.pcd" ,cloud); }
许可
建议每个文件报验一个描述代码作者的许可,使用户对约束有清晰的了解。PCL是100%的BSD许可的,可以在文件中以C++注释的形式嵌入该许可证。如果需要声明其他版权,则添加类似内容即可
代码注释
PCL支持Doxygen的文档生成,注释中可以对API进行完善
异常处理
自定义异常
PCL规定新定义的异常类必须继承PCLException类,具体定义在pcl/exceptions.h中
class PCL_EXPROTS MyException :public PCLException{public : MyException(const std ::string &error_description,const std ::string & file_name="" ,const std ::string & function_name="" ,unsigned line_number=0 ) throw ():pcl::PCLException(error_description,file_name,function_name,line_number){} };
使用异常
#define PCL_THROW_EXCEPTION(ExceptionName,message){ std ::ostringstream s; s<<message; throw ExceptionName(s.str(),__FILE__,"" ,__LINE__); }
if (my_requirements!=the_parameters_used_) PCL_THROW_EXCEPTION(MyExcption,"My requirements are not set" <<the_parameter_used);
异常处理
try { myObject.myFunc(some_params); } catch (pcl::MyException& e){ } #if 0 catch (exception& e){ } #endif
数据操作
简单使用
cmake_minimum_required (VERSION 2.6 FATAL_ERROR)project (MY_GRAND_PROJECT)find_package (PCL 1.3 REQUIRED COMPONENTS common io)include_directories (${PCL_INCLUDE_DIRS} )link_directories (${PCL_LIBRARY_DIRS} )add_definitions (${PCL_DEFINITIONS} )add_executable (pcd_write_test pcd_write.cpp)target_link_libraries (pcd_write_test ${PCL_LIBRARIES} )
#include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ> cloud; cloud.width = 5 ; cloud.height = 1 ; cloud.is_dense = false ; cloud.points.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 ); }
#include <iostream> #include <fstream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/registration/icp.h> #include <pcl/visualization/cloud_viewer.h> using namespace std ;using namespace pcl;PointCloud<PointXYZI>::Ptr getPoint (const string & infile) { fstream input (infile.c_str(), ios::in | ios::binary) ; if (!input.good()){ cerr << "Could not read file: " << infile << endl ; exit (EXIT_FAILURE); } input.seekg(0 , ios::beg); PointCloud<PointXYZI>::Ptr points (new pcl::PointCloud<PointXYZI>) ; int i; for (i=0 ; input.good() && !input.eof(); i++) { PointXYZI point; input.read((char *) &point.x, 3 *sizeof (float )); input.read((char *) &point.intensity, sizeof (float )); points->push_back(point); } input.close(); return points; } void view (const pcl::PointCloud<pcl::PointXYZI>::Ptr& cloud) { visualization::CloudViewer viewer ("Simple Cloud Viewer" ) ; viewer.showCloud (cloud); while (!viewer.wasStopped ()){ } } int main (int argc, char ** argv) { PointCloud<PointXYZI>::Ptr cloud_in = getPoint("velodyne/000000.bin" ); PointCloud<PointXYZI>::Ptr cloud_out = getPoint("velodyne/000001.bin" ); view(cloud_in); view(cloud_out); return (0 ); }
滤波
方法
方法
类
说明
直通滤波器
pcl::PassThrought<pcl::PointXYZ>pass;
想要固定某一区域的
体素滤波器
pcl::VoxelGrid<pcl::PCLPointCloud2>vox;pcl::ApproximateVoxelGrid<pcl::PCLPointCloud2>vox;哈希map,速度更快
体素化
统计滤波器
pcl::StatistcalOutlierRemoval<pcl::PointXYZ>sor;
去除噪声
半径滤波器
pcl::RadiusOutlierRemoval<pcl::PointXYZ>rador;
统计半径里面多少点,不足就去除
均匀采样
pcl::UniformSampling<pcl::PointXYZ>rador;
setInputCloud(cloud)setRadiusSearch(0.5)comput(indices)
条件滤波器
pcl::ConditionalRemoval<pcl::PointXYZ>condr;
投影滤波器
pcl::ProjectInliers<pcl::PointXYZ>proj;
模型滤波器
pcl::ModelOutlierRemoval<pcl::PointXYZ>modr;
通过模型参数模拟,将超出阈值的点去除
索引提取
pcl::ExtractIndices<pcl::PointXYZ>extr;
空间裁减滤波
pcl::Clipper3D<pcl::PointXYZ>;pcl::BoxCliper3D<pcl::PointXYZ>pcl::CropBax<pcl::PointXYZ>pcl::CropHull<pcl::PointXYZ>
CropHull凸包
双边滤波器
pcl::BilateralFilter<pcl::PointXYZ>bf;
保存边界的去噪声
高斯滤波器
pcl::filters::GuassianKernal<PointInT,PointOutT>;
直通滤波器
#include <iostream> #include <pcl/point_types.h> #include <pcl/filters/passthrough.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>) ; cloud->width = 5 ; cloud->height = 1 ; cloud->points.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 ); } std ::cerr << "Cloud before filtering: " << std ::endl ; for (const auto & point: *cloud) std ::cerr << " " << point.x << " " << point.y << " " << point.z << std ::endl ; pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud (cloud); pass.setFilterFieldName ("z" ); pass.setFilterLimits (0.0 , 1.0 ); pass.filter (*cloud_filtered); std ::cerr << "Cloud after filtering: " << std ::endl ; for (const auto & point: *cloud_filtered) std ::cerr << " " << point.x << " " << point.y << " " << point.z << endl ; return (0 ); }
体素滤波器
#include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/voxel_grid.h> int main (int argc, char ** argv) { pcl::PCLPointCloud2::Ptr cloud (new pcl::PCLPointCloud2 ()) ; pcl::PCLPointCloud2::Ptr cloud_filtered (new pcl::PCLPointCloud2 ()) ; pcl::PCDReader reader; reader.read ("table_scene_lms400.pcd" , *cloud); std ::cerr << "PointCloud before filtering: " << cloud->width * cloud->height << " data points (" << pcl::getFieldsList (*cloud) << ")." << std ::endl ; pcl::VoxelGrid<pcl::PCLPointCloud2> sor; sor.setInputCloud (cloud); sor.setLeafSize (0.01f , 0.01f , 0.01f ); sor.filter (*cloud_filtered); std ::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points (" << pcl::getFieldsList (*cloud_filtered) << ")." << std ::endl ; pcl::PCDWriter writer; writer.write ("table_scene_lms400_downsampled.pcd" , *cloud_filtered, Eigen::Vector4f::Zero (), Eigen::Quaternionf::Identity (), false ); return (0 ); }
在每个voxel中保留了重心,可以看出并不是特别均匀。
统计滤波器
#include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/statistical_outlier_removal.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>) ; pcl::PCDReader reader; reader.read<pcl::PointXYZ> ("table_scene_lms400.pcd" , *cloud); std ::cerr << "Cloud before filtering: " << std ::endl ; std ::cerr << *cloud << std ::endl ; pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; sor.setInputCloud (cloud); sor.setMeanK (50 ); sor.setStddevMulThresh (1.0 ); sor.filter (*cloud_filtered); std ::cerr << "Cloud after filtering: " << std ::endl ; std ::cerr << *cloud_filtered << std ::endl ; pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("table_scene_lms400_inliers.pcd" , *cloud_filtered, false ); sor.setNegative (true ); sor.filter (*cloud_filtered); writer.write<pcl::PointXYZ> ("table_scene_lms400_outliers.pcd" , *cloud_filtered, false ); return (0 ); }
条件滤波器
#include <iostream> #include <pcl/point_types.h> #include <pcl/filters/radius_outlier_removal.h> #include <pcl/filters/conditional_removal.h> int main (int argc, char ** argv) { if (argc != 2 ) { std ::cerr << "please specify command line arg '-r' or '-c'" << std ::endl ; exit (0 ); } pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>) ; cloud->width = 5 ; cloud->height = 1 ; 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 ); } if (strcmp (argv[1 ], "-r" ) == 0 ){ pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem; outrem.setInputCloud(cloud); outrem.setRadiusSearch(0.8 ); outrem.setMinNeighborsInRadius (2 ); outrem.setKeepOrganized(true ); outrem.filter (*cloud_filtered); } else if (strcmp (argv[1 ], "-c" ) == 0 ){ pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond (new pcl::ConditionAnd<pcl::PointXYZ> ()); range_cond->addComparison (pcl::FieldComparison<pcl::PointXYZ>::ConstPtr (new pcl::FieldComparison<pcl::PointXYZ> ("z" , pcl::ComparisonOps::GT, 0.0 ))); range_cond->addComparison (pcl::FieldComparison<pcl::PointXYZ>::ConstPtr (new pcl::FieldComparison<pcl::PointXYZ> ("z" , pcl::ComparisonOps::LT, 0.8 ))); pcl::ConditionalRemoval<pcl::PointXYZ> condrem; condrem.setCondition (range_cond); condrem.setInputCloud (cloud); condrem.setKeepOrganized(true ); condrem.filter (*cloud_filtered); } else { std ::cerr << "please specify command line arg '-r' or '-c'" << ::endl ; exit (0 ); } std ::cerr << "Cloud before filtering: " << std ::endl ; for (const auto & point: *cloud) std ::cerr << " " << point.x << " " << point.y << " " << point.z << std ::endl ; std ::cerr << "Cloud after filtering: " << std ::endl ; for (const auto & point: *cloud_filtered) std ::cerr << " " << point.x << " " << point.y << " " << point.z << std ::endl ; return (0 ); }
索引提取
#include <iostream> #include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/filters/voxel_grid.h> #include <pcl/filters/extract_indices.h> int main (int argc, char ** argv) { pcl::PCLPointCloud2::Ptr cloud_blob (new pcl::PCLPointCloud2), cloud_filtered_blob (new pcl::PCLPointCloud2); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>), cloud_p (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); pcl::PCDReader reader; reader.read ("table_scene_lms400.pcd" , *cloud_blob); std ::cerr << "PointCloud before filtering: " << cloud_blob->width * cloud_blob->height << " data points." << std ::endl ; pcl::VoxelGrid<pcl::PCLPointCloud2> sor; sor.setInputCloud (cloud_blob); sor.setLeafSize (0.01f , 0.01f , 0.01f ); sor.filter (*cloud_filtered_blob); pcl::fromPCLPointCloud2 (*cloud_filtered_blob, *cloud_filtered); std ::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points." << std ::endl ; pcl::PCDWriter writer; writer.write<pcl::PointXYZ> ("table_scene_lms400_downsampled.pcd" , *cloud_filtered, false ); pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients ()) ; pcl::PointIndices::Ptr inliers (new pcl::PointIndices ()) ; pcl::SACSegmentation<pcl::PointXYZ> seg; seg.setOptimizeCoefficients (true ); seg.setModelType (pcl::SACMODEL_PLANE); seg.setMethodType (pcl::SAC_RANSAC); seg.setMaxIterations (1000 ); seg.setDistanceThreshold (0.01 ); pcl::ExtractIndices<pcl::PointXYZ> extract; int i = 0 , nr_points = (int ) cloud_filtered->size (); while (cloud_filtered->size () > 0.3 * nr_points) { seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0 ) { std ::cerr << "Could not estimate a planar model for the given dataset." << std ::endl ; break ; } extract.setInputCloud (cloud_filtered); extract.setIndices (inliers); extract.setNegative (false ); extract.filter (*cloud_p); std ::cerr << "PointCloud representing the planar component: " << cloud_p->width * cloud_p->height << " data points." << std ::endl ; std ::stringstream ss; ss << "table_scene_lms400_plane_" << i << ".pcd" ; writer.write<pcl::PointXYZ> (ss.str (), *cloud_p, false ); extract.setNegative (true ); extract.filter (*cloud_f); cloud_filtered.swap (cloud_f); i++; } return (0 ); }
最近邻搜索
空间索引结构
点云中常用的基础结构
作用
搜索(邻域、半径、体素)
将采样(体素网格、体素质心滤波)
点云压缩
空间变化检测
空间点密度分析
占用检查、占用地图
碰撞检测
点云合并
KDTree
#include <pcl/point_cloud.h> #include <pcl/kdtree/kdtree_flann.h> #include <iostream> #include <vector> #include <ctime> int main (int argc, char ** argv) { srand (time (NULL )); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; cloud->width = 1000 ; cloud->height = 1 ; cloud->points.resize (cloud->width * cloud->height); for (std ::size_t i = 0 ; i < cloud->size (); ++i) { (*cloud)[i].x = 1024.0f * rand () / (RAND_MAX + 1.0f ); (*cloud)[i].y = 1024.0f * rand () / (RAND_MAX + 1.0f ); (*cloud)[i].z = 1024.0f * rand () / (RAND_MAX + 1.0f ); } pcl::KdTreeFLANN<pcl::PointXYZ> kdtree; kdtree.setInputCloud (cloud); pcl::PointXYZ searchPoint; searchPoint.x = 1024.0f * rand () / (RAND_MAX + 1.0f ); searchPoint.y = 1024.0f * rand () / (RAND_MAX + 1.0f ); searchPoint.z = 1024.0f * rand () / (RAND_MAX + 1.0f ); int K = 10 ; std ::vector <int > pointIdxNKNSearch (K) ; std ::vector <float > pointNKNSquaredDistance (K) ; std ::cout << "K nearest neighbor search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z<< ") with K=" << K << std ::endl ; if ( kdtree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0 ) { for (std ::size_t i = 0 ; i < pointIdxNKNSearch.size (); ++i) std ::cout << " " << (*cloud)[ pointIdxNKNSearch[i] ].x << " " << (*cloud)[ pointIdxNKNSearch[i] ].y << " " << (*cloud)[ pointIdxNKNSearch[i] ].z << " (squared distance: " << pointNKNSquaredDistance[i] << ")" << std ::endl ; } std ::vector <int > pointIdxRadiusSearch; std ::vector <float > pointRadiusSquaredDistance; float radius = 256.0f * rand () / (RAND_MAX + 1.0f ); std ::cout << "Neighbors within radius search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ") with radius=" << radius << std ::endl ; if ( kdtree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0 ) { for (std ::size_t i = 0 ; i < pointIdxRadiusSearch.size (); ++i) std ::cout << " " << (*cloud)[ pointIdxRadiusSearch[i] ].x << " " << (*cloud)[ pointIdxRadiusSearch[i] ].y << " " << (*cloud)[ pointIdxRadiusSearch[i] ].z << " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std ::endl ; } return 0 ; }
pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal>ne; pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>()) ; ne.setSearchMethod(tree); ne.setRadiusSearch(r); ne.setInputCloud(cloud); pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>) ; ne.compute(*cloud_normals);
OcTree
使用|说明
检查给定坐标体素是否存在|double X=1.,Y=2.,Z=3.;bool occupied=octree.isVoxelOccupiedAtPoint(X,Y,Z);
获取所占用体素的中心点|vector<PointXYZ>pointGrid;octree.getOccupiedVoxelCenters(pointGrid);
查询点所在的体素内的近邻点|vector<int>pointIdxVec;octree.voxelSearch(searchPoint,pointIdxVec);
查询点所在的半径内的近邻点|vector<int>pointIdxVec;vector<float>pointSquaredDistance;float radius=0.1;octree.radiusSearch(searchPoint,radius,pointIdxVec,pointSquaredDistance);扫描所有候选体素octree.approxNearestSearch(searchPoint,radius,pointIdxVec,pointSquaredDistance);只扫描搜索点体素
K紧邻搜索|vector<int>pointIdxVec;vector<float>pointSquaredDistance;int K=10;octree.nearestSearch(searchPoint,K,pointIdxVec,pointSquaredDistance);
光线跟踪|PointCloud<PointXYZ>::Ptr voxelCenters(new PointCloud<PointXYZ>());Eigen::Vector3f origin(0.,0.,0.),direction(0.1,0.2,0.3);octree.getIntersectedVoxelCenters(origin,direction,voxelCenters->points)
密度估计|OctreePointCloudDensity<PintXYZ>
空间变化检测(双缓冲)|octree.setInputCloud(cloudA_ptr);设置输入点云octree.addPointsFromInputCloud();从输入点云构建八叉树octree.switchBuffers();交换八叉树缓存,但cloudA对应的八叉树结构仍在内存中octree.setInputCloud(cloudB_ptr);添加Boctree.addPointsFromInputCloud();vector<int>newPointIdxVector;octree.getPointIndicesFromNewVoxels(newPointIdxVector);获取A的八叉树,在B对应的八叉树中A内没有的点集
删除体素|PointXYZ point_arg(1.,2.,3.);octree.deleteVoxelAtPoint(point);
迭代器|OctreePointCloud<PointXYZ>::Iterator it(octreeA);迭代所有octree节点OctreePointCloud<PointXYZ>::LeafNodeIterator itL(octreeA);迭代所有octree叶节点
示例
#include <pcl/point_cloud.h> #include <pcl/octree/octree_search.h> #include <iostream> #include <vector> #include <ctime> int main (int argc, char ** argv) { srand ((unsigned int ) time (NULL )); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; cloud->width = 1000 ; cloud->height = 1 ; cloud->points.resize (cloud->width * cloud->height); for (std ::size_t i = 0 ; i < cloud->size (); ++i){ (*cloud)[i].x = 1024.0f * rand () / (RAND_MAX + 1.0f ); (*cloud)[i].y = 1024.0f * rand () / (RAND_MAX + 1.0f ); (*cloud)[i].z = 1024.0f * rand () / (RAND_MAX + 1.0f ); } float resolution = 128.0f ; pcl::octree::OctreePointCloudSearch<pcl::PointXYZ> octree (resolution) ; octree.setInputCloud (cloud); octree.addPointsFromInputCloud (); pcl::PointXYZ searchPoint; searchPoint.x = 1024.0f * rand () / (RAND_MAX + 1.0f ); searchPoint.y = 1024.0f * rand () / (RAND_MAX + 1.0f ); searchPoint.z = 1024.0f * rand () / (RAND_MAX + 1.0f ); std ::vector <int > pointIdxVec; if (octree.voxelSearch (searchPoint, pointIdxVec)){ std ::cout << "Neighbors within voxel search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ")" << std ::endl ; for (std ::size_t i = 0 ; i < pointIdxVec.size (); ++i) std ::cout << " " << (*cloud)[pointIdxVec[i]].x << " " << (*cloud)[pointIdxVec[i]].y << " " << (*cloud)[pointIdxVec[i]].z << std ::endl ; } int K = 10 ; std ::vector <int > pointIdxNKNSearch; std ::vector <float > pointNKNSquaredDistance; std ::cout << "K nearest neighbor search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z<< ") with K=" << K << std ::endl ; if (octree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0 ) { for (std ::size_t i = 0 ; i < pointIdxNKNSearch.size (); ++i) std ::cout << " " << (*cloud)[ pointIdxNKNSearch[i] ].x << " " << (*cloud)[ pointIdxNKNSearch[i] ].y << " " << (*cloud)[ pointIdxNKNSearch[i] ].z << " (squared distance: " << pointNKNSquaredDistance[i] << ")" << std ::endl ; } std ::vector <int > pointIdxRadiusSearch; std ::vector <float > pointRadiusSquaredDistance; float radius = 256.0f * rand () / (RAND_MAX + 1.0f ); std ::cout << "Neighbors within radius search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z<< ") with radius=" << radius << std ::endl ; if (octree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0 ) { for (std ::size_t i = 0 ; i < pointIdxRadiusSearch.size (); ++i) std ::cout << " " << (*cloud)[ pointIdxRadiusSearch[i] ].x << " " << (*cloud)[ pointIdxRadiusSearch[i] ].y << " " << (*cloud)[ pointIdxRadiusSearch[i] ].z << " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std ::endl ; } }
分割
通过数据拟合获取平面
采样一致性算法
说明
随机采样一致性算法
RANSAC
加权采样一致性算法
MSAC
最大似然一致性算法
MLESAC
最小中值方差一致性算法
LMEDS
渐进采样一致性算法
PROSAC
pcl::SampleConsensusModel<PointT>; pcl::SampleConsensusModelPlane<PointT>; pcl::SampleConsensusModelLine<PointT>; pcl::SampleConsensusModelCircle3D<PointT>; pcl::SampleConsensusModelCircle2D<PointT>; pcl::SampleConsensusModelSphere<PointT>; pcl::SampleConsensusModelCone<PointT,PointT>; pcl::SampleConsensusModelCylinder<PointT,PointT>; pcl::SampleConsensusModelRegistration<PointT>; pcl::SampleConsensusModelStick<PointT>;
拟合模型
说明
平面模型
SACMODEL_PLANE[normal_x,normal_y,normal_z,d]
线模型
SACMODEL_LINE[point_on_line.x,point_on_line.y,point_on_line.z,point_direction.x,point_direction.y,point_direction.z]
平面圆模型
SACMODEL_CIRCLE2D[center.x,center.y,center.z,radius]
三维圆模型
SACMODEL_CIRCLE3D[center.x,center.y,center.z,radius,normal.x,normal.y,normal.z]
球模型
SACMODEL_SPHERE
圆柱体模型
SACMODEL_CYLINDER[point_on_axis.x,point_on_axis.y,point_on_axis.z,axis_direction.x,axis_direction.y,axis_direction.z,radius]
圆锥体模型
SACMODEL_CONE[apex.x,apex.y,apex.z,aixis_direction.x,aixis_direction.y,aixis_direction.z,opening_angle]
Plane model
#include <iostream> #include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; cloud->width = 15 ; cloud->height = 1 ; cloud->points.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 = 1.0 ; } (*cloud)[0 ].z = 2.0 ;(*cloud)[3 ].z = -2.0 ;(*cloud)[6 ].z = 4.0 ; std ::cerr << "Point cloud data: " << cloud->size () << " points" << std ::endl ; for (const auto & point: *cloud) std ::cerr << " " << point.x << " " << point.y << " " << point.z << std ::endl ; pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients) ; pcl::PointIndices::Ptr inliers (new pcl::PointIndices) ; 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); if (inliers->indices.size () == 0 ) { PCL_ERROR ("Could not estimate a planar model for the given dataset." ); return (-1 ); } std ::cerr << "Model coefficients: " << coefficients->values[0 ] << " " << coefficients->values[1 ] << " " << coefficients->values[2 ] << " " << coefficients->values[3 ] << std ::endl ; std ::cerr << "Model inliers: " << inliers->indices.size () << std ::endl ; for (std ::size_t i = 0 ; i < inliers->indices.size (); ++i) for (const auto & idx: inliers->indices) std ::cerr << idx << " " << cloud->points[idx].x << " " << cloud->points[idx].y << " " << cloud->points[idx].z << std ::endl ; return (0 ); }
聚类
根据平面模型分割出物体表面上的物体
聚类找出例如桌面上的东西
计算平面的凸包,分割出物体
分割拟合聚类
ExtractPolygonalPrismData
include <pcl/ModelCoefficients.h> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/filters/passthrough.h> #include <pcl/filters/project_inliers.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/surface/convex_hull.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>), cloud_projected (new pcl::PointCloud<pcl::PointXYZ>); pcl::PCDReader reader; reader.read ("table_scene_mug_stereo_textured.pcd" , *cloud); pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud (cloud); pass.setFilterFieldName ("z" ); pass.setFilterLimits (0 , 1.1 ); pass.filter (*cloud_filtered); std ::cerr << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std ::endl ; pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients) ; pcl::PointIndices::Ptr inliers (new pcl::PointIndices) ; 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_filtered); seg.segment (*inliers, *coefficients); pcl::ProjectInliers<pcl::PointXYZ> proj; proj.setModelType (pcl::SACMODEL_PLANE); proj.setInputCloud (cloud_filtered); proj.setIndices (inliers); proj.setModelCoefficients (coefficients); proj.filter (*cloud_projected); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_hull (new pcl::PointCloud<pcl::PointXYZ>) ; pcl::ConvexHull<pcl::PointXYZ> chull; chull.setInputCloud (cloud_projected); chull.reconstruct (*cloud_hull); std ::cerr << "Convex hull has: " << cloud_hull->size () << " data points." << std ::endl ; pcl::PCDWriter writer; writer.write ("table_scene_mug_stereo_textured_hull.pcd" , *cloud_hull, false ); return (0 ); }
欧几里德聚类算法
#include <pcl/ModelCoefficients.h> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/filters/extract_indices.h> #include <pcl/filters/voxel_grid.h> #include <pcl/features/normal_3d.h> #include <pcl/kdtree/kdtree.h> #include <pcl/sample_consensus/method_types.h> #include <pcl/sample_consensus/model_types.h> #include <pcl/segmentation/sac_segmentation.h> #include <pcl/segmentation/extract_clusters.h> int main (int argc, char ** argv) { pcl::PCDReader reader; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>), cloud_f (new pcl::PointCloud<pcl::PointXYZ>); reader.read ("table_scene_lms400.pcd" , *cloud); std ::cout << "PointCloud before filtering has: " << cloud->size () << " data points." << std ::endl ; pcl::VoxelGrid<pcl::PointXYZ> vg; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>) ; vg.setInputCloud (cloud); vg.setLeafSize (0.01f , 0.01f , 0.01f ); vg.filter (*cloud_filtered); std ::cout << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std ::endl ; pcl::SACSegmentation<pcl::PointXYZ> seg; pcl::PointIndices::Ptr inliers (new pcl::PointIndices) ; pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients) ; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane (new pcl::PointCloud<pcl::PointXYZ> ()) ; pcl::PCDWriter writer; seg.setOptimizeCoefficients (true ); seg.setModelType (pcl::SACMODEL_PLANE); seg.setMethodType (pcl::SAC_RANSAC); seg.setMaxIterations (100 ); seg.setDistanceThreshold (0.02 ); int i=0 , nr_points = (int ) cloud_filtered->size (); while (cloud_filtered->size () > 0.3 * nr_points){ seg.setInputCloud (cloud_filtered); seg.segment (*inliers, *coefficients); if (inliers->indices.size () == 0 ){ std ::cout << "Could not estimate a planar model for the given dataset." << std ::endl ; break ; } pcl::ExtractIndices<pcl::PointXYZ> extract; extract.setInputCloud (cloud_filtered); extract.setIndices (inliers); extract.setNegative (false ); extract.filter (*cloud_plane); std ::cout << "PointCloud representing the planar component: " << cloud_plane->size () << " data points." << std ::endl ; extract.setNegative (true ); extract.filter (*cloud_f); *cloud_filtered = *cloud_f; } pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>) ; tree->setInputCloud (cloud_filtered); 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_filtered); ec.extract (cluster_indices); int j = 0 ; for (std ::vector <pcl::PointIndices>::const_iterator it = cluster_indices.begin (); it != cluster_indices.end (); ++it){ pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster (new pcl::PointCloud<pcl::PointXYZ>) ; for (std ::vector <int >::const_iterator pit = it->indices.begin (); pit != it->indices.end (); ++pit) cloud_cluster->push_back ((*cloud_filtered)[*pit]); cloud_cluster->width = cloud_cluster->size (); cloud_cluster->height = 1 ; cloud_cluster->is_dense = true ; std ::cout << "PointCloud representing the Cluster: " << cloud_cluster->size () << " data points." << std ::endl ; std ::stringstream ss; ss << "cloud_cluster_" << j << ".pcd" ; writer.write<pcl::PointXYZ> (ss.str (), *cloud_cluster, false ); j++; } return (0 ); }
区域分割
#include <iostream> #include <vector> #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/search/search.h> #include <pcl/search/kdtree.h> #include <pcl/features/normal_3d.h> #include <pcl/visualization/cloud_viewer.h> #include <pcl/filters/passthrough.h> #include <pcl/segmentation/region_growing.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>) ; if ( pcl::io::loadPCDFile <pcl::PointXYZ> ("region_growing_tutorial.pcd" , *cloud) == -1 ){ std ::cout << "Cloud reading failed." << std ::endl ; return (-1 ); } pcl::search::Search<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ>) ; pcl::PointCloud <pcl::Normal>::Ptr normals (new pcl::PointCloud <pcl::Normal>) ; pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_estimator; normal_estimator.setSearchMethod (tree); normal_estimator.setInputCloud (cloud); normal_estimator.setKSearch (50 ); normal_estimator.compute (*normals); pcl::IndicesPtr indices (new std ::vector <int >) ; pcl::PassThrough<pcl::PointXYZ> pass; pass.setInputCloud (cloud); pass.setFilterFieldName ("z" ); pass.setFilterLimits (0.0 , 1.0 ); pass.filter (*indices); pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg; reg.setMinClusterSize (50 ); reg.setMaxClusterSize (1000000 ); reg.setSearchMethod (tree); reg.setNumberOfNeighbours (30 ); reg.setInputCloud (cloud); reg.setInputNormals (normals); reg.setSmoothnessThreshold (3.0 / 180.0 * M_PI); reg.setCurvatureThreshold (1.0 ); std ::vector <pcl::PointIndices> clusters; reg.extract (clusters); std ::cout << "Number of clusters is equal to " << clusters.size () << std ::endl ; std ::cout << "First cluster has " << clusters[0 ].indices.size () << " points." << std ::endl ; std ::cout << "These are the indices of the points of the initial" << std ::endl << "cloud that belong to the first cluster:" << std ::endl ; int counter = 0 ; while (counter < clusters[0 ].indices.size ()){ std ::cout << clusters[0 ].indices[counter] << ", " ; counter++; if (counter % 10 == 0 ) std ::cout << std ::endl ; } std ::cout << std ::endl ; pcl::PointCloud <pcl::PointXYZRGB>::Ptr colored_cloud = reg.getColoredCloud (); pcl::visualization::CloudViewer viewer ("Cluster viewer" ) ; viewer.showCloud(colored_cloud); while (!viewer.wasStopped ()) {} return (0 ); }
配准
ICP
算法步骤:
(1)对点集A中每个点Pa施加出事变换T0,得到Pa’;
(2)从点集B中寻找距离点Pa’最近的点Pb,形成对应点对;
(3)求解最优变换△T;
(4)根据前后两次的迭代误差以及迭代次数等条件判断是否收敛,如果收敛,则输出最终结果:T=△T*T0,否则T0=△T*T0,重复步骤(1)
△ T = arg min ∑ i = 1 n ∥ p b i − ( R p a i + t ) ∥ 2 2 \triangle T=\argmin\sum_{i=1}{n}\|p_{bi}-(Rp_{ai}+t)\|^2_2 △ T = a r g m i n ∑ i = 1 n ∥ p b i − ( R p a i + t ) ∥ 2 2
△ T = [ R t 0 1 ] \triangle T=\begin{bmatrix}R&t\\0&1\end{bmatrix} △ T = [ R 0 t 1 ]
终止条件:
(1)最大迭代次数
(2)收敛标准:估计点云变换不再变化:当前变换和上次变换的差值之和小于用户定义的阈值
(3)找到解:误差平方和小于用户自定义阈值
简单使用
pcl::IterativeClosestPoint<pcl::PointXYZ,pcl::PointXYZ> icp; icp.setInputCloud(cloud_in); icp.setInputTarget(cloud_out); icp.setMaximumIterations(nr_iterations); icp.setTransformationEpsilon(epsilon); icp.setEuclideanFitnessEpsilon(distance); pcl::PointCloud<pcl::PointXYZ>Final; icp.align(Final);
接口
常用接口
说明
实例化
pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
最大迭代次数
icp.setMaximumIterations(100);
源点云,转换的点云
icp.setInputSource(cloud_in);
目标点云,参考点云
icp.setInputTarget(cloud_out);
设置查找近邻时是否双向搜索
icp.setUseReciprocalCorrespondences(false);
设置最大临近点距离,超过则不参与计算
icp.setMaxCorrespondenceDistance(0.5);
设置匹配对剔除器
icp.addCorrespondenceRejector(rejector);
设置迭代误差收敛阈值
icp.setEuclideanFitnessEpsilon(0.0001);
设置icp计算方法:SVD、LM
icp.setTransformationEstimation(te);
执行运算
icp.align(*cloud_icp);
获取icp是否收敛
icp.hasConverged();
获取icp匹配得到的变换矩阵
Eigen::Matrix4f transformation_matrix = icp.getFinalTransformation();
获取icp得分
icp.getFitnessScore();
点云配准方法
Eigen::Matrix4f transformation; TransformationEstimationSVD<PointT,PointT>svd; svd.estimateRigidTransformation(src,target,corres,trans);
广义配准
ICP使用最近邻迭代搜索,容易调入局部极小,并且初始变换矩阵影响算法结果。广义配准利用点云特征匹配,可以很好的估计两组点云的变换状态,能够得到良好的效果。
算法步骤:
(1)采集原始点云,分析两个原点云关键点k;
(2)在每个关键点k处,计算相应的特征描述子f
(3)根据特征描述子f和对应的xyz坐标值,估计对应匹配关系;
(4)因为噪声或虚假匹配,并非所有的对应关系都有效,根据规则排除虚假对应匹配;
(5)根据有效对应匹配关系,估计刚体变换关系;
Matching Features
CorrespondenceEstimation<FeatureT,FeatureT>est; est.setInputClout(source_features); est.setInputTarget(target_features); est.determineCorrespondences(correspondences);
虚假匹配
用sac排除虚假匹配(outliers)
(1)选择三对对应匹配对d,m;
(2)估计选择样本的变换关系(R,t);
(3)用((Rd+t)-m)2<c确定内点对;
(4)重复N次,使得(R,t)有更多的内点;
CorrespondenceRejectorSampleConsensus<PointT> sac; sac.setInputCloud(source); sac.setTargetCloud(target); sac.setInlierThreshold(epsilon); sac.setMaxIterations(N); sac.setInputCorrespondences(correspondences); sac.getCorrespondences(inliers); Eigen::Matrix4f t_matrix = sac.getBestTransformation();
sac-ia(Sampled Consesus-Initial Alignment)
(1)在原点云中选择n个点d;
(2)对每个点d:1、选择k个最近匹配;2、选择其中一个为匹配点m;
(3)估计选择样本的变换关系(R,t);
(4)用((Rd+t)-m)2<c确定内点对;
(5)重复N次,使得(R,t)有更多的内点;
pcl::SampleConsensusInitalAlignment<PointT,PointT,FeatureT> sac_ia; sac_ia.setNumberOfSamples(n); sac_ia.setMinSampleDistance(d); sac_ia.setCorrespondenceRandomness(k); sac_ia.setMaximumIterations(N); sac_ia.setInputCloud(source); sac_ia.setInputTarget(target); sac_ia.setSourceFeatures(source_features); sac_ia.setTargetFeatures(target_features); sac_ia.align(aligned_source); Eigen::Matrix4f t_matrix = sac_ia.getFinalTransformation();
接口
方法
说明
广义配准实例化
CorrespondenceEstimation<FeatureT,FeatureT>est;
输入点云
est.setInputSource(source_feature);
目标点云
est.setInputTarget(target_feature);
执行运算
est.determineCorrespondences(correspondences);
RANSAC去除虚假匹配
CorrespondenceRejectorSampleConsensus<PointT>sac;
输入点云
sac.setInputSource(source);
目标点云
sac.setInputTarget(target);
inlier阈值
sac.InlierThreshold(epsilon);
最大迭代次数
sac.setMaximumIterations(100);
执行运算
sac.setInputCorrespondences(correspondences);
执行运算
sac.getInputCorrespondences(inliers);
获取匹配的变换矩阵
Eigen::Matrix4f transformation_matrix = sac.getBestTransformation();
SAC-IA初始对齐
SampleConsensusInitialAlignment<PointT,PointT,FeatureT>sac_ia;
输入点云
sac_ia.setInputCloud(source);
目标点云
sac_ia.setInputTarget(target);
特征点个数
sac_ia.setNumberOfSamples(n);
最小距离
sac_ia.setMinSampleDistance(d);
随机参数
sac_ia.setCorrespondenceRandomness(k);
最大迭代次数
sac_ia.setMaximumIterations(100);
输入特征
sac_ia.setSourceFeatures(source_features);
目标特征
sac_ia.setTargetFeatures(target_features);
获取匹配的变换矩阵
Eigen::Matrix4f transformation_matrix = sac_ia.getFinalTransformation();
配准API
类
说明
CorrespondenceEstimation
确定两点云中对应匹配关系
CorrespondenceRejectorFeatures
能够实现基于特征匹配的对应点对提取
SampleConsensusPrerejective
实现了基于RANSAC方法的对应对外点去除,用于姿态评估和点云配准中的几何一致性限制
TransformationEstimation3Point
实现使用三个匹配点计算两点云变换矩阵
TransfromationFromCorrsespondences
实现三个及以上匹配点间变换矩阵的计算
TransfromationEstimationDQTransfromationEstimationDualQuaternion
实现基于对偶四元数的点云姿态估计
TransfromationEstimationLM
实现基于对应匹配点对,用LM优化的变换矩阵估计
TransfromationEstimationPointToPlane
实现基于LM优化的匹配点对间最小点到平面距离的最优变换矩阵计算
TransfromationEstimationPointtoPlaneWeighted
在TransfromationEstimationPointToPlane基础上加入了对应匹配点的权重,以适应更为复杂场景点云配准任务
TransfromationEstimationPointToPlaneLLS
基于现行最小二乘的点到平面距离累计最小化约束,对应点到平面的距离计算加入法向量约束
TransfromationEstimationPointToPlaneLLSWeighted
带权重的PointToPlaneLLS
TransfromationEstimationSVD
使用SVD分解策略完成匹配点对的变换矩阵计算
TransfromationEstimationEuclidean
实现了给定对应匹配的变换,并计算可靠性得分
IterativeClosestPoint
ICP算法
IterativeClosestPointNoLinear
ICP后端使用LM作为优化
GeneratlizedIterativeClosestPoint
一般化ICP算法
GeneratlizedIterativeClosestPoint6D
集成La b颜色空间到Generalized-ICP中
IterativeClosestPointWithNormal
以点到平面的最小距离作为迭代优化对象实现ICP配准
FPCSInitialAlignment
KFPCSInitialAlignment
NormalDistributionsTransform
NDT方法
关键点
ISS
ISS
说明
实例化
pcl::ISSKeypoint3d<pcl::PointXYZ,pcl::PointXYZ>iss_detector;
-
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>)iss_detector.setSearchMethod(tree)
-
iss_detector.setSalientRadius(support_radius)
-
iss_detector.setNonMaxRadius(nms_radius);
-
iss_detector.setThreshold21(0.975);
-
iss_detector.setThreshold32(0.975);
-
iss_detector.setMinNeighbors(5);
-
iss_detector.setNumberOfThreads(4);
-
iss_detector.setInputCloud(cloud);
-
iss_detector.compute(keyPoints);
Harris3D
Harris3D
说明
实例化
pcl::HarrisKeypoint3d<pcl::PointXYZ,pcl::PointXYZI,pcl::Normal>harris;输出保存在I分量
-
harris.setInputCloud(point_cloud_ptr);
-
harris.setNonMaxSupression(true);
块半径
harris.setRadius(0.02f);
数量阈值
harris.setThreshold(0.01f);
-
harris.compute(cloud_out);
SIFT
SIFT
说明
实例化
pcl::SIFTKeypoint<pcl::PointXYZ,pcl::PointWithScale>sift_src;
-
sift_src.setSearchMethod(tree);
-
sift_src.setScales(min_scale,n_octaves,n_scales_per_octave);min_scale:尺度空间中最小尺度,n_octaves:尺度空间层数,n_scales_per_octave:尺度空间层中计算的尺度个数
-
sift_src.setMinimumContrast(min_contrast);min_contrast:根据点云设置,越小关键点越多
-
sift_src.setInputCloud(cloud_src);
-
sift_src.compute(result);
描述子
pcl::features类
局部描述子
Spin Image
3DSC
PFH/FPFH
SHOT/SHOT for RGBD
全局描述子
基于直方图
Shape Distributions
3D Shape Histograms
Orientation Histograms
Viewpoint Feature Histogram
Clustered VFH
OUR-CVFH
基于变换
3D Fourier Transform
Angular Radial Tr.
3D Radon Tr
Spherical Harmonics
wavelets
基于2D
Fourier Descriptors
Zernike Moments
SIFT
SURF
基于连通图
topology-based
Reeb graph
skeleton-based
方法
方法|说明
Spin Image|pcl::SpinImageEstimation
3D Unique shape Contexts|pcl::ShapeContext3DEstimation
PFH/FPFH|pcl::PFHEstimationpcl::FPFHEstimation
SHOT/SHOT for RGBD|pcl::SHOTEstimationpcl::SHOTColorEstimation
示例
#include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/registration/icp.h> int main (int argc, char ** argv) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in (new pcl::PointCloud<pcl::PointXYZ>(5 ,1 )) ; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out (new pcl::PointCloud<pcl::PointXYZ>) ; for (auto & point : *cloud_in) { point.x = 1024 * rand() / (RAND_MAX + 1.0f ); point.y = 1024 * rand() / (RAND_MAX + 1.0f ); point.z = 1024 * rand() / (RAND_MAX + 1.0f ); } std ::cout << "Saved " << cloud_in->size () << " data points to input:" << std ::endl ; for (auto & point : *cloud_in) std ::cout << point << std ::endl ; *cloud_out = *cloud_in; std ::cout << "size:" << cloud_out->size() << std ::endl ; for (auto & point : *cloud_out) point.x += 0.7f ; std ::cout << "Transformed " << cloud_in->size () << " data points:" << std ::endl ; for (auto & point : *cloud_out) std ::cout << point << std ::endl ; pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp; icp.setInputSource(cloud_in); icp.setInputTarget(cloud_out); pcl::PointCloud<pcl::PointXYZ> Final; icp.align(Final); std ::cout << "has converged:" << icp.hasConverged() << " score: " << icp.getFitnessScore() << std ::endl ; std ::cout << icp.getFinalTransformation() << std ::endl ; return (0 ); }