KinectAzureDK编程实战_PCL

  • 2019 年 10 月 8 日
  • 笔记

前几篇文章,我讲了如何用 OpenCV 操作并可视化 Kinect 的 RGB + IR + Depth 图像。但是 OpenCV 只能用来操作 2D 图像。对于三维情况,主要就是三维重建,比如基于图片集的离线三维重建——SfM(Structured from Motion)等。在 OpenCV 的 rgbd 模块中实现了一个实时的三维重建 Kinect Fusion。说到三维重建,对于计算机视觉、计算机图形学主要从业者,用的很多的是 PCL(Point Cloud Library)。几乎所有的实时、离线三维重建开源系统都是基于 PCL 对实时点云的处理。

我们还是从一个例子讲如何实现 Kinect Azure DK 的 grabber。对于 2代 Kinect 来说,我们看 PCL 中的 grabber 怎么用。

首先声明一个基于 pcl::visualization::PCLVisualizer 的 viewer。并设置 visualizer 的摄像机参数,对应于 OpenGL 中的 gluLookat 函数。

viewer->setCameraPosition( 0.0, 0.0, -2.5, 0.0, 0.0, 0.0 );

前三个坐标表示,相机在世界坐标中的位置。后三个坐标表示,相机向上方向在世界坐标系中的方向。

在一个可视化环境中,可视化 Kinect 数据的同时,还要接收 Kinect 输出的数据。这必然涉及到多线程同步。

我们需要用一个线程来可视化,一个线程用来接收数据。在增强现实系统中,经常会用这种方式,OpenGL可视化当前场景,OpenCV实时图像处理。如我曾经在知乎上回答的问题。

想用OpenCV做AR该如何入手? https://www.zhihu.com/question/26983174/answer/35328819

如上代码,我声明了一个 mutex,作为互斥量,控制 Kinect 数据同一时间只能可视化或者处理。

boost::mutex mutex;

这个 mutex 同时处理实时点云。

boost::function<void(const pcl::PointCloud<PointType>::ConstPtr&)> function =    [&cloud, &mutex](const pcl::PointCloud<PointType>::ConstPtr& ptr)  {    boost::mutex::scoped_lock lock(mutex);      /* Point Cloud Processing */      cloud = ptr->makeShared();  };

然后,我们声明一个 grabber 对象。

boost::shared_ptr<pcl::Grabber> grabber = boost::make_shared<pcl::Kinect2Grabber>();

pcl 的 grabber 实现了一个类似于 Qt 的 signal/slots 模式,用于完成 "raw data数据获取"“生成点云” 之间的通信。

声明一个 connection。

boost::signals2::connection connection = grabber->registerCallback( function );

当 grabber 接收到 raw data 就调用上面说到的回调函数用来处理点云。

然后就是启动 grabber。

grabber->start();

可视化线程。

