OpenCVの初期化処理，終了処理

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

# はじめに
この記事では，OpenCVの初期化処理，終了処理時に内部的に行われている処理について解説します．
以降，モジュール毎の初期化処理，終了処理時についてまとめます．
※筆者はWindowsをメインに確認しているため他のプラットフォームでは多少異なる可能性があります．

# 初期化処理
## coreモジュール
### 全般
coreモジュールでは以下のような初期化処理が行われます．

* CvType::CvTypeメソッドによる型情報登録（内部的にcvRegisterType関数がコールされる）
* cv::__terminationの初期化（falseがセットされる）

後者は具体的に<code>modules/core/src/system.cpp</code>の下記処理で初期化が行われます．
cv::\_\_terminationについては後述する終了処理の説明にてもう少し触れます．

```cpp:
namespace cv {
bool __termination = false;
}
```

### 並列処理フレームワーク関連
coreモジュールでは並列処理フレームワークの初期化も行われます．
OpenCVライブラリのビルド時に選択した並列処理フレームワークによって以下のような初期化が行われます．

| 並列処理フレームワーク | 処理 |
|:--|:--|:--|
| TBB | tbb::task_schedulerクラスのインスタンス生成 |
| Concurrency | SchedPtrクラスのインスタンス生成 |
| OpenMP | omp_get_max_threads関数の実行 |


## imgcodecsモジュール
imgcodecsモジュール<code>modules/imgcodecs/src/loadsave.cpp</code>に下記のコードがあることから**ImageCodecInitializer**で各種コーデックのデコーダー，エンコーダー初期化が行われます．

<code>static ImageCodecInitializer codecs;</code>
`
ImageCodecInitializerによるコーデックの初期化処理は<code>modules/imgcodecs/src/loadsave.cpp</code>にて実装されています．以下にその処理を抜粋します．

```cpp:
struct ImageCodecInitializer
{
    /**
     * Default Constructor for the ImageCodeInitializer
    */
    ImageCodecInitializer()
    {
        /// BMP Support
        decoders.push_back( makePtr<BmpDecoder>() );
        encoders.push_back( makePtr<BmpEncoder>() );

        decoders.push_back( makePtr<HdrDecoder>() );
        encoders.push_back( makePtr<HdrEncoder>() );
    #ifdef HAVE_JPEG
        decoders.push_back( makePtr<JpegDecoder>() );
        encoders.push_back( makePtr<JpegEncoder>() );
    #endif
    #ifdef HAVE_WEBP
        decoders.push_back( makePtr<WebPDecoder>() );
        encoders.push_back( makePtr<WebPEncoder>() );
    #endif

    // 中略

    std::vector<ImageDecoder> decoders;
    std::vector<ImageEncoder> encoders;
};
```

変数<code>decoders</code>には，現在使用しているOpenCVライブラリがサポートしているフォーマット（デコード処理）がvector配列として格納されます．vector配列への格納は上記コードのdecoders.push_backにあたります．

同様に変数<code>encoders</code>には，現在使用しているOpenCVライブラリがサポートしているフォーマット（エンコード処理）がvector配列として格納されます．vector配列への格納は上記コードのencoders.push_backにあたります．

### デコード/エンコード処理
前述の<code>decoders</code>，<code>encoders</code>というvector配列が実際どのタイミングで使われるかというと，cv::imwrite，cv::imread関数の内部で参照されます．

具体的にはcv::imwrite，cv::imreadの内部で呼ばれるcv::findDecoder，cv::findEncoderにて目的のコーデックを処理できるDecoder，Encoderを検索する際に参照され，目的のDecoder，Encoderが見付かると後段の処理でこれらを用いてデコード/エンコード処理が行われます．

## videoioモジュール
**WITH_DSHOW**，**WITH_MSMF**を有効にした場合は，OpenCV初期化時にvideoInputクラスのインスタンスが生成され，videoInput libraryによってDirectShowもしくはMSMFの初期化が行われます．

また，**WITH_FFMPEG**を有効にした場合は，以下のシーケンスでFFMPEGの初期化が行われます．icvInitFFMPEGメソッドではopencv_ffmpeg.dll（64bitビルドの場合はopencv_ffmpeg_64.dll）をロードして，各種コールバック関数のセットも行います．

<pre>
cv::VideoCapture::VideoCapture
　cv::VideoCapture::open
　　cvCreateFileCapture
　　　cvCreateFileCapture_FFMPEG_proxy
　　　　icvInitFFMPEG::Init
　　　　　icvInitFFMPEG::icvInitFFMPEG
</pre>

## highguiモジュール
cv::namedWindow関数を呼び出した場合，ウィンドウ描画に関する初期化処理が実行されます．

**WITH_WIN32UI**を有効にした場合は，以下のシーケンスでWNDCLASS初期化が行われます．

<pre>
cv::namedWindow
　cvNamedWindow
　　cvInitSystem
　　　RegisterClass
</pre>

**WITH_QT**を有効にした場合は，以下のシーケンスでQtの初期化が行われます．

<pre>
cv::namedWindow
　cvNamedWindow
　　GuiReceiver::GuiReceiver
　　　icvInitSystem
　　　　QApplication::instance
</pre>


# 終了処理
## coreモジュール
DLLオプションのエントリポイントであるDllMain関数にて，変数fdwReasonがDLL_THREAD_DETACHもしくはDLL_PROCESS_DETACHの場合に終了処理が行われます．DllMain関数については，[MSDN](https://msdn.microsoft.com/ja-jp/library/cc429094.aspx)を参照ください．

以下にOpenCVに実装されているDllMain関数を引用します．

```cpp:
extern "C"
BOOL WINAPI DllMain(HINSTANCE, DWORD fdwReason, LPVOID lpReserved)
{
    if (fdwReason == DLL_THREAD_DETACH || fdwReason == DLL_PROCESS_DETACH)
    {
        if (lpReserved != NULL) // called after ExitProcess() call
        {
            cv::__termination = true;
        }
        else
        {
            // Not allowed to free resources if lpReserved is non-null
            // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583.aspx
            cv::deleteThreadAllocData();
            cv::deleteThreadData();
        }
    }
    return TRUE;
}
#endif
```

上記の実装からもわかるように変数lpReservedが非NULLの場合は前述のcv::\_\_terminationをtrueに設定することで二重解放を防止しています．一方で変数lpReservedがNULLの場合にはcv::deleteThreadAllocData，cv::deleteThreadDataを呼び出してリソースの解放を行います．

## videoioモジュール
デストラクタ実行時にビデオキャプチャの終了処理が行われます．また，**WITH_FFMPEG**を有効にした場合はFFMPEGの終了処理が行われます．

# おわりに
OpenCVの初期化処理，終了処理時に内部的に行われている処理について解説しました．
これらの振る舞いを頭の片隅に入れておくとデバッグ時に活用できるかもしれません．

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

* [OpenCV 3.0.0](https://github.com/Itseez/opencv/releases/tag/3.0.0)
* Windows 8.1 Pro（64bit）
* Visual Studio 2013 Update5