基于同轴聚类点云去重的铆钉高度测量
- 一、引言
- 二、解决方案
- 三、实验效果
- 四、实验部分代码
一、引言
本实验起源于工业检测领域一个普遍存在的需求——精确测量铆钉相对于料盘(即基准平面)的高度。
需求:如何准确测定铆钉至料盘表面的高度?
待检测数据如下所示
二、解决方案
解决方案:
1.点云去噪;
2.针对现有数据特点,进行高度滤波,去除料盘地面点云数据;
3.平面分割,计算料盘地面;
4.铆钉第一次聚类,提取出料盘地面;
5.铆钉第二次聚类,输入高度滤波后的点云,对每一个铆钉进行聚类;
6.同轴点云去除,具体地:计算每一聚类点云的质心,若两个质心小于一定阈值,则比较z值并标记较低质心的聚类点云,然后删除。
7.针对去重后的铆钉点云,计算高度,具体的取每一个铆钉最大高度200的平均值作为铆钉高度。
三、实验效果
输出
处理后的铆钉点云数据
四、实验部分代码
struct Color {
uint8_t r, g, b;
};
//八叉树滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr octreeFiltering(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, float resolution, int minNumm) {
pcl::octree::OctreePointCloudSearch<pcl::PointXYZ> octree(resolution);
octree.setInputCloud(cloud);
octree.addPointsFromInputCloud();
pcl::Indices pointIdxVec;
std::vector<int> allPointIdxVec;
for (auto iter = octree.leaf_begin(); iter != octree.leaf_end(); ++iter) {
auto key = iter.getCurrentOctreeKey();
if (octree.findLeaf(key.x, key.y, key.z) != nullptr) {
pointIdxVec = iter.getLeafContainer().getPointIndicesVector();
if (pointIdxVec.size() > minNumm) {
allPointIdxVec.insert(allPointIdxVec.end(), pointIdxVec.begin(), pointIdxVec.end());
}
}
}
pcl::PointCloud<pcl::PointXYZ>::Ptr result(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud, allPointIdxVec, *result);
return result;
}
//高度滤波
pcl::PointCloud<pcl::PointXYZ>::Ptr heightFiltering(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud) {
pcl::PointCloud<pcl::PointXYZ>::Ptr height_filtered(new pcl::PointCloud<pcl::PointXYZ>);
for (const auto& point : cloud->points) {
if (point.z > -10) {
height_filtered->points.push_back(point);
}
}
height_filtered->width = height_filtered->points.size();
height_filtered->height = 1;
height_filtered->is_dense = true;
return height_filtered;
}
//平面分割,计算托盘底面
double segmentPlane(pcl::PointCloud<pcl::PointXYZ>::Ptr& filtered_cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_plane, double planeHeight, double ret) {
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_f(new pcl::PointCloud<pcl::PointXYZ>);
pcl::SACSegmentation<pcl::PointXYZ> seg;
pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
//pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZ>());
pcl::PCDWriter writer;
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setMaxIterations(100);
seg.setDistanceThreshold(2.2);
int i = 0, nr_points = (int)filtered_cloud->points.size();
while (filtered_cloud->points.size() > 0.9 * nr_points)
{
// Segment the largest planar component from the remaining cloud
seg.setInputCloud(filtered_cloud);
seg.segment(*inliers, *coefficients);
if (inliers->indices.size() == 0)
{
std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
break;
}
// Extract the planar inliers from the input cloud
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud(filtered_cloud);
extract.setIndices(inliers);
extract.setNegative(false);
// Write the planar inliers to disk
extract.filter(*cloud_plane);
std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size() << " data points." << std::endl;
// 移去平面局内点,提取剩余点云
extract.setNegative(true);
extract.filter(*cloud_f);
filtered_cloud = cloud_f;
}
// 找到Z值最大的200个点
//std::vector<pcl::PointXYZ> sorted_points = cluster->points; // 复制聚类点
int clusterId0 = 1;
auto sorted_points = cloud_plane->points; // 复制聚类点
std::sort(sorted_points.begin(), sorted_points.end(), [](const pcl::PointXYZ& a, const pcl::PointXYZ& b) {
return a.z > b.z; // 按Z值降序排序
});
// 计算平均Z值
int sampleCount = std::max(5000, static_cast<int>(sorted_points.size()));
for (int i = 0; i < sampleCount; ++i) {
planeHeight += sorted_points[i].z;
}
planeHeight /= sampleCount;
// 打印平均高度到控制台
std::cout << "托盘地面的平均高度为: " << planeHeight << std::endl;
ret = planeHeight;
cout << "ret:" << ret << endl;
clusterId0++;
return ret;
}
//聚类
std::vector<pcl::PointIndices> extractClusters(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_filtered) {
std::vector<pcl::PointIndices> cluster_indices;
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud_filtered);
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(0.04);
ec.setMinClusterSize(1500);
ec.setMaxClusterSize(70000);
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_filtered);
ec.extract(cluster_indices);
return cluster_indices;
}
//
double calculateAverageHeight(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cluster) {
double averageHeight = 0.0;
int sampleCount = std::min(200, static_cast<int>(cluster->size()));
for (int i = 0; i < sampleCount; ++i) {
averageHeight += cluster->points[i].z;
}
return averageHeight / sampleCount;
}
//铆钉的平均高度
void RivetHeight(const std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr>& filtered_clusters,
const pcl::PointCloud<pcl::PointXYZ>::Ptr& height_filtered,
double averageHeight,
int ret
) {
int clusterId = 0;
while (clusterId < filtered_clusters.size()) {
uint8_t r = rand() % 256;
uint8_t g = rand() % 256;
uint8_t b = rand() % 256;
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster = filtered_clusters[clusterId];
averageHeight = calculateAverageHeight(cluster);
std::cout << "铆钉 " << clusterId << " 的平均高度1为: " << averageHeight << std::endl;
std::cout << "铆钉 " << clusterId << " 的平均高度2为: " << averageHeight - ret << std::endl;
// 根据 averageHeight 分类
if (2.13 <(averageHeight - ret) && (averageHeight - ret) < 2.43) {
std::cout << "铆钉 " << clusterId << " 属于 M3 类中的A型号" << std::endl;
}
else if (5.97 <(averageHeight - ret) && (averageHeight - ret) < 6.17) {
std::cout << "铆钉 " << clusterId << " 属于 M3 类中的B型号" << std::endl;
}
else if (2.88 < (averageHeight - ret) && (averageHeight - ret) < 3.18) {
std::cout << "铆钉 " << clusterId << " 属于 M3 类中的C型号" << std::endl;
}
else if (6.97 < (averageHeight - ret) && (averageHeight - ret) < 7.17) {
std::cout << "铆钉 " << clusterId << " 属于 M3 类中的D型号" << std::endl;
}
else if (1.3 < (averageHeight - ret) && (averageHeight - ret) < 1.5) {
std::cout << "铆钉 " << clusterId << " 属于 M2 类中的E型号" << std::endl;
}
else if (0.9 < (averageHeight - ret) && (averageHeight - ret) < 1.1) {
std::cout << "铆钉 " << clusterId << " 属于 M2 类中的G型号" << std::endl;
}
else {
std::cout << "铆钉 " << clusterId << " 属于 C 类" << std::endl;
}
++clusterId;
}
}
// 同轴聚类点云去重
std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> filter_clusters(
std::vector<Eigen::Vector4f>& centroids,
std::vector<double>& heights,
const std::vector<pcl::PointIndices>& cluster_indices,
const pcl::PointCloud<pcl::PointXYZ>::Ptr height_filtered
) {
std::vector<int> clusters_to_remove;
// 为每个聚类分配随机颜色并可视化
int clusterId = 0;
std::vector<Color> colors;
std::srand(time(0)); // 初始化随机种子
while (clusterId < cluster_indices.size()) {
// 生成随机颜色
uint8_t r = rand() % 256;
uint8_t g = rand() % 256;
uint8_t b = rand() % 256;
// 提取每个聚类
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
for (const auto& index : cluster_indices[clusterId].indices) {
cluster->points.push_back(height_filtered->points[index]);
}
cluster->width = cluster->points.size();
cluster->height = 1;
cluster->is_dense = true;
// 计算聚类的质心
Eigen::Vector4f centroid;
pcl::compute3DCentroid(*cluster, centroid);
centroids.push_back(centroid);
heights.push_back(centroid[2]); // 保存质心的Z坐标
// 输出质心到控制台
std::cout << "铆钉 " << clusterId << " 的质心为: ("
<< centroid[0] << ", "
<< centroid[1] << ", "
<< centroid[2] << ")" << std::endl;
++clusterId
}
// 检查并过滤聚类
for (int i = 0; i < centroids.size(); ++i) {
for (int j = i + 1; j < centroids.size(); ++j) {
double distance = (centroids[i] - centroids[j]).norm(); // 计算质心之间的距离
// double distance = (centroids[i].head<3>() - centroids[j].head<3>()).norm(); // 计算质心之间的距离
std::cout << "铆钉点云质心距离为:" << distance << std::endl;
if (distance < 3) { // 3cm
// 比较 Z 值并标记较低质心的聚类以删除
if (heights[i] < heights[j]) {
clusters_to_remove.push_back(i);
}
else {
clusters_to_remove.push_back(j);
}
}
}
}
// 去重
std::set<int> unique_remove(clusters_to_remove.begin(), clusters_to_remove.end());
// 如果要删除的聚类索引大于等于0,则重建聚类
std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> filtered_clusters;
if (!unique_remove.empty()) {
for (int i = 0; i < cluster_indices.size(); ++i) {
if (unique_remove.find(i) == unique_remove.end()) {
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>());
for (const auto& index : cluster_indices[i].indices) {
cluster->points.push_back(height_filtered->points[index]);
}
filtered_clusters.push_back(cluster);
}
}
}
else {
// 如果没有需要删除的聚类,直接复制所有聚类
for (const auto& indices : cluster_indices) {
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>());
for (const auto& index : indices.indices) {
cluster->points.push_back(height_filtered->points[index]);
}
filtered_clusters.push_back(cluster);
}
}
return filtered_clusters;
}
//可视化
void visualizer(const std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr>& filtered_clusters, const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud) {
// 点云可视化
boost::shared_ptr<pcl::visualization::PCLVisualizer>viewer0(new pcl::visualization::PCLVisualizer("显示点云"));
//左边窗口显示输入的点云,右边的窗口显示分割后的点云
int v1(0), v2(0);
viewer0->createViewPort(0, 0, 0.5, 1, v1);
viewer0->createViewPort(0.5, 0, 1, 1, v2);
viewer0->setBackgroundColor(0, 0, 0, v1);
viewer0->setBackgroundColor(0.3, 0.3, 0.3, v2);
int clusterId = 0;
while (clusterId < filtered_clusters.size()) {
uint8_t r = rand() % 256;
uint8_t g = rand() % 256;
uint8_t b = rand() % 256;
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster = filtered_clusters[clusterId];
double averageHeight = calculateAverageHeight(cluster);
viewer0->addPointCloud<pcl::PointXYZ>(cluster, std::string("cluster_") + std::to_string(clusterId), v2);
viewer0->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, r / 255.0, g / 255.0, b / 255.0, std::string("cluster_") + std::to_string(clusterId), v2);
++clusterId;
}
viewer0->addPointCloud<pcl::PointXYZ>(cloud, "cloud_out", v1);
viewer0->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud_out", v1);
while (!viewer0->wasStopped()) {
viewer0->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(1000));
}
}