OpenCV(Android)で顔認識した結果のテクスチャをUnityで表示する
はじめに
UnityでOpenCVを使用する場合、
AssetStoreで販売されているOpenCV for Unityを使用するか、
自前でC++などでUnityネイティブプラグインを開発するかの2つ方法があります。
OpenCV for Unityは自分は使ったことはありませんが、言及しているブログも多く、
マルチプラットフォームそれぞれ用にネイティブプラグインを作るのが面倒くさかったりする場合は
お金で解決してしまった方が手っ取り早いかもしれません。
さて、今回の内容は、
いやAssetStoreに頼らず自分でネイティブプラグイン作ってみたい!
とか、お金がない!
などといった事情をお持ちの方向けとなってます。
構成
Unity側でAndroidの機能を使うにはいくつか方法があります。
Android用プラグインのビルドと使用
ここにまとまっています。
このプロジェクトでは
AAR プラグインと Android ライブラリ(1)
と
UnityPlayerActivity Java コードの拡張(2)
の2つのドキュメントで述べられている機能を使います。
Unity(C#)からAndroid(Java)のコードを呼ぶには(1)だけやればよいのですが、
今回はAndroid(Java)側が起動したカメラ画像を使用したかったため(2)が必要でした。
Unityでカメラ画像使いたいならWebCamTextureが簡単じゃん、と思うのですが今回は後述の理由のため使用していません。
構成としてはこの様になっています。
Android
|- OpenCVでカメラ画像を取得/顔検出
|- OpenGLでテクスチャを生成
Unity
|- テクスチャを受け取り表示
また、今回使用したバージョンは次の通りです。
Unity 2017.4.0f1 Perfonal(64bit)
Android Studio 3.0.1
Android OpenCV library 3.4.1
チュートリアル
簡単ですが作成方法を記載します。
プロジェクトはGithubに上げています。
詳細はこちらでご確認ください。
Unityプロジェクトの作成
まずはUnityプロジェクトを作成します。
シーンにはカメラ画像を表示するためのQuadを追加したのみです。
このQuadにはネイティブプラグイン側の機能を呼び出すためのスクリプト:FaceDetectionをつけています。
Unity側は大体これだけです!
Androidプロジェクトの作成
Android Studioで新規プロジェクトを作成します。
パッケージ名は
com.example.opencvfacedetection
とします。
プロジェクトの設定/OpenCVモジュールのインポート
UnityにインポートするAARファイルを作成するために新しくModuleを作成します。
プロジェクトを右クリック>New>Module>Android Library
名前はlibとします。
パッケージ名は
com.example.opencvfacedetection.lib
となるように修正します。
次に、AndroidでOpenCVを使う準備をします。
OpenCV4Android SDKをダウンロードページから取得します。
現時点での最新の3.4.1を取得しました。
参考:Android OpenCV library
zipを解凍して顔検出用の学習済みデータ、
OpenCV-android-sdk\samples\face-detection\res\raw\lbpcascade_frontalface.xml
をlibモジュール中の
res\raw\以下にコピーします。(rawフォルダはなければ作成します。)
次にFile>New>Import Moduleで
OpenCV-android-sdk\sdk\java
を選択します。
すると
OpenCVLibrary341
というモジュールが追加されます。
OpenCVLibrary341モジュールのbuild.gradleで
compileSdkVersion 14
というのがエラーになっていたのでメインのプロジェクトと同じ24を指定します。
モジュールのインポートが完了したら
File>Project Structure>(左ペイン)Modules>(メインプロジェクト名)>Dependenciesタブ>+ボタン
をクリックして、OpenCVLibrary341モジュールを選択します。
これでやっとAndroidでOpenCVを使う準備が整いました!
これ以降はJavaからOpenCVのクラスを呼ぶことができます。
顔認識Activityの開発
新しくUnityPlayerActivityを継承したActivityを作成します。
つまり冒頭で述べたUnityPlayerActivity Java コードの拡張の方法です。
CustomMainActityというクラスを作成しました。
これがデフォルトのUnityPlayerActivityの代わりに、Unity起動時に呼ばれるようにします。
そのためにはUnityのAssets/Plugins/Android以下にAndroidManifest.xmlを配置します。
(詳細はGithub)
大まかに処理を説明すると、
CustomMainActity起動
↓
OpenCVのクラスでカメラが起動
↓
画像取得時のリスナーで顔認識
↓
認識結果を描画した画像でテクスチャ用Bitmapオブジェクトを作成
↓
OpenGLでテクスチャ作成
となります。
ソースはGithubに上げていますのでCloneしてご確認ください。
ビルド
Gradleメニューのassembleをクリックするとビルドが開始され、
うまくいけば、OpenCVFaceDetection\build\outputs\aar以下にAARファイルが出力されます。
(エラーになった場合Build>Clean Projectをしてからやり直してみてください)
また、OpenCVのARRもビルドします。
成功すると
OpenCVFaceDetection\openCVLibrary341\build\outputs\aar\openCVLibrary341-release.aar
が出力されます。
Unityにインポートしてビルド
出力された2つのAARファイル(lib-release.aar、openCVLibrary341-release.aar)
をUnityのAssets>Plugins>Android以下にコピーします。
また、OpenCV SDKの中に
OpenCV-android-sdk\sdk\native\libs\アーキテクチャ名
の下にOpenCVの機能をJavaから呼ぶために必要な.soファイルが入っています。
アーキテクチャはいろいろあるみたいですがひとまずここでは
armeabi-v7aとx86だけ対応することにします。
OpenCV-android-sdk\sdk\native\libs\armeabi-v7a\libopencv_java3.so
と
OpenCV-android-sdk\sdk\native\libs\x86\libopencv_java3.so
をAssets/Pulugins/Androidの下の同じアーキテクチャ名のフォルダにコピーします。
実行
動作するとこんな感じになります。
1280×720で実行しています。
端末はMoto g5という結構お安めの機種です。
それでもかなりサクサク動作しています。
ハマりどころ
Android+OpenGLを扱ったことがなかったのでテクスチャ生成に手間取りました。
ひとまずちゃんとテクスチャが生成されているかサンプルプログラムを動かしてみたり。
UnityからAndroidのアクティビティを呼ぶときのスレッドの違いが腹落ちできていません…。
createTexture()を読んだ時のポインタの値は同じなのにテクスチャが更新されなかったりといろいろハマり、
結果今の様になっています。
Unityでビルドするとエラーが出ました。
CommandInvokationFailure: Gradle build failed. C:/Program Files/Android/Android Studio/jre\bin\java.exe -classpath "H:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\gradle\lib\gradle-launcher-4.0.1.jar" org.gradle.launcher.GradleMain "-Dorg.gradle.jvmargs=-Xmx2048m" "assembleDebug" stderr[ FAILURE: Build failed with an exception. * What went wrong: A problem occurred configuring root project 'gradleOut'. > Failed to find Build Tools revision 28.0.0
BuildSettingでBuild SystemをInternalにする。
or
こちらで解決。
おまけ: UnityでOpenCVを使う他の方法
今回の方法を使う前は、以下の構成をとっていました。
Unity
|- WebCamTextureを使ってカメラ画像を取得
|- カメラ画像のポインタをC++に渡す
C++
|- OpenCVで顔認識
Unity
|- 顔認識結果(画像の座標)をC++側から取得、表示
こちらの方が構成としては簡単です。
しかし、恐らくWebCamTextureから取得できる画像データのフォーマットをうまくcv::Matに変換できなかったせいだと思うのですが、
全然顔認識されませんでした。
Android OpenCV libraryに付属のサンプルではかなり高速&正確に顔認識されていたので、
Unityからその部分を拝借する構成にしました。