【问题标题】:Generate image from an unorganized Point Cloud in PCL在 PCL 中从无组织的点云生成图像
【发布时间】:2023-03-17 05:33:01
【问题描述】:

我有一个杂乱无章的场景点云。下面是点云的截图-

我想从这个点云中合成一个图像。下面是代码sn-p-

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <opencv2/opencv.hpp>

int main(int argc, char** argv)
{
    pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBA>);
    pcl::io::loadPCDFile("file.pcd", *cloud);

    cv::Mat image = cv::Mat(cloud->height, cloud->width, CV_8UC3);
    for (int i = 0; i < image.rows; i++)
    {
        for (int j = 0; j < image.cols; j++)
        {
            pcl::PointXYZRGBA point = cloud->at(j, i);
            image.at<cv::Vec3b>(i, j)[0] = point.b;
            image.at<cv::Vec3b>(i, j)[1] = point.g;
            image.at<cv::Vec3b>(i, j)[2] = point.r;
        }
    }
    cv::imwrite("image.png", image);

    return (0);
}

PCD 文件可以在here 找到。上面的代码在运行时抛出以下错误-

terminate called after throwing an instance of 'pcl::IsNotDenseException'
  what():  : Can't use 2D indexing with a unorganized point cloud

由于云是无组织的,HEIGHT 字段为 1。这让我在定义图像的尺寸时感到困惑。

问题

  1. 如何从无组织的点云中合成图像?
  2. 如何将位于合成图像中的像素转换回点云(3D 空间)?

PS:我在 Ubuntu 14.04 LTS 操作系统中使用 PCL 1.7。

