作者I Roar冷颜@CSDN

编辑I 3D视觉开发者社区

前言

目前,深度图像的获取方法有:激光雷达深度成像法、计算机立体视觉成像、坐标测量机法、莫尔条纹法、结构光法等。针对深度图像的研究重点主要集中在以下几个方面:深度图像的分割技术,深度图像的边缘检测技术,基于不同视点的多幅深度图像的配准技术,基于深度数据的三维重建技术,基于深度图像的三维目标检测技术,深度数据的多分辨率建模和几何压缩技术等。在PCL中深度图像与点云最主要的区别在于,其近邻的检索方式不同,并且可以互相转换。

本文首先对深度图像的概念以及表示方法进行简介,其次对PCL的两个RangeImage类进行简单介绍,最后通过如何从一个点云创建一个深度图像以及如何从深度图像中提取物体边界的两个应用实例,来展示如何对PCL中的RangeImage相关类进行灵活运用。

RangeImage概念及相关算法

1.1 深度图像简介

深度图像(Depth Images),也被称为距离影像(Range Images),是指将图像采集器到场景中各点的距离(深度)值作为像素值的图像,它直接反映了景物可见表面的几何形状,利用它可以很方便的解决3D目标描述中的许多问题。深度图像经过坐标转换可以计算为点云数据,有规则及必要信息的点云数据也可以反算为深度图像数据。

从数学模型上看,深度图像可以看作是标量函数  在集合  上的离散采样,得到  ,其中  为二维网格(矩阵)的索引,  ,

, ,如下图所示:

1.2 PCL中RangeImage的相关类

PCL中的range_image库包含两个表达深度图像和对深度图像进行操作的类,其依赖于pcl::common模块。深度图像(或距离图像)的像素值代表从传感器到物体的距离或者深度,如下图所示。深度图像是物体三维表示形式,一般通过立体相机或者ToF相机获取。如果知道相机的内标定参数,就可以将深度图像转化为点云。

从一个点云创建一个深度图像

本节将介绍如何从点云和给定的传感器位置来创建深度图像:

首先创建一个工作空间range_image_creation,然后再在工作空间创建一个文件夹src用于存放源代码:

mkdir -p range_image_creation/src

接着,在range_image_creation/src路径下,创建一个文件并命名为range_image_creation.cpp,拷贝如下代码:

#include <pcl/range_image/range_image.h>          // 深度图像头文件int main (int argc, char** argv){    pcl::PointCloud<pcl::PointXYZ> pointCloud;    // 定义点云对象/* 生成点云数据 */for (float y=-0.5f; y<=0.5f; y+=0.01f)     {for (float z=-0.5f; z<=0.5f; z+=0.01f)         {            pcl::PointXYZ point;            point.x = 2.0f - y;            point.y = y;            point.z = z;            pointCloud.points.push_back(point);        }    }    pointCloud.width = (uint32_t) pointCloud.points.size();    pointCloud.height = 1;    // 设置点云对象的头信息float angularResolution = (float) (  1.0f * (M_PI/180.0f));   // 按弧度1°float maxAngleWidth     = (float) (360.0f * (M_PI/180.0f));   // 按弧度360°float maxAngleHeight    = (float) (180.0f * (M_PI/180.0f));   // 按弧度180°    Eigen::Affine3f sensorPose = (Eigen::Affine3f)Eigen::Translation3f(0.0f, 0.0f, 0.0f); // 采集位置    pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;    // 深度图像遵循的坐标系统float noiseLevel=0.00;float minRange = 0.0f;int borderSize = 1;    pcl::RangeImage rangeImage;    rangeImage.createFromPointCloud(pointCloud,                                     angularResolution,                                     maxAngleWidth,                                     maxAngleHeight,                                     sensorPose,                                     coordinate_frame,                                     noiseLevel,                                     minRange,                                     borderSize);std::cout << rangeImage << "\n";}

【解释说明】

/* 生成点云数据 */for (float y=-0.5f; y<=0.5f; y+=0.01f) {for (float z=-0.5f; z<=0.5f; z+=0.01f)     {        pcl::PointXYZ point;        point.x = 2.0f - y;        point.y = y;        point.z = z;        pointCloud.points.push_back(point);    }}pointCloud.width = (uint32_t) pointCloud.points.size();pointCloud.height = 1;    // 设置点云对象的头信息

这段程序首先创建一组数据作为点云的数据内容,再设置点云头的信息,整个实现生成一个呈现矩形形状的点云,如下图所示:

