一、引言

基于点的特征识别算法相对较少,刚好又需要对项目中的圆孔特征进行滤波,故采取点云密度+RANSAC对圆孔特征进行识别和滤波。

  1. 首先,根据采集点云的密度差异提取出包含圆孔特征,边界特征的点云集合;
  2. 然后,利用RANSAC进行圆孔拟合;
  3. 最后,获取拟合的圆孔的半径,圆心利用包围盒进行去除。

二、实现

2.1点云密度差异聚类

计算点云数据的点密度差异,提取出密度差异的特征点云,并进行欧式聚类,具体实现见点云计算点密度特征 | Sifanのblog (liangzhouzz.github.io)

原始图像

image-20230806152034159

提取后的图像:

image-20230806152143989

2.2点云聚类

将经过密度提取后的点云进行聚类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
pcl::search::KdTree<pcl::PointXYZ>::Ptr kd_tree(new pcl::search::KdTree<pcl::PointXYZ>); //创建kdtree
kd_tree->setInputCloud(cloud);

std::vector<pcl::PointIndices>cluster_indices;//点索引
pcl::EuclideanClusterExtraction<pcl::PointXYZ>ec;//创建欧式聚类
ec.setClusterTolerance(0.2);//聚类容忍度
ec.setMinClusterSize(20);//最小聚类点数量
ec.setMaxClusterSize(cloud->size());//最大聚类点数量
ec.setSearchMethod(kd_tree);//搜索方式
ec.setInputCloud(cloud);
ec.extract(cluster_indices);

2.3RANSAC拟合圆

RANSAC拟合圆,可选择拟合2D圆pcl::SACMODEL_CIRCLE2D或3D圆pcl::SACMODEL_CIRCLE3D,这里选择用3D,RANSAC需传入法向量,对点云进行法向量估计。RANSAC会存在拟合错误的情况,需要对拟合出圆的半径进行限制,限制在其范围内找到目标圆。

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
//类创建
pcl::SACSegmentationFromNormals<pcl::PointXYZ,pcl::Normal>seg;
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);//创建索引对象
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
std::vector<pcl::ModelCoefficients>coeff;

// 估计法向量
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud(cluster);
ne.setSearchMethod(tree);
ne.setRadiusSearch(0.01);
ne.compute(*normals);

//ransac
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_CIRCLE3D);//拟合3D圆
seg.setMethodType(pcl::SAC_RANSAC);//RANSAC
seg.setNormalDistanceWeight(0.9);//设置法向量权重
seg.setMaxIterations(10000);//设置最大迭代次数
seg.setDistanceThreshold(0.1);//eps
seg.setRadiusLimits(1.5,3);//添加拟合圆的半径限制,防止拟合过大或过小的圆
seg.setInputNormals(normals);//输入法向量
seg.setInputCloud(cluster);//传入点集
seg.segment(*inliers,*coefficients);//分割

2.4包围盒分割

根据提取出圆的索引,可以得到其圆心坐标和半径,然后利用最小包围盒进行分割。

拟合出3D圆的参数如下:

  • 圆心坐标x:value[0]
  • 圆心坐标y:value[1]
  • 圆心坐标z:value[2]
  • 半径r:value[3]
  • 法向量nx:value[4]
  • 法向量ny:value[5]
  • 法向量nz:value[6]

设置包围盒的最小点坐标min_pt,和最大点坐标max_pt,然后创建包围盒对象,得到在包围盒内的点云索引。

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
for (size_t i = 0; i < coeff.size(); i++)
{
//提取拟合圆环的中心点坐标
Eigen::Vector3f center(coeff.at(i).values[0],coeff.at(i).values[1],coeff.at(i).values[2]);
//设置分割的包围盒长宽高,圆环的半径+阈值
float length0 = fabs(coeff.at(i).values[3])+0.3,width0 = fabs(coeff.at(i).values[3])+0.3, height0 = 5.0;
Eigen::Vector4f min_pt(center.x()-(length0),center.y()-(width0),center.z()-(height0),0);//半径
Eigen::Vector4f max_pt(center.x()+(length0),center.y()+(width0),center.z()+(height0),0);

//创建包围盒对象
pcl::CropBox<pcl::PointXYZ>crop;
crop.setMin(min_pt);//输入最小坐标
crop.setMax(max_pt);//最大坐标
crop.setInputCloud(cloud);
crop.setKeepOrganized(false);//提取被删除点的索引则为true
crop.setUserFilterValue(0.1f);//
pcl::IndicesPtr indexes(new std::vector<int>);
crop.filter(*indexes);
//创建分割对象
pcl::ExtractIndices<pcl::PointXYZ>extract;
extract.setInputCloud(cloud);
extract.setIndices(indexes);
extract.setNegative(true);
extract.filter(*cloud);//保存点云
}

