一、引言
个人的blog:sifanのblog
基于点的特征识别算法相对较少,刚好又需要对项目中的圆孔特征进行滤波,故采取点云密度+RANSAC对圆孔特征进行识别和滤波。
- 首先,根据采集点云的密度差异提取出包含圆孔特征,边界特征的点云集合;
- 然后,利用RANSAC进行圆孔拟合;
- 最后,获取拟合的圆孔的半径,圆心利用包围盒进行去除。
二、实现
2.1点云密度差异聚类
计算点云数据的点密度差异,提取出密度差异的特征点云,并进行欧式聚类,具体实现见点云计算点密度特征 | Sifanのblog (liangzhouzz.github.io)
原始图像
提取后的图像:
2.2点云聚类
将经过密度提取后的点云进行聚类,代码如下:
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会存在拟合错误的情况,需要对拟合出圆的半径进行限制,限制在其范围内找到目标圆。
//类创建
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
,然后创建包围盒对象,得到在包围盒内的点云索引。
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代码实现
//头文件
#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搜索的对象
包围盒分割图像