UnityからOpenCVを利用する(iOS)

UnityからiOSのネイティブプラグイン経由でOpenCVでカメラを利用するサンプル

内容としては、

  1. OpenCV経由で端末のカメラ画像を取得
  2. 取得した画像をOpenCVで処理(シンプルにグレースケールへ変換)
  3. ネイティブプラグイン経由でUnity側(C#)へ渡す
  4. Unity側でテクスチャを生成して表示する

といった流れになる

手順

OpenCVのフレームワークの準備

  1. 公式のダウンロードページからfor iOSVer.2系をダウンロードする
    (サンプルではVer.2.4.13

  2. ダウンロードしたファイルを解凍してopencv2.frameworkを取り出しておく

なお、Ver.2を利用したのは、作成時点ではVer.3だとエラーが出てしまったので・・・ 多分ソースからビルドするかバージョン上がれば、Ver.3系でも問題ないかと

iOSネイティブ部分のソースを作成

OpenCVがC++なので、言語はObjective-C++を利用する
(残念ながらSwiftはC++を扱うことができないのでObjC一択)

それに伴い、忘れずにファイルの拡張子を.mmにしておくこと
ただその場合、ヘッダファイル(.hpp)はUnityがプラグインのファイルとして認識してくれないので、 今回は一つのファイルに収まるように書いている
(通常の.hはちゃんと認識してくれるのに・・・)

プラグインの呼び出しは以下の感じ

[iOS]
OpenCV(C++)での処理
 |
ObjC++のクラスでラップ
 |
Cの関数でラッパークラスをエクスポート
 |
[Unity]
C#でDLLimportして呼び出し

ObjC++で一旦ラップしているのは、OpenCV部分の開発は別にXCode上で行う為。 ただ、場合によってはメソッド呼び出しが遅いのでそこは注意

ネイティブ側のソースの全文は以下の通り

通常のOpenCV周りの処理(4〜51行目あたり)

ややこしいのが、C++はARC管理外の為、手動でメモリ管理が必要。 今回はメモリの確保/破棄をラッパークラスの初期化/破棄と合わせて、 ラッパークラスの生存期間とOpenCVのオブジェクトの生存期間が一致するようにしてある

カメラの画像の取得は、初期化と同時にcv::VideoCaptureを生成し、 その後は毎フレーム*camera >> imgで画像を読み込んでいる

この時、取得された画像のフォーマットはBGRなのがポイント。 最終的にUnityのテクスチャのサイズとフォーマットRGBAに変換してUnity側へ渡す
(Unity側ではARGBの表記なので、最初それで変換してハマってた・・・)

プラグインのエントリポイントを用意(54〜77行目あたり)

用意するのはラッパークラスVideoCaptureの生成、毎フレーム呼び出す用、破棄の3つのCの関数

これを55〜59行目のように宣言してC#へエクスポートする。 このファイルはC++の扱いになっているのでextern "C"が必要
(逆に言うと、C#からはこれ以外が見えない状態となっている)

関数の本体は61行目以降にあるように、単純にブリッジしているだけ

Unityでネイティブプラグイン作成

Unityにネイティブのソース類を組み込む

  1. Assets直下にPluginsフォルダを作る
  2. 作成したPluginsフォルダにiOSネイティブ用のソースファイルを入れる
    Unityのエディタ上のインスペクタの設定を念のため確認
    • Select platforms for plugin:iOS
    • Platform settings:チェック不要
  3. 同じくPluginsフォルダにダウンロードしておいたopencv2.frameworkを丸ごと入れる
    Unity上ではフォルダとして認識されて、中にいろいろあればOK

コンポーネントの作成

ネイティブの画像データからテクスチャを生成し、 そのテクスチャを指定したRendererのマテリアルにセットするコンポーネントを作成

当然、ネイティブプラグイン部分は、iOS上での実行時しか動かないので、 該当部分は#if UNITY_IOS#endifでエディタ実行時にエラーにならないようにしておく

ネイティブ側で準備したエントリポイントを利用する為にインポートの宣言(16〜26行目あたり)が必要

Cの関数宣言をそのままC#での宣言にするが、同じデータ型がC#にない場合は対応するデータ型を代わりに指定する (今回だとvoid*IntPtr

なお、構造体をやりとりするような場合はマーシャリングが必要となるので結構面倒

宣言すれば後は通常のメソッドと同じ様に利用できる。ただし、C側と引数などが異なっていた場合は、 実行時にエラーとなるので注意

後は、コンポーネントのライフサイクルと、キャプチャ用とテクスチャのオブジェクトのライフサイクルを一致させればOK

シーン作成

  1. キャプチャしたテクスチャ用のMaterialを作成
    • ShaderUnlit/Textureを選択
      (キャプチャ画像にライトが反映して光らないように)
  2. Quadでテクスチャを貼るオブジェクトを作成
    • ScaleをX:3, Y:4, Z:1にする
      (数値はキャプチャ画像のアスペクト比と合わせる)
    • Materialに1で作成したマテリアルをセット
  3. プラグインのコンポーネントを追加
    • プラグインのrenderTargetに自身をセット

感想

サンプルでは出てこないけど、C#のアンマネージドの辺りが、ObjCやSwiftに比べると結構大変な気がした。 慣れてないからかも知れないけど、特にメモリ周りやマーシャリングはいずれちゃんと勉強しないと

あと、OpenCV周りの細かい挙動もまだ把握しきれていないので調べたい

開発環境

ソース

こちら

ただし、上記ソースにはopencv2.frameworkが含まれていない
(Githubの100MB制限にかかってしまったので)
動かすには、OpenCVのフレームワークの準備の項目で準備した opencv2.frameworkを手動でUnityOpenCV/Assets/Plugins/直下に追加する必要がある。