この記事はOpenCV Advent Calendar 2015の12日目の記事です.
はじめに
この記事では,OpenCVの初期化処理,終了処理時に内部的に行われている処理について解説します.
以降,モジュール毎の初期化処理,終了処理時についてまとめます.
※筆者はWindowsをメインに確認しているため他のプラットフォームでは多少異なる可能性があります.
初期化処理
coreモジュール
全般
coreモジュールでは以下のような初期化処理が行われます.
- CvType::CvTypeメソッドによる型情報登録(内部的にcvRegisterType関数がコールされる)
- cv::__terminationの初期化(falseがセットされる)
後者は具体的にmodules/core/src/system.cpp
の下記処理で初期化が行われます.
cv::__terminationについては後述する終了処理の説明にてもう少し触れます.
namespace cv {
bool __termination = false;
}
並列処理フレームワーク関連
coreモジュールでは並列処理フレームワークの初期化も行われます.
OpenCVライブラリのビルド時に選択した並列処理フレームワークによって以下のような初期化が行われます.
| 並列処理フレームワーク | 処理 |
|:--|:--|:--|
| TBB | tbb::task_schedulerクラスのインスタンス生成 |
| Concurrency | SchedPtrクラスのインスタンス生成 |
| OpenMP | omp_get_max_threads関数の実行 |
imgcodecsモジュール
imgcodecsモジュールmodules/imgcodecs/src/loadsave.cpp
に下記のコードがあることからImageCodecInitializerで各種コーデックのデコーダー,エンコーダー初期化が行われます.
static ImageCodecInitializer codecs;
`
ImageCodecInitializerによるコーデックの初期化処理はmodules/imgcodecs/src/loadsave.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;
};
変数decoders
には,現在使用しているOpenCVライブラリがサポートしているフォーマット(デコード処理)がvector配列として格納されます.vector配列への格納は上記コードのdecoders.push_backにあたります.
同様に変数encoders
には,現在使用しているOpenCVライブラリがサポートしているフォーマット(エンコード処理)がvector配列として格納されます.vector配列への格納は上記コードのencoders.push_backにあたります.
デコード/エンコード処理
前述のdecoders
,encoders
という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)をロードして,各種コールバック関数のセットも行います.
cv::VideoCapture::VideoCapture
cv::VideoCapture::open
cvCreateFileCapture
cvCreateFileCapture_FFMPEG_proxy
icvInitFFMPEG::Init
icvInitFFMPEG::icvInitFFMPEG
highguiモジュール
cv::namedWindow関数を呼び出した場合,ウィンドウ描画に関する初期化処理が実行されます.
WITH_WIN32UIを有効にした場合は,以下のシーケンスでWNDCLASS初期化が行われます.
cv::namedWindow
cvNamedWindow
cvInitSystem
RegisterClass
WITH_QTを有効にした場合は,以下のシーケンスでQtの初期化が行われます.
cv::namedWindow
cvNamedWindow
GuiReceiver::GuiReceiver
icvInitSystem
QApplication::instance
終了処理
coreモジュール
DLLオプションのエントリポイントであるDllMain関数にて,変数fdwReasonがDLL_THREAD_DETACHもしくはDLL_PROCESS_DETACHの場合に終了処理が行われます.DllMain関数については,MSDNを参照ください.
以下にOpenCVに実装されているDllMain関数を引用します.
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
- Windows 8.1 Pro(64bit)
- Visual Studio 2013 Update5