【问题讨论】:

    标签: c++ point-cloud-library


    【解决方案1】:

    无组织点云的意思是这些点没有分配到一个固定的(有组织的)网格,因此-&gt;at(j, i)不能被使用(高度总是1,宽度就是云的大小。

    如果你想从你的云中生成图像,我建议如下过程:

    1. 将点云投影到平面。
    2. 在该平面上生成网格(有组织的点云)。
    3. 将颜色从无组织的云插入到网格(有组织的云)。
    4. 从有组织的网格生成图像(您的初始尝试)。

    为了能够转换回 3D:

    • 投影到平面时保存“投影向量”(从原点到投影点的向量)。
    • 也将其插入到网格中。

    创建网格的方法:

    将点云投影到平面(无组织的云),并可选择将重建信息保存在法线中:

    pcl::PointCloud<pcl::PointXYZINormal>::Ptr ProjectToPlane(pcl::PointCloud<pcl::PointXYZINormal>::Ptr cloud, Eigen::Vector3f origin, Eigen::Vector3f axis_x, Eigen::Vector3f axis_y)
        {
            PointCloud<PointXYZINormal>::Ptr aux_cloud(new PointCloud<PointXYZINormal>);
            copyPointCloud(*cloud, *aux_cloud);
    
        auto normal = axis_x.cross(axis_y);
        Eigen::Hyperplane<float, 3> plane(normal, origin);
    
        for (auto itPoint = aux_cloud->begin(); itPoint != aux_cloud->end(); itPoint++)
        {
            // project point to plane
            auto proj = plane.projection(itPoint->getVector3fMap());
            itPoint->getVector3fMap() = proj;
            // optional: save the reconstruction information as normals in the projected cloud
            itPoint->getNormalVector3fMap() = itPoint->getVector3fMap() - proj;
        }
    return aux_cloud;
    }
    

    根据一个原点和两个轴矢量生成一个网格(长度和 image_size 可以根据您的云计算预先确定):

    pcl::PointCloud<pcl::PointXYZINormal>::Ptr GenerateGrid(Eigen::Vector3f origin, Eigen::Vector3f axis_x , Eigen::Vector3f axis_y, float length, int image_size)
    {
        auto step = length / image_size;
    
        pcl::PointCloud<pcl::PointXYZINormal>::Ptr image_cloud(new pcl::PointCloud<pcl::PointXYZINormal>(image_size, image_size));
        for (auto i = 0; i < image_size; i++)
            for (auto j = 0; j < image_size; j++)
            {
                int x = i - int(image_size / 2);
                int y = j - int(image_size / 2);
                image_cloud->at(i, j).getVector3fMap() = center + (x * step * axisx) + (y * step * axisy);
            }
    
        return image_cloud;
    }
    

    插值到有组织的网格(其中法线存储重建信息,曲率用作指示空像素(无对应点)的标志):

    void InterpolateToGrid(pcl::PointCloud<pcl::PointXYZINormal>::Ptr cloud, pcl::PointCloud<pcl::PointXYZINormal>::Ptr grid, float max_resolution, int max_nn_to_consider)
    {   
        pcl::search::KdTree<pcl::PointXYZINormal>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZINormal>);
        tree->setInputCloud(cloud);
    
        for (auto idx = 0; idx < grid->points.size(); idx++)
        {
            std::vector<int> indices;
            std::vector<float> distances;
            if (tree->radiusSearch(grid->points[idx], max_resolution, indices, distances, max_nn_to_consider) > 0)
            {
                // Linear Interpolation of:
                //      Intensity
                //      Normals- residual vector to inflate(recondtruct) the surface
                float intensity(0);
                Eigen::Vector3f n(0, 0, 0);
                float weight_factor = 1.0F / accumulate(distances.begin(), distances.end(), 0.0F);
                for (auto i = 0; i < indices.size(); i++)
                {
                    float w = weight_factor * distances[i];
                    intensity += w * cloud->points[indices[i]].intensity;
                    auto res = cloud->points[indices[i]].getVector3fMap() - grid->points[idx].getVector3fMap();
                    n += w * res;
                }
                grid->points[idx].intensity = intensity;
                grid->points[idx].getNormalVector3fMap() = n;
                grid->points[idx].curvature = 1;
            }
            else
            {
                grid->points[idx].intensity = 0;
                grid->points[idx].curvature = 0;
                grid->points[idx].getNormalVector3fMap() = Eigen::Vector3f(0, 0, 0);
            }
        }
    }
    

    现在您有了一个网格(有组织的云),您可以轻松地将其映射到图像。您对图像所做的任何更改,都可以映射回网格,并使用法线投影回原始点云。

    创建网格的使用示例:

    pcl::PointCloud<pcl::PointXYZINormal>::Ptr original_cloud = ...;
    
    // reference frame for the projection
    // e.g. take XZ plane around 0,0,0 of length 100 and map to 128*128 image
    Eigen::Vector3f origin = Eigen::Vector3f(0,0,0);
    Eigen::Vector3f axis_x = Eigen::Vector3f(1,0,0);
    Eigen::Vector3f axis_y = Eigen::Vector3f(0,0,1);
    float length    = 100
    int image_size  = 128
    
    auto aux_cloud = ProjectToPlane(original_cloud, origin, axis_x, axis_y);
    // aux_cloud now contains the points of original_cloud, with:
    //      xyz coordinates projected to XZ plane
    //      color (intensity) of the original_cloud (remains unchanged)
    //      normals - we lose the normal information, as we use this field to save the projection information. if you wish to keep the normal data, you should define a custom PointType. 
    // note: for the sake of projection, the origin is only used to define the plane, so any arbitrary point on the plane can be used
    
    
    auto grid = GenerateGrid(origin, axis_x , axis_y, length, image_size)
    // organized cloud that can be trivially mapped to an image
    
    float max_resolution = 2 * length / image_size;
    int max_nn_to_consider = 16;
    InterpolateToGrid(aux_cloud, grid, max_resolution, max_nn_to_consider);
    // Now you have a grid (an organized cloud), which you can easily map to an image. Any changes you make to the images, you can map back to the grid, and use the normals to project back to your original point cloud.
    

    关于如何使用网格的其他辅助方法:

    // Convert an Organized cloud to cv::Mat (an image and a mask)
    //      point Intensity is used for the image
    //          if as_float is true => take the raw intensity (image is CV_32F)
    //          if as_float is false => assume intensity is in range [0, 255] and round it (image is CV_8U)
    //      point Curvature is used for the mask (assume 1 or 0)
    std::pair<cv::Mat, cv::Mat> ConvertGridToImage(pcl::PointCloud<pcl::PointXYZINormal>::Ptr grid, bool as_float)
    {   
        int rows = grid->height;
        int cols = grid->width;
    
        if ((rows <= 0) || (cols <= 0)) 
            return pair<Mat, Mat>(Mat(), Mat());
    
        // Initialize
    
        Mat image = Mat(rows, cols, as_float? CV_32F : CV_8U);
        Mat mask  = Mat(rows, cols, CV_8U);
    
        if (as_float)
        {
            for (int y = 0; y < image.rows; y++)
            {
                for (int x = 0; x < image.cols; x++)
                {
                    image.at<float>(y, x) = grid->at(x, image.rows - y - 1).intensity;
                    mask.at<uchar>(y, x) = 255 * grid->at(x, image.rows - y - 1).curvature;
                }
            }
        }
        else
        {
            for (int y = 0; y < image.rows; y++)
            {
                for (int x = 0; x < image.cols; x++)
                {
                    image.at<uchar>(y, x) = (int)round(grid->at(x, image.rows - y - 1).intensity);
                    mask.at<uchar>(y, x) = 255 * grid->at(x, image.rows - y - 1).curvature;
                }
            }
        }
    
        return pair<Mat, Mat>(image, mask);
    }
    
    // project image to cloud (using the grid data)
    // organized - whether the resulting cloud should be an organized cloud
    pcl::PointCloud<pcl::PointXYZI>::Ptr BackProjectImage(cv::Mat image, pcl::PointCloud<pcl::PointXYZINormal>::Ptr grid, bool organized)
    {
        if ((image.size().height != grid->height) || (image.size().width != grid->width))
        {
            assert(false);
            throw;
        }
    
        PointCloud<PointXYZI>::Ptr cloud(new PointCloud<PointXYZI>);
        cloud->reserve(grid->height * grid->width);
    
        // order of iteration is critical for organized target cloud
        for (auto r = image.size().height - 1; r >= 0; r--)
        {
            for (auto c = 0; c < image.size().width; c++)
            {
                PointXYZI point;
                auto mask_value = mask.at<uchar>(image.rows - r - 1, c);
                if (mask_value > 0) // valid pixel
                {
                    point.intensity = mask_value;
                    point.getVector3fMap() = grid->at(c, r).getVector3fMap() + grid->at(c, r).getNormalVector3fMap();
                }
                else // invalid pixel
                {
                    if (organized)
                    {
                        point.intensity = 0;
                        point.x = numeric_limits<float>::quiet_NaN();
                        point.y = numeric_limits<float>::quiet_NaN();
                        point.z = numeric_limits<float>::quiet_NaN();
                    }
                    else
                    {
                        continue;
                    }
                }
    
                cloud->push_back(point);
            }
        }
    
        if (organized)
        {
            cloud->width = grid->width;
            cloud->height = grid->height;
        }
    
        return cloud;
    }
    

    使用网格的使用示例:

    // image_mask is std::pair<cv::Mat, cv::Mat>
    auto image_mask = ConvertGridToImage(grid, false);
    
    ...
    do some work with the image/mask
    ...
    
    auto new_cloud = BackProjectImage(image_mask.first, grid, false);
    

    【讨论】:

    • 您能否分享您的main.cpp 以获得此答案?如何从无组织的云中插入颜色似乎有点令人困惑。
    • 我更新了答案,如果您还有任何问题,请告诉我。
    • 我有一个问题,在第一个代码块中,这一行 itPoint->getNormalVector3fMap() = itPoint->getVector3fMap() - proj;因为 itPoint->getVector3fMap() = proj;那么在这个赋值之后,itPoint->getNormalVector3fMap() 都为零
    • 在第二个代码块中,在这一行中 image_cloud->at(i, j).getVector3fMap() = center + (x * step * axisx) + (y * step * axisy);什么是center、axisx和axisy?谢谢。
    • @Kaiwen,你是对的。好像我的答案有错误。我会在接下来的几天里仔细研究一下。
    【解决方案2】:

    您可能已经注意到,对于无组织的点云,高度和宽度具有不同的含义。 http://pointclouds.org/documentation/tutorials/basic_structures.php

    将无组织的点云转换为图像并不那么简单,因为点表示为浮点数并且没有定义的透视图。但是,您可以通过确定视角并为点创建离散箱来解决此问题。类似的问题和答案可以在这里找到:Converting a pointcloud to a depth/multi channel image

    【讨论】:

    • 好的。谢谢你。我要试一试。但是,如何将像素转换为 3D?
    猜你喜欢
    • 1970-01-01
    • 2018-11-27
    • 2023-01-18
    • 2020-04-22
    • 1970-01-01
    • 2013-12-17
    • 2016-03-08
    • 1970-01-01
    • 2021-01-13
    相关资源
    最近更新 更多