Bootstrap

【PCL自学:Range Images】深度图与点云数据的转换及其应用(持续更新)

一、什么是深度图

  目前深度图像的获取方法有激光雷达深度成像法、计算机立体视觉成像、坐标测量机法、莫尔条纹法、结构光法等等,针对深度图像的研究重点主要集中在以下几个方面,深度图像的分割技术 ,深度图像的边缘检测技术 ,基于不同视点的多幅深度图像的配准技术,基于深度数据的三维重建技术,基于三维深度图像的三维目标识别技术,深度图像的多分辨率建模和几何压缩技术等等,在PCL 中深度图像与点云最主要的区别在于 其近邻的检索方式的不同,并且可以互相转换。
  深度图像(Depth Images)也被称为距离影像(Range Image),是指将从图像采集器到场景中各点的距离值作为像素值的图像,它直接反应了景物可见表面的几何形状,利用它可以很方便的解决3D目标描述中的许多问题,深度图像经过点云变换可以计算为点云数据,有规则及有必要信息的点云数据可以反算为深度图像数据。

二、点云转深度图 PointCloud->Range Image

  本节演示了如何从点云和一个给定的传感器位姿创建一个深度图像。以下代码创建了一个矩形点云的示例,它浮动在观察者的前面。具体注释非常详细。

#include <pcl/range_image/range_image.h> // 深度图头文件
int main () {
  pcl::PointCloud<pcl::PointXYZ> pointCloud; // 点云
  
  // 创造矩形点云数据,0.01分辨率
  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.push_back(point);
    }
  }
  pointCloud.width = pointCloud.size();
  pointCloud.height = 1;
  
  // 利用以上创造的矩形体点云,创造一个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); // 传感器位姿roll = pitch = yaw = 0(6DOF视角位姿)
  // 深度图所使用的坐标系
  pcl::RangeImage::CoordinateFrame coordinate_frame = pcl::RangeImage::CAMERA_FRAME;
  float noiseLevel=0.00; 
  float minRange = 0.0f; 
  int borderSize = 1;   
  
  pcl::RangeImage rangeImage; // 深度图对象
  // 从以上的点云和设置的参数来创建深度图
  // ================================下面函数参数解释如下======================================
  // [in] 点云、角分辨率1°、
  // [in]maxAngleWidth = 360和maxAngleHeight = 180表示我们正在模拟的深度传感器具有完整的360度周围视图。(注:深度图像将仅裁剪为自动观察到某些内容的区域)。也可以通过减少值来节省一些计算。例如,对于180度视图朝前的激光扫描仪,可以观察到传感器后面没有点,maxAngleWidth = 180就足够了。
  // [in]sensorPose将虚拟深度传感器的6DOF位置定义为roll = pitch = yaw = 0的原点。
  // [in]coordinate_frame = CAMERA_FRAME告诉系统x面向右,y向下,z轴向前。另一种选择是LASER_FRAME,x面向前方,y位于左侧,z位于上方。
  // [in]对于noiseLevel = 0,沿Z轴剖分创建深度图像。然而,想平均落在同一个单元格中的点数,可以使用更高的值。比如0.05表示所有与最近点的最大距离为5cm的点用于计算深度。
  // [in]minRange 如果minRange大于0,所有靠近虚拟深度传感器的点将被忽略。
  // [in]borderSize,当裁剪图像时,如果borderSize大于0将在图像四周留下未观察到的点的边界。
  // [return]rangeImage 深度图
  //=========================================================================================
  rangeImage.createFromPointCloud(pointCloud, angularResolution, maxAngleWidth, maxAngleHeight,
                                  sensorPose, coordinate_frame, noiseLevel, minRange, borderSize);
  std::cout << rangeImage << "\n";
}

  以上代码执行结果如下:

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

  深度图像是从PointCloud类派生出来的,它的点类有成员x,y,z和range。有三种可能形式,被观测点有一个大于零的实际深度range。未观测到的点有x=y=z=NAN和range=-INFINITY。远距离点有x=y=z=NAN和range=INFINITY。