float angularResolution = (float) (  1.0f * (M_PI/180.0f));   // 按弧度1°
float maxAngleWidth     = (float) (360.0f * (M_PI/180.0f));   // 按弧度360°
float maxAngleHeight    = (float) (180.0f * (M_PI/180.0f));   // 按弧度180°Eigen::Affine3f sensorPose = (Eigen::Affine3f)Eigen::Translation3f(0.0f, 0.0f, 0.0f); // 采集位置
pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;    // 深度图像遵循的坐标系统
float noiseLevel=0.00;
float minRange = 0.0f;
int borderSize = 1;

这部分定义了创建深度图像时需要的设置参数,将角度分辨率angularResolution定义为1°,意味着邻近的像素点所对应的每个光束之间相差1°;maxAngleWidth = 360 和 maxAngleHeight = 180 意味着,我们进行模拟的距离传感器对周围的环境拥有一个完整的360°视角,用户在任意数据集下都可以使用此设置,因为最终获取的深度图像将被裁剪到有空间物体存在的区域范围。但是,用户可以通过减小数值来节省一些计算资源,例如:当传感器后面没有可以观测的点时,一个水平视角为180°的激光扫描仪,即maxAngleWidth = 180 就足够了,这样只需要观察距离传感器前面就可以了,因为后面没有需要观察的场景。sensorPose定义了模拟深度图像获取传感器的6自由度位置,其原始值横滚角roll、俯仰角pitch、偏航角yaw都为0。coordinate_frame = pcl::RangeImage::CAMERA_FRAME说明系统的X轴是向右的,Y轴是向下的,Z轴是向前的。另外一个选择是LASER_FRAME,其X轴向前,Y轴向左,Z轴向上。noiseLevel = 0是指使用一个归一化的Z缓冲器来创建深度图像,但是如果想让邻近点集都落在同一像素单元,用户可以设置一个较高的值,例如noiseLevel = 0.05可以理解为,深度距离值是通过查询点半径为5cm的圆内包含的点以平均计算而得到的。如果minRange > 0,则所有模拟器所在的位置半径minRange内的邻近点都将被忽略,即为盲区。在裁剪图像时,如果borderSize > 0,将在图像周围留下当前视点不可见点的边界。

pcl::RangeImage rangeImage;rangeImage.createFromPointCloud(pointCloud,                                 angularResolution,                                 maxAngleWidth,                                 maxAngleHeight,                                 sensorPose,                                 coordinate_frame,                                 noiseLevel,                                 minRange,                                 borderSize);std::cout << rangeImage << "\n";

最后,使用上述设定的参数,从点云创建深度图像,并且打印一些信息。

深度图像继承于PointCloud类,它的点类型具有x,y,z和range距离字段,共有三种类型的点集,有效点集是距离大于0的点集,当前视点不可见点集x = y = z = NAN且值域为负无穷大,远距离点集x = y = z = NAN且值域为无穷大。

【编译和运行程序】

在工作空间根目录range_image_creation下,编写CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 2.6 FATAL_ERROR)project(range_image_creation)find_package(PCL 1.2 REQUIRED)include_directories(${PCL_INCLUDE_DIRS})link_directories(${PCL_LIBRARY_DIRS})add_definitions(${PCL_DEFINITIONS})add_executable (${PROJECT_NAME}_node src/range_image_creation.cpp)target_link_libraries (${PROJECT_NAME}_node ${PCL_LIBRARIES})

在工作空间根目录range_image_creation下创建一个build文件夹,用于存放编译过程中产生的文件,然后执行编译:

mkdir build
cd build
cmake ..
make

此时,会在build文件夹下生成一个可执行文件range_image_creation_node,运行该可执行文件:

./range_image_creation_node

在终端中可以看到如下输出结果:

header: seq: 0 stamp: 0 frame_id: points[]: 1360width: 40height: 34sensor_origin_: 0 0 0sensor_orientation_: 0 0 0 1is_dense: 0angular resolution: 1deg/pixel in x and 1deg/pixel in y.

从深度图像中提取边界

本小节将学习如何从深度图像中提取边界(从前景跨越到背景的位置定义为边界):

一般,我们只对三种类型的点集感兴趣:物体边界,这是物体最外层和阴影边界的可见点集;阴影边界:毗连于遮挡的背景上的点集;Veil点集,在被遮挡物边界和阴影边界之间的内插点,它们是由激光雷达获取的3D距离数据中的典型数据类型。这三类数据及深度图像的边界如下图所示:

