简介
halcon 中的connection_object_model_3d 可以使用不同的属性来实现将一块点云分解成多块点云,而在PCL 中可以使用聚类的方式来完成,具体如下:
pcl 的聚类:
欧式聚类、
欧式+法向量
区域生长、
高斯分类(1.81 中没有)
K-Means
一、Halcon
read_object_model_3d ('C:/Users/Albert/Desktop/Halcon2PCL/Connect3D_test.ply', 'm', [], [], connect3d, Status1)
connection_object_model_3d (connect3d, 'distance_3d', 0.01, ObjectModel3DConnected_222)
二、PCL
1、欧式聚类
int Connection_3d_models(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, Connection_3d_Param &p, Connection_3d_Value &v, vector<pcl::PointCloud<pcl::PointXYZ>>& clouds)
{
if (cloud->size()<1)
{
return -1;
}
pcl::PointIndices::Ptr inliner(new pcl::PointIndices);
vector<pcl::PointIndices>ece_inlier;
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ece;
ece.setInputCloud(cloud);
ece.setClusterTolerance(v.distance_3d_value);
ece.setMinClusterSize(v.minClusterSize);
ece.setMaxClusterSize(v.maxClusterSize);
ece.setSearchMethod(tree);
ece.extract(ece_inlier);
for (int i = 0; i < ece_inlier.size(); i++)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_copy(new pcl::PointCloud<pcl::PointXYZ>);
vector<int> ece_inlier_ext = ece_inlier[i].indices;
pcl::copyPointCloud(*cloud, ece_inlier_ext, *cloud_copy);//按照索引提取点云数据
clouds.push_back(*cloud_copy);
std::string filename_result224 = "C:\\Users\\Albert\\Desktop\\Halcon2PCL\\classfiy\\"+to_string(i)+".pcd";
pcl::io::savePCDFileBinaryCompressed(filename_result224, *cloud_copy);
}
}
2、欧式+法向量
在欧式分类的基础上加入了法向量角度的维度
int Euclidean_Normal_Clusters(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, Connection_3d_Value &v, vector<pcl::PointIndices>&ece_inlier)
{
if (cloud->size()<1)
{
return -1;
}
pcl::PointCloud<pcl::Normal>::Ptr normal(new pcl::PointCloud<pcl::Normal>);
// 计算法向量
pcl::search::KdTree<pcl::PointXYZ> ::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> normalomp;
normalomp.setInputCloud(cloud);
normalomp.setNumberOfThreads(10); // 设置加速线程书
normalomp.setSearchMethod(tree);
normalomp.setRadiusSearch(v.normal_Radius); // 计算法向量的 半径大小
normalomp.compute(*normal);
// KDtree用于欧式聚类
pcl::KdTree<pcl::PointXYZ>::Ptr tree_cluster(new pcl::KdTreeFLANN<pcl::PointXYZ>());
tree_cluster->setInputCloud(cloud);
// -------------------------------基于法向量的欧式聚类--------------------------------
pcl::extractEuclideanClusters(*cloud, *normal, v.Euclidean_distance_3d_value, tree_cluster, ece_inlier, v.normal_Esp_Angle, v.minClusterSize);
}
3、区域生长
算法流程:
1. 计算法线normal和曲率curvatures,依据曲率升序排序;
2. 选择曲率最低的为初始种子点,种子周围的临近点和种子点云相比较;
3. 法线的方向是否足够相近(法线夹角足够rpy),法线夹角阈值;
4. 曲率是否足够小(表面处在同一个弯曲程度),区域差值阈值;
5. 如果满足2,3则该点可用做种子点;
6. 如果只满足2,则归类而不做种子。
https://blog.csdn.net/weixin_53660567/article/details/120675139
int Regional_Growth_Clustering(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, Connection_3d_Value &v, vector<pcl::PointIndices>&ece_inlier)
{
if (cloud->size()<1)
{
return -1;
}
// 计算法向量
pcl::PointCloud<pcl::Normal>::Ptr normal(new pcl::PointCloud<pcl::Normal>);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> nor_Omp;
nor_Omp.setInputCloud(cloud);
nor_Omp.setRadiusSearch(v.normal_Radius);
nor_Omp.setNumberOfThreads(10);
nor_Omp.setSearchMethod(tree);
nor_Omp.compute(*normal);
// 法向量和曲率的 区域生长分类
pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal>rg;
rg.setInputCloud(cloud);
rg.setInputNormals(normal);
rg.setSearchMethod(tree);// 搜索方法 搜索树
rg.setNumberOfNeighbours(v.rg_Neighbours_Number); // 领域搜索点的个数
rg.setMinClusterSize(v.minClusterSize);
rg.setMaxClusterSize(v.maxClusterSize);
rg.setCurvatureThreshold(v.rg_Curvature_Threshold); // 设置曲率的阈值
rg.setSmoothnessThreshold(v.rg_SmoothnessThreshold); // 设置平滑度 法线插值阈值
rg.extract(ece_inlier);
}
4、K-Means
最简单的分类方法,就是确定蔟的个数,然后计算当前点到各个蔟中心的距离,然后不断的迭代。可以分类出不同的蔟,但是局限性也很大。当点云中的距离比较接近,或者点云中有大的崆峒的时候容易出错。
1、设置初始蔟中心
2、计算当前点到各个蔟中心的欧式距离v
3、v 中选距离最小的,保存到那个蔟中
4、跟新几何中心(点云中我计算的是质心,这个和分类的结果有很大的关系)
5、迭代,重复2 3 4
// k-means 分类算法
int K_Means_Clusters(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, Connection_3d_Value &v, vector<pcl::PointIndices>&ece_inlier)
{
if (cloud->size()<1)
{
return -1;
}
//1、 初始质心的设置 尽可能的将质心分开点
pcl::IndicesPtr index(new vector<int>(cloud->size()));
//================// 提取所有点云中的索引===========================
//pcl::ExtractIndices<pcl::PointXYZ> extract;
//extract.setInputCloud(cloud);
//extract.setIndices(index);
//extract.setNegative(true);
//extract.filter(*index);
//===========================================
index->resize(v.k_Means_Clusters_Number);
std::iota(std::begin(*index), std::end(*index), (int)0);
std::random_device rd;
std::mt19937 prng(rd());
// 将数据打乱 并开始重新排列
std::shuffle(index->begin(), index->end(), prng);
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster_Centroids(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud, *index, *cluster_Centroids);
ece_inlier.clear();
// k_Means_Clusters_Number 个点云蔟
ece_inlier.resize(v.k_Means_Clusters_Number);
// 2、迭代 质心 点云蔟
while (v.k_Means_Iterator_Number)
{
float acc_Tolerance = 0; // 点云蔟之间的累计误差
// 定义一个临时索引 vector
vector<pcl::PointIndices> ece_Inlier;
// 定义当前迭代1次的 点云蔟 中各种的点云索引
ece_Inlier.clear();
// k_Means_Clusters_Number 个点云蔟
ece_Inlier.resize(v.k_Means_Clusters_Number);
// 2.1 计算点云到中每个点到cluster 质心的欧式距离
for (size_t i = 0; i < cloud->size(); i++)
{
vector<float> v;
for (size_t j = 0; j< cluster_Centroids->size(); j++)
{
// 计算当前点到各个蔟的欧式距离 并保存起来
v.emplace_back(pcl::euclideanDistance(cloud->points[i], cluster_Centroids->points[j]));
}
// 2.2 找到那个里面最小的值 和索引 那个索引就是当前点所要假如的点云cluster
std::vector<float>::const_iterator min_v = std::min_element(v.cbegin(),v.cend());
int index = std::distance(v.cbegin(), min_v); // 欧式距离最小的那个位置index
//2.3 将该点点云的索引放到各自的蔟中
ece_Inlier.at(index).indices.push_back(i);
}
// 2.4 开始迭代, 重新计算点云的质心
pcl::PointCloud<pcl::PointXYZ>::Ptr update_Centroid(new pcl::PointCloud<pcl::PointXYZ>);
for (size_t i = 0; i < v.k_Means_Clusters_Number; ++i)
{
Eigen::Vector4f centroid;
pcl::compute3DCentroid(*cloud, ece_Inlier.at(i), centroid);
cout <<i<< " 点云的个数 :" << ece_Inlier.at(i).indices.size() << endl;
pcl::PointXYZ center{ centroid[0] ,centroid[1] ,centroid[2] };
update_Centroid->points.push_back(center);
}
// 2.5 判断推出的误差 :迭代档次 和 上次 点云的质心的欧氏距离
for (size_t i = 0; i < v.k_Means_Clusters_Number; ++i)
{
acc_Tolerance+= pcl::euclideanDistance(update_Centroid->points[i], cluster_Centroids->points[i]);
}
if (v.k_Means_Clusters_Tolerance>= acc_Tolerance)
{
ece_inlier = ece_Inlier;
break;
}
cluster_Centroids->clear();
cluster_Centroids = update_Centroid;
cout <<"迭代次数 : "<< v.k_Means_Iterator_Number << endl;
ece_inlier = ece_Inlier;
v.k_Means_Iterator_Number--;
}
}
不适合的点云:
这种就不适合采用k-means 方法,前面我做测试点云用k-meas方法,不管则么调参数都是有问题的,和我的更新的质心有关,我这里应该更新的是点云的中心,不是质心。