三、利用深度图提取点云边缘

  本节演示了如何从深度图像中提取边界(即从前景到背景的分割)。我们感兴趣的是三种不同的点集:
物体边界点,即仍然属于物体,但是位于物体最外面的可见点(图中黑色点);
阴影边界点,即背景中被物体投影所遮挡的边界点(图中绿色点);
遮挡点,即障碍物边界与阴影边界之间的插值点,也称纱点,是激光雷达获取的三维距离数据中的典型现象。
在这里插入图片描述
以下代码表述了如何利用深度图将这些边缘点分割出来。详细注释请看代码内容。

 /* \代码作者:Bastian Steder */
 
 #include <iostream>
 
 #include <pcl/range_image/range_image.h>  				// 深度图头文件
 #include <pcl/io/pcd_io.h>								// IO头文件
 #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> 						// 控制台语句分析头文件
 #include <pcl/common/file_io.h> 						// 获取无拓展名文件
 
 typedef pcl::PointXYZ PointType;						// 点云类型
 
 // --------------------
 // -----参数-----
 // --------------------
 float angular_resolution = 0.5f;	// 角度分辨率
  //coordinate_frame = CAMERA_FRAME告诉系统x面向右,y向下,z轴向前
 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;//设置所有不能观察到的点都为远距离的点
     std::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);
     std::cout << "Using coordinate frame "<< (int)coordinate_frame<<".\n";
   }
   if (pcl::console::parse (argc, argv, "-r", angular_resolution) >= 0)
     std::cout << "Setting angular resolution to "<<angular_resolution<<"deg.\n";
   angular_resolution = pcl::deg2rad (angular_resolution);
   
   // ------------------------------------------------------------------
   // -----读取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)
    {
       std::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
   {
   // 如果pcd文件不存在则创建点云数据
     std::cout << "\nNo *.pcd file given => Generating 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.push_back (point);
      }
    }
    point_cloud.width = point_cloud.size ();  point_cloud.height = 1;
  }
  
  // -----------------------------------------------
  // -----利用点云创建深度图像,方法见上一节-----
  // -----------------------------------------------
  float noise_level = 0.0;
  float min_range = 0.0f;
  int border_size = 1;
  pcl::RangeImage::Ptr 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 ();  //设置所有不能观察到的点都为远距离的点

  // --------------------------------------------
  // ---------使用3D点云查看器来查看源点云----------
  // --------------------------------------------
  pcl::visualization::PCLVisualizer viewer ("3D Viewer"); 	// 窗口名
  viewer.setBackgroundColor (1, 1, 1);						// 背景色 
 viewer.addCoordinateSystem (1.0f, "global");				// 添加全局坐标系
  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);					// 开始利用深度图计算边界,并返回给边界描述子。
  
  // ----------------------------------
  // -----使用3D查看器显示点云的物体边界点,阴影边界点,插值点-----
  // ----------------------------------
  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[y*range_image.width + x].traits[pcl::BORDER_TRAIT__OBSTACLE_BORDER])
        border_points.push_back (range_image[y*range_image.width + x]);
      if (border_descriptions[y*range_image.width + x].traits[pcl::BORDER_TRAIT__VEIL_POINT])
        veil_points.push_back (range_image[y*range_image.width + x]);
      if (border_descriptions[y*range_image.width + x].traits[pcl::BORDER_TRAIT__SHADOW_BORDER])
        shadow_points.push_back (range_image[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);
  }
}

代码效果如下:
在这里插入图片描述


【博主简介】
  斯坦福的兔子,男,天津大学机械工程工学硕士。毕业至今从事光学三维成像及点云处理相关工作。因工作中使用的三维处理库为公司内部库,不具有普遍适用性,遂自学开源PCL库及其相关数学知识以备使用。谨此将自学过程与君共享。
博主才疏学浅,尚不具有指导能力,如有问题还请各位在评论处留言供大家共同讨论。
若前辈们有工作机会介绍欢迎私信。

;