首先创建一个工作空间range_image_border_extraction,然后再在工作空间创建一个文件夹src用于存放源代码:

mkdir -p range_image_border_extraction/src

接着,在range_image_border_extraction/src路径下,创建一个文件并命名为range_image_border_extraction.cpp,拷贝如下代码:

#include <iostream>#include <boost/thread/thread.hpp>#include <pcl/range_image/range_image.h>#include <pcl/io/pcd_io.h>#include <pcl/visualization/range_image_visualizer.h>#include <pcl/visualization/pcl_visualizer.h>#include <pcl/features/range_image_border_extractor.h>#include <pcl/console/parse.h>typedef pcl::PointXYZ PointType;/* 参数初始化 */float angular_resolution = 0.5f;pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;bool setUnseenToMaxRange = false;/* 帮助 */void printUsage (const char* progName){    std::cout << "\n\nUsage: "<<progName<<" [options] <scene.pcd>\n\n"              << "Options:\n"              << "-------------------------------------------\n"              << "-r <float>   angular resolution in degrees (default "<<angular_resolution<<")\n"              << "-c <int>     coordinate frame (default "<< (int)coordinate_frame<<")\n"              << "-m           Treat all unseen points to max range\n"              << "-h           this help\n"              << "\n\n";}int  main (int argc, char** argv){/* 解析命令行参数 */if (pcl::console::find_argument (argc, argv, "-h") >= 0)    {        printUsage (argv[0]);return 0;    }if (pcl::console::find_argument (argc, argv, "-m") >= 0)    {        setUnseenToMaxRange = true;        cout << "Setting unseen values in range image to maximum range readings.\n";    }    int tmp_coordinate_frame;if (pcl::console::parse (argc, argv, "-c", tmp_coordinate_frame) >= 0)    {        coordinate_frame = pcl::RangeImage::CoordinateFrame (tmp_coordinate_frame);        cout << "Using coordinate frame "<< (int)coordinate_frame<<".\n";    }if (pcl::console::parse (argc, argv, "-r", angular_resolution) >= 0)    {        cout << "Setting angular resolution to "<<angular_resolution<<"deg.\n";    }    angular_resolution = pcl::deg2rad (angular_resolution);/* 读取pcd文件,如果没有给出pcd文件则创建一个示例点云 */    pcl::PointCloud<PointType>::Ptr point_cloud_ptr (new pcl::PointCloud<PointType>);    pcl::PointCloud<PointType>& point_cloud = *point_cloud_ptr;    pcl::PointCloud<pcl::PointWithViewpoint> far_ranges;    Eigen::Affine3f scene_sensor_pose (Eigen::Affine3f::Identity ());    std::vector<int> pcd_filename_indices = pcl::console::parse_file_extension_argument (argc, argv, "pcd");if (!pcd_filename_indices.empty ())    {        std::string filename = argv[pcd_filename_indices[0]];if (pcl::io::loadPCDFile (filename, point_cloud) == -1)        {            cout << "Was not able to open file \""<<filename<<"\".\n";            printUsage (argv[0]);return 0;        }        scene_sensor_pose = Eigen::Affine3f (Eigen::Translation3f (point_cloud.sensor_origin_[0],                                                                   point_cloud.sensor_origin_[1],                                                                   point_cloud.sensor_origin_[2])) *                                             Eigen::Affine3f (point_cloud.sensor_orientation_);        std::string far_ranges_filename = pcl::getFilenameWithoutExtension (filename)+"_far_ranges.pcd";if (pcl::io::loadPCDFile(far_ranges_filename.c_str(), far_ranges) == -1)        {            std::cout << "Far ranges file \""<<far_ranges_filename<<"\" does not exists.\n";        }    }else    {        cout << "\nNo *.pcd file given => Genarating example point cloud.\n\n";for (float x=-0.5f; x<=0.5f; x+=0.01f)        {for (float y=-0.5f; y<=0.5f; y+=0.01f)            {                PointType point;  point.x = x;  point.y = y;  point.z = 2.0f - y;                point_cloud.points.push_back (point);            }        }        point_cloud.width = (int) point_cloud.points.size ();  point_cloud.height = 1;    }/* 从点云创建深度图像 */    float noise_level = 0.0;    float min_range = 0.0f;    int border_size = 1;    boost::shared_ptr<pcl::RangeImage> range_image_ptr (new pcl::RangeImage);    pcl::RangeImage& range_image = *range_image_ptr;       range_image.createFromPointCloud (point_cloud,                                       angular_resolution,                                       pcl::deg2rad (360.0f),                                       pcl::deg2rad (180.0f),                                       scene_sensor_pose,                                       coordinate_frame,                                       noise_level,                                       min_range,                                       border_size);    range_image.integrateFarRanges (far_ranges);if (setUnseenToMaxRange)        range_image.setUnseenToMaxRange ();/* 打开可视化窗口并添加点云 */    pcl::visualization::PCLVisualizer viewer ("3D Viewer");    viewer.setBackgroundColor (1, 1, 1);    viewer.addCoordinateSystem (1.0f);    pcl::visualization::PointCloudColorHandlerCustom<PointType> point_cloud_color_handler (point_cloud_ptr, 0, 0, 0);    viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud");//PointCloudColorHandlerCustom<pcl::PointWithRange>   range_image_color_handler (range_image_ptr, 150, 150, 150);//viewer.addPointCloud (range_image_ptr, range_image_color_handler, "range image");//viewer.setPointCloudRenderingProperties (PCL_VISUALIZER_POINT_SIZE, 2, "range image");/* 提取边界 */    pcl::RangeImageBorderExtractor border_extractor (&range_image);    pcl::PointCloud<pcl::BorderDescription> border_descriptions;    border_extractor.compute (border_descriptions);/* 在可视化窗口中显示点集*/    pcl::PointCloud<pcl::PointWithRange>::Ptr border_points_ptr(new pcl::PointCloud<pcl::PointWithRange>),                                                                 veil_points_ptr(new pcl::PointCloud<pcl::PointWithRange>),                                                                 shadow_points_ptr(new pcl::PointCloud<pcl::PointWithRange>);    pcl::PointCloud<pcl::PointWithRange>& border_points = *border_points_ptr, & veil_points = * veil_points_ptr, & shadow_points = *shadow_points_ptr;for (int y=0; y< (int)range_image.height; ++y)    {for (int x=0; x< (int)range_image.width; ++x)        {if (border_descriptions.points[y*range_image.width + x].traits[pcl::BORDER_TRAIT__OBSTACLE_BORDER])                border_points.points.push_back (range_image.points[y*range_image.width + x]);if (border_descriptions.points[y*range_image.width + x].traits[pcl::BORDER_TRAIT__VEIL_POINT])                veil_points.points.push_back (range_image.points[y*range_image.width + x]);if (border_descriptions.points[y*range_image.width + x].traits[pcl::BORDER_TRAIT__SHADOW_BORDER])                shadow_points.points.push_back (range_image.points[y*range_image.width + x]);        }    }    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> border_points_color_handler (border_points_ptr, 0, 255, 0);    viewer.addPointCloud<pcl::PointWithRange> (border_points_ptr, border_points_color_handler, "border points");    viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "border points");    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> veil_points_color_handler (veil_points_ptr, 255, 0, 0);    viewer.addPointCloud<pcl::PointWithRange> (veil_points_ptr, veil_points_color_handler, "veil points");    viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "veil points");    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointWithRange> shadow_points_color_handler (shadow_points_ptr, 0, 255, 255);    viewer.addPointCloud<pcl::PointWithRange> (shadow_points_ptr, shadow_points_color_handler, "shadow points");    viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "shadow points");/* 在深度图像中显示点集 */    pcl::visualization::RangeImageVisualizer* range_image_borders_widget = NULL;    range_image_borders_widget = pcl::visualization::RangeImageVisualizer::getRangeImageBordersWidget (range_image,                                                       -std::numeric_limits<float>::infinity (),                                                                         std::numeric_limits<float>::infinity (), false,                                                                                                        border_descriptions, "Range image with borders" );                     while (!viewer.wasStopped ())    {      range_image_borders_widget->spinOnce ();      viewer.spinOnce ();      pcl_sleep(0.01);    }}

