基于同轴聚类点云去重的铆钉高度测量

news/2025/2/25 7:31:47

基于同轴聚类点云去重的铆钉高度测量

  • 一、引言
  • 二、解决方案
  • 三、实验效果
  • 四、实验部分代码

一、引言

本实验起源于工业检测领域一个普遍存在的需求——精确测量铆钉相对于料盘(即基准平面)的高度。
需求:如何准确测定铆钉至料盘表面的高度?
待检测数据如下所示
在这里插入图片描述

二、解决方案

解决方案:
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));
    }
}


http://www.niftyadmin.cn/n/5865174.html

相关文章

C++对象模型之C++额外成本

1.介绍 C与C最大的区别&#xff0c;无疑在于面向对象&#xff0c;面向对象编程给C带来了强大的特性和灵活性。但同时也带来了一定的运行时和编译时的开销。下面介绍C对象模型的额外成本及其来源。 2.C的额外成本 &#xff08;1&#xff09;虚函数和动态多态的成本 虚函数表&am…

硬件加速与技术创新双轮驱动:DeepSeek和ChatGPT性能进阶的未来蓝图

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux网络编程 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 ​ Linux网络编程笔记&#xff1a; https://blog.cs…

UE地编材质世界对齐纹理旋转

帖子地址 https://forums.unrealengine.com/t/how-to-rotate-a-world-aligned-texture/32532/4世界对齐纹理本身不能改 自己创建了个函数 把世界对齐纹理的内容赋值粘贴 在纹理偏移里给值 不要局限0-1 给值给大一点

deepseek本地部署,ragflow,docker

先下载ollama 1.官网下载 deepseek-r1:14bhttps://ollama.com/library/deepseek-r1:14b 2.GitHub下载GitHub - ollama/ollama: Get up and running with Llama 3.3, DeepSeek-R1, Phi-4, Gemma 2, and other large language models. 两种方式 安装完后&#xff0c;cmd-&g…

服务器广播需要广播的服务器数量

服务器广播需要广播的服务器数量 真题目录: 点击去查看 E 卷 100分题型 题目描述 服务器连接方式包括直接相连,间接连接。 A和B直接连接,B和C直接连接,则A和C间接连接。 直接连接和间接连接都可以发送广播。 给出一个N*N数组,代表N个服务器, matrix[i][j] == 1, 则…

3D Web轻量化引擎HOOPS Communicator如何赋能航空航天制造?

在当今航空航天制造领域&#xff0c;精确度、效率和协作是推动行业发展的关键要素。随着数字化技术的飞速发展&#xff0c;3D Web可视化开发包HOOPS Communicator 为航空航天制造带来了革命性的变化。它凭借强大的功能和灵活的应用&#xff0c;助力企业在设计、生产、培训等各个…

Python在实际工作中的运用-CSV数据的几个处理方法

相信工作时间较长的“表哥们”一定都遇到过需要对存在固定格式的一些比较特殊的CSV文件进行处理的工作&#xff0c;比如CSV文本是从数据库里导出的格式文本&#xff0c;此时如果CSV文件中再出现个什么像身份证号码这类超过15个字符的数据时&#xff0c;如采用Excel直接打开保存…

html中的元素(1)

​大家好&#xff01;我叫补三补四 欢迎学习讨论 ​ 常用属性 表单元素form用于创建提供用户输入的表单&#xff0c;常用属性 1.action&#xff1a;规定表单提交网址 2.method&#xff1a;规定提交方法是get还是post 3.enctype&#xff1a;规定编码方式 4.name&#xff1a;表…