专栏名称: 新机器视觉
最前沿的机器视觉与计算机视觉技术
目录
相关文章推荐
大皖新闻  ·  元宵节!安徽一市公共自行车停运 ·  3 天前  
安徽省发展改革委  ·  安徽省新能源汽车产业集群建设企业巡展——【2 ... ·  3 天前  
安徽省发展改革委  ·  安徽省新能源汽车产业集群建设企业巡展——【2 ... ·  3 天前  
51好读  ›  专栏  ›  新机器视觉

如何从点云创建深度图像,看这篇你就懂了

新机器视觉  · 公众号  ·  · 2024-09-11 09:00

正文


作者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           // 深度图像头文件int main (int argc, char** argv){    pcl::PointCloud<: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 <"\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 buildcd buildcmake ..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 #include #include #include #include #include #include #include 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: "<\n\n"              << "Options:\n"              << "-------------------------------------------\n"              << "-r    angular resolution in degrees (default "<     coordinate frame (default "<< (int)coordinate_frame<= 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<= 0)    {        cout << "Setting angular resolution to "<::Ptr point_cloud_ptr (new pcl::PointCloud);    pcl::PointCloud& point_cloud = *point_cloud_ptr;    pcl::PointCloud<:pointwithviewpoint> far_ranges;    Eigen::Affine3f scene_sensor_pose (Eigen::Affine3f::Identity ());    std::vector 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 \""< 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<: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 point_cloud_color_handler (point_cloud_ptr, 0, 0, 0);    viewer.addPointCloud (point_cloud_ptr, point_cloud_color_handler, "original point cloud");//PointCloudColorHandlerCustom<: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<:borderdescription> border_descriptions;    border_extractor.compute (border_descriptions);/* 在可视化窗口中显示点集*/    pcl::PointCloud<:pointwithrange>::Ptr border_points_ptr(new pcl::PointCloud<:pointwithrange>),                                                                 veil_points_ptr(new pcl::PointCloud<:pointwithrange>),                                                                 shadow_points_ptr(new pcl::PointCloud<:pointwithrange>);    pcl::PointCloud<: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<:pointwithrange> border_points_color_handler (border_points_ptr, 0, 255, 0);    viewer.addPointCloud<:pointwithrange> (border_points_ptr, border_points_color_handler, "border points");    viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "border points");    pcl::visualization::PointCloudColorHandlerCustom<:pointwithrange> veil_points_color_handler (veil_points_ptr, 255, 0, 0);    viewer.addPointCloud<:pointwithrange> (veil_points_ptr, veil_points_color_handler, "veil points");    viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "veil points");    pcl::visualization::PointCloudColorHandlerCustom<:pointwithrange> shadow_points_color_handler (shadow_points_ptr, 0, 255, 255);    viewer.addPointCloud<: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::infinity (),                                                                         std::numeric_limits::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 \""<}
使用以下代码之后将它们并入到深度图像中:
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 buildcd buildcmake ..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






请到「今天看啥」查看全文