【解释说明】

首先,我们进行命令行解析,然后,读取点云(或者如果没有提供,则创建一个点云),最后,创建深度图像并使其可视化。提取边界信息时很重要的一点是区分深度图像中的当前视点不可见点集合和应该可见但处于传感器获取距离范围外的点集,后者可以标记为典型边界,然而当前视点不可见点则不能成为边界。因此,如果后者的测量值存在,则提供那些超出传感器距离获取范围外的数据对于边界提取是非常有用的,我们希望找到另外的包含这些值的pcd文件,这里代码中用far_range.pcd作为这类数据的实例。

std::string far_ranges_filename = pcl::getFilenameWithoutExtension (filename)+"_far_ranges.pcd";
if (pcl::io::loadPCDFile(far_ranges_filename.c_str(), far_ranges) == -1)
{
std::cout << "Far ranges file \""<<far_ranges_filename<<"\" does not exists.\n";
}

使用以下代码之后将它们并入到深度图像中:

range_image.integrateFarRanges (far_ranges);

如果用户没有这些远距离的点云,则可采用命令行参数-m,这样设置所有不能观察到的点都为远距离的,以下代码来执行这一步:

if (setUnseenToMaxRange)range_image.setUnseenToMaxRange ();

此部分创建了RangeImageBorderExtractor这个对象,给了它深度图像,并且计算后存储边界信息在border_descriptions中(详见BorderDescription结构中的common/include/pcl/point_types.h),余的代码只是用来可视化的。

