KinectAzureDK編程實戰_PCL

前幾篇文章,我講了如何用 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 圖像。

敬請期待。