Android + OpenCV 4 で、顔認識をする

# 概要

Android と OpenCV 4.0.1 のまとまった情報がなかったので、まとめてみました。
Android 開発の経験がある前提で、記載します。

## OpenCV の用意

https://opencv.org/releases.html

* 4.0.1 の Android pack をダウンロードします。
* 適当な場所に解凍します。

## Android

* モジュールをソースからインポートする

    Android プロジェクトで、プロジェクトを選択して、右クリックで「新規→モジュール」を選択します。
    <img src="https://qiita-image-store.s3.amazonaws.com/0/148871/8bb74a59-389d-799b-98f9-3e98fee33836.png" width=50%>

  * ソース・ディレクトリー/モジュール名

    ソース・ディレクトリー：
    C:\OpenCV-android-sdk\sdk など解凍したディレクトリーを入力します。
    ここで、注意するのが他のサイトだと「C:\OpenCV-android-sdk\sdk\java」になっていますが、「C:\OpenCV-android-sdk\sdk」が正しくなります。

    モジュール名：
    opencv など入力します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/148871/4c1296a5-b662-d995-50f6-ec888a125f04.png" width=50%>

* opencv の build.gradle を編集

compileSdkVersion が古いバージョンになっている可能性がありますので、アプリケーションのバージョンと合わせてください。

* モジュール依存関係を追加

プロジェクトを選択して、右クリックで「モジュール設定を開く」を選択します。
依存関係タブを選択して、先ほど入力したモジュール名を選びます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/148871/2a9b20fd-2ef4-0c03-cd76-434a2bacbc64.png" width=50%>

* 顔認識に必要なファイルをコピー

解凍した中から「haarcascade_frontalface_alt.xml」をassetsなどにコピーする。
C:\OpenCV-android-sdk\sdk\etc\haarcascades

---
準備は、上記で完了です。
後は、実装していきます。
顔認識した顔に枠線を付けて、ImageViewに表示します。
assetsに顔が入った画像がある前提で、記載します。

```java

    private static final String FILE_NAME = "haarcascade_frontalface_alt.xml";

    private BaseLoaderCallback onLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            super.onManagerConnected(status);

            // haarcascade_frontalface_alt.xml をコピー
            File file = new File(getFilesDir().getPath() + File.separator + FILE_NAME);
            if (!file.exists()) {
                try (InputStream inputStream = getAssets().open(FILE_NAME);
                        FileOutputStream fileOutputStream = new FileOutputStream(file, false)) {
                    byte[] buffer = new byte[1024];
                    int read;
                    while ((read = inputStream.read(buffer)) != -1) {
                        fileOutputStream.write(buffer, 0, read);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            CascadeClassifier cascadeClassifier = new CascadeClassifier(file.getAbsolutePath());

            try (InputStream inputStream = getAssets().open("image.jpg")) {
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

                Mat mat = new Mat();
                Utils.bitmapToMat(bitmap, mat);

                MatOfRect matOfRect = new MatOfRect();
                cascadeClassifier.detectMultiScale(mat, matOfRect);

                if (matOfRect.toArray().length > 0) {
                    for (Rect rect : matOfRect.toArray()) {
                        Imgproc.rectangle(mat, new Point(rect.x, rect.y),
                                new Point(rect.x + rect.width, rect.y + rect.height),
                                new Scalar(0, 255, 0), 5);
                    }
                    Utils.matToBitmap(mat, bitmap);
                }

                ImageView imageView = findViewById(R.id.imageView);
                imageView.setImageBitmap(bitmap);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        if (!OpenCVLoader.initDebug()) {
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, getApplicationContext(),
                    onLoaderCallback);
        } else {
            onLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }
}
```

---
古いOpenCVの記事だと*.soをコピーするなどが書かれていますが、インポートするだけで問題ありません。