KinectAzureDK编程实战_PCL
- 2019 年 10 月 8 日
- 筆記
我们还是从一个例子讲如何实现 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 图像。
敬请期待。