前言:最近在做点云的工作,通过资料及其他网页,总结一些比较常用且实用的操作,留给自己查看,同时也希望能给别人带来方便。

 

1. 两片点云cloudA、cloudB,若在cloudB中找到cloudA的数据点,则从cloudB中删除该点。

#include <pcl/point_cloud.h>        //点类型定义头文件
#include <pcl/kdtree/kdtree_flann.h> //kdtree类定义头文件

#include <iostream>
#include <vector>
#include <ctime>
const double eps = 1.0e-6;

int
main(int argc, char** argv)
{
    //srand(time(NULL));   //用系统时间初始化随机种子
    //创建一个PointCloud<pcl::PointXYZ>
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloudA(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloudB(new pcl::PointCloud<pcl::PointXYZ>);
    // 随机点云生成
    cloudA->width = 20;             //此处点云数量
    cloudA->height = 1;                //表示点云为无序点云
    cloudA->points.resize(cloudA->width * cloudA->height);

    for (size_t i = 0; i < cloudA->points.size(); ++i)   //循环填充点云数据
    {
        cloudA->points[i].x = i + 1;
        cloudA->points[i].y = i + 1;
        cloudA->points[i].z = i + 1;
    }

    cloudB->width = 20;             //此处点云数量
    cloudB->height = 1;                //表示点云为无序点云
    cloudB->points.resize(cloudB->width * cloudB->height);
    for (size_t i = 0; i < cloudB->points.size(); ++i)   //循环填充点云数据
    {
        cloudB->points[i].x = i + 8;
        cloudB->points[i].y = i + 8;
        cloudB->points[i].z = i + 8;
    }
    //cloudBackup->points = cloud->points;
    //创建KdTreeFLANN对象,并把创建的点云设置为输入,创建一个searchPoint变量作为查询点
    pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;

    pcl::PointXYZ searchPoint;
    int K = 1;
    std::vector<int> pointIdxNKNSearch(K);      //存储查询点近邻索引
    std::vector<float> pointNKNSquaredDistance(K); //存储近邻点对应距离平方
    std::vector<pcl::PointXYZ> DeleteData;
    int num = 0;
    for (auto iterA = cloudA->begin(); iterA != cloudA->end(); iterA++)
    {
        searchPoint.x = iterA->x;
        searchPoint.y = iterA->y;
        searchPoint.z = iterA->z;
        kdtree.setInputCloud(cloudB);    //在cloudB中找到对应点后,在cloudB中直接删除该点
        num = kdtree.nearestKSearch(searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance);
        if (num > 0)
        {
            if (sqrt(pointNKNSquaredDistance[0])<eps)
            {
                auto iterB = cloudB->begin() + pointIdxNKNSearch[0];
                cloudB->erase(iterB);
                DeleteData.push_back(searchPoint);
                if (cloudB->size()==0)
                {
                    break;
                }
                searchPoint.x = 0;
                searchPoint.y = 0;
                searchPoint.z = 0;
                num = 0;
                pointIdxNKNSearch.clear();
                pointNKNSquaredDistance.clear();
            }
        }
        
    }
    
    for (auto iter = DeleteData.begin(); iter != DeleteData.end(); iter++)
    {
        std::cout << iter->x << " " << iter->y << " " << iter->z << std::endl;
    }

    system("pause");
    return 0;
}

2.几种常用的操作

(1)保存点云数据

//普通格式ASSIC(占用内存较大)
pcl::PCDWriter writer;
writer.write(s_pcd,laserCloudIn);

//bin格式(占用内存较小)
pcl::io::savePCDFileBinary(s_pcd, *cloud);

(2)查找点云在X、Y、Z轴的极值

pcl::PointXYZ minPt, maxPt;
pcl::getMinMax3D (*cloud, minPt, maxPt);

(3)知道要保存点的索引,从原点云中拷贝点到新点云

pcl::PointCloud::Ptr cloudOut(new pcl::PointCloud);
std::vector indexs = { 1, 2, 5 };
pcl::copyPointCloud(*cloud, indexs, *cloudOut);

(4)从点云里删除和添加点

pcl::PointCloud::iterator index = cloud->begin();
cloud->erase(index);//删除第一个
index = cloud->begin() + 5;
cloud->erase(index);//删除第5个
pcl::PointXYZ point = { 1, 1, 1 };
//在索引号为5的位置上插入一点,之后的所有点后移一位
cloud->insert(cloud->begin() + 5, point);
cloud->push_back(point);//从点云最后面插入一点

(5)对点云进行全局或局部变换

//全部
pcl::transformPointCloud(*cloud,*transform_cloud1,transform_1);
        
//局部
//第一个参数为输入,第二个参数为输入点云中部分点集索引,第三个为存储对象,第四个是变换矩阵。
pcl::transformPointCloud(*cloud,pcl::PointIndices indices,*transform_cloud1,matrix);

(6)链接两个点云字段(两点云大小必须相同)

pcl::PointCloud::Ptr cloud_with_nomal (new pcl::PointCloud);
pcl::concatenateFields(*cloud,*cloud_normals,*cloud_with_nomal);

(7)从点云中删除无效点

vector indices;
pcl::removeNaNFromPointCloud(*cloud,*output,indices);

(8)计算质心

Eigen::Vector4f centroid;//质心
pcl::compute3DCentroid(*cloud_smoothed,centroid); //估计质心的坐标

(9)计算点云的法向量

pcl::NormalEstimation ne;
pcl::PointCloud::Ptr pcNormal(new pcl::PointCloud);  
pcl::search::KdTree::Ptr tree(new pcl::search::KdTree);
tree->setInputCloud(inCloud);  
ne.setInputCloud(inCloud);  
ne.setSearchMethod(tree);  
ne.setKSearch(50);  
//ne->setRadiusSearch (0.03);   
ne.compute(*pcNormal);

(10)提取点云的边界

pcl::PointCloud boundaries; //保存边界估计结果pcl::BoundaryEstimation boundEst; //定义一个进行边界特征估计的对象
pcl::NormalEstimation normEst; //定义一个法线估计的对象
pcl::PointCloud::Ptr normals(new pcl::PointCloud); //保存法线估计的结果
pcl::PointCloud::Ptr cloud_boundary (new pcl::PointCloud); 
normEst.setInputCloud(pcl::PointCloud::Ptr(cloud)); 
normEst.setRadiusSearch(reforn); //设置法线估计的半径
normEst.compute(*normals); //将法线估计结果保存至normals

 
boundEst.setInputCloud(cloud); //设置输入的点云
boundEst.setInputNormals(normals); //设置边界估计的法线,因为边界估计依赖于法线
boundEst.setRadiusSearch(re); //设置边界估计所需要的半径
boundEst.setAngleThreshold(M_PI/4); //边界估计时的角度阈值
boundEst.setSearchMethod(pcl::search::KdTree::Ptr (new pcl::search::KdTree)); //设置搜索方式KdTree
boundEst.compute(boundaries); //将边界估计结果保存在boundaries

(11)k近邻和半径搜索

pcl::KdTreeFLANNkdtree; //创建kdtree搜索对象
kdtree.setInputCloud(cloud);           //载入点云
pcl::PointXYZ searchPoint;              //设置查询点并赋随机值
//K近邻搜索
int K = 10;                             //搜索最近邻的点数
vectorpointIdxNKNSearch(K);        //存放最近邻点的索引
vectorpointNKNSquaredDistance(K);//对应的距离平方
kdtree.nearestKSearch(searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance)
//半径内搜索
std::vector pointIdxRadiusSearch;
std::vector pointRadiusSquaredDistance;
float radius; //定义搜索半径
kdtree.radiusSearch(searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance);

(12)添加新的点云类

//有自定义点云类型时要加入该头文件才能使用PCL库模板函数
#include   
//运用相应的函数需加入以下相应头文件
//如:直通滤波器
#include 

//定义一个新的点云类
struct PointXYZIR
{
  PCL_ADD_POINT4D
  float intensity;
  uint16_t ring;                   ///< laser ring number
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW  //确保new操作符对齐
} EIGEN_ALIGN16; //强制SSE对齐

POINT_CLOUD_REGISTER_POINT_STRUCT(PointXYZIR, (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)(uint16_t, ring, ring))

3. 几种点云常用的滤波器

(1)直通滤波器

// 创建滤波器对象
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");
pass.setFilterLimits (0.0, 1.0);
//pass.setFilterLimitsNegative (true);
pass.filter (*cloud_filtered);

(2)VoxelGrid滤波器

创建三维体素栅格,将体素栅格内所有点的重心代替体素中其他点,实现下采样。

// 创建滤波器对象
pcl::VoxelGrid<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setLeafSize(0.01f, 0.01f, 0.01f);
sor.filter(*cloud_filtered);  

(3)StatisticalOutlireRemoval滤波器

对每一个点的近邻进行一个统计分析,计算点到近邻点的距离,计算所有近邻点的平均距离,平均距离在标准范围之外的点被视为离群点

// 创建滤波器对象
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud (cloud);
sor.setMeanK (50);
sor.setStddevMulThresh (1.0);
sor.filter (*cloud_filtered);

(4)使用参数化模型投影点云

基于对模型的结构和尺寸等信息,选择特殊模型,如:球等,设置参数进行滤波。

首先,填充ModelCoefficients的值,该例中使用X-Y平面

//创建一个系数为(0,0,1,0)的平面
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
coefficients->values.resize(4);
coefficients->values[0] = coefficients->values[1] = 0;
coefficients->values[2] = 1.0;
coefficients->values[3] = 0;

然后,创建ProjectInliers对象,使用ModelCoefficients作为投影对象的模型参数

//创建滤波器对象
pcl::ProjectInliers<pcl::PointXYZ>proj;
proj.setModelType(pcl::SACMODEL_PLANE);
proj.setInputCloud(cloud);
proj.setModelCoefficients(coefficients);
proj.filter(*cloud_projected);

(5)ExtractIndices滤波器

基于某一分割算法提取点云的一个子集

首先,使用VoxelGrid滤波器对数据下采样,加快处理速度

// 创建滤波器对象:使用叶大小为1cm的下采样
pcl::VoxelGrid<pcl::PCLPointCloud2> sor;
sor.setInputCloud(cloud_blob);
sor.setLeafSize(0.01f, 0.01f, 0.01f);
sor.filter(*cloud_filtered_blob);

接着,参数化分割

然后,设置extraction filter实际参数

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->points.size();
// 当还有30%原始点云数据时
while (cloud_filtered->points.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++;
}

 需要将sensor_msgs::PointCloud2::Ptr改为pcl::PCLPointCloud2::Ptr;pcl::fromROSMsg()改为pcl::fromPCLPointCloud2();

 (6)ConditionalRemoval或RadiusOutlierRemoval类

ConditionalRemoval滤波器可以删除设定的一个或多个条件指标的所有数据点;

首先,创建条件限定对象,为限定对象添加比较算子,创建滤波器并用条件定义对象初始化,

// 创建环境
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(range_cond);
condrem.setInputCloud(cloud);
condrem.setKeepOrganized(true);
// 应用滤波
condrem.filter(*cloud_filtered);

然后,RadiusOutlinerRemoval滤波器删除搜索半径内不满足设定的近邻点数,可用于移除离群点。

pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem;
// 创建滤波器
outrem.setInputCloud(cloud);
outrem.setRadiusSearch(0.8);  // 设置搜索半径
outrem.setMinNeighborsInRadius(2);   // 设置查询点的近邻点数
// 应用滤波器
outrem.filter(*cloud_filtered);

(7)CropHull滤波器

获取2D封闭多边形内部或者外部的点云

首先,设置2D封闭多边形顶点;

然后,使用ConvexHull类构造2D凸包;

最后,创建Crophull对象,滤波获取凸包范围内的点云。

pcl::PointCloud<pcl::PointXYZ>::Ptr boundingbox_ptr(new pcl::PointCloud<pcl::PointXYZ>);
boundingbox_ptr->push_back(pcl::PointXYZ(0.1, 0.1, 0));
boundingbox_ptr->push_back(pcl::PointXYZ(0.1, -0.1, 0));
boundingbox_ptr->push_back(pcl::PointXYZ(-0.1, 0.1, 0));
boundingbox_ptr->push_back(pcl::PointXYZ(-0.1, -0.1, 0));
boundingbox_ptr->push_back(pcl::PointXYZ(0.15, 0.1, 0));

pcl::ConvexHull<pcl::PointXYZ> hull;
hull.setInputCloud(boundingbox_ptr);
hull.setDimension(2);
std::vector<pcl::Vertices> polygons;
pcl::PointCloud<pcl::PointXYZ>::Ptr surface_hull(new pcl::PointCloud<pcl::PointXYZ>);
hull.reconstruct(*surface_hull, polygons);

pcl::PointCloud<pcl::PointXYZ>::Ptr objects(new pcl::PointCloud<pcl::PointXYZ>);
pcl::CropHull<pcl::PointXYZ> bb_filter;
bb_filter.setDim(2);
bb_filter.setInputCloud(cloud);
bb_filter.setHullIndices(polygons);
bb_filter.setHullCloud(surface_hull);
bb_filter.filter(*objects);

 

参考:

https://www.jianshu.com/p/57bbad9c39d6

https://blog.csdn.net/zhan_zhan1/article/details/103991733?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param

https://www.it610.com/article/1282154616984715264.htm