K4W v2 C++ Part 3:讀取彩色影像與紅外線影像


在之前的《Part 1:簡單的深度讀取方法》已經解釋過,要怎麼在 C++ 的環境下、讀取 Kinect for Windows v2 的深度影像了,而在《Part 2:使用 OpenCV 顯示深度影像》,也示範了怎麼把深度影像用 OpenCV 畫出來了。接下來這篇,則是來簡單講一下,怎麼讀取彩色影像和紅外線影像吧!

首先,在《Kinect for Windows SDK v2 C++ API 簡介》一文中有提過,K4W SDK v2 有提供許多 Frame Source 的介面,深度影像的 IDepthFrameSource 就是其中一種;而其他原始的資料來源,則還包含了彩色影像的 IColorFrameSourceMSDN)、以及紅外線影像的 IInfraredFrameSourceMSDN)和 ILongExposureInfraredFrameSourceMSDN)。

不過說實話,Heresy 自己也不知道 IInfraredFrameSourceILongExposureInfraredFrameSource 的差別…只能說從字面上來看,ILongExposureInfraredFrameSource 是提供長曝光的紅外線影像,但是在 Heresy 這邊自己測試的時候,不管是 FPS 還是呈現的結果,其實都沒有太大的差別。所以,如果有人知道兩者的差別,也麻煩告訴 Heresy 一下吧…


讀取彩色影像

接下來,就開始看要怎麼寫吧。基本上,如果已經知道怎麼讀取深度影像的話,要讀取其他的影像資料,就非常地簡單了~因為他們的方法,基本上是幾乎相同的~

在讀取深度影像的時候,是透過 IKinectSensorget_DepthFrameSource() 這個函式,來取得深度影像的 Frame Source 物件(IDepthFrameSource),然後再透過 IDepthFrameSource 取得 IDepthFrameReader,並藉此取得深度影像(IDepthFrame)。

而如果要改成讀取彩色影像的話,就只要把這些深度影像的介面,換成彩色影像的型別就可以了~也就是做下面的替換:

  • IDepthFrameSourceIColorFrameSource 
  • IDepthFrameReaderIColorFrameReader 
  • IDepthFrameIColorFrame 

而當然,在取得 IColorFrameSource 物件的時候,也要改成呼叫 get_ColorFrameSource()。而基本上,主要的流程這樣修改就可以改讀取彩色影像了!

在經過這樣的修改後,主程式大致上會是像下面這樣(這不算完整的程式):

// 1a. Get default Sensor
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維基百科)的格式;如果要確認的話,可以透過呼叫 IColorFrameget_RawColorImageFormat() 來取得這個資訊。

ColorImageFormat mFormat;
pFrame->get_RawColorImageFormat(&mFormat);

如果要直接讀取彩色影像的原始資料的話,基本上就是使用 IColorFrame 所提供的 AccessRawUnderlyingBuffer() 這個函式;基本上可以寫成:

UINT uSize = 0;
BYTE* pData = nullptr;
pFrame->AccessRawUnderlyingBuffer(&uSize, &pData);

不過,由於 YUY2 這類的色彩空間處理起來其實比較複雜,所以個人不太建議直接這樣使用…

比較簡單的處理方法,應該是透過 CopyConvertedFrameDataToArray() 這個函式,讓 K4W SDK 幫我們做好轉換的動作~而他支援的影像格式有 ColorImageFormat_RgbaColorImageFormat_YuvColorImageFormat_BgraColorImageFormat_BayerColorImageFormat_Yuy2 這幾種。一般比較常用的,應該會是 RGBA、BRGA 和 YUV了~

其中,RGBA 和 BRGA 的「A」是 alpha、指的是這一點的透明度;不過雖然他有提供這個值,但是實際上拿到的應該都是 255。

而使用上,如果是用比較常見的 RGBA 的話,大致可以寫成:

UINT uBufferSize = iWidth * iHeight * 4 * sizeof(BYTE);
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 的話,其實也相當簡單,

cv::Mat    mImg(iHeight, iWidth, CV_8UC4);
pFrame->CopyConvertedFrameDataToArray(
        uBufferSize, mImg.data, ColorImageFormat_Bgra);

