AndroidアプリにOpenCVを導入してみる

#はじめに
AndroidStudioのデフォルトだけで完結するアプリ作りにも飽きてきたので、
OpenCVを使って画像をあーだこーだしてアプリの幅を広げてみる。
手始めにインストールして試しに使ってみる。

[Androidアプリで端末の画像を取り込む](https://qiita.com/satodayo/items/0dd592f6d92c87ee0ac4)の続き

#環境
OpenCV 4.1.2
AndroidStudio 3.4.1

#インストール
こちらの記事を参考にさせていただきました。
特に支障なくインストールとインポートは完了。

[Android StudioでOpenCVを使う](https://qiita.com/tifa2chan/items/a2ec4f5d90eeb9cf1802)

#Mat形式で取り込む
前回の記事でデバイスの画像データをbitmapで取り込むところまでやりました。
このbitmapをOpenCVで扱うためのMat形式に変換します。

OpenCVのLibraryに含まれているandroid.packageのUtilsクラスに以下のメソッドが用意されています。
これを使ってbitmapをMatに変換する。
Mat形式に画像を変換することでOpenCVのLibraryを用いて画像を色々と編集することができるようになります。

```java:Utils.java
/**
 * Short form of the bitmapToMat(bmp, mat, unPremultiplyAlpha=false).
 * @param bmp is a valid input Bitmap object of the type 'ARGB_8888' or 'RGB_565'.
 * @param mat is a valid output Mat object, it will be reallocated if needed, so Mat may be empty.
 */
public static void bitmapToMat(Bitmap bmp, Mat mat) {
    bitmapToMat(bmp, mat, false);
}
```

```java:Main.java
//bmp_img:取り込んだbitmap
//mat_img:あらかじめMat形式の変数を用意しておく
Utils.bitmapToMat(bmp_img,mat_img); 
```

#Matを使ってみる
今回は試しにボタンを押したらカラー画像をグレースケールに変換するアプリを作って見ます。
どのメソッドを使うかというと、公式ドキュメントから地道に探します。
[Open Source Computer Vision
](https://docs.opencv.org/4.1.2/index.html)
それかOpenCVをインストールした状態でプロジェクト全体を[gray]とかで検索するとそれっぽいのが引っかかります。

今回はImageprocクラスの中に以下のようなメソッドが用意されていました。

```java:Imageproc.java
public static void cvtColor(Mat src, Mat dst, int code) {
    cvtColor_1(src.nativeObj, dst.nativeObj, code);
}
```
codeの引数には、どのような変換を行うのかの定数を入れてあげます。
`imageproc.hpp`のファイルに変換方法一覧があるのでそちらを参照。
今回はカラー画像からグレースケールに変換したいので`COLOR_RGB2GRAY`を利用する。

```java:Main.java
Utils.bitmapToMat(bmp_img,mat_img); //bmp_img:取り込んだbitmap,mat_img:Mat形式の変数を用意しておく
Mat mat_output = new Mat();
cvtColor(mat_img,mat_output,COLOR_RGB2GRAY);
```

これで`mat_img`の画像がグレースケールに変換され`mat_output`に入れられるはず。
最後にこの`mat_output`を再度bitmapに変換し、ImageViewに出力する。

```java:Main.java
private Mat mat_output = new Mat();

public void onClick(View v) {
    Utils.bitmapToMat(bmp_img,mat_img); //BitmapからMatへ変換
    cvtColor(mat_img,mat_output,COLOR_RGB2GRAY);    //GrayScaleへ変換
    Utils.matToBitmap(mat_output,bmp_img);  //Bitmapに戻す
    img_picture.setImageBitmap(bmp_img);    //ImageViewへ表示
}
```

#クラッシュ発生！
これで完成！と思いきや、ボタンを押した瞬間にクラッシュ発生。
デバッグしてみると、そもそもMatをnewしているところでクラッシュが発生している。


エラーメッセージを見てみると...
`java.lang.UnsatisfiedLinkError: No implementation found for long org.opencv.core.Mat.n_Mat() (tried Java_org_opencv_core_Mat_n_1Mat and Java_org_opencv_core_Mat_n_1Mat__)`

Mat()が見つかりません、みたいなことが書いてあります。
困ったときのStackoverflowを見てみたら同じ境遇の人が見つかりました。
[No implementation found for long org.opencv.core.Mat.n_Mat() error Using OpenCV](https://stackoverflow.com/questions/35090838/no-implementation-found-for-long-org-opencv-core-mat-n-mat-error-using-opencv)

記事の内容をまとめると

- OpenCVのLibraryを読み込む前にOnCreate()が呼ばれてしまっている
- **OpenCVManager**とやらを使って非同期でOpenCVを初期化する
- OnCreate()前にBaseLoaderCallbackを実行し、この中でMat()のインスタンスを宣言してあげる
- OnResume()で毎回OpenCVが導入されているかをチェックする

さらに少し調べてわかったことは

- この**OpenCVManager**はユーザーごとに別途インストールしてもらう。
- Libraryをアプリに含めないことによりアプリの容量が小さくなるとのこと。
- **OpenCVManager**を起動するために**OpenCVLoader.initAsync**を実行する
- Initが完了したタイミングでBaseLoaderCallbackが呼ばれるので、その中でMatの生成を行う

# やってみた
とりあえずStackOverflowに書いてあったソースコードをそのまま流用しました。
デバッグしてみたところ、

1. OnResume()内でmLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS)が呼ばれる
2. onManagerConnected内でMatのインスタンスが生成される

という流れが確認できました。

てっきりアプリ起動時に**OpenCVManager**のインストールを要求されるかと思ったら何もありませんでした。
またOnResume()内のOpenCVLoader.initAsyncも呼ばれなかった。(初回起動時見逃した...?)
AndroidやOpenCVのバージョンにもよるかもしれないです。

# 結果
できました！
写真は台湾のかき氷屋さんの「IceMonstar」のキャラクター？です。
マンゴーかき氷がすごい美味しかったので台湾行った際はぜひ。
![gray.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/137041/76bd9989-04f3-7612-039e-07ce320cc706.png)

# 最後に
環境構築に少しだけ手間取りましたが、一応OpenCVが扱えるようになりました。
公式ドキュメントを見たところ膨大なライブラリが用意されているようなので、これから色々触っていきたいと思います。