直近の作業でブラウザ上からOpenCVを利用したくなったので、Emscriptenを用いてOpenCVとそれを呼び出すC++コードをwasmに変換し、それを呼び出すサンプルのレポジトリを作成しました。
基本的にはレポジトリのコードだけ見れば事足りる話ではあるのですが、細かい補足等をメモ程度にこの記事に残しておこうと思います。
レポジトリ: https://github.com/totegamma/opencv-web-sample
レポジトリはサブモジュールが含まれているので、--recursive
をつけてcloneするのをお忘れなく。
実行するとカメラ画像を入力にとり、カメラ映像の平均色をdocumentの背景色として表示するアプリケーションが動きます。
ビルド方法
emsdkのインストール
まず、C++コードをwasmにコンパイルするEmscriptenをインストールする必要があります。 homeディレクトリなどの適当なレポジトリに本体を配置する形のインストール方法になります。
|
|
環境変数を通すためにemsdk_env_sh
を実行する必要があるのですが、デフォルトだとめちゃめちゃログを垂れ流します。
zshrc等でこれをやられるとあまりにもうるさすぎるので、export EMSDK_QUIET=1
を直前で実行しておくことでこれを黙らせることができます。
OpenCVのwasmモジュールのビルド
今回のサンプルレポジトリはサブモジュールとしてOpenCVのレポジトリが含めてありますが、共用の場所に置いておく場合は別途cloneします。
|
|
opencvのディレクトリ内で、emcmakeを実行することでopencvをビルドします。5分くらいかかります。
|
|
OpenCV本体のインストール
ヘッダーファイルが共通で必要になるので本体もインストールしておきます。 これはわざわざ自分でビルドしなくてもパッケージマネージャ等からもインストールできるのですが、僕の場合はバージョン違いなどでアプリケーションのビルド時に参照エラーを起こしてしまったので、同じレポジトリからインストールすることにしました。
|
|
C++コードのビルド
やっとアプリケーションをビルドできます。
build.shを実行するのみですが、中身はこのようになっています。
|
|
includeやlibraryの指定に注意です。ヘッダーファイルは共通のものを利用していますが、ここでリンクしているライブラリopencv_core
はwasmモジュールなので、-L
オプションで共通の方ではなくbuild_wasm
を向くように仕向けてあげる必要があることに注意です。
また、exported_functions
で_malloc
, _free
を個別に指定していますが、これはjavascript側からc++側の配列にアクセスする際に必要になるので、別途exportしておく必要があります。
webアプリケーションの実行
あとは通常のviteアプリケーションと同様に実行可能です。
|
|
詳細
アプリケーション
|
|
javascript側から呼び出す関数はextern "C"
に指定しておきます。
また、勝手にコンパイラに消されないように、EMSCRIPTEN_KEEPALIVE
マクロを指定しておきます。
これだけ気を付ければ、ほかの関数は特に普通のc++として記述できます。すごい。
wasmのjavascriptへの読み込み
ビルドすると、デフォルトでa.out.js
とa.out.wasm
が作成されます。
この二つをpublicディレクトリに入れて(/
で解決できるようにしておいて)、index.htmlでa.out.js
を読み込みます。
|
|
これで、javascript側からはModule
の名前空間でwasmモジュールにアクセスできるようになります。
javascript側からの呼び出し・データのやり取り
まずtypescriptから利用するために型定義が必要です。
|
|
これをc++の定義から自動で作れたらそれはそれて便利そうなんですが、どう解釈させるかというのは一意に定まらなさそうなところもあり、難しい…。ひとまず手動で作ってしまいます。
また、関数はCマングルされているため、先頭に_
がつきます。
配列としてデータを渡すためには、まずはc++アプリケーション側でメモリを確保して、そのポインタを得る必要があります。ここで、exportしたmallocを利用します。
|
|
また、TypedArrayを利用することにより、こうして確保したpointerに対してjavascriptからデータをバインドすることができます。
|
|
これで、c++側に配列データ(ここでは動画データ)を送る準備ができました。
呼び出し
あとは、通常通り呼び出しできます。
|
|
通常のc++と同じく、利用後のメモリをfree
するのを忘れないように。
データの読み込み
読み込みは簡単で、返ってきたポインタをUint8Arrayとしてバインドします。
|
|
ここでも、返ってきてるmeanColorポインタは呼び出し先の関数でmallocしたものなので、読み込んだらfreeしてあげます。 ただし、Uint8Arrayでバインドしているのは共通のメモリになるので、この配列の値を利用する前にfreeすると中身の完全性は保証されません。
中身を読み込んでから、freeするようにします。