OpenCVから参照される環境変数


# はじめに
これは、OpenCV Advent Calendar 2018 3日目の記事です。関連記事は[目次](http://qiita.com/advent-calendar/2018/opencv)にまとめられています。なお、本記事は筆者個人の意見であり、筆者の所属組織とは無関係です。

# OpenCVの挙動を変える
OpenCVの挙動は基本的にビルド時に決定されます。挙動というのはCUDA、OpenCL、Eigen、IPPやPLCなどの外部連携ライブラリを使う/使わない、SIMD命令を使う/使わない、と言った類の挙動です。
またビルド時に連携するライブラリのパスも、場合によっては一々指定する必要があります。

その時に、毎回手動でパスを指定したり、挙動を変えるためにビルドをやり直すのも、なかなかイケてない状況です。そこで、一部限られた挙動に限り、環境変数で挙動を変えることが可能です。

具体例も交えて紹介していきます。

# 実行時の挙動を変える

実行時の挙動の一つとして、OpenCLのデバイスが挙げられます。OpenCLはご存知のようにアクセラレータで動作するカーネルを、単一の言語で記述することができます。ここで言う「アクセラレータ」はGeforeceやRadeonなどのGPUが一般的ですが、Intel HD GraphicsなどのCPU内蔵のGPU、FPGAやCPU自身でも実行することが[^1]できます。

[^1]: デバイスが対応していれば

例えば、筆者の私物のラップトップは、以下のような構成です

| Device |  |
|:----|:-----------------------|
| CPU | Intel Core i7 8650U    |
| iGPU| Intel HD Graphics 620  | 
| dGPU| NVIDIA Geforce GTX 1060|

 
ここに記述されているCPU、iGPU、dGPUはそれぞれOpenCLに対応しているので、OpenCLでカーネルを記述すると、勝手にベクタライズ/並列化されたプログラムをそれぞれのプラットフォームで走らせることができます。ただし、基本的にはOpenCLが列挙した1番目のデバイスが選ばれます。他のプラットフォームで走らせたい場合はどうすれば良いのでしょうか？

ここで、環境変数```OPENCV_OPENCL_DEVICE```を指定すると、任意のプラットフォームを指定できます

環境変数の指定方法はコントロールパネルからしたり、Visual Studioでデバッグしてる場合は、Debugの項目からも指定できます。Visual Studioでデバッグ実行する際に環境変数を指定する方法は結構便利なので、皆さん覚えてくださいね。

<img width="778" alt="Screenshot 2018-11-27 05.03.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/12162/ecdeeb70-0bfb-4d34-2bf4-7073b8a76f71.png">


便利は便利なのですが、本記事ではGit bashコマンドラインで直接指定して、OpenCVのテストプログラムを実行してみます。

以下が実行したときのログです

```
$ ./opencv_test_imgproc --gtest_filter=*CvtColor.BGR2YCrCb/0
CTEST_FULL_OUTPUT
OpenCV version: 4.0.0
OpenCV VCS version: 4.0.0
Build type: N/A
WARNING: build value differs from runtime: Release
Compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/x86_amd64/cl.exe  (ver 19.0.24215.1)
Parallel framework: ms-concurrency
CPU features: SSE SSE2 SSE3 *SSE4.1 *SSE4.2 *FP16 *AVX *AVX2
Intel(R) IPP version: ippIP AVX2 (l9) 2017.0.3 (-) Jul 31 2017
OpenCL Platforms:
    NVIDIA CUDA
        dGPU: GeForce GTX 1060 (OpenCL 1.2 CUDA)
    Intel(R) OpenCL
        iGPU: Intel(R) UHD Graphics 620 (OpenCL 2.1 )
        CPU: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz (OpenCL 2.1 (Build 10))
Current OpenCL device:
    Type = dGPU
    Name = GeForce GTX 1060
    Version = OpenCL 1.2 CUDA
    Driver version = 411.63
    Address bits = 64
    Compute units = 10
    Max work group size = 1024
    Local memory size = 48 KB
    Max memory allocation size = 1 GB 512 MB
    Double support = Yes
    Host unified memory = No
    Device extensions:
        cl_khr_global_int32_base_atomics
        cl_khr_global_int32_extended_atomics
        cl_khr_local_int32_base_atomics
        cl_khr_local_int32_extended_atomics
        cl_khr_fp64
        cl_khr_byte_addressable_store
        cl_khr_icd
        cl_khr_gl_sharing
        cl_nv_compiler_options
        cl_nv_device_attribute_query
        cl_nv_pragma_unroll
        cl_nv_d3d10_sharing
        cl_khr_d3d10_sharing
        cl_nv_d3d11_sharing
        cl_nv_copy_opts
        cl_nv_create_buffer
    Has AMD Blas = No
    Has AMD Fft = No
    Preferred vector width char = 1
    Preferred vector width short = 1
    Preferred vector width int = 1
    Preferred vector width long = 1
    Preferred vector width float = 1
    Preferred vector width double = 1
Note: Google Test filter = *CvtColor.BGR2YCrCb/0
```
## 解説
この出力を全部読むと日が暮れるので、かいつまんで説明すると、OpenCVをロードした時点でデバイスの列挙が行われます

```
OpenCL Platforms:
    NVIDIA CUDA
        dGPU: GeForce GTX 1060 (OpenCL 1.2 CUDA)
    Intel(R) OpenCL
        iGPU: Intel(R) UHD Graphics 620 (OpenCL 2.1 )
        CPU: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz (OpenCL 2.1 (Build 10))
```
前述の通り、GeForce GTX 1060、Intel UHD Graphics 620とIntel Core i7-8650Uの3種類が列挙されています。その後読み解くと、以下のような出力があります。

```
Current OpenCL device:
    Type = dGPU
    Name = GeForce GTX 1060
    Version = OpenCL 1.2 CUDA
```
```dGPU```とは"Discrete GPU"の略で、いわゆるGPUのことです。広義のGPUには、CPU内蔵のIntel HD Graphicsも含みます。明示的に区別するためにCPU内蔵のGPUを```iGPU```、独立したGPUを```dGPU```と指定します。

これがデフォルトの挙動です。では、環境変数を指定してデバイスを切り替えてみましょう。

## iGPU (Intel HD Graphics 620)を使う

起動時に```変数名=値```というペアをコマンドの前に指定すると、環境変数を上書きできます。以下では```:igpu```を環境変数に指定しています。（コロンを忘れないように）

```
$ OPENCV_OPENCL_DEVICE=:igpu ./opencv_test_imgproc --gtest_filter=*CvtColor.BGR2YCrCb/0
(中略)
Current OpenCL device:
    Type = iGPU
    Name = Intel(R) UHD Graphics 620
    Version = OpenCL 2.1
```
先程と出力が代わり、iGPUを使用するように切り替わっていることがわかります。OpenCLの対応バージョンも変わっています。この様に、OpenCLのカーネルを実行するデバイスを、実行時（プログラム起動時）に切り替えられていることがわかります。

## CPU (Intel Core i7 8650U)を使う

最近のCore i7 シリーズは、CPUでもOpenCLを実行できます。並列化したプログラムを、わざわざ書き換えなくても、一度書いたOpenCLのカーネルを再利用できます。

```
$ OPENCV_OPENCL_DEVICE=:cpu ./opencv_test_imgproc --gtest_filter=*CvtColor.BGR2YCrCb/0
(中略)
Current OpenCL device:
    Type = CPU
    Name = Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
    Version = OpenCL 2.1 (Build 10)
```

今度はちゃんとCPU上で実行されました。

# その他実行時に参照される環境変数
書きなぐるだけなのも何なので、筆者が使ってる変数を抜粋した上で解説を書き加えておきます

## OPENCV_CPU_DISABLE
 - OpenCV 3.3 から導入されたDispatchの機能を使うことで、一部のCPUの特殊命令を使用せずに画像処理を行います。例えば、デフォルトのコンフィグだとSSE4、AVX、AVX2あたりの命令は実行時にチェックがおこなれます。このとき、```OPENCV_CPU_DISABLE=AVX```と設定することでAVX命令を使う部分を迂回して画像処理が行われます。これにより、実行結果が遅くなりますが、この差から、AVX版のパフォーマンス向上がどれ位なのか知ることができます。実行時に命令無効化が行われるので、ビルドし直す必要がありません。
 - 当然ながらDispatchを利用せず、Baselineだけで指定した場合はこの環境変数は無視されます。

## OPENCV_DUMP_CONFIG
 - OpenCVのデバッグ情報を出力します。OpenCVをロードした時点で、DLL内部にあるビルド情報が吐き出されます。

```

OpenCV build configuration is:

General configuration for OpenCV 4.0.0 =====================================
  Version control:               4.0.0

  Platform:
    Timestamp:                   2018-11-20T14:48:49Z
    Host:                        Windows 10.0.17134 AMD64
    CMake:                       3.11.0
    CMake generator:             Visual Studio 14 2015 Win64
    CMake build tool:            C:/Program Files (x86)/MSBuild/14.0/bin/MSBuild.exe
    MSVC:                        1900

  CPU/HW features:
    Baseline:                    SSE SSE2 SSE3
      requested:                 SSE3
    Dispatched code generation:  SSE4_1 SSE4_2 FP16 AVX AVX2
      requested:                 SSE4_1 SSE4_2 AVX FP16 AVX2 AVX512_SKX
      SSE4_1 (5 files):          + SSSE3 SSE4_1
      SSE4_2 (2 files):          + SSSE3 SSE4_1 POPCNT SSE4_2
      FP16 (1 files):            + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
      AVX (6 files):             + SSSE3 SSE4_1 POPCNT SSE4_2 AVX
      AVX2 (11 files):           + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2
(中略)
  Install to:                    C:/work/opencv-fork/build/install
-----------------------------------------------------------------

```
 - 内部では```getBuildInformation()```を呼んでるだけです。空文字以外を指定するとビルド情報が出力されます[^4]。


```cpp:system.cpp
        if (getenv("OPENCV_DUMP_CONFIG"))
        {
            fprintf(stderr, "\nOpenCV build configuration is:\n%s\n",
                cv::getBuildInformation().c_str());
        }
```

[^4]: なので```OPENCV_DUMP_CONFIG=OFF```を指定しても```getBuildInformation```がcall/出力されます。


## OPENCV_FOR_THREADS_NUM
 - CPU上で並列実行の並列数を指定します。```imgproc```モジュールに代表されるような、全画素をなめるようなループをCPU実行する場合、OpenCVは内部で```parallell_for```で実行します。
 - デフォルトではCPUの論理スレッド数が指定されます。(ただし、Android版では2スレッドがデフォルト値)この値を変える事によって、コア数の違いでパフォーマンスがスケールするか実験できます。また、OpenCV内部をデバッグする場合、並列に処理される部分はトレースが著しく面倒くさいのですが、この変数を1に設定することで並列化を一時的に無効化できます。OpenCVビルド職人がデバッグする際に重宝する環境変数です。

 - 無指定の場合

```
$ opencv_perf_imgproc --gtest_filter=Size_CvtMode_cvtColor8u.cvtColor8u/123
Time compensation is 0
CTEST_FULL_OUTPUT
(中略)
[ RUN      ] Size_CvtMode_cvtColor8u.cvtColor8u/123, where GetParam() = (640x480, COLOR_BGR2GRAY)
[ PERFSTAT ]    (samples=100   mean=0.20   median=0.19   min=0.19   stddev=0.01 (4.4%))
[       OK ] Size_CvtMode_cvtColor8u.cvtColor8u/123 (106 ms)
```

 - スレッド数を1に固定した場合

```
$ OPENCV_FOR_THREADS_NUM=1 opencv_perf_imgproc --gtest_filter=Size_CvtMode_cvtColor8u.cvtColor8u/123
Time compensation is 0
CTEST_FULL_OUTPUT
(中略)
[ RUN      ] Size_CvtMode_cvtColor8u.cvtColor8u/123, where GetParam() = (640x480, COLOR_BGR2GRAY)
[ PERFSTAT ]    (samples=100   mean=0.42   median=0.40   min=0.38   stddev=0.04 (8.7%))
[       OK ] Size_CvtMode_cvtColor8u.cvtColor8u/123 (223 ms)
```

 - 100回計測した中央値が```median```に表示されてますが、倍遅くなってることが分かります[^9]。

[^9]: でも、本当は8倍差が出てほしいのだが？

## OPENCV_IPP
 - IPPライブラリが実行するモジュールを実行時に選択できる

```
OPENCV_IPP=disabled|sse42|avx2|avx512
```
 - disabledにするとOpenCV独自の実装が呼ばれる。またavx512は64bit環境下でのみ選べる。
 - 複数の項目は指定できないし、一つ選択すると、下位の命令セットも自動的に有効になる。(avx2を指定するとsse42も有効になる)
  - IPP 自体の設定はもっと細かいのだが、OpenCVでまとめられて、指定できるのは前述の3つ+disabledのみ

## OPENCV_LEGACY_WAITKEY
 - 特殊キーを拾う挙動が3.2から変わりました[^2]。その挙動を3.2より前に戻すためにはこの環境変数を使用します。空文字以外を指定すると3.2より前の挙動に戻ります。

[^2]: 厳密には3.2.0-rcから

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">一方で、 cv::waitKey は、特殊キーが押されても反応するが、キーコードは0が返される。（矢印キーとかファンクションキーとか）。この挙動は、環境変数 OPENCV_LEGACY_WAITKEY に１を設定することで、3.2.0 より前の挙動と一致する。</p>&mdash; Tomoaki Teshima (@tomoaki_teshima) <a href="https://twitter.com/tomoaki_teshima/status/998445379099049984?ref_src=twsrc%5Etfw">2018年5月21日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



## OPENCV_OPENCL_DEVICE
 - 前述の通り

```
OPENCV_OPENCL_DEVICE=disabled|platform:deviceTypes:deviceName
```
 - ```disabled``` は問答無用に無効化します。
 - deviceTypesは現状以下の中から選びます
    - ```gpu```
    - ```dgpu```
    - ```igpu```
    - ```cpu```
    - ```accelerator```
    - ```all```
 - deviceNameは
    - 1桁の数字の場合は列挙されたn番目のデバイスを選ぶ
    - 2桁以上の場合は名前の中に含まれた数字にマッチするかどうか
        - ex: 1080 -> Geforce GTX1080
        - ex: 580 -> Radeon RX 580, Geforce GTX 580
        - ex: 620 -> Intel(R) UHD Graphics 620

## OPENCV_OPENCL_RUNTIME
 - OpenCL.dllもしくはlibOpenCL.soをロードするためのパスを環境変数で指定できる
 - disabledを指定すると、libOpenCL.soのロードを諦める(```OPENCV_OPENCL_DEVICE=disabled```と等価)


## OPENCV_TEST_DATA_PATH
 - opencv_extraリポジトリのパス。テスト実行時にこのパスが参照され、リポジトリ内にあるテストデータやリファレンスの結果が参照され、テストが実行される。

## OPENCV_TRACE
 - ```TRACE```に関してはdandelion1124先生に聞いて下さい。

## OPENCV_VIDEOCAPTURE_DEBUG
 - OpenCVのビデオ読み込み周りのバックグラウンドエンジンからのデバッグ情報を表示する。

# ビルド時に参照される環境変数一覧

特定のライブラリと、OpenCVを連携することができます。このとき、連携させるライブラリがどこにインストールされているか、環境変数に登録するライブラリが多いです。[^6]

[^6]: Windowsでは、レジストリに書き込むライブラリもある

この環境変数が設定されており、かつ環境変数が示す先に実体が存在するとき、OpenCVは指定されたライブラリと連携したビルドを試します。

- ANT_DIR
- Atlas_ROOT_DIR
- DESTDIR
- EIGEN_ROOT
- GSTREAMER_DIR
- HOME
- IPP_ASYNC_ROOT
- MFX_HOME
- MKLROOT
- NVSDKCOMPUTE_ROOT
- OPEN_NI_INSTALL_PATH
- OPEN_NI_INSTALL_PATH64
- OPEN_NI_LIB
- OPEN_NI_LIB64
- OpenBLAS_HOME
- OpenBLAS
- OPENCV_DOWNLOAD_PATH
- OPENCV_IPP_PATH
- OPENNI2_INCLUDE
- OPENNI2_INCLUDE64
- OPENNI2_LIB
- OPENNI2_LIB64
- OPENNI2_REDIST
- PCSDK_DIR
- ProgramFiles
- SystemDrive
- TBBROOT
- VA_INTEL_IOCL_ROOT
- VA_INTEL_MSDK_ROOT

# 終わりに
OpenCV を再ビルドするの、疲れますよね。（疲れないけれど）

筆者は以下の環境で確認しました

 - OpenCV 4.0.0
 - Windows 10 64bit
 - Visual Studio Community 2015 (Update 3)
 - CMake 3.11.0
 - CPU Intel Core i7 8650U
 - iGPU Intel HD Graphics 620
 - dGPU NVIDIA Geforce GTX 1060

明日も私の予定で、なんかネタを考えます。