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. 老师您好,我使用了您在github上的获取红外图像的代码,发现显示后确实很黑。kinect 2.0自带的红外图像获取后发现就很亮。我现在就是没搞明白您说的那个如何把16bit的值经过您说的根据自己的需求选出范围然后显示处理。您的意思是不是比如我就想要某个红外数据强度内的数据(比如 l~h),我就将l以下的,h以上的值全部用0 和 255代替,然后把l~h的值等比放缩到0~255呢?我其实就是想实现自带的sample里的红外图像显示,但无奈他们的代码我有点没看懂0.0。希望老师指点。

    • 這邊的範例是直接建立一張 16bit 的影像,轉換成 8bit 顯示的部分是丟給 OpenCV 去做了。

      你提的方法是最簡單的線性對應,如果可以滿足需求也是可以直接這樣寫。

  2. 老師您好,妳寫的思路很清晰,對我們初學者很有用!我想請教您,如何調節彩色圖像視角的大小?就是能像達到相機條調節遠近焦距那樣實現畫面放大或者縮小。因為我覺得您的代碼顯示的彩色圖像視角有點窄了,我想調節大壹點看見更多背景,應該怎麼做呢?求指教~

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料