GpuMatの画像データをhighguiのウィンドウに表示する

# はじめに
この記事では**cv::cuda::GpuMat**の画像データをhighguiのウィンドウに表示する方法について紹介します．

# 前提条件
- CUDAが動作するPC上で動作させる必要あり
    - **cv::cuda::GpuMat**を使うため<br><br>
- OpenCVをビルドする際に**WITH_CUDA**を有効にする必要あり
    - **cv::cuda::GpuMat**を使うため<br><br>
- OpenCVをビルドする際に**WITH_OPENGL**を有効にする必要あり
    - ウィンドウ描画にOpenGLの機能を用いるため

# GpuMatデータの表示方法
**cv::cuda::GpuMat**の画像データをhighguiのウィンドウに表示する方法は大まかに以下の3パターンがあります．

- ホストメモリに転送して表示する
- highgui(OpenGL)を使って表示する
- ogl::Texture2Dを使って表示する

## ホストメモリに転送して表示する
おそらく一般的に用いられているのは，

- **cv::cuda::GpuMat**のデータを[downloadメソッド](http://docs.opencv.org/trunk/d0/d60/classcv_1_1cuda_1_1GpuMat.html#ac1866bfc45d6ded137caf6d17b12fa56)等を使ってホスト側に転送する
- <code>cv::imshow</code>関数を使って表示する

という手順で表示する方法です．ただし，表示のためにホスト側にデータを転送する処理が入ってしまいます．

## highgui(OpenGL)を使って表示する
downloadメソッド等の転送処理を介さずに表示する方法として最も簡単なのは

- <code>cv::namedWindow</code>関数のフラグとして**cv::WINDOW_OPENGL**をセットする
- <code>cv::imshow</code>関数に**cv::cuda::GpuMat**の変数を直接渡す

という手順で表示する方法です．highguiの内部処理では後述する**「ogl::Texture2Dを使って表示する」**とほぼ同じ処理が動いています．

## ogl::Texture2Dを使って表示する
OpenCVにはOpenGLとの連携を行うための機能が入っています（詳細は[公式ドキュメント](http://docs.opencv.org/trunk/d2/d3c/group__core__opengl.html#gsc.tab=0)を参照ください）．
ここでは<code>cv::ogl::Texture2D</code>を使った方法について紹介することとします．

<code>cv::ogl::Texture2D</code>を使ってGpuMatデータを表示するには，

- <code>cv::namedWindow</code>関数のフラグとして**cv::WINDOW_OPENGL**をセットする
- **cv::cuda::GpuMat**のデータから**cv::ogl::Texture2D**のデータを生成する
- <code>cv::setOpenGlDrawCallback</code>関数によりOpenGLコールバック関数をセットする
- <code>cv::updateWindow</code>関数によりウィンドウ描画内容を更新する

という手順で実現できます．
ウィンドウ描画内容の更新タイミング等を制御したい場合はこちらの方法を用いた方がよいかもしれません．

### ウィンドウサイズ変更
画像サイズに応じて表示するウィンドウサイズを変えたい場合は，<code>cv::resizeWindow</code>関数を呼ぶ必要があります．

# サンプルコード
- ホストメモリに転送して表示する
- highgui(OpenGL)を使って表示する
- ogl::Texture2Dを使って表示する

これまで紹介した上記3つの方法を用いて**cv::cuda::GpuMat**の画像データをhighguiのウィンドウに表示するサンプルコードです．

```cpp:display_gpumat.cpp
#include <opencv2/core.hpp>
#include <opencv2/core/opengl.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

#include <iostream>

void drawTexture_cb(void* userdata)
{
    cv::ogl::Texture2D* texture = static_cast<cv::ogl::Texture2D*>(userdata);
    cv::ogl::render(*texture);
}

int main(int argc, char *argv[])
{
    cv::Mat src = cv::imread("lena.jpg", cv::IMREAD_UNCHANGED), dst;
    if(src.empty())
    {
        return -1;
    }

    cv::cuda::GpuMat d_src(src), d_dst;
    cv::cuda::cvtColor(d_src, d_dst, cv::COLOR_BGR2GRAY);

    // highgui(normal)を使った表示
    d_dst.download(dst); // ホストメモリに転送する
    cv::namedWindow("normal", cv::WINDOW_AUTOSIZE);
    cv::imshow("normal", dst);

    // highgui(OpenGL)を使った表示
    cv::namedWindow("highgui(OpenGL)", cv::WINDOW_AUTOSIZE | cv::WINDOW_OPENGL);
    cv::imshow("highgui(OpenGL)", d_dst);

    // ogl::Texture2Dを使った表示
    cv::namedWindow("highgui(Texture2D)", cv::WINDOW_OPENGL);
    cv::ogl::Texture2D texture(d_dst);
    cv::resizeWindow("highgui(Texture2D)", d_dst.cols, d_dst.rows);
    cv::setOpenGlContext("highgui(Texture2D)");
    cv::setOpenGlDrawCallback("highgui(Texture2D)", drawTexture_cb, &texture);
    cv::updateWindow("highgui(Texture2D)");

    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}
```

# おわりに
この記事では**cv::cuda::GpuMat**の画像データをhighguiのウィンドウに表示する方法について紹介しました．

# 備考
筆者は以下の環境で動作確認しました．

* [OpenCV 3.1.0](https://github.com/Itseez/opencv/releases/tag/3.1.0)
* Windows 10 Pro（64bit）
* Visual Studio 2013 Update5
* NVIDIA CUDA Toolkit 7.5