cudacodecモジュールを使ってみよう

この記事は[OpenCV Advent Calendar 2016](http://qiita.com/advent-calendar/2016/opencv)の20日目の記事です．

# はじめに
この記事では~~Web上で語られることが少ない~~cudacodecモジュールについて紹介します．

# cudacodecモジュールとは
OpenCVのcudacodecモジュールはGPUを用いて動画のエンコード/デコードを行うための機能を提供しています．また，内部的には[NVIDIA VIDEO CODEC SDK](https://developer.nvidia.com/nvidia-video-codec-sdk)を使って実装されています．

このモジュールのAPIについては[公式ドキュメント](http://docs.opencv.org/3.1.0/d0/d61/group__cudacodec.html)を参照下さい．

## NVIDIA VIDEO CODEC SDKとは
[NVIDIA VIDEO CODEC SDK](https://developer.nvidia.com/nvidia-video-codec-sdk)では動画のエンコード/デコード処理をGPUで行うためのAPIが提供されています．以下に[NVIDIA VIDEO CODEC SDK公式サイト](https://developer.nvidia.com/nvidia-video-codec-sdk)にある図を引用します．

![VCSDK_001_b.png](https://qiita-image-store.s3.amazonaws.com/0/31158/aa567bf5-a141-dea3-0133-ab0bba97bc7f.png)

この図からもわかるようにNVDEC，NVENCにてGPUを用いて動画のデコード処理，エンコード処理を行います．また，GPUのアーキテクチャによってサポートする機能，コーデック等が異なる点に注意が必要です．詳細は下記ページを参照ください．

* <https://developer.nvidia.com/nvidia-video-codec-sdk#NVENCFeatures>
* <https://developer.nvidia.com/nvidia-video-codec-sdk#NVDECFeatures>

# cudacodecモジュールを有効にする方法
ただし，OpenCV 3.1時点のcudacodecモジュールにはバグがあるため，そのままではこれらの機能を有効にすることができません．

そのため，cudacodecモジュールを有効にするためには以下の変更（筆者のプルリク）を適用して下さい．この変更はmasterブランチにマージされているためOpenCV 3.2以降では特にこの変更なしで有効にできるはずです．

* [fixed to use NVCUVID in 'cudacodec' module. #6036](https://github.com/opencv/opencv/pull/6036)

また，cudacodecモジュールとは直接関係ありませんが，今回の開発環境（**VS2015**，**CUDA8.0**）でOpenCV 3.1を動作させるために以下の変更も適用しています．

* [changed to find TBB library(VS2015) #5868](https://github.com/opencv/opencv/pull/5868)：VS2015版のTBBを見つけるための対応
* [GraphCut deprecated in NPP 7.5 and removed in 8.0 #5672](https://github.com/opencv/opencv/pull/5672)：CUDA8でビルドできない問題の対応

さらに[こちら](https://developer.nvidia.com/nvidia-video-codec-sdk#NVDECFeatures)を読むとわかるように今回用いたGeForce GTX 1060ではハードウェアでH.265を処理できるため，[cv::cudacodec::detail::VideoDecoder::create](https://github.com/opencv/opencv/blob/3.1.0/modules/cudacodec/src/video_decoder.cpp#L60)関数に

```cpp:
cudaVideoCodec_HEVC  == _codec ||
```

という条件式を追加し，H.265のファイルも処理できるようにしています（この条件式を追加しないとASSERTが起きてしまう）．

## CMakeオプション
cudacodecモジュールを有効にするには以下のCMakeオプションをONにしてOpenCVライブラリをビルドする必要があります．

- [x] BUILD_opencv_cudacodec
- [x] BUILD_opencv_cudev
- [x] WITH_CUDA
- [x] WITH_NVCUVID

## 実装手順
ここまでの手順でライブラリ生成ができるようになったので実装手順に入ります．
cv::cudacodec::VideoReaderクラスを使った実装手順は以下の通りです．

1. cv::cudacodec::VideoReaderクラスのインスタンスを生成する
1. cv::cudacodec::VideoReaderクラスのnextFrameメソッドで1フレームデコードする

以下にこの手順に従ったサンプルコードを示します．

```cpp:
// cv::cudacodec::VideoReaderクラスのインスタンスを生成する
cv::Ptr<cv::cudacodec::VideoReader> d_reader = cv::cudacodec::createVideoReader("hoge.mpg");

cv::cuda::GpuMat d_frame;

// 1フレームデコードする
d_reader->nextFrame(d_frame)
```

# パフォーマンス計測
以下のURLにあるテストデータを用いて1フレーム当たりのデコード時間を計測しました．

* <https://github.com/opencv/opencv_extra/tree/3.1.0/testdata/gpu/video>
* <http://www.jell.yfish.us/>
* <http://www3.jvckenwood.com/pro/video/prohd/>

また，処理時間を計測するために用いたテストプログラムは[こちら](https://gist.github.com/atinfinity/a7c098c3ddcf1ea27a53bee7af679e4e)を参照ください．各ファイルに対するCPU処理（<code>cv::VideoCapture </code>）、GPU処理（<code>cudacodec::createVideoReader</code>）のデコード時間は以下の通りです．

| ファイル名                     | コーデック | 画像サイズ | cv::VideoCapture | cv::cudacodec::createVideoReader |
|:-------------------------------|:-----------|:-----------|:-------------|:-----------------------------|
| 1920x1080.avi                  | MPEG-4     | 1920x1080  | 3.99ms       | 0.82ms                       |
| jellyfish-10-mbps-hd-h264.mkv  | H.264      | 1920x1080  | 3.90ms       | 0.56ms                       |
| jellyfish-10-mbps-hd-hevc.mkv  | H.265      | 1920x1080  | 4.03ms       | 0.56ms                       |
| jellyfish-50-mbps-hd-h264.mkv  | H.264      | 1920x1080  | 4.10ms       | 0.56ms                       |
| jellyfish-50-mbps-hd-hevc.mkv  | H.265      | 1920x1080  | 4.86ms       | 0.56ms                       |
| jellyfish-100-mbps-hd-h264.mkv | H.264      | 1920x1080  | 4.48ms       | 0.57ms                       |
| jellyfish-100-mbps-hd-hevc.mkv | H.265      | 1920x1080  | 6.35ms       | 0.56ms                       |
| TRM10098_01.MOV                | H.264      | 1920x1080  | 6.97ms       | 1.51ms                       |
| TRM10115_01.MOV                | H.264      | 1920x1080  | 6.50ms       | 1.49ms                       |

上記の結果からも<code>cudacodec::createVideoReader</code>を使うことで高速にデコード処理を行うことができていることがわかります（お使いのGPUによって速度は変わると思います）．

ただし，<http://www.jell.yfish.us/>にあるMain10プロファイルのものは2016/12/18時点で正しく処理できませんでした．これはおそらく<https://developer.nvidia.com/nvidia-video-codec-sdk#NVDECFeatures>にある

> &#42; HEVC/VP9 10/12 bit decoding SW support coming in Video Codec SDK 8.0

という制約のためだと考えられます．また，コーデックやプロファイルによってはデコード結果が正しくない場合もあるため，使用する前に確認しておくとよいでしょう．

# TODO
また，現時点でのcudacodecモジュールに関する積み残しは以下の通りです．
**ご興味ある方は対応してPRを投げてみてください！**

* cudacodecモジュールの非Windows対応
  * CMakeの[FindCUDA](https://cmake.org/cmake/help/v3.7/module/FindCUDA.html)で**CUDA_nvcuvid_LIBRARY**を見つけられるのは現状Windowsのみなので，Linux側の処理を書いてあげる必要あり
* [cv::cudacodec::detail::VideoDecoder::create](https://github.com/opencv/opencv/blob/3.1.0/modules/cudacodec/src/video_decoder.cpp#L60)関数のHEVC対応
  * GPUのアーキテクチャによって対応コーデックが異なるのでその辺をちゃんとハンドリングしながらチェックする必要があるはず
* cudacodecモジュールのエンコード処理対応
  * NVEncode APIを使った実装が必要

# おわりに
この記事では~~Web上で語られることが少ない~~cudacodecモジュールについて紹介しました．正しくデコードできるかの動作確認は必要になるものの，動画のデコード処理時間を短縮できる魅力的な機能なので利用を検討してみてもよいかもしれません．

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

* CPU：Intel Core i7-6700HQ
* メモリ：64GB
* GPU：NVIDIA GeForce GTX 1060 / 6GB
* Windows 10 Pro(64bit)
* [OpenCV 3.1.0](https://github.com/Itseez/opencv/releases/tag/3.1.0)
* CUDA 8.0
* Visual Studio 2015 Professional