【编译和运行程序】

在工作空间根目录range_image_border_extraction下,编写CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 2.6 FATAL_ERROR)project(range_image_border_extraction)find_package(PCL 1.3 REQUIRED)include_directories(${PCL_INCLUDE_DIRS})link_directories(${PCL_LIBRARY_DIRS})add_definitions(${PCL_DEFINITIONS})add_executable (${PROJECT_NAME}_node src/range_image_border_extraction.cpp)target_link_libraries (${PROJECT_NAME}_node ${PCL_LIBRARIES})

在工作空间根目录range_image_border_extraction下创建一个build文件夹,用于存放编译过程中产生的文件,然后执行编译:

mkdir build
cd build
cmake ..
make

此时,会在build文件夹下生成一个可执行文件range_image_border_extraction_node,运行该可执行文件:

./range_image_border_extraction_node -m

这将使用一个自动生成的、矩形状浮点型点云,运行结果如下图所示,检测出的边缘点用绿色较大的点表示,其他点用默认的黑色普通大小点表示:

点云到深度图的变换与曲面重建

本节将介绍如何将点云数据转化为深度图像,进而使用PCL内部只适用于深度图的算法来进行曲面重建等:

有时我们获取的点云数据是从一个视点获取的,为了使用深度图相关的计算方法,以提高效率等,我们需要将点云数据转化为深度图。这两种数据的主要区别在于,点云数据需要通过k-d tree等索引来对数据进行检索,而深度图和图像类似,可以通过上下左右等近邻来直接进行索引。

首先创建一个工作空间greedy_projection,然后再在工作空间创建一个文件夹src用于存放源代码:

mkdir -p greedy_projection/src

接着,在greedy_projection/src路径下,创建一个文件并命名为greedy_projection.cpp,拷贝如下代码:

