Home People Research Blog Courses Links Search Download
NCHC

Blog

Blog 最新文章

  1. Visual Studio 的遠端偵錯:Windows
    2021/03/24 14:45
  2. Visual Studio 2019 16.9 支援使用 OpenMP LLVM
    2021/03/03 13:52
  3. Valve 推出完全支援 OpenXR 的 SteamVR 1.16.8
    2021/02/25 09:43

Blog 最新回應

  1. 加入斜體文字...
    2021/02/07 21:06
  2. 加入斜體文字...
    2021/02/07 21:06
  3. 加入斜體文字...
    2021/02/07 21:06

Keyword 關鍵字

javascript 資訊地圖 開放資料 Oculus VR C++11 VR MR Oculus Quest 2 C++14 Java ASUS Xtion OpenMP CubeX Oculus Rift S PHP xml git Pandas Windows MR Pandas HTC Vive Pro Python Boost NiTE2 iFlyover C++14 OpenCL 3d print Docker Docker 3D立體 Valve Index HTC Vive Vulkan Qt CUDA C++17 C++ GitLab Kinect HTC Vive Focus svn 資料視覺化 WebGL OpenCV HoloLens 2 Python 開放資料 OpenVR C++20 OpenNI2 OpenXR OpenNI OpenGL

類別:技術相關 » 技術研究
文章發表|我要回應|RSS訂閱

用 OpenCV 畫出 OpenNI 2 的深度、彩色影像

這篇基本上是《OpenNI 2 VideoStream 與 Device 的設定與使用》一文的延伸,主要是提供一個完整的範例,用OpenCV(官網)這套知名的電腦視覺、影像處理的函式庫,來顯示 OpenNI 2 所讀取到的彩色、深度影像。

實際上,在《OpenNI 2 VideoStream 與 Device 的設定與使用》中已經有提到過,該怎麼把 OpenNI VideoStream 讀取到的 VideoFrameRef,轉換成 OpenCV C 的 cv::Mat 的形式了~以彩色影像來說,他的基本做法,就是:

const cv::Mat mImageRGB( mColorFrame.getHeight(), mColorFrame.getWidth(),
CV_8UC3, (void*)mColorFrame.getData() );

不過由於 OpenCV 內部彩色影像實際上是使用 BGR、而非 RGB 的形式,所以如果要做後續的處理的話,是需要把 RGB 影像轉換成 BGR 的。而如果是深度影像的話,資料的型別也需要從 CV_8UC3 改成 CV_16UC1 才行。

而如果只是為了要顯示的話,一個完整的程式,會像下面這樣:

// STL Header
#include <iostream>
 
// OpenCV Header
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
 
// OpenNI Header
#include <OpenNI.h>
 
// namespace
using namespace std;
using namespace openni;
 
