OpenCVのプラグイン機能

この記事は[OpenCV Advent Calendar 2021](https://qiita.com/advent-calendar/2021/opencv)の5日目の記事です。

## はじめに

従来、OpenCVの`cv::parallel_for_` backend、highgui backend、videoio backendはOpenCVインストール時に決定されてしまうため、OpenCVインストール後にbackendを変更しようと思うとOpenCVのビルドオプションを変更して再インストールする必要がありました。

OpenCV 4.5.xからプラグイン機構が導入され、OpenCVインストール後にこれらのbackendを動的に変更することができるようになりました。OpenCV 4.5.4時点で用意されているプラグインは以下のとおりです

- threadingプラグイン
  - `cv::parallel_for_` backendを動的に変更できるプラグイン
- highguiプラグイン
  - highgui backendを動的に変更できるプラグイン
- videoioプラグイン
  - videoio backendを動的に変更できるプラグイン

とはいえ、なぜプラグイン機構を導入するに至ったかの経緯が気になってきます。<https://docs.opencv.org/4.5.3/d0/d3d/tutorial_general_install.html#tutorial_general_install_plugins_4>には以下の記述があります。

> It is possible to decouple some of OpenCV dependencies and make them optional by extracting parts of the code into dynamically-loaded plugins. It helps to produce adaptive binary distributions which can work on systems with less dependencies and extend functionality just by installing missing libraries.

プラグインとして切り離しておき動的にロードする仕組みにしておけば、それぞれの環境に合ったプラグインを使用する形態にでき、依存ライブラリが入っていなくてプログラムが動かないなどのトラブルが減らせそうなことが書いてあります。

ということで、今回はOpenCVのプラグイン機能について簡単に紹介したいと思います。

## threadingプラグイン

threadingプラグインを使うことで`cv::parallel_for_`のバックエンドを動的に変更することができるようになります。OpenCV 4.5.2以降のバージョンでthreadingプラグインを使用することができます。  

### threadingプラグイン有効化

OpenCVビルド時に`PARALLEL_ENABLE_PLUGINS=ON`にしておき、プラグイン機能を有効にします。

|CMakeオプション|デフォルト値|オプションの意味|
|---|---|---|
|PARALLEL_ENABLE_PLUGINS|ON|threadingプラグインサポートの有効化、無効化を指定|

### threadingプラグイン生成

threadingプラグインを生成します。<https://docs.opencv.org/4.5.3/d0/d3d/tutorial_general_install.html#tutorial_general_install_plugins_4>にはCMakeオプションが以下のように紹介されています

```
cmake -G<generator> \
    -DOPENCV_PLUGIN_NAME=opencv_core_tbb_<suffix> \
    -DOPENCV_PLUGIN_DESTINATION=<dest-folder> \
    -DCMAKE_BUILD_TYPE=<config> \
    <opencv>/modules/core/misc/plugins/parallel_tbb
```

今回は以下のようなコマンドでMakefileを生成しました。

```shell
cmake -G"Unix Makefiles" \
    -DOPENCV_PLUGIN_NAME=opencv_core_tbb_1 \
    -DOPENCV_PLUGIN_DESTINATION=$HOME/opencv_plugin \
    -DCMAKE_BUILD_TYPE=Release \
    $HOME/dev/opencv-4.5.3/modules/core/misc/plugins/parallel_tbb
```

その後、

```shell
make
```

を実行すると`$HOME/opencv_plugin`以下に`libopencv_core_tbb_1.so`が生成されます（今回、`suffix`は適当に1を付けただけなので深い意味はありません）。

### threadingプラグインの使い方

先程生成したthreadingプラグイン（前述の例だと`libopencv_core_tbb_1.so`）をOpenCVライブラリが配置されているパスに格納します。

#### TBBを使う場合

`cv::parallel_for_`のバックエンドとしてTBBを使う場合のサンプルコードを以下に示します。

```c++
// TBB backendを使う場合はインクルードする
#include <opencv2/core/parallel/backend/parallel_for.tbb.hpp>

int main()
{
    // cv::parallel_for_のバックエンドとしてTBBを使う
    cv::parallel::setParallelForBackend(std::make_shared<cv::parallel::tbb::ParallelForBackend>());

    return 0;
}
```

<https://docs.opencv.org/4.5.4/de/d55/group__core__parallel__backend.html#ga7791f3c18e50f00b5fa88ac9d44cd52f>にも以下の記載があり、`cv::parallel::setParallelForBackend`の呼び出しについては注意が必要です。

> This call is not thread-safe. Consider calling this function from the `main()` before any other OpenCV processing functions (and without any other created threads).

#### OpenMPを使う場合

`cv::parallel_for_`のバックエンドとしてOpenMPを使う場合のサンプルコードを以下に示します。
前述の通り、`cv::parallel::setParallelForBackend`の呼び出しについては注意が必要です。

```c++
// OpenMP backendを使う場合はインクルードする
#include <opencv2/core/parallel/backend/parallel_for.openmp.hpp>

int main()
{
    // cv::parallel_for_のバックエンドとしてOpenMPを使う
    cv::parallel::setParallelForBackend(std::make_shared<cv::parallel::openmp::ParallelForBackend>());

    return 0;
}
```

## highguiプラグイン

highguiプラグインを使うことでhighguiモジュールのバックエンドを動的に変更することができるようになります。OpenCV 4.5.3以降のバージョンでhighguiプラグインを使用することができます。  

### highguiプラグイン有効化

OpenCVビルド時に`HIGHGUI_ENABLE_PLUGINS=ON`にしておき、プラグイン機能を有効にします。

|CMakeオプション|デフォルト値|オプションの意味|
|---|---|---|
|HIGHGUI_ENABLE_PLUGINS|ON|highguiプラグインサポートの有効化、無効化を指定|
|HIGHGUI_PLUGIN_LIST|empty|バックエンド名をカンマ、もしくはセミコロンで区切って指定します。gtk, gtk2, gtk3が選択できます|

`HIGHGUI_PLUGIN_LIST`に関して、

> gtk, gtk2, gtk3が選択できます

はOpenCVドキュメントにある情報ですが、<https://github.com/opencv/opencv/pull/20281>を読むとわかるように<https://github.com/opencv/opencv/blob/4.5.4/modules/highgui/cmake/init.cmake#L39>に

```cmake
add_backend("win32ui" WITH_WIN32UI)
```

とあるので、HIGHGUI_PLUGIN_LISTに`win32ui`が指定できそうです（ドキュメントの更新漏れ？）。

また、<https://github.com/opencv/opencv/blob/4.5.4/modules/highgui/cmake/init.cmake#L40-L42>を読むと以下のようになっているので、OpenCV 4.5.4時点でCocoa、Qt対応などはまだのようです。

```cmake
# TODO cocoa
# TODO qt
# TODO opengl
```

### highguiプラグイン生成

highguiプラグインを生成します。
今回は以下のようなコマンドでMakefileを生成しました。

```shell
cmake -G"Unix Makefiles" \
    -DOPENCV_PLUGIN_NAME=opencv_highgui_gtk_3 \
    -DOPENCV_PLUGIN_DESTINATION=$HOME/opencv_plugin \
    -DCMAKE_BUILD_TYPE=Release \
    $HOME/dev/opencv-4.5.3/modules/highgui/misc/plugins/plugin_gtk
```

その後、

```shell
make
```

を実行すると`$HOME/opencv_plugin`以下に`libopencv_highgui_gtk_3.so`が生成されます（今回、`suffix`は適当に3を付けただけなので深い意味はありません）。

### highguiプラグインの使い方

先程生成したhighguiプラグイン（前述の例だと`libopencv_highgui_gtk_3.so`）をOpenCVライブラリが配置されているパスに格納します。

## videoioプラグイン

videoioプラグインを使うことでvideoioモジュールのバックエンドを動的に変更することができるようになります。  

### videoioプラグイン有効化

OpenCVビルド時に`VIDEOIO_ENABLE_PLUGINS=ON`にしておき、プラグイン機能を有効にします。

|CMakeオプション|デフォルト値|オプションの意味|
|---|---|---|
|VIDEOIO_ENABLE_PLUGINS|ON|videoioプラグインサポートの有効化、無効化を指定|
|VIDEOIO_PLUGIN_LIST|empty|バックエンド名をカンマ、もしくはセミコロンで区切って指定します。ffmpeg, gstreamer, msmf, mfxが選択できます|

`VIDEOIO_PLUGIN_LIST`に関して、

> ffmpeg, gstreamer, msmf, mfxが選択できます

はOpenCVドキュメントにある情報ですが、<https://github.com/opencv/opencv/blob/4.5.4/modules/videoio/cmake/init.cmake#L11-L33>を読むと以下のようになっています。

```cmake
add_backend("ffmpeg" WITH_FFMPEG)
add_backend("gstreamer" WITH_GSTREAMER)
add_backend("v4l" WITH_V4L)

add_backend("aravis" WITH_ARAVIS)
add_backend("dc1394" WITH_1394)
add_backend("gphoto" WITH_GPHOTO2)
add_backend("msdk" WITH_MFX)
add_backend("openni2" WITH_OPENNI2)
add_backend("pvapi" WITH_PVAPI)
add_backend("realsense" WITH_LIBREALSENSE)
add_backend("ueye" WITH_UEYE)
add_backend("ximea" WITH_XIMEA)
add_backend("xine" WITH_XINE)

add_backend("avfoundation" WITH_AVFOUNDATION)
add_backend("ios" WITH_CAP_IOS)

add_backend("dshow" WITH_DSHOW)
add_backend("msmf" WITH_MSMF)

add_backend("android_mediandk" WITH_ANDROID_MEDIANDK)
add_backend("android_camera" WITH_ANDROID_NATIVE_CAMERA)
```

これだけ見ると`VIDEOIO_PLUGIN_LIST`で指定できるパラメータがドキュメント記載のものよりも揃っているように見えます。時間の都合上、ドキュメントに記載のないフレームワークを指定しても動作するかの確認までできていないのでドキュメントの更新漏れなのかどうかはわかりません。

### videoioプラグイン生成

videoioプラグインを生成します。
今回は以下のようなコマンドでMakefileを生成しました。

```shell
cmake -G"Unix Makefiles" \
    -DOPENCV_PLUGIN_NAME=opencv_videoio_ffmpeg_3 \
    -DOPENCV_PLUGIN_DESTINATION=$HOME/opencv_plugin \
    -DCMAKE_BUILD_TYPE=Release \
    $HOME/dev/opencv-4.5.3/modules/videoio/misc/plugin_ffmpeg
```

その後、

```shell
make
```

を実行すると`$HOME/opencv_plugin`以下に`libopencv_videoio_ffmpeg_3.so`が生成されます（今回、`suffix`は適当に3を付けただけなので深い意味はありません）。

### videoioプラグインの使い方

先程生成したvideoioプラグイン（前述の例だと`libopencv_videoio_ffmpeg_3.so`）をOpenCVライブラリが配置されているパスに格納します。

## おわりに

本記事では最近OpenCVに追加されたプラグイン機能について紹介しました。OpenCV 4.5.4時点だと一部バックエンドのみのサポートではありますが、揃ってくると配布時に便利なケースも出てきそうです。

## 参考URL

### 関連PR

- threadingプラグインのPR：<https://github.com/opencv/opencv/pull/19470>
- highguiプラグインのPR：<https://github.com/opencv/opencv/pull/20116>
- videoioプラグインのPR：<https://github.com/opencv/opencv/pull/19320>

### 公式チュートリアル

- <https://docs.opencv.org/4.5.4/d0/d3d/tutorial_general_install.html#tutorial_general_install_plugins_4>