You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实时拼接RealSense D415点云的两大技术问题求助

RealSense D415点云实时拼接问题解决方案

一、重叠区域重复点去除方案

既然Voxel下采样不适用你的场景,推荐以下两种精准去重方式:

1. PCL内置坐标去重函数

使用pcl::removeDuplicatePoints,基于点的空间坐标(可包含RGB信息)设置距离阈值,将小于阈值的点判定为重复点过滤。代码示例:

pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_no_duplicates(new pcl::PointCloud<pcl::PointXYZRGB>);
// 0.001米阈值,可根据实际精度需求调整
pcl::removeDuplicatePoints(*Final, *cloud_no_duplicates, 0.001); 
*Final = *cloud_no_duplicates;

2. KD-Tree近邻过滤(针对重叠区精准筛选)

利用ICP得到的变换矩阵,通过KD-Tree搜索第二个点云中与第一个点云距离过近的点,只保留非重叠部分:

pcl::KdTreeFLANN<pcl::PointXYZRGB> kdtree;
kdtree.setInputCloud(cloud_in);
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud2_filtered(new pcl::PointCloud<pcl::PointXYZRGB>);

float threshold = 0.001; // 距离阈值,单位米
for (const auto& point : cloud2_in->points) {
    std::vector<int> indices;
    std::vector<float> distances;
    if (kdtree.nearestKSearch(point, 1, indices, distances) == 1) {
        if (distances[0] > threshold) {
            cloud2_filtered->points.push_back(point);
        }
    } else {
        cloud2_filtered->points.push_back(point);
    }
}
// 合并去重后的点云
*Final = *cloud_in + *cloud2_filtered;
// 保留原有序结构属性
cloud2_filtered->width = cloud2_in->width;
cloud2_filtered->height = cloud2_in->height;
cloud2_filtered->is_dense = cloud2_in->is_dense;

二、有序点云生成实现

有序点云的核心是保持width×height的网格结构,每个点对应相机图像的像素位置。你当前直接相加width并resize的方式会破坏原有有序结构,正确实现方式如下:

1. 前提校验

先确认输入的两个点云都是有序的(RealSense D415默认输出有序点云,isOrganized()返回true),否则无法生成有效有序拼接结果。

2. 有序点云拼接(以左右拼接为例)

假设两个相机为左右摆放,拼接后高度取两者最大值(若分辨率一致则取相同值),宽度为两者宽度之和,逐个填充点并处理无效区域:

if (!cloud_in->isOrganized() || !cloud2_in->isOrganized()) {
    NODELET_WARN("Input clouds are not organized, cannot generate organized output");
    return;
}

// 确定拼接后尺寸
int final_height = std::max(cloud_in->height, cloud2_in->height);
int final_width = cloud_in->width + cloud2_in->width;

// 初始化有序点云
Final->height = final_height;
Final->width = final_width;
Final->is_dense = false; // 边缘存在无效点,需设为false
Final->points.resize(final_height * final_width);
Final->header = cloud_in->header;

// 填充第一个点云的像素点
for (int h = 0; h < cloud_in->height; ++h) {
    memcpy(&Final->points[h * final_width], &cloud_in->points[h * cloud_in->width], cloud_in->width * sizeof(pcl::PointXYZRGB));
    // 填充右侧无效点(NaN)
    for (int w = cloud_in->width; w < final_width; ++w) {
        auto& p = Final->points[h * final_width + w];
        p.x = p.y = p.z = std::numeric_limits<float>::quiet_NaN();
    }
}

// 填充第二个点云的像素点
for (int h = 0; h < cloud2_in->height; ++h) {
    memcpy(&Final->points[h * final_width + cloud_in->width], &cloud2_in->points[h * cloud2_in->width], cloud2_in->width * sizeof(pcl::PointXYZRGB));
}

// 填充下方无效点(NaN)
for (int h = cloud_in->height; h < final_height; ++h) {
    for (int w = 0; w < final_width; ++w) {
        auto& p = Final->points[h * final_width + w];
        p.x = p.y = p.z = std::numeric_limits<float>::quiet_NaN();
    }
}