int main( int argc, char **argv )
{
// 1. Initial OpenNI
if( OpenNI::initialize() != STATUS_OK )
{
cerr << "OpenNI Initial Error: "
<< OpenNI::getExtendedError() << endl;
return -1;
}
 
// 2. Open Device
Device mDevice;
if( mDevice.open( ANY_DEVICE ) != STATUS_OK )
{
cerr << "Can't Open Device: "
<< OpenNI::getExtendedError() << endl;
return -1;
}
 
// 3. Create depth stream
VideoStream mDepthStream;
if( mDevice.hasSensor( SENSOR_DEPTH ) )
{
if( mDepthStream.create( mDevice, SENSOR_DEPTH ) == STATUS_OK )
{
// 3a. set video mode
VideoMode mMode;
mMode.setResolution( 640, 480 );
mMode.setFps( 30 );
mMode.setPixelFormat( PIXEL_FORMAT_DEPTH_1_MM );
 
if( mDepthStream.setVideoMode( mMode) != STATUS_OK )
{
cout << "Can't apply VideoMode: "
<< OpenNI::getExtendedError() << endl;
}
}
else
{
cerr << "Can't create depth stream on device: "
<< OpenNI::getExtendedError() << endl;
return -1;
}
}
else
{
cerr << "ERROR: This device does not have depth sensor" << endl;
return -1;
}
 
// 4. Create color stream
VideoStream mColorStream;
if( mDevice.hasSensor( SENSOR_COLOR ) )
{
if( mColorStream.create( mDevice, SENSOR_COLOR ) == STATUS_OK )
{
// 4a. set video mode
VideoMode mMode;
mMode.setResolution( 640, 480 );
mMode.setFps( 30 );
mMode.setPixelFormat( PIXEL_FORMAT_RGB888 );
 
if( mColorStream.setVideoMode( mMode) != STATUS_OK )
{
cout << "Can't apply VideoMode: "
<< OpenNI::getExtendedError() << endl;
}
 
// 4b. image registration
if( mDevice.isImageRegistrationModeSupported(
IMAGE_REGISTRATION_DEPTH_TO_COLOR ) )
{
mDevice.setImageRegistrationMode( IMAGE_REGISTRATION_DEPTH_TO_COLOR );
}
}
else
{
cerr << "Can't create color stream on device: "
<< OpenNI::getExtendedError() << endl;
return -1;
}
}
 
// 5. create OpenCV Window
cv::namedWindow( "Depth Image",  CV_WINDOW_AUTOSIZE );
cv::namedWindow( "Color Image",  CV_WINDOW_AUTOSIZE );
 
// 6. start
VideoFrameRef mColorFrame;
VideoFrameRef mDepthFrame;
mDepthStream.start();
mColorStream.start();
int iMaxDepth = mDepthStream.getMaxPixelValue();
while( true )
{
// 7. check is color stream is available
if( mColorStream.isValid() )
{
// 7a. get color frame
if( mColorStream.readFrame( &mColorFrame ) == STATUS_OK )
{
// 7b. convert data to OpenCV format
const cv::Mat mImageRGB(
mColorFrame.getHeight(), mColorFrame.getWidth(),
CV_8UC3, (void*)mColorFrame.getData() );
// 7c. convert form RGB to BGR
cv::Mat cImageBGR;
cv::cvtColor( mImageRGB, cImageBGR, CV_RGB2BGR );
// 7d. show image
cv::imshow( "Color Image", cImageBGR );
}
}
 
// 8a. get depth frame
if( mDepthStream.readFrame( &mDepthFrame ) == STATUS_OK )
{
// 8b. convert data to OpenCV format
const cv::Mat mImageDepth(
mDepthFrame.getHeight(), mDepthFrame.getWidth(),
CV_16UC1, (void*)mDepthFrame.getData() );
// 8c. re-map depth data [0,Max] to [0,255]
cv::Mat mScaledDepth;
mImageDepth.convertTo( mScaledDepth, CV_8U, 255.0 / iMaxDepth );
// 8d. show image
cv::imshow( "Depth Image", mScaledDepth );
}
 
// 6a. check keyboard
if( cv::waitKey( 1 ) == 'q' )
break;
}
 
// 9. stop
mDepthStream.destroy();
mColorStream.destroy();
mDevice.close();
OpenNI::shutdown();
 
return 0;
}

這樣的程式執行之後,除了本來的命令提示字元的視窗外,還會由 OpenCV 建立出像下圖這樣的兩個視窗,各別顯示深度影像和彩色影像,並透過無窮迴圈不停地更新畫面:

而程式會一直進行更新,直到使用者按下鍵盤上的「q」,才會結束程式。

接下來,則是程式原始碼的一些說明。


初始化

在上面的範例裡面,1 到 4 的部分,都是 OpenNI 初始化的部分。依序就是:

  1. OpenNI 環境初始化
  2. 開啟裝置(Device)
  3. 建立深度影像的 VideoStream,
    並將解析度設定成 640 x 480、30FPS,格式為 PIXEL_FORMAT_DEPTH_1_MM
  4. 建立深度影像的 VideoStream,
    並將解析度設定成 640 x 480、30FPS,格式為 PIXEL_FORMAT_RGB888;
    同時,也設定裝置的影像校正,將深度影象對到彩色影像的位置。

不過由於 Heresy 有考慮到 ASUS Xtion Pro 沒有彩色攝影影機,所以無法得到彩色影像的資料,因此為了讓程式也可以在 Xtion Pro 上執行,所以在程式的概念上是設計成一定要有深度攝影機,但是可以沒有彩色攝影機的。也因此,可以看到深度和彩色影像的部分,在處理的時候都有些微的不同。

而 5 之後的部分,才開始有用到 OpenCV 的功能。首先,5 的部分,就是透過 cv::namedWindow(),建立出兩個名字分別是「Depth Image」和「Color Image」視窗,分別用來顯示深度影像和彩色影像。

程式主迴圈

6 則是開始 OpenNI VideoStream 的資料讀取,並以一個無窮迴圈、來做資料的更新。而在迴圈裏面,則有搭配一個 6a、使用 cv::waitKey() 這個函式,來讀取鍵盤的輸入,如果使用者按下鍵盤上的「q」的話,就會離開迴圈,進到 9 的部分,開始關閉 OpenNI 的物件、把程式結束程掉。

在迴圈內,則主要是 7 和 8 兩塊,前者是針對彩色影像作處理,後者則是針對深度影像作處理。

彩色影像的處理

以彩色影像來說,在確定讀到資料後,是會先把資料,轉換成一個 cv::Mat 的物件 mImageRGB(7b);不過由於 OpenCV 的彩色影像是採用 BGR 的順序,所以接下來,就是要透過 cv::cvtColor() 來做色彩空間的轉換,把 RGB 排列的 mImageRGB 轉換成 BGR 排列的 mImageBGR(7c)。