#include <pcl/range_image/range_image.h>#include <pcl/range_image/range_image_planar.h>#include <pcl/io/io.h>#include <pcl/io/pcd_io.h>#include <pcl/features/integral_image_normal.h>#include <pcl/visualization/cloud_viewer.h>#include <pcl/point_types.h>#include <pcl/features/normal_3d.h>#include <pcl/console/print.h>#include <pcl/surface/organized_fast_mesh.h>#include <pcl/console/time.h>#include <Eigen/StdVector>#include <Eigen/Geometry>#include <iostream>#include <pcl/surface/impl/organized_fast_mesh.hpp> #include <boost/thread/thread.hpp>#include <pcl/common/common_headers.h>#include <pcl/visualization/range_image_visualizer.h>#include <pcl/visualization/pcl_visualizer.h>#include <pcl/console/parse.h>using namespace pcl::console;int main (int argc, char** argv){// Generate the dataif (argc<2)  {    print_error ("Syntax is: %s input.pcd -w 640 -h 480 -cx 320 -cy 240 -fx 525 -fy 525 -type 0 -size 2\n", argv[0]);    print_info ("  where options are:\n");    print_info ("                     -w X = width of detph iamge\n ");return -1;  }int width=640,height=480,size=2,type=0;float fx=525,fy=525,cx=320,cy=240;  parse_argument (argc, argv, "-w", width);       // 深度图像宽度  parse_argument (argc, argv, "-h", height);      // 深度图像高度  parse_argument (argc, argv, "-cx", cx);         // 光轴在深度图像上的x坐标  parse_argument (argc, argv, "-cy", cy);         // 光轴在深度图像上的y坐标  parse_argument (argc, argv, "-fx", fx);         // 水平方向焦距  parse_argument (argc, argv, "-fy", fy);         // 垂直方向焦距  parse_argument (argc, argv, "-type", type);     // 曲面重建时三角化的方式  parse_argument (argc, argv, "-size", size);     // 曲面重建时的面片的大小  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);  pcl::io::loadPCDFile ("../pcd/1.pcd", *cloud);  print_info ("Read pcd file successfully\n");/* 原始点云数据可视化*/    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer1 (new pcl::visualization::PCLVisualizer ("original point cloud"));    viewer1->setBackgroundColor (0.5,0.5,0.5);    viewer1->addCoordinateSystem ();    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> point_cloud_color_handler (cloud, 0, 0, 0);    viewer1->addPointCloud (cloud, point_cloud_color_handler, "original point cloud");/* 点云数据转化为深度图像 */  Eigen::Affine3f sensorPose;  sensorPose.setIdentity();   pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;float noiseLevel=0.00;  // 成像时模拟噪声的水平float minRange = 0.0f;  // 成像时考虑该阈值外的点  pcl::RangeImagePlanar::Ptr rangeImage(new pcl::RangeImagePlanar);  rangeImage->createFromPointCloudWithFixedSize(*cloud,width,height,cx,cy,fx,fy,sensorPose,coordinate_frame);std::cout << rangeImage << "\n";/* 深度图像可视化 */  pcl::visualization::RangeImageVisualizer range_image_widget ("RangeImage");  range_image_widget.showRangeImage (*rangeImage);  range_image_widget.setWindowTitle("RangeImage");  pcl::OrganizedFastMesh<pcl::PointWithRange>::Ptr tri(new pcl::OrganizedFastMesh<pcl::PointWithRange>);  pcl::search::KdTree<pcl::PointWithRange>::Ptr tree (new pcl::search::KdTree<pcl::PointWithRange>);  tree->setInputCloud(rangeImage);  pcl::PolygonMesh triangles;  tri->setTrianglePixelSize(size);  tri->setInputCloud(rangeImage);  tri->setSearchMethod(tree);  tri->setTriangulationType((pcl::OrganizedFastMesh<pcl::PointWithRange>::TriangulationType)type);  tri->reconstruct(triangles);  boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer (new pcl::visualization::PCLVisualizer ("RangeImage"));  viewer->setBackgroundColor(0.5,0.5,0.5);  viewer->addPolygonMesh(triangles,"tin");  viewer->addCoordinateSystem();while (!range_image_widget.wasStopped ()&&!viewer->wasStopped())  {    range_image_widget.spinOnce ();    pcl_sleep (0.01);    viewer->spinOnce ();  }}

【解释说明】

首先声明了RangeImage结构和RangeImagePlanar等类对应的头文件:

#include <pcl/range_image/range_image.h>
#include <pcl/range_image/range_image_planar.h>
#include <pcl/surface/impl/organized_fast_mesh.hpp>
#include <pcl/visualization/range_image_visualizer.h>

随后,我们首先定义从点云变换到深度图像时所需要的变量,并让用户可以通过命令行参数进行设置,这里包含深度图像的宽度、高度,光轴在深度图像上的坐标点,成像的焦距等,其他参数是后面进行曲面重建时候的参数。在实际使用时,用户需要根据自己的点云数据来源来确定这些参数,本文给出的参数是根据Xtion Pro live深度相机参数确定的。

int width=640,height=480,size=2,type=0;float fx=525,fy=525,cx=320,cy=240;parse_argument (argc, argv, "-w", width);       // 深度图像宽度parse_argument (argc, argv, "-h", height);      // 深度图像高度parse_argument (argc, argv, "-cx", cx);         // 光轴在深度图像上的x坐标parse_argument (argc, argv, "-cy", cy);         // 光轴在深度图像上的y坐标parse_argument (argc, argv, "-fx", fx);         // 水平方向焦距parse_argument (argc, argv, "-fy", fy);         // 垂直方向焦距parse_argument (argc, argv, "-type", type);     // 曲面重建时三角化的方式parse_argument (argc, argv, "-size", size);     // 曲面重建时的面片的大小

下面是对原始点云进行加载,同时创建了RangeImagePlanar对象,利用该对象的函数createFromPointCloudWithFixedSize()进行深度图像的生成。这里的参数除了上面用户提供的参数外,需要提供相机成像时相机的位姿,以及成像时遵循的坐标系统。