while (!viewer->wasStopped())  {    // Update Viewer    viewer->spinOnce();    boost::mutex::scoped_try_lock lock(mutex);    if (lock.owns_lock() && cloud)    {      // Update Point Cloud      if (!viewer->updatePointCloud(cloud, "cloud"))      {        viewer->addPointCloud(cloud, "cloud");      }    }  }

当 viewer 窗口关闭的时候,关闭 grabber,并断开 raw data 数据获取与生成点云之间的 connection。

if( connection.connected())  {      connection.disconnect();  }

相对应的,我们来看我们自己写的 Kinect Azure DK 的 grabber 的用法。

这里有几处不同。

因为这一代 Kinect 初始化,需要配置一个变量 k4a_device_configuration_t config

所以,在声明 grabber 的同时,需要初始化这个变量。如上篇文章,我们初始化摄像头的帧率,彩色摄像头分辨率,深度摄像头分辨率,是否同步 RGB+Depth 图像等等。

boost::shared_ptr<pcl::Grabber> grabber =      boost::make_shared<pcl::KinectAzureDKGrabber>(0,      K4A_DEPTH_MODE_NFOV_UNBINNED,      K4A_IMAGE_FORMAT_COLOR_BGRA32, K4A_COLOR_RESOLUTION_720P);

PCL 的 Visualizer 需要初始化可视化环境中摄像机的参数。实际上,Visualizer 可视化环境中摄像机对应实际场景中 Kinect 的深度摄像头。

我们可以获取 Kinect深度摄像头的内外参。

并传入 PCL 的 Visualizer。

viewer->setCameraParameters(intrinsics_eigen, extrinsics_eigen);

设置,viewer 的摄像机参数与 Kinect 深度摄像头摄像机参数一致。

这样设置的好处就是,我们可以看到 在深度摄像头视角下的三维世界

如上gif,可以看到窗口一开始展示给我们的是类似于平面的结构。但当我们用鼠标操作时,就看到原来是三维的点云。说明我们一开始看到的就是深度摄像头视角下的点云。

下面我们看 Kinect Azure DK 的 grabber 类。

k4a_grabber.h

代码借鉴于 PCL 内的 openni grabber。

绝大部分代码都和 openni grabber类似,当然没有 openni grabber 写的大而全。我这里只是简略地实现。

这里只有一类函数最重要——转换 RGB + Depth 图像为彩色点云的函数。

按照 Kinect Azure DK 的开源SDK中的 sample

Azure Kinect Transformation Example https://github.com/microsoft/Azure-Kinect-Sensor-SDK/tree/v1.1.0/examples/transformation

我们把 depthImage 和 colorImage 通过 Kinect SDK 中的内置函数转为 k4a::image 形式的点云。

首先,我们先把 depthImage 转到彩色摄像头的空间。

transformation.depth_image_to_color_camera(depthImage, &transformed_depth_image);

如下图。

根据微软 Kinect 文档

函数 k4a_transformation_depth_image_to_color_camera() 将深度图从深度相机的视点转换为彩色相机的视点。 此函数旨在生成所谓的 RGB-D 图像,其中,D 表示录制深度值的附加图像通道。 在下图中可以看到,k4a_transformation_depth_image_to_color_camera() 的彩色图像和输出如同它们取自同一视点(即,彩色相机的视点)。 https://docs.microsoft.com/zh-cn/azure/Kinect-dk/use-image-transformation

重要的是,这个函数内部的实现方式是GPU加速的。我们不需要关心微软是怎么实现的。

此转换函数比单纯针对每个像素调用 k4a_calibration_2d_to_2d() 更为复杂。 它将深度相机几何结构中的三角网格扭曲成彩色相机的几何结构。 使用三角网格可以避免转换的深度图像出现孔洞。Z 缓冲区确保正确处理遮挡物。 默认已为此函数启用 GPU 加速。 https://docs.microsoft.com/zh-cn/azure/Kinect-dk/use-image-transformation

而且,微软也建议,相比于另一个“镜像”函数 k4a_transformation_color_image_to_depth_camera()

由于此方法会在转换的彩色图像中产生孔洞,并且不会处理遮挡物,因此我们建议改用函数 k4a_transformation_depth_image_to_color_camera() https://docs.microsoft.com/zh-cn/azure/Kinect-dk/use-image-transformation

接下来就是生成 PCL 格式的点云,这里我们生成的是 PoinXYZRGB 格式的点云,即包含点云每个点的三维坐标 (x, y z) 和颜色信息 (r, g, b)。

在上述代码中,需要说明的是这部分代码。

Eigen::Matrix3f m;  m = Eigen::AngleAxisf(-M_PI, Eigen::Vector3f::UnitZ());

声明了一个旋转矩阵,绕 Z 轴,顺时针方向旋转 180 度。

为什么会多这么一句呢?

我们来看 Kinect 的坐标系统。

如上图,右手坐标系,X 轴向左,Y轴向下,Z轴朝向用户。

但是我们的可视化环境是 PCL 的 Visualizer 类。这个类基于 VTK,而 VTK 是基于 OpenGL 的渲染。

所以,我们的可视化窗口的坐标系统应该是符合 OpenGL 的坐标系统。而OpenGL 窗口的坐标系统是 X 轴向右,Y轴向上,Z轴朝向用户。

刚好是把Kinect的深度摄像头坐标系绕 Z 轴,顺时针方向旋转 180 度。

如果没有这句话,我们的窗口会出现什么情况呢?

点云倒着显示在窗口。

所以,我们需要将点云旋转,以适应 PCL Visualizer的窗口坐标系统。

另外,Kinect 输出的深度数据单位是 mm。我们需要把数据都除 1000,换算成 m 以适应窗口坐标系统。

后面,我会讲解如何用 OpenGL 通过多线程的方式显示背景纹理的方式显示 RGB 或 IR 或 Depth 图像。

敬请期待。