這邊只要先建立一個 CV_8UC4cv::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 來讀取紅外線影像的話,基本上就是透過 IKinectSensorget_InfraredFrameSource() 這個函式來取得 frame source 的物件,之後只要一樣、把對應的型別都改掉就好了。

  • IDepthFrameSourceIInfraredFrameSource 
  • IDepthFrameReaderIInfraredFrameReader 
  • IDepthFrameIInfraredFrame 

而如果是使用 ILongExposureInfraredFrameSource 的話,也是一樣做對應的修改就可以了。

由於寫法非常地相似,Heresy 這邊就不特別列出來了,請參考 GitHib 上的檔案:IRWithOpenCVLongExIRWithOpenCV。他們顯示出來的畫面,大概會像下面這樣:

可以看到這個畫面很暗,這是因為 Heresy 直接把 16bit 的值拿來顯示,所以只看的到近的東西;如果真正想拿來使用的話,應該會需要根據自己的需求、選出要的範圍,拿來做顯示、處理。(例如和深度影像一樣,用 cv::MatconvertTo() 來做轉換)

另外,可能要注意的是,由於 Kinect 本身有打出紅外線光,所以如果物體距離感應器太近的話,那紅外線影樣的值會變得太強、導致一片白的。所以如果要用紅外線影像的話,也要記得不要離得太近。


Kinect for Windows v2 C++ 程式開發目錄

廣告

對「K4W v2 C++ Part 3:讀取彩色影像與紅外線影像」的想法

  1. 您好,我原本使用KINECT V1加上OPENNI實作。
    但是發現V2深度回饋更佳,所以開始使用K4W SDK2.0。
    想請問要如何設定拿到的RESOLUTION,並不是RESIZE他,而是向OPENNI那樣可以直接設定他的MODE。我有試過大大的MapColorFrameToDepthSpace 但是可能是因為大量迴圈的關係,或是我哪裡寫錯造成他很盪QQ。
    感謝HERESY大大!!

  2. 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 顯示彩色影像

  3. Heresy大您好

    我是一個初學者 最近才剛開始上網看visual 看了您的文章 了解到很多

    目前還在照者寫的部分 我在按照您的程式執行彩色繪圖時

    視窗都會放大很多

    想請問是為什麼呢

  4. 你好,我想要同時顯示彩色跟深度影像,所以我把程式碼修改了一下(基本上就是參考PART 2的程式,將Reader、Source、Frame都多設一個給深度用的),但是運行到
    if (pDepthFrame->CopyFrameDataToArray(iWidth * iHeight, reinterpret_cast(mDepthImg.data)) == S_OK)
    的時候,一直無法顯示深度圖像,出現copy error,請問可能是發生什麼情況??

    • 單就你的敘述很難確實判斷問題在哪。
      不過,你是否有檢查過 iWidth 和 iHeight 的值?他們是否和 mDepthImg 的大小一致?

  5. heresy老师,您好。
    经过测试发现AcquireLatestFrame的失败的几率非常高,大概有30%到50%的概率读不到图像。请问这么高的概率是正常的么?请问AcquireLatestFram的失败会与什么有关联?
    谢谢。

  6. 你好。请问在用openCV显示彩色图像时,把CopyConvertedFrameDataToArray中第三个参数,从Bgra改为Rgba之后显示的图像颜色就变了,这是为什么?四个通道只是顺序不一样,混合后的结果不是应该是一样的吗?

    • 因為 OpenCV 會把第一個值當成 b,然後是 g、第三個是 r。
      所以你給他 rgb,他就會把 r 的值當作 b 來用,把 b 的值當成 r 來用,所以就錯了。

  7. Heresy大您好
    想請問您知道kinect studio v2.0所錄的紅外線影像是經過什麼樣的處理嗎?
    因為那個錄出來的紅外線影像還蠻漂亮的, 應該可以拿來作進一步的利用!
    只用API提供的InfraredSource錄出來實在是有點太暗…
    感謝Heresy大的文章!!

    • 文章最後有提到了,基本上一般就是從 16bit 的範圍裏面,選出你要的資料範圍。
      比如說只選前半段,把它對應到 8bit(0-255)再畫出來,這樣效果就會好很多了。

      要能根據狀況作調整的話,就是針對每個畫面下去分析,找出最佳的資料範圍。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.