2.5代码实现

代码汇总:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//头文件
#include<pcl/sample_consensus/sac_model_circle3d.h>
#include<pcl/segmentation/extract_clusters.h>//提取聚类
#include<pcl/filters/crop_box.h>//包围盒
#include<pcl/segmentation/sac_segmentation.h>
#include<pcl/filters/extract_indices.h>


void ExtractCircle(const pcl::PointCloud<pcl::PointXYZ>::Ptr &cloud)
{
pcl::search::KdTree<pcl::PointXYZ>::Ptr kd_tree(new pcl::search::KdTree<pcl::PointXYZ>);
kd_tree->setInputCloud(cloud);
//聚类
std::vector<pcl::PointIndices>cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZ>ec;
ec.setClusterTolerance(0.2);
ec.setMinClusterSize(20);
ec.setMaxClusterSize(cloud->size());
ec.setSearchMethod(kd_tree);
ec.setInputCloud(cloud);
ec.extract(cluster_indices);

//RANSAC拟合
pcl::SACSegmentationFromNormals<pcl::PointXYZ,pcl::Normal>seg;
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);//创建索引对象
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
std::vector<pcl::ModelCoefficients>coeff;

int counti=0;
for (const auto & indices : cluster_indices)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cir(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud,indices.indices,*cluster);
pcl::PointCloud<pcl::Normal>::Ptr normals (new pcl::PointCloud<pcl::Normal>);
// 估计法向量
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud(cluster);
ne.setSearchMethod(tree);
ne.setRadiusSearch(0.01);
ne.compute(*normals);
//RANSAC
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_CIRCLE3D);//拟合3D圆
seg.setMethodType(pcl::SAC_RANSAC);//RANSAC
seg.setNormalDistanceWeight(0.9);//法向量权重
seg.setMaxIterations(10000);//设置最大迭代次数
seg.setDistanceThreshold(0.1);//eps
seg.setRadiusLimits(1.5,3);//添加拟合圆的半径限制,防止拟合过大或过小的圆
seg.setInputNormals(normals);//输入法向量
seg.setInputCloud(cluster);//传入点集
seg.segment(*inliers,*coefficients);//分割
//拟合圆的点数限制,因为很容易会拟合半圆,需根据实际的需求调整,防止拟合半圆
if (inliers->indices.size() < 80)
{
//cerr<<"none"<<endl;
continue;
}
coeff.push_back(*coefficients);
counti++;
//提取
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud(cluster);
extract.setIndices(inliers);
extract.setNegative(false);
extract.filter(*cloud_cir);
}

for (size_t i = 0; i < coeff.size(); i++)
{
//提取拟合圆环的中心点坐标
Eigen::Vector3f center(coeff.at(i).values[0],coeff.at(i).values[1],coeff.at(i).values[2]);
//设置分割的包围盒长宽高,圆环的半径+阈值
float length0 = fabs(coeff.at(i).values[3])+0.3,width0 = fabs(coeff.at(i).values[3])+0.3, height0 = 5.0;
Eigen::Vector4f min_pt(center.x()-(length0),center.y()-(width0),center.z()-(height0),0);//半径
Eigen::Vector4f max_pt(center.x()+(length0),center.y()+(width0),center.z()+(height0),0);

//创建包围盒对象
pcl::CropBox<pcl::PointXYZ>crop;
crop.setMin(min_pt);//输入最小坐标
crop.setMax(max_pt);//最大坐标
crop.setInputCloud(cloud);
crop.setKeepOrganized(false);//提取被删除点的索引则为true
crop.setUserFilterValue(0.1f);//
pcl::IndicesPtr indexes(new std::vector<int>);
crop.filter(*indexes);
//创建分割对象
pcl::ExtractIndices<pcl::PointXYZ>extract;
extract.setInputCloud(cloud);
extract.setIndices(indexes);
extract.setNegative(true);
extract.filter(*cloud);//保存点云
}

}

2.6结果

RANSAC搜索的对象

image-20230806155049673

包围盒分割图像

image-20230806155158365