處理好了之後,則就是透過 cv::imshow(),把 mImageBGR 的影像用「Color Image」這個視窗顯示出來。

深度影像的處理

而深度影像的部分,則是先轉換成 CV_16UC1 形式的 cv::Mat 物件 mImageDepth(8b)。雖然 OpenCV 有支援 16bit 的影像,但是實際上因為 OpenNI 在目前的感應器上,讀到的深度值範圍最大就是到 10,000(iMaxDepth、透過深度的 VideoStream 的 getMaxPixelValue() 這個函式取得),所以如果直接把 16bit 的影像畫出來的話,應該是會看到接近一片黑的畫面…

所以為了強化顯示的效果,這邊就去使用 cv::Mat 提供的 convertTo() 這個函式,把 16bit(CV_16UC1)的 mImageDepth,轉換成 8bit(CV_8UC1)的影像 mScaledDepth(8c)。而 convertTo() 這個函式,在做轉換的時候,可以透過第三個參數和第四個參數,來設定轉換時的縮放、以及位移。詳細的說明,可以參考 OpenCV 官方的文件(連結),而像 Heresy 這邊就是把深度值乘上「255.0 / iMaxDepth」,讓本來介於 0 ~ 10,000 之間的深度值,變成 0 ~ 255。

最後,則是一樣透過 cv::imshow(),把 mScaledDepth 這個影像用「Depth Image」這個視窗顯示出來。


這邊 Heresy 基本上只是單純把 OpenNI 的影像資料,轉換成 OpenCV 的格式、然後就直接畫出來了。如果有需要的話,也是可以再透過 OpenCV,來做額外的處理的~像在 OpenNI 1.x 的時候,Heresy 就有寫一篇《OpenNI OpenCV》,裡面就有額外把彩色影像和深度影像,都拿來做邊緣偵測~有需要的話,也可以自己試試看。

張貼者:heresy於2013/01/09 18:16 下午有27則回應,瀏覽次數:6,525次
shimingD 於 2013/01/09 22:02 下午 回應:
博主你好,我想在用OpenNI获得的图像上用OpenGL进行渲染,不知您是否可写一下相关教程。
作者作者heresy 於 2013/01/10 09:53 上午 回應:
to shimingD
之後應該會寫,不過可以先參考之前開課的範例
雖然是 OpenNI 1.x 的範例,但是 OpenGL 的部分應該不影響
http://sdrv.ms/RcKxox
shimingD 於 2013/01/10 10:31 上午 回應:
非常感谢!
wobuaishangdiao 於 2013/01/17 19:10 下午 回應:
有没有将深度图像和彩色图像用opencv进行叠加到一起的,我直接cv::add()进行叠加有误,能介绍下方法吗?
作者作者heresy 於 2013/01/18 08:56 上午 回應:
to wobuaishangdiao
不確定你遇到的問題是什麼?
不過 cv::add 把兩個 cv::Mat 相加,基本上是需要同樣大小的 cv::Mat,所以在相加前,需要透過 cv::cvtColor 把 mScaledDepth 從單一色彩轉換成 BGR 才行。

另外,建議不要使用 cv::Add,而是使用 cv::AddWeighted

參考:http://dasl.mem.drexel.edu/~noahKuntz/openCVTut2.html
chen 於 2013/04/02 18:48 下午 回應:
老师,您好!这里我想问一下,kinect for windows是支持了近景模式。默认情况下有效深度范围是0.8--4.0m,近景模式下是0.4--
3.0m。但是,找了好多资料,如何选择近景模式呢?
作者作者heresy 於 2013/04/03 08:45 上午 回應:
to chen

在 OpenNI 裡應該無法做這個切換。
chen 於 2013/04/03 13:46 下午 回應:
那您对近景模式,有没有研究?能不能给些指导呢?
作者作者heresy 於 2013/04/03 16:27 下午 回應:
to chen

如果要使用 Kinect for Windows 的 near mode,目前應該只能使用 Kinect for Windows SDK,OpenNI 是不能使用的。
刚蛋 於 2013/06/01 17:28 下午 回應:
博主,你好,我想请教下,将介於 0 ~ 10,000 之間的深度值,變成 0 ~ 255之后,如何将深度数据直接保存起来为后续处理呢?这里对速度有较高的需求啊,还有就是如何将彩色和深度进行融合呢?
作者作者heresy 於 2013/06/03 17:04 下午 回應:
@刚蛋

儲存的部分單純看你要怎麼存而已,你可以存成文字檔,也可以存成圖檔,純粹視你的需求而定。

