一、引言
基于点的特征识别算法相对较少,刚好又需要对项目中的圆孔特征进行滤波,故采取点云密度+RANSAC对圆孔特征进行识别和滤波。
- 首先,根据采集点云的密度差异提取出包含圆孔特征,边界特征的点云集合;
- 然后,利用RANSAC进行圆孔拟合;
- 最后,获取拟合的圆孔的半径,圆心利用包围盒进行去除。
二、实现
2.1点云密度差异聚类
计算点云数据的点密度差异,提取出密度差异的特征点云,并进行欧式聚类,具体实现见点云计算点密度特征 | Sifanのblog (liangzhouzz.github.io)
原始图像
提取后的图像:
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>); 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);
seg.setOptimizeCoefficients(true); seg.setModelType(pcl::SACMODEL_CIRCLE3D); seg.setMethodType(pcl::SAC_RANSAC); seg.setNormalDistanceWeight(0.9); seg.setMaxIterations(10000); seg.setDistanceThreshold(0.1); 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); 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);
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); seg.setOptimizeCoefficients(true); seg.setModelType(pcl::SACMODEL_CIRCLE3D); seg.setMethodType(pcl::SAC_RANSAC); seg.setNormalDistanceWeight(0.9); seg.setMaxIterations(10000); seg.setDistanceThreshold(0.1); seg.setRadiusLimits(1.5,3); seg.setInputNormals(normals); seg.setInputCloud(cluster); seg.segment(*inliers,*coefficients); if (inliers->indices.size() < 80) { 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); 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搜索的对象
包围盒分割图像