在之前的《Part 1:簡單的深度讀取方法》已經解釋過,要怎麼在 C++ 的環境下、讀取 Kinect for Windows v2 的深度影像了,而在《Part 2:使用 OpenCV 顯示深度影像》,也示範了怎麼把深度影像用 OpenCV 畫出來了。接下來這篇,則是來簡單講一下,怎麼讀取彩色影像和紅外線影像吧!
首先,在《Kinect for Windows SDK v2 C++ API 簡介》一文中有提過,K4W SDK v2 有提供許多 Frame Source 的介面,深度影像的 IDepthFrameSource 就是其中一種;而其他原始的資料來源,則還包含了彩色影像的 IColorFrameSource(MSDN)、以及紅外線影像的 IInfraredFrameSource(MSDN)和 ILongExposureInfraredFrameSource(MSDN)。
不過說實話,Heresy 自己也不知道 IInfraredFrameSource 和 ILongExposureInfraredFrameSource 的差別…只能說從字面上來看,ILongExposureInfraredFrameSource 是提供長曝光的紅外線影像,但是在 Heresy 這邊自己測試的時候,不管是 FPS 還是呈現的結果,其實都沒有太大的差別。所以,如果有人知道兩者的差別,也麻煩告訴 Heresy 一下吧…
讀取彩色影像
接下來,就開始看要怎麼寫吧。基本上,如果已經知道怎麼讀取深度影像的話,要讀取其他的影像資料,就非常地簡單了~因為他們的方法,基本上是幾乎相同的~
在讀取深度影像的時候,是透過 IKinectSensor 的 get_DepthFrameSource() 這個函式,來取得深度影像的 Frame Source 物件(IDepthFrameSource),然後再透過 IDepthFrameSource 取得 IDepthFrameReader,並藉此取得深度影像(IDepthFrame)。
而如果要改成讀取彩色影像的話,就只要把這些深度影像的介面,換成彩色影像的型別就可以了~也就是做下面的替換:
- IDepthFrameSource → IColorFrameSource
- IDepthFrameReader → IColorFrameReader
- IDepthFrame → IColorFrame
而當然,在取得 IColorFrameSource 物件的時候,也要改成呼叫 get_ColorFrameSource()。而基本上,主要的流程這樣修改就可以改讀取彩色影像了!
在經過這樣的修改後,主程式大致上會是像下面這樣(這不算完整的程式):
IKinectSensor* pSensor = nullptr;
GetDefaultKinectSensor(&pSensor);
// 1b. Open sensor
pSensor->Open();
// 2a. Get frame source
IColorFrameSource* pFrameSource = nullptr;
pSensor->get_ColorFrameSource(&pFrameSource);
// 3a. get frame reader
IColorFrameReader* pFrameReader = nullptr;
pFrameSource->OpenReader(&pFrameReader);
// Enter main loop
size_t uFrameCount = 0;
while (uFrameCount < 100)
{
// 4a. Get last frame
IColorFrame* pFrame = nullptr;
if (pFrameReader->AcquireLatestFrame(&pFrame) == S_OK)
{
/// read data
pFrame->Release();
++uFrameCount;
}
}
和之前的程式相比,可以看到架構基本上是相同的,除了「2a」所呼叫的函式從 get_DepthFrameSource() 改成 get_ColorFrameSource() 外,其他就僅有與深度有關的型別、都換成彩色影像的型別而已。
不過,在真正要讀取資料的部分(上面「read data」的部分),由於彩色影像的格式和深度影像只有一個 UINT16 的深度值不同,所以在這部分的程式,會和深度的讀取不一樣。
基本上,彩色影像的紀錄方法有許多種方法,一般最常見的應該就是 RGB、用紅綠藍三原色來記錄;其他常見的則還有 YUV(維基百科)等方法。
而 K4W v2 所提供的彩色影像的原生格式,基本上應該是 YUY2(ColorImageFormat_Yuy2、維基百科)的格式;如果要確認的話,可以透過呼叫 IColorFrame 的 get_RawColorImageFormat() 來取得這個資訊。
pFrame->get_RawColorImageFormat(&mFormat);
如果要直接讀取彩色影像的原始資料的話,基本上就是使用 IColorFrame 所提供的 AccessRawUnderlyingBuffer() 這個函式;基本上可以寫成:
BYTE* pData = nullptr;
pFrame->AccessRawUnderlyingBuffer(&uSize, &pData);
不過,由於 YUY2 這類的色彩空間處理起來其實比較複雜,所以個人不太建議直接這樣使用…
比較簡單的處理方法,應該是透過 CopyConvertedFrameDataToArray() 這個函式,讓 K4W SDK 幫我們做好轉換的動作~而他支援的影像格式有 ColorImageFormat_Rgba、ColorImageFormat_Yuv、ColorImageFormat_Bgra、ColorImageFormat_Bayer、ColorImageFormat_Yuy2 這幾種。一般比較常用的,應該會是 RGBA、BRGA 和 YUV了~
其中,RGBA 和 BRGA 的「A」是 alpha、指的是這一點的透明度;不過雖然他有提供這個值,但是實際上拿到的應該都是 255。
而使用上,如果是用比較常見的 RGBA 的話,大致可以寫成:
BYTE* pBuffer = new BYTE[iWidth * iHeight];
pFrame->CopyConvertedFrameDataToArray(
uBufferSize, pBuffer, ColorImageFormat_Rgba);
int x = iWidth / 2,
y = iHeight / 2;
int idx = 4 * (x + y * iWidth);
BYTE pPixelR = pBuffer[idx];
BYTE pPixelG = pBuffer[idx + 1];
BYTE pPixelB = pBuffer[idx + 2];
BYTE pPixelA = pBuffer[idx + 3];
這邊的 uBufferSize 是指整個彩色影像在轉換後的大小,基本上就寬度(iWidth)乘上高度(iHeight)、再乘上每一個像素有幾個值(4)、以及個值的大小(sizeof(BYTE))。
而之後呼叫 CopyConvertedFrameDataToArray() 的時候,就會彩色影像的資料轉換成指定的格式(這邊是 ColorImageFormat_Rgba)後,複製到指定大小(uBufferSize)的記憶體空間 pBuffer 裡面。
之後如果要取得 ( x, y ) 這點的值的話,基本上就是以「 4 * ( x + y * iWidth )」這樣的公式、計算出這個像素在 pBuffer 中的開始位置 idx,然後依序去讀出四個值就可以了。
而如果是要搭配 OpenCV 的話,其實也相當簡單,
pFrame->CopyConvertedFrameDataToArray(
uBufferSize, mImg.data, ColorImageFormat_Bgra);
這邊只要先建立一個 CV_8UC4 的 cv::Mat 的物件 mImg,然後再透過 CopyConvertedFrameDataToArray(),要求他把影像轉換成 BGRA 後、直接複製到 mImg.data 就可以了~
用 OpenCV 來顯示彩色影像的完整程式,可以參考 GitHub 上的檔案。
這邊和之前深度影像的程式比起來,程式的結構上做了一些調整,不過在意義上沒有太大的變化;比較不一樣的,就是實際上 Frame Source 在用來開啟 Frame Reader 後,就可以直接關閉了。
另外,這邊稍微提一下,IColorFrame 實際上還有一個 get_ColorCameraSettings() 的函式,可以取得 IColorCameraSettings 的物件;而透過這個物件,則可以取得彩色影像的曝光時間、gain、gamma 等資訊。如果有需要的話,應該可以試試看。
讀取紅外線影像
其實和彩色影像相比,紅外線影像的性質和深度影像更為接近。基本上,K4W v2 的紅外線影像和深度影像一樣,每個像素都是一個 UINT16 的值(實際上是對應到 unsigned short);而在深度影像的時候,這個值代表的是離感應器的距離,在紅外線影像,則是代表感應器偵測到的紅外線強度(intensity)。
不過,深度影像雖然是 UINT16,但是由於是紀錄深度值,所以值的範圍其實並沒有那麼大,基本上是侷限在 500 – 8,000 之間而已。相較於此,紅外線影像的值的範圍就非常地大,從 0 – 65,535 都有可能。
如果要把深度影像的讀取程式,改成讀取紅外線影像,基本上比改成彩色影像更簡單,因為在資料讀取的部分,紅外線的資料讀取的函式介面和深度影像是相同的。
所以,如果是要用 IInfraredFrameSource 來讀取紅外線影像的話,基本上就是透過 IKinectSensor 的 get_InfraredFrameSource() 這個函式來取得 frame source 的物件,之後只要一樣、把對應的型別都改掉就好了。
- IDepthFrameSource → IInfraredFrameSource
- IDepthFrameReader → IInfraredFrameReader
- IDepthFrame → IInfraredFrame
而如果是使用 ILongExposureInfraredFrameSource 的話,也是一樣做對應的修改就可以了。
由於寫法非常地相似,Heresy 這邊就不特別列出來了,請參考 GitHib 上的檔案:IRWithOpenCV、LongExIRWithOpenCV。他們顯示出來的畫面,大概會像下面這樣:
可以看到這個畫面很暗,這是因為 Heresy 直接把 16bit 的值拿來顯示,所以只看的到近的東西;如果真正想拿來使用的話,應該會需要根據自己的需求、選出要的範圍,拿來做顯示、處理。(例如和深度影像一樣,用 cv::Mat 的 convertTo() 來做轉換)
另外,可能要注意的是,由於 Kinect 本身有打出紅外線光,所以如果物體距離感應器太近的話,那紅外線影樣的值會變得太強、導致一片白的。所以如果要用紅外線影像的話,也要記得不要離得太近。
您好,我原本使用KINECT V1加上OPENNI實作。
但是發現V2深度回饋更佳,所以開始使用K4W SDK2.0。
想請問要如何設定拿到的RESOLUTION,並不是RESIZE他,而是向OPENNI那樣可以直接設定他的MODE。我有試過大大的MapColorFrameToDepthSpace 但是可能是因為大量迴圈的關係,或是我哪裡寫錯造成他很盪QQ。
感謝HERESY大大!!
讚讚
K4W SDK 沒有這樣的功能,需要自己處理。
讚讚
謝謝我會自己處理!!!
讚讚
Heresy 大哥
我在實作
UINT uBufferSize = iWidth * iHeight * 4 * sizeof(BYTE);
BYTE* pBuffer = new BYTE[iWidth * iHeight];
pFrame->CopyConvertedFrameDataToArray(
uBufferSize, pBuffer, ColorImageFormat_Rgba);
這塊時,發現UINT 和 BYTE 給的空間會不夠(iwidth, iheight 分別是 480, 270)
想請問H大有甚麼建議~~
目前想實現由OpenGl 搭配 kinect 顯示彩色影像
讚讚
既然都知道是空間不夠,那就給他足夠的空間吧。
寬和高不是隨便給的,請使用 SDK 提供的 API,確認他的大小。
這部分可以參考: https://kheresy.wordpress.com/2015/01/08/k4w-v2-simple-depth-reader-cpp/ 和範例程式的內容
讚Liked by 1 person
我想說GL會不會也有resize這種東西XDD,畢竟原圖超級大的
讚讚
建議你先釐清你在呼叫的是什麼東西的 API…
這邊你提到的 API 都是 Kinect for Windows SDK 的,和 OpenGL 無關。
讚讚
Heresy大您好
我是一個初學者 最近才剛開始上網看visual 看了您的文章 了解到很多
目前還在照者寫的部分 我在按照您的程式執行彩色繪圖時
視窗都會放大很多
想請問是為什麼呢
讚讚
因為 Kinect for Windows 第二代感應器的彩色攝影機解析度比較高。
讚讚
Heresy大您好 所以 第二代感應器感應器較高就無法調了嗎 還是我依然可以從程式改呢
讀得很慢 還沒能看懂程式的意思….
讚讚
如果只是要修改顯示的視窗大小,請自行參考 OpenCV 的文件做修改。
http://docs.opencv.org/2.4/modules/highgui/doc/user_interface.html#namedwindow
讚讚
你好,我想要同時顯示彩色跟深度影像,所以我把程式碼修改了一下(基本上就是參考PART 2的程式,將Reader、Source、Frame都多設一個給深度用的),但是運行到
if (pDepthFrame->CopyFrameDataToArray(iWidth * iHeight, reinterpret_cast(mDepthImg.data)) == S_OK)
的時候,一直無法顯示深度圖像,出現copy error,請問可能是發生什麼情況??
讚讚
單就你的敘述很難確實判斷問題在哪。
不過,你是否有檢查過 iWidth 和 iHeight 的值?他們是否和 mDepthImg 的大小一致?
讚讚
我已經解決了,我以為iWidth 和 iHeight可以共用,結果是要多設一個給深度用的。非常感謝你的回答。
讚讚
兩個的大小不一樣,當然不能共用啊…
讚讚
heresy老师,您好。
经过测试发现AcquireLatestFrame的失败的几率非常高,大概有30%到50%的概率读不到图像。请问这么高的概率是正常的么?请问AcquireLatestFram的失败会与什么有关联?
谢谢。
讚讚
那是正常的。
只要在呼叫的當下沒有新資料,就會錯誤。
所以只要兩次呼叫間隔小於更新頻率,基本上就會失敗。
讚讚
原来如此,难怪加了延时之后成功率会上升。谢谢heresy老师。
讚讚
你好。请问在用openCV显示彩色图像时,把CopyConvertedFrameDataToArray中第三个参数,从Bgra改为Rgba之后显示的图像颜色就变了,这是为什么?四个通道只是顺序不一样,混合后的结果不是应该是一样的吗?
讚讚
因為 OpenCV 會把第一個值當成 b,然後是 g、第三個是 r。
所以你給他 rgb,他就會把 r 的值當作 b 來用,把 b 的值當成 r 來用,所以就錯了。
讚讚
[…] 在這邊的例子,Heresy 是把本來的彩色影像先畫出來(請參考之前的《Part 3:讀取彩色影像與紅外線影像》),然後在把骨架畫上去。 […]
讚讚
[…] OpenCV 顯示深度影像》、《讀取彩色影像與紅外線影像》和《讀取人體位置(Body […]
讚讚
Heresy大您好
想請問您知道kinect studio v2.0所錄的紅外線影像是經過什麼樣的處理嗎?
因為那個錄出來的紅外線影像還蠻漂亮的, 應該可以拿來作進一步的利用!
只用API提供的InfraredSource錄出來實在是有點太暗…
感謝Heresy大的文章!!
讚讚
文章最後有提到了,基本上一般就是從 16bit 的範圍裏面,選出你要的資料範圍。
比如說只選前半段,把它對應到 8bit(0-255)再畫出來,這樣效果就會好很多了。
要能根據狀況作調整的話,就是針對每個畫面下去分析,找出最佳的資料範圍。
讚讚
[…] OpenCV 顯示深度影像》和《讀取彩色影像與紅外線影像》,基本上,算是把把 Kinect […]
讚讚