OpenCV for Android 2.4.3 (その2)

OpenCV for androidのインストールが終わったので、実際にアプリケーションを書いてみよう。
OpenCVといえば、まずすることは2値化ということでImageViewに表示する前に画像をOpenCVで2値化するコードを書いてみた。

MainActivity.java
private void doBinarize() {
    Bitmap bm = ((BitmapDrawable)this.cropCanvas.getDrawable()).getBitmap();
    Mat src = new Mat();
    Utils.bitmapToMat(bm, src);
    Mat bined = OpenCVUtil.binarize(src, 0); 
    Imgproc.cvtColor(bined, bined, Imgproc.COLOR_GRAY2RGBA, 4);
    Bitmap dst = Bitmap.createBitmap(bined.width(), bined.height(), Bitmap.Config.ARGB_8888);
    Utils.matToBitmap(bined, dst);
    this.cropCanvas.setImageBitmap(dst);
}

BitMapとMatの相互変換が最初からユーティリティとして用意されている。iOSの時は自分で用意していたので凄く有り難い。

OpenCVUtil.java
public static final Mat binarize(Mat src, double thresholdValue) {
    Mat tmp = new Mat(src.size(), CvType.CV_8UC1);
    Mat bin = new Mat(src.size(), CvType.CV_8UC1);
    Imgproc.cvtColor(src, tmp, Imgproc.COLOR_BGR2GRAY);
    if ( thresholdValue != 0 ) {
        Imgproc.threshold(tmp, bin, thresholdValue, 250, Imgproc.THRESH_BINARY );
    } else {
        Imgproc.threshold(tmp, bin, thresholdValue, 250, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
    }
    return bin;
}

cv::MatクラスはJavaではそのままMatクラスとして使える。しかしネィティブ型として定義されているのでJavaのクラスのように継承構造は持たず、名前で分類しているようだ。

さて、androidでは初OpenCVだぞとワクワクしながら起動したのだが、早速例外。

E/AndroidRuntime(12866): FATAL EXCEPTION: main
E/AndroidRuntime(12866): java.lang.UnsatisfiedLinkError: Native method not found: org.opencv.core.Mat.n_Mat:()J
E/AndroidRuntime(12866): at org.opencv.core.Mat.n_Mat(Native Method)
E/AndroidRuntime(12866): at org.opencv.core.Mat.<init>(Mat.java:441)
E/AndroidRuntime(12866): at jp.blackbeans.MainActivity.doBinarize(MainActivity.java:76)
:

Matクラスの根っこはネィティブ側で定義された型なので、ここでライブラリィのロードが必要になるのだが、ライブラリィのロードをどこにも書いていないのでロードに失敗するのは当然。これは想定内なのだ。

さて、ではライブラリィのロードの箇所を探そうかとソースコードを見ていたのだが、そんなコードは無い。そればかりかビルド済みのネィティブライブラリィ自体、OpenCVのパッケージに含まれていないのである。

ならばどうやってopenCVはライブラリィをロードしているのだろう。分からない場合はサンプルを動かしてみれば良いのだ。とサンプルのアプリケシーョンを動かしてみて分かった。

なんとOpenCVのネィティブライブラリィ部分はランタイム時にインストールする必要があるのである。
促されるままにYESを押下するとGoogle Playに接続して"OpenCV Manager"なるAPKをダウンロード、インストールしたのだった。

最初はWindowsのDLLのようにアプリケーションサイズを小さくするための考え(Xcodeでビルドすると良くわかるが、OpenCVのライブラリィはかなり大きい)だと思ったのだが、それにしてもこれではネットに接続できない場所では使えないし、あまり良い方法とは思えないのではと最初はいぶかしかった。

しかしAndroidのネィティブコードはiOSと違って複数のハードウェアに対応してライブラリィを用意する必要がある上、対象はMPU向けだけでは無い。件のハードウェアアクセラレーションのためにSoCやGPU毎にもバイナリが必要なケースがある訳で、NDKといってもARMとIntelだけビルドすれば良かった時代ではないのだった。

SoC、GPUに合わせたライブラリィを選択的に使うためにもこのように別建てにするしかないのだろう。(一台の端末に使わないであろうアーキテクチャのためのバイナリを毎回全て含めるのは賢い選択とは言えない)


ではOpenCVのアプリケーションではどのようにライブラリィをロードするのだろう。
これはサンプルコードのActivityを見ればちゃんと書いてあった。

MainActivity.java
public class MainActivity extends Activity {
    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
            case LoaderCallbackInterface.SUCCESS:
            {
                Log.i(TAG, "OpenCV loaded successfully");
            }
            break;
            default:
            {
                super.onManagerConnected(status);
            } 
            break;
        }
    }};

    @Override
    public void onResume() {
        super.onResume();
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
    }
}

OpenCVLoaderクラスのinitAsyncメソッドに必要なopenCVのバージョンとコールバックオブジェクトを渡してやることで、OpenCVが必要なライブラリの有無を判断し、必要であれば外部(Google Play)からインストールして通知する仕組である。
Introduction ― OpenCV v2.4.3 documentation

ということでOpenCV for androidを使用するアプリケーションでは必ずこの仕組みによりOpenCV Managerの初期化処理が必要だということだ。

実行結果

2値化前

2値化後

Objective-Cと比べて性能がどれくらい違うかにも興味はあるが、それよりも同じコードをJavaでさっと書けるというのがとても大きい。Objective-Cとは違って(少なくとも表向きは)単一の言語で記述されるためコードの統一感が高く可読性が高いのも良い。

と、今日はここまで。