融合的部分實際上也沒一定的標準方法,一般基本上就是自己根據自己的需要,去實作類似 Alpha blending 的效果了。
刚蛋 於 2013/06/17 08:41 上午 回應:
博主,你好,我想问下,深度值范围是0-
10000,那在由深度坐标系统到世界坐标系统转换的过程中,x,y有范围限制吗?也就是他们各自的最大值和最小值。期待你的回
信。
作者作者heresy 於 2013/06/17 08:50 上午 回應:
@刚蛋
他是用公式做計算的,所以你可以給他任意值下去做計算。
但是計算出來的結果有沒有意義就是另一回事了。
gangan1121 於 2013/08/05 16:33 下午 回應:
heresy , kinect默认是30fps, 但是当获取到每一帧后的这个循环内的计算量很大的时候,下一个循环还是换取下一帧么?还是最新的一帧???

如果计算量大,花费时间超过1/30秒了,如果下一次还是获取上一帧的下一帧的话,那不就取的是旧的数据么?
作者作者heresy 於 2013/08/05 16:50 下午 回應:
@gangan1121
他會取得最新的畫面,中間來不及處理的資料會直接被跳過。
刚蛋 於 2013/08/28 21:37 下午 回應:
博主,您好,我想问下,用openni2检测时,kinect能识别出物体的深度值范围是多少呢?应该不是简单的0-
10000吧。还有你用过NYKO放大镜吗?我用了下,感觉用与不用没什么差异,按理说应能缩短距离的,不知哪里出问题了。期待
您的回信!
作者作者heresy 於 2013/08/29 17:32 下午 回應:
@刚蛋

0 - 10,000 是 OpenNI 感應器(Xtion/Kinect)在定義上可以回傳的值的範圍,也就是說值不會超過這個範圍。
建議可以參考
http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&
SUB_ID=1&PAPER_ID=278


另外,Heresy 沒有過過放大鏡。
lusile 於 2013/10/22 16:09 下午 回應:
怎樣得到openni2的Zero Plane Pixel Size?在openni1.x是通過depthGenerator.GetRealProperty("ZPPS", xn_zpps);
作者作者heresy 於 2013/10/22 16:41 下午 回應:
to lusile

OpenNI 2 一樣有提供 getProperty() 的函式,可以用來讀取額外的資料。
而 ZPPS 在 OpenNI2 應該就是 depth video stream 的 XN_STREAM_PROPERTY_ZERO_PLANE_PIXEL_SIZE。
chao 於 2014/07/04 22:09 下午 回應:
heresy大大,在ubuntu下运行这个案例,不知道是出了什么问题,cmake编译可以通过,但是就是没有图像显示出来,
只是显示Warning: USB events thread - failed to set priority. This might cause loss of data...刚开始以为是驱动没装好,但是官方的例子又可以运行,找了很久问题都没解决,heresy大大您知道是哪个地方出
问题了吗?
作者作者heresy 於 2014/07/07 09:34 上午 回應:
to chao

抱歉,Heresy 自己沒有在 Ubuntu 下執行過,所以不確定問題是什麼。
不過,個人會建議自己去建置官方的範例程式來試試看。
翔翔 於 2014/12/17 15:29 下午 回應:
您好 請問一下
為什麼我使用本篇的程式碼
我的彩色影像的視窗 是沒有東西的
深度的視窗是有 可是更新的非常慢
會頓頓的
作者作者heresy 於 2014/12/17 16:39 下午 回應:
to 翔翔

個人會建議先使用官方的 NiViewer、或是把解析度降到 320 x 240 試試看。
有可能是 USB 相容性的問題。
冯小包 於 2015/12/29 21:50 下午 回應:
博主,你好。。。运行程序之后,提示LINK : fatal error LNK1104: 无法打开文件“C:Program Files (x86)OpenNI2Lib\.obj”错误,不知是什么原因?希望能给点思路,谢谢!
作者作者heresy 於 2015/12/31 09:02 上午 回應:
to 冯小包
建議請先檢查您的 Visual C 專案有正確的設定。
理論上它不應該會需要用到 OpenNI 目錄下的 obj 檔
whr 於 2016/05/15 22:34 下午 回應:
为什么我彩色图正常,但是640*480的深度图就无法正常显示,但换成320*240的深度图就可以了?怎么能显示640*4
80的呢?
作者作者heresy 於 2016/05/17 13:35 下午 回應:
to whr

建議請先確認 NiViewer 可否正常讀取?
另外,也建議換一個 USB Port、甚至換一台電腦試試看。

-- TOP --

我要回應
* 身份  訪客 (暱稱:)
 本篇文章作者 (帳號:密碼:)
* 內容      
很高興 悲傷 震驚 疑惑 大笑 發瘋 傷心
* 留言密碼 (請輸入下方圖片中去除前、後位數的數字,共五碼。)
說明 1. * 表示必填欄位。
2. 不支援HTML Tag。
   

-- TOP --

© Visualization and Interactive Media Laboratory of NCHC, 2007 - 2021, All Rights Reserved. Contact E-mail