点云初步学习③
主要工具:visual studio 2019 和cloudcompare(自行编译)
参考引用:https://blog.csdn.net/luolaihua2018/article/details/120113848
1.点云关键点、特征描述与提取
2.点云分割算法
点云分割&&点云分类
点云分割(point cloud segmentation):
根据空间、几何和纹理等特征点进行划分,同一划分内的点云拥有相似的特征。点云分割的目的是分块,从而便于单独处理。
点云分类(point cloud classification):
为每个点分配一个语义标记。点云的分类是将点云分类到不同的点云集。同一个点云集具有相似或相同的属性,例如地面、树木、人等。也叫做点云语义分割
①随机抽样一致算法(Random Sample Consensus,RANSAC)
采用迭代的方式从一组包含离群的被观测数据中估算出数学模型的参数。假设给定一组正确的数据,存在可以计算出符合这些数据的模型参数的方法。该算法核心思想就是随机性和假设性:
随机性是根据正确数据出现概率去随机选取抽样数据,根据大数定律,随机性模拟可以近似得到正确结果。
假设性是假设选取出的抽样数据都是正确数据,然后用这些正确数据通过问题满足的模型,去计算其他点 ,然后对这次结果进行一个评分。
基于RANSAC的基本检测算法虽然具有较高的鲁棒性和效率,但是目前仅针对平面,球,圆柱体,圆锥和圆环物种基本的基元。
算法流程
要得到一个直线模型,需要两个点唯一确定一个直线方程。
1.所以第一 步随机选择两个点。
2.通过这两个点,可以计算出这两个点所表示的模型方程y=ax+b。
3.将所有的数据点套到这个模型中计算误差。
4.找到所有满足误差阈值的点。
然后我们再重复1~4这个过程,直到达到一定迭代次数后,选出那个被 支持的最多的模型,作为问题的解。
PCL中的 Sample_consensus 模块
PCL中的 Sample_consensus库实现了随机采样一致性及其泛化估计算法,以及例如平面、柱面等各种常见几何模型,用不同的估计算法和不同的几何模型自由结合估算点云中隐含的具体几何模型的系数, 实现点云中所处的几何模型的分割。
支持以下模型:
SACMODEL_PLANE - 用于确定平面模型。平面的四个系数。
SACMODEL_LINE - 用于确定线模型。直线的六个系数由直线上的一个点和直线的方向给出。
SACMODEL_CIRCLE2D - 用于确定平面中的 2D 圆。圆的三个系数由其中心和半径给出。
SACMODEL_CIRCLE3D - 用于确定平面中的 3D 圆。圆的七个系数由其中心、半径和法线给出。
SACMODEL_SPHERE - 用于确定球体模型。球体的四个系数由其 3D 中心和半径给出。
SACMODEL_CYLINDER - 用于确定气缸模型。圆柱体的七个系数由其轴上的点、轴方向和半径给出。
SACMODEL_CONE - 用于确定锥模型。锥体的七个系数由其顶点、轴方向和张角给出。
SACMODEL_TORUS - 尚未实施
SACMODEL_PARALLEL_LINE -在最大指定角度偏差内确定与给定轴平行的线的模型。线系数类似于SACMODEL_LINE。
SACMODEL_PERPENDICULAR_PLANE -在最大指定角度偏差内确定垂直于用户指定轴的平面的模型。平面系数类似于SACMODEL_PLANE。
SACMODEL_PARALLEL_LINES - 尚未实现
SACMODEL_NORMAL_PLANE - 使用附加约束确定平面模型的模型:每个内点的表面法线必须平行于输出平面的表面法线,在指定的最大角度偏差内。平面系数类似于SACMODEL_PLANE。
SACMODEL_NORMAL_SPHERE - 类似于SACMODEL_SPHERE,但具有额外的表面法线约束。
SACMODEL_PARALLEL_PLANE -在最大指定角度偏差内确定平行于用户指定轴的平面的模型。平面系数类似于SACMODEL_PLANE。
SACMODEL_NORMAL_PARALLEL_PLANE使用附加表面法线约束定义 3D 平面分割模型。平面法线必须平行于用户指定的轴。因此,SACMODEL_NORMAL_PARALLEL_PLANE 等效于SACMODEL_NORMAL_PLANE + SACMODEL_PERPENDICULAR_PLANE。平面系数类似于SACMODEL_PLANE。
SACMODEL_STICK - 3D 棒分割模型。一根棍子是一条给定用户最小/最大宽度的线。
②欧式聚类分割
聚类方法,通过特征空间确定点与点之间的亲疏程度
算法流程:
找到空间中某点p,有kdTree找到离他最近的n个点,判断这n个点到p的距离。将距离小于阈值r的点p1,p2,p3… 放在类Q里
在 Q里找到一点p1,重复1,找到p22,p23,p24 全部放进Q里
当 Q 再也不能有新点加入了,则完成搜索了
参考链接
https://blog.csdn.net/weixin_46098577/article/details/116129817
完整代码
#include <pcl/io/pcd_io.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/visualization/cloud_viewer.h>
using namespace std;
typedef pcl::PointXYZRGB PointT;
int main()
{
pcl::PointCloud<PointT>::Ptr cloud_in(new pcl::PointCloud<PointT>);
pcl::PCDReader reader;
pcl::PCDWriter writer;
//---------------------------------
//-------------加载点云-------------
//---------------------------------
if (reader.read("trees.pcd", *cloud_in) < 0)
{
PCL_ERROR("点云文件不存在!\a\n");
return -1;
}
/// 定义颜色数组,用于可视化
float colors[] = {
255, 0, 0, // red 1
0, 255, 0, // green 2
0, 0, 255, // blue 3
255, 255, 0, // yellow 4
0, 255, 255, // light blue5
255, 0, 255, // magenta 6
255, 255, 255, // white 7
255, 128, 0, // orange 8
255, 153, 255, // pink 9
51, 153, 255, // 10
153, 102, 51, // 11
128, 51, 153, // 12
153, 153, 51, // 13
163, 38, 51, // 14
204, 153, 102, // 15
204, 224, 255, // 16
128, 179, 255, // 17
206, 255, 0, // 18
255, 204, 204, // 19
204, 255, 153, // 20
};
//---------------------------------
//------------欧式聚类--------------
//---------------------------------
/// 创建kd树
pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
tree->setInputCloud(cloud_in);
/// 设置分割参数
vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<PointT> ec;
ec.setClusterTolerance(0.5); //设置近邻搜索的半径
ec.setMinClusterSize(999); //设置最小聚类点数
ec.setMaxClusterSize(999999); //设置最大聚类点数
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_in);
ec.extract(cluster_indices);
//----- 可视化1/3-----↓
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D viewer"));
int v1(0);
viewer->createViewPort(0.0, 0.0, 0.5, 1.0, v1); //设置第一个视口在X轴、Y轴的最小值、最大值,取值在0-1之间
viewer->setBackgroundColor(0.1, 0.1, 0.1, v1);
viewer->addText("cloud_in", 10, 10, "v1 text", v1);
viewer->addPointCloud<PointT>(cloud_in, "cloud_in", v1);
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud_in", v1);
int v2(0);
viewer->createViewPort(0.5, 0.0, 1.0, 1.0, v2);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v2);
viewer->addText("cloud_cluster", 10, 10, "v2 text", v2);
//-----可视化1/3-----↑
/// 执行欧式聚类分割,并保存分割结果
int j = 0;
for (vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin(); it != cluster_indices.end(); ++it)
{
pcl::PointCloud<PointT>::Ptr cloud_cluster(new pcl::PointCloud<PointT>);
for (vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); pit++)
cloud_cluster->points.push_back(cloud_in->points[*pit]);
cloud_cluster->width = cloud_cluster->points.size();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
stringstream ss;
ss << "tree_" << j + 1 << ".pcd";
writer.write<PointT>(ss.str(), *cloud_cluster, true);
cout << "-----" << ss.str() << "详情-----" << endl;
cout << *cloud_cluster << endl;
//-----可视化2/3-----↓
viewer->addPointCloud<PointT>(cloud_cluster, ss.str(), v2);
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, ss.str(), v2);
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, colors[j*3] / 255, colors[j*3+1] / 255, colors[j*3 + 2] / 255, ss.str(), v2);
//-----可视化2/3-----↑
j++;
}
//-----可视化3/3-----↓
while (!viewer->wasStopped())
{
viewer->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
//-----可视化3/3-----↑
return 0;
}
聚类结果:
条件聚类分割
使用类pcl::ConditionEuclideanClustering实现点云分割,与其他分割方法不同的是该方法的聚类约束条件(欧式距离、平滑度、RGB颜色等)可以由用户自己定义,即当搜索到一个近邻点时,用户可以自定义该邻域点是否合并到当前聚类的条件。
pcl::ConditionalEuclideanClustering<PointTypeFull> cec (true);//创建条件聚类分割对象,并进行初始化。cec.setInputCloud (cloud_with_normals);//设置输入点集
//用于选择不同条件函数
switch(Method)
{
case 1:
cec.setConditionFunction (&enforceIntensitySimilarity); break;
case 2:
cec.setConditionFunction (&enforceCurvatureOrIntensitySimilarity); break;
case 3:
cec.setConditionFunction (&customRegionGrowing); break;
default:
cec.setConditionFunction (&customRegionGrowing); break;
}
cec.setClusterTolerance (500.0);//设置聚类参考点的搜索距离
cec.setMinClusterSize (cloud_with_normals->points.size () / 1000);//设置过小聚类的标准cec.setMaxClusterSize (cloud_with_normals->points.size () / 5);//设置过大聚类的标准cec.segment (*clusters);//获取聚类的结果,分割结果保存在点云索引的向量中
cec.getRemovedClusters (small_clusters, large_clusters);//获取无效尺寸的聚类
这个条件的设置是可以由我们自定义的,因为除了距离检查,聚类的点还需要满足一个特殊的自定义的要 求,就是以第一个点为标准作为种子点,候选其周边的点作为它的对比或者比较的对象,如果满足条件就加入到聚类的对象中。
//如果此函数返回true,则将添加候选点到种子点的簇类中。
bool customCondition(const pcl::PointXYZ& seedPoint, const pcl::PointXYZ& candidatePoin t, float squaredDistance) {
// 在这里你可以添加你自定义的条件
return false;
return true;
}
③区域生长分割算法
曲率和法线的介绍和计算:https://blog.csdn.net/qq_36686437/article/details/116369956
PCL 计算点云法向量并显示:https://blog.csdn.net/qq_36686437/article/details/105559280
种子点:根据点的曲率值对点云进行排序,曲率最小的点叫做初始种子点,
(1)区域生长算法从曲率最小的种子点开始生长,初始种子点所在区域为最平滑区域,从初始种子点所在的区域开始生长可减小分割片段的总数,从而提高算法的效率。
(2)设置一空的聚类区域C和空的种子点序列Q,聚类数组L。
(3)选好初始种子点,将其加入种子点序列Q中,并搜索该种子点的领域点,计算每一个领域点法线与种子点法线之间的夹角,小于设定的平滑阀值时,将领域点加入到C中,同时判断该领域点的曲率值是否小于曲率阀值,将小于曲率阈值的领域点加入种子点序列Q中,在邻域点都判断完成后,删除当前种子点,在Q中重新选择新的种子点重复上述步骤,直到Q中序列为空,一个区域生长完成,将其加入聚类数组L中。
(4)利用曲率值从小到大排序,顺序选择输入点集 的点作为种子点加入到种子点序列中,重复以上生长的步骤。
#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/pcl_visualizer.h>
#include <pcl/filters/passthrough.h>
#include <pcl/segmentation/region_growing.h>
#include <pcl/console/print.h>
#include <pcl/console/parse.h>
#include <pcl/console/time.h>
#include <stdio.h>
using namespace pcl::console;
int
main(int argc, char** argv)
{
if (argc < 2)
{
std::cout << ".exe xx.pcd -kn 50 -bc 0 -fc 10.0 -nc 0 -st 30 -ct 0.05" << endl;
return 0;
}
time_t start, end, diff[5], option;
start = time(0);
int KN_normal = 10;
bool Bool_Cuting = false;
float far_cuting = 10, near_cuting = 0, SmoothnessThreshold = 5.0, CurvatureThreshold = 0.05;
parse_argument(argc, argv, "-kn", KN_normal);
parse_argument(argc, argv, "-bc", Bool_Cuting);
parse_argument