3. 注意事项

  • 若两个相机分辨率不同,需先将其中一个点云重采样至相同分辨率(使用pcl::resize),再进行拼接,否则有序结构无实际意义。
  • 有序点云的is_dense必须设为false,因为拼接边缘必然存在无效点。

修改后的核心代码片段

将去重与有序拼接整合到你的原有逻辑中:

if (cloud1RW_ != 0 && cloud2RW_ != 0) {
    // Transformation matrix for cloud 2 to align with cloud 1 (obtained via PCL ICP)
    static Eigen::Matrix4f transformationMatrix = (Eigen::Matrix4f() << 0.999949,0.0058447,0.00823151,-0.0132738,-0.00563437,0.999663,-0.025349,0.360606,-0.0083768,0.0253014,0.999645,0.0215044,0,0,0,1).finished();
    pcl::transformPointCloud( *cloud2_in, *cloud2_in, transformationMatrix);

    if ((cloud1RW_->width != 0) && (cloud2RW_->width != 0)) { 
        // --- 第一步:去除重叠区域重复点 ---
        pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud2_filtered(new pcl::PointCloud<pcl::PointXYZRGB>);
        pcl::KdTreeFLANN<pcl::PointXYZRGB> kdtree;
        kdtree.setInputCloud(cloud_in);
        float dist_threshold = 0.001; // 1mm阈值,可调整

        for (const auto& p : cloud2_in->points) {
            std::vector<int> nn_idx;
            std::vector<float> nn_dist;
            if (kdtree.nearestKSearch(p, 1, nn_idx, nn_dist) == 1 && nn_dist[0] <= dist_threshold) {
                continue; // 跳过重复点
            }
            cloud2_filtered->points.push_back(p);
        }
        cloud2_filtered->width = cloud2_in->width;
        cloud2_filtered->height = cloud2_in->height;
        cloud2_filtered->is_dense = cloud2_in->is_dense;

        // --- 第二步:生成有序点云 ---
        if (!cloud_in->isOrganized() || !cloud2_filtered->isOrganized()) {
            NODELET_WARN("Input clouds are not organized, falling back to unorganized merge");
            *Final = *cloud_in + *cloud2_filtered;
        } else {
            int final_h = std::max(cloud_in->height, cloud2_filtered->height);
            int final_w = cloud_in->width + cloud2_filtered->width;

            Final->height = final_h;
            Final->width = final_w;
            Final->is_dense = false;
            Final->points.resize(final_h * final_w);
            Final->header = cloud_in->header;

            // 填充cloud_in的点
            for (int h = 0; h < cloud_in->height; ++h) {
                memcpy(&Final->points[h * final_w], &cloud_in->points[h * cloud_in->width], cloud_in->width * sizeof(pcl::PointXYZRGB));
                // 填充右侧无效点
                for (int w = cloud_in->width; w < final_w; ++w) {
                    auto& p = Final->points[h * final_w + w];
                    p.x = p.y = p.z = std::numeric_limits<float>::quiet_NaN();
                }
            }
            // 填充cloud2_filtered的点
            for (int h = 0; h < cloud2_filtered->height; ++h) {
                memcpy(&Final->points[h * final_w + cloud_in->width], &cloud2_filtered->points[h * cloud2_filtered->width], cloud2_filtered->width * sizeof(pcl::PointXYZRGB));
            }
            // 填充下方无效点
            for (int h = cloud_in->height; h < final_h; ++h) {
                for (int w = 0; w < final_w; ++w) {
                    auto& p = Final->points[h * final_w + w];
                    p.x = p.y = p.z = std::numeric_limits<float>::quiet_NaN();
                }
            }
        }

        pcl::toROSMsg(*Final, output_);
        NODELET_INFO("Final height %d", output_.height);
        output_.header.frame_id = target_frame_;
        output_.header.stamp = ros::Time::now();
        output_.is_bigendian = cloud1RW_->is_bigendian;
        output_.is_dense = Final->is_dense;
    }
}

内容的提问来源于stack exchange,提问作者Pran-7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 16:20:44