/* 读取原始点云 */pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);pcl::io::loadPCDFile ("../pcd/1.pcd", *cloud);print_info ("Read pcd file successfully\n");/* 原始点云数据可视化*/boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer1 (new pcl::visualization::PCLVisualizer ("original point cloud"));viewer1->setBackgroundColor (0.5,0.5,0.5);viewer1->addCoordinateSystem ();pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> point_cloud_color_handler (cloud, 0, 0, 0);viewer1->addPointCloud (cloud, point_cloud_color_handler, "original point cloud");/* 点云数据转化为深度图像 */Eigen::Affine3f sensorPose;sensorPose.setIdentity(); pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;float noiseLevel=0.00;  // 成像时模拟噪声的水平float minRange = 0.0f;  // 成像时考虑该阈值外的点pcl::RangeImagePlanar::Ptr rangeImage(new pcl::RangeImagePlanar);rangeImage->createFromPointCloudWithFixedSize(*cloud,width,height,cx,cy,fx,fy,sensorPose,coordinate_frame);std::cout << rangeImage << "\n";

下面使用RangeImageVisualizer 对象来对创建的深度图像进行可视化。

pcl::visualization::RangeImageVisualizer range_image_widget ("RangeImage");range_image_widget.showRangeImage (*rangeImage);

完成从点云到深度图像的生成后,利用该深度图像作为输入,来使用曲面重建模块中的简单三角化类生成曲面模型。创建OrganizedFastMesh对象,该算法的输入参数有size,通过setTrianglePixelSize()函数接口来实现,其控制重建曲面的精细程度。另外一个参数是setTriangulationType()方法设置的三角化类型,是个枚举变量,包含三角形、四边形等。

pcl::OrganizedFastMesh<pcl::PointWithRange>::Ptr tri(new pcl::OrganizedFastMesh<pcl::PointWithRange>);
pcl::search::KdTree<pcl::PointWithRange>::Ptr tree (new pcl::search::KdTree<pcl::PointWithRange>);
tree->setInputCloud(rangeImage);
pcl::PolygonMesh triangles;
tri->setTrianglePixelSize(size);
tri->setInputCloud(rangeImage);
tri->setSearchMethod(tree);
tri->setTriangulationType((pcl::OrganizedFastMesh<pcl::PointWithRange>::TriangulationType)type);
tri->reconstruct(triangles);

最后,对重建结果进行可视化。

【编译和运行程序】

在工作空间根目录greedy_projection下,编写CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(greedy_projection)find_package(PCL 1.2 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable (${PROJECT_NAME}_node src/greedy_projection.cpp)
target_link_libraries (${PROJECT_NAME}_node ${PCL_LIBRARIES})

在工作空间根目录greedy_projection下创建一个build文件夹,用于存放编译过程中产生的文件,然后执行编译:

mkdir buildcd buildcmake ..make

此时,会在build文件夹下生成一个可执行文件greedy_projection_node,运行该可执行文件:

./greedy_projection_node -size 5

此时会看到类似下面三幅图的结果,其中第一幅图为原始点云数据的可视化结果,第二幅图为深度图像可视化结果,第三幅图为曲面重建结果。

版权声明:本文为作者授权转载,由3D视觉开发者社区编辑整理发布,仅做学术分享,未经授权请勿二次传播,版权归原作者所有,图片来源于网络,若涉及侵权内容请联系删文。

本文仅做学术分享,如有侵权,请联系删文。

3D视觉精品课程推荐:

1.面向自动驾驶领域的多传感器数据融合技术

2.面向自动驾驶领域的3D点云目标检测全栈学习路线!(单模态+多模态/数据+代码)
3.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进
4.国内首个面向工业级实战的点云处理课程
5.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解
6.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦
7.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化
8.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)

9.从零搭建一套结构光3D重建系统[理论+源码+实践]

10.单目深度估计方法:算法梳理与代码实现

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

▲长按加微信群或投稿

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列、三维点云系列、结构光系列、手眼标定、相机标定、激光/视觉SLAM自动驾驶等)、知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近4000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

圈里有高质量教程资料、答疑解惑、助你高效解决问题

觉得有用,麻烦给个赞和在看~  

