cv::VideoCaptureでRealSenseからデータを取得する

この投稿は[OpenCV Advent Calendar 2018](https://qiita.com/advent-calendar/2018/opencv)の9日目の記事です。

# はじめに
この記事では、OpenCV 4.0からサポートされた<code>cv::VideoCapture</code>でRealSenseからデータを取得する方法について紹介します。

## 動作確認環境
* RealSense D415/D435 (FW 5.10.13)
* Windows 10 Pro (1809)
* Visual Studio 2017 (15.9.3)
* OpenCV 4.0.0
* RealSense SDK 2.17.0
* CMake 3.13.1

# RealSenseとは？
RealSenseとは、Intelから発売されているRGB-Dセンサーです。
RealSenseには過去にもいくつかのモデルが存在しますが、現行のモデルはRealSense D400シリーズ（RealSense D415、RealSense D435、RealSense D435iなど）になります。

RealSense D400シリーズはInfraredのステレオカメラを搭載してDepthを計算します。（厳密には、赤外線波長をカットしていないRGBまたはGrayscaleのカメラです。）
ステレオカメラなのでテクスチャの乏しいシーンは苦手ですが、RealSense D400シリーズではプロジェクターからInfraredのパターンを投影してテクスチャを補うことができます。よく勘違いされますが、Kinect v1のようなStructured Light方式ではありません。

RealSense D400シリーズは、RealSense SDK 2.xを使用してデータを取得することができます。
このSDKを直接利用してデータを取得してもよいですが、この記事ではOpenCVの<code>cv::VideoCapture</code>で他のカメラと同じようにデータを取得する方法を紹介します。[^1]

## RealSense D415
<img src="https://qiita-image-store.s3.amazonaws.com/0/34834/bed28767-4fbc-9741-3a61-fa2c8c4ae0e2.jpeg" alt="RealSense D415" width=50%>

## RealSense D435
<img src="https://qiita-image-store.s3.amazonaws.com/0/34834/e3dbcb36-1f96-d924-38f0-e3fc7776db3a.jpeg" alt="RealSense D435" width=50%>

# OpenCVでRealSenseからデータを取得する方法
## RealSenseをサポートしたOpenCVをビルド、インストールする
公式に配布されいてるビルド済みのOpenCVはRealSenseのサポートが有効になっていません。まずは、OpenCVをソースコードからビルドしましょう。OpenCVのビルド方法の詳細については、多くの記事があるのでそちらを参考にしてください。

* **[OpenCV入門【3.0対応】（3）OpenCVの環境構築（OpenCV 3.0／3.1） | Build Insider](https://www.buildinsider.net/small/opencv/003#cmake)**

ここでは、要点だけをまとめておきます。

1. RealSense SDKをダウンロード、インストールする。

2. OpenCVのソースコードをダウンロード、バグを修正する。
    * **[Pull Request #13341 | OpenCV](https://github.com/opencv/opencv/pull/13341)** 


3. CMakeで以下の設定を行う。
    * **LIBREALSENSE**
        * **LIBREALSENSE_INCLUDE_DIR** C:/Program Files (x86)/Intel RealSense SDK 2.0/include
        * **LIBREALSENSE_LIBRARIES**  C:/Program Files (x86)/Intel RealSense SDK 2.0/lib/x86/realsense2.lib（またはC:/Program Files (x86)/Intel RealSense SDK 2.0/lib/x64/realsense2.lib）
    * **WITH**
        * **WITH_LIBREALSENSE** ☑(check)

4. 生成したOpenCVのプロジェクトをビルド、インストールする。

## cv::VideoCaptureでRealSenseからデータを取得する
```main.cpp
#include <opencv2/opencv.hpp>

int main( int argc, char* argv[] )
{
    // (1) Open cv::VideoCapture() with RealSense
    cv::VideoCapture capture( cv::VideoCaptureAPIs::CAP_INTELPERC );
    if( !capture.isOpened() ){
        return -1;
    }

    while( true ){
        // (2) Grab All Frames
        capture.grab();
        
        // (3) Retrieve Each Frames
        // (3.1) Color Frame
        cv::Mat color_frame;
        capture.retrieve( color_frame, cv::CAP_INTELPERC_IMAGE );

        // (3.2) Depth Frame
        cv::Mat depth_frame;
        capture.retrieve( depth_frame, cv::CAP_INTELPERC_DEPTH_MAP );

        // (3.3) Infrared Frame
        cv::Mat infrared_frame;
        capture.retrieve( infrared_frame, cv::CAP_INTELPERC_IR_MAP );

        // (4) Show Image
        cv::imshow( "Color", color_frame );
        depth_frame.convertTo( depth_frame, CV_8U, -255.0 / 10000.0, 255.0 ); // Scaling
        cv::imshow( "Depth", depth_frame );
        cv::imshow( "Infrared", infrared_frame );

        const int32_t key = cv::waitKey( 33 );
        if( key == 'q' ){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}
```

### (1) Open cv::VideoCapture() with RealSense
```cpp
// (1) Open cv::VideoCapture() with RealSense
cv::VideoCapture capture( cv::VideoCaptureAPIs::CAP_INTELPERC );
if( !capture.isOpened() ){
    return -1;
}
```
<code>cv::VideoCapture()</code>に<code>cv::VideoCaptureAPIs::CAP_INTELPERC</code>を指定することでRealSenseからキャプチャできます。
<code>CAP_INTELPERC</code>なのは、祖先にあたるIntel Perceptual Computing SDKの名残りですね。

### (2) Grab All Frames
```cpp
// (2) Grab All Frames
capture.grab();
```
<code>cv::VideoCapture::grab()</code>を呼ぶことで、フレームの取得を待ちます。

### (3) Retrieve Each Frames
```cpp
// (3) Retrieve Each Frames
// (3.1) Color Frame
cv::Mat color_frame;
capture.retrieve( color_frame, cv::CAP_INTELPERC_IMAGE );

// (3.2) Depth Frame
cv::Mat depth_frame;
capture.retrieve( depth_frame, cv::CAP_INTELPERC_DEPTH_MAP );

// (3.3) Infrared Frame
cv::Mat infrared_frame;
capture.retrieve( infrared_frame, cv::CAP_INTELPERC_IR_MAP );
```
<code>cv::VideoCapture::retrieve()</code>で<code>cv::Mat</code>にフレームを取得します。
第2引数に<code>cv::CAP_INTELPERC_IMAGE</code>を指定することでColor、<code>cv::CAP_INTELPERC_DEPTH_MAP</code>を指定することでDepth、<code>cv::CAP_INTELPERC_IR_MAP</code>を指定することでInfraredのフレームをそれぞれ取得することができます。
Colorは<code>CV_8UC3</code>（BGR）、Depthは<code>CV_16UC1</code>、Infraredは<code>CV_8UC1</code>のフォーマットでデータが格納されています。

### (4) Show Image
```cpp
// (4) Show Image
cv::imshow( "Color", color_frame );
depth_frame.convertTo( depth_frame, CV_8U, -255.0 / 10000.0, 255.0 ); // Scaling
cv::imshow( "Depth", depth_frame );
cv::imshow( "Infrared", infrared_frame );
```
Depthは<code>CV_16UC1</code>のフォーマットのため、そのまま可視化できません。
ここでは、<code>CV_16U</code>（[0, 10000]）を<code>CV_8U</code>（[255, 0]）にスケーリングして表示しています。

## 実行結果
<img src="https://qiita-image-store.s3.amazonaws.com/0/34834/beac443a-25cf-8f0a-4823-15278a00bd02.png" alt="Color" width=30%> <img src="https://qiita-image-store.s3.amazonaws.com/0/34834/06fc1784-0b5e-5041-791e-c014d108b511.png" alt="Depth" width=30%> <img src="https://qiita-image-store.s3.amazonaws.com/0/34834/c7de4527-d28f-d68d-7bac-470d4026d2be.png" alt="Infrared" width=30%>

# OpenCVのRealSenseサポートについて検討する
OpenCVでは他のカメラと同じようにRealSenseを簡単に扱うことができます。
安価で手軽に利用できるRGB-Dセンサーを簡単に扱えることは大きな利点ですね。
ただし、未実装の機能や制限も多く、まだ実用に耐えられるものではないかもしれません。
これらの機能を使いたいときは、RealSense SDKを直接利用するべきでしょう。[^1]

## 未実装機能
* 解像度、フレームレート、フォーマットの設定、取得
* ステレオカメラの両方のフレームを取得
* ファイルからのキャプチャ
* 複数台のRealSenseのサポート

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">自社の製品なんだから中の人が良い感じに実装すればいいのでは？🤔</p>&mdash; ゆるふわUnaさん (@UnaNancyOwen) <a href="https://twitter.com/UnaNancyOwen/status/1069256941967335425?ref_src=twsrc%5Etfw">2018年12月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# おわりに
この記事では、OpenCVの<code>cv::VideoCapture</code>でRealSenseからデータを取得する方法について紹介しました。
明日もすーぱーUnaさんたいむで「cv::kinfu::KinFuでKinect FusionをRealSenseで動かしてみる」です。

[^1]: もちろん、RealSense SDKを直接利用してデータを取得する方が柔軟に対応できます。RealSense SDKのサンプルを公開しているので、興味がある方は[こちら](https://github.com/UnaNancyOwen/RealSense2Sample)を参考にしてください。