如何从点云创建深度图像,看这篇你就懂了(附详细代码)相关推荐

  1. PCL点云与深度图像

    PCL点云与深度图像 1 RangeImage概念及相关算法 1.1 深度图像简介 1.2 PCL中RangeImage的相关类 2 从一个点云创建一个深度图像 3 从深度图像中提取边界 4 点云到深 ...

  2. PCL 由点云生成深度图像

    前言:在电脑上的pcl1.8.0版本可能是由于版本问题,无法在窗口显示深度图像,但是深度图像确实是生成了的,可以通过一个API将深度图像保存为一个png格式的图片然后查看. 该函数如下: //save ...

  3. PCL点云生成深度图像

    https://www.cnblogs.com/li-yao7758258/p/6476046.html (1)点云到深度图与可视化的实现 区分点云与深度图本质的区别 1.深度图像也叫距离影像,是指将 ...

  4. 茶的分类计算机基础知识,【收藏】六大茶类的本质区别,看这篇就够了!(附茶叶分类图)...

    原标题:[收藏]六大茶类的本质区别,看这篇就够了!(附茶叶分类图) 其实六大茶类的划分标准和本质区别在于,制作工艺和茶叶中茶多酚的氧化程度.接下来就按茶叶发酵程度由低到高排序,简要介绍各类茶之间的区别 ...

  5. Python实现图像直方图规定化(直方图匹配)-附完整代码

    以下内容需要直方图均衡化.规定化知识 均衡化:https://blog.csdn.net/macunshi/article/details/79815870 规定化:https://blog.csdn ...

  6. 独家揭秘:阿里小程序的一云多端!看这篇就够了!

    2019独角兽企业重金招聘Python工程师标准>>> 专家介绍 视频回放 https://yq.aliyun.com/live/1097 阿里小程序的一云多端 相信绝大部分同学知道 ...

  7. 阿里云盘内测_阿里云盘深度体验,70m/s下载速度真恐怖!附内测码领取方法~

    吊足大家胃口的阿里云网盘终于来了,前两天小帮就收到了阿里云盘种子用户的邀请码,距离上次申请邀请码差不多过了半个月的时间. 之所以对阿里云网盘的期待,罪魁祸首还是因某度网盘针对非会员用户的下载限速问题, ...

  8. 创建线程的几种方式---最全面的创建线程方式总结---线程创建方式面试看这篇就够了

    前言:我相信创建线程的方式对于所有的java开发程序员来说都不陌生,在面试过程中这个问题也是高频考点.鉴于此,小编用本篇博文来整理几种线程的创建方式,希望对同学们有所帮助~ 文章目录 一.什么是进程? ...

  9. 【图像检测】基于深度学习 (CAE) 实现材料异常检测和定位附matlab代码

    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信.

最新文章

  1. 如何实现后台向前台传数据
  2. js基础篇——localStorage使用要点
  3. python教程:有参装饰器
  4. 专家:电脑看多了掉头发怎么办?
  5. java sax解析复杂xml_SAX解析复杂的xml字符串
  6. 统计图源代码_openlayers4 入门开发系列结合 echarts4 实现统计图(附源码下载)
  7. M1芯片的Mac在开发iOS项目时遇到的问题汇总(模拟器无法运行,Cocoapods错误等)
  8. 全新版大学英语综合教程第三册学习笔记(原文及全文翻译)——8 - A Clone Is Born(克隆生命诞生了)
  9. 个人技术博客的选择:CSDN、博客园、简书、知乎专栏、Github、新浪、个人建站等?
  10. 一周总结——2020.5.31
  11. Python数据挖掘-OneR算法简介
  12. android倒计时日历软件,Hurry - 一款颜值超高的日历+倒计时 APP - Android 应用 - 纪念日 - 【最美应用】...
  13. 什么是真正的转运?常见的五种转运方法
  14. 数字化转型顶层设计怎么做?建筑央企数字化转型给出答案
  15. 管理工程师错误率较高的真题
  16. Linux系统编程第六节——进程的替换(execl、exelp、execle、execv、execvp、execve)
  17. 本地电脑连接阿里云RDS Mysql数据库问题(10038错误)
  18. 软件流程和管理(七):个人、激励和团队
  19. Git+Gitlab+Ansible剧本实现一键部署动态网站(二)--技术流ken
  20. 网络安全(Web-safe)字体

热门文章

  1. python中def的用法详解_Python3中def的用法
  2. Day_12,归并排序时间复杂度计算
  3. 不同gym游戏 reward的设置探究
  4. Win10开发之UWP控件的隐藏空间
  5. 选房买房请注意这些规则!
  6. 常用开发软件注释及取消注释快捷键汇总
  7. Golang中 int int8 int16 int32 int64的区别和取值范围
  8. 记一次安装银河麒麟arm虚拟机,并在其上安装程序
  9. 目标跟踪算法——KCF入门详解
  10. 打印后显示发送服务器错误怎么办,跟后台打印程序系统服务通讯时出现错误的解决方法...