CMakeでAndroid向けのOpenCVを利用する

# この記事について
この記事は、以下のような人に役立ちます。つまり、私のことです。
- OpenCVを使ったAndroidアプリを作りたい
- OpenCV Managerは使いたくない
- **Android Studio 2.2から使えるCmakeでOpenCVをビルドしたい**

[OpenCV Manager](https://play.google.com/store/apps/details?id=org.opencv.engine&hl=ja)を使う場合、自分のアプリとは別にOpenCV Managerというアプリをユーザーにインストールさせる必要があるので、使いたくない方が多いのではと思います。また、Android.mkを利用する方法を紹介した記事はありますが、Android Studio 2.2から使えるようになったCmakeを利用する記事が見つからなかったので、「せっかくなら新しいやり方でやりたい」という方に向けて書いていきたいと思います。

# 環境
私の環境です。
- OS X 10.11.6 (15G1421)
- Android Studio 2.3.2
- OpenCV for Android 3.2.0
- Android NDK と CMake ダウンロード済み（まだの人は https://developer.android.com/studio/projects/add-native-code.html?hl=ja#download-ndk を参考にダウンロードしてください）

# サンプルアプリ
https://github.com/takuaraki/AndroidCmakeOpenCV

# 新規プロジェクトを作成
Android Studio で新規プロジェクトを作成します。Include C++ support にチェックを入れるようにしてください。

![Create new project.png](https://qiita-image-store.s3.amazonaws.com/0/98669/5f74028d-8d09-7e34-f122-f60e3672d16d.png)

# OpenCV for Android をダウンロード
http://opencv.org/releases.html から、3.2.0のAndroid packをダウンロードします。zipファイルなので、適当な場所に解凍しておきます。

# OpenCVモジュールの導入
## インポート
作成したプロジェクトで、`File > New > Import Module...`から、OpenCVをインポートします。インポートするモジュールのパスは、`<OpenCVForAndroidを解凍した場所>/sdk/java`です。正しくパスを指定していれば、以下のように`openCVLibrary320`というモジュール名が入るはずです。
![opencvlibrary320.png](https://qiita-image-store.s3.amazonaws.com/0/98669/9510b2e9-0529-2d18-b7d4-b02df3b35e00.png)

また、オプションには全てチェックを入れていました。とくに意味はないかもしれません。
![import options.png](https://qiita-image-store.s3.amazonaws.com/0/98669/a5d7ed87-eb76-45d3-d85a-a88582d22eac.png)

## build.gradleの設定
`app/build.gradle`の`dependencies`ブロックにインポートしたOpenCVモジュールを追加します。

``` app/build.gradle
dependencies {
    compile project(':openCVLibrary320')
}
```

また、インポート時の`openCVLibrary320`モジュールのAndroid SDKバージョンがかなり低いので、以下のように`app`モジュールと同じものを使います。SDKバージョンが低いままだと、javaからOpenCVを呼び出すときにエラーになりました。

``` openCVLibrary320/build.gradle
android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
    }
}
```

# ネイティブライブラリの導入
OpenCVのネイティブライブラリを導入します。コピペ先のディレクトリは各自好みで書き換えても問題ありません。
## ライブラリ本体
OpenCVライブラリの本体となる`.so`ファイルをコピーします。`/src/main/jniLibs`というディレクトリを作り、そこに`<OpenCVForAndroidを解凍した場所>/sdk/native/libs`内のディレクトリ（arm64-v8a, ..., x86_64）を全てコピーします。必要なのは`.so`ファイルだけなので、`.a`ファイルは消しても構いません。
つづいて、`app/build.gradle`の`android`ブロックに以下の設定を追加します。

``` app/build.gradle
android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}
```

## ヘッダーファイル
`<OpenCVForAndroidを解凍した場所>/sdk/native/jni/include`ディレクトリをプロクジェクトにコピーします。今回は、`/src/main/jniLibs`にコピーしました。これで、`/src/main/jniLibs/include`にOpenCVライブラリのヘッダーファイルが入りました。

# CmakeLists.txt の設定
新規プロジェクト作成時にInclude C++ supportにチェックを入れていると、`app/CmakeLists.txt`というCmakeの設定ファイルがあるはずです。その設定ファイルを以下のように書いてください。

``` app/CmakeLists.txt
cmake_minimum_required(VERSION 3.4.1)

# Your library
add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

# OpenCV
add_library( lib_opencv SHARED IMPORTED )
include_directories(${pathToProject}/app/src/main/jniLibs/include)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${pathToProject}/app/src/main/jniLibs/${ANDROID_ABI}/libopencv_java3.so)

target_link_libraries( native-lib
                       lib_opencv )
```

また、`app/build.gradle`の`android.defaultConfig.externalNativeBuild.cmake`ブロックに`arguments`を追加してください。

``` app/build.gradle
android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments "-DpathToProject:STRING=" + project.rootProject.getProjectDir().absolutePath
            }
        }
    }
}
```

## 説明
``` app/build.gradle
arguments "-DpathToProject:STRING=" + project.rootProject.getProjectDir().absolutePath
```
CmakeLists.txtで使える変数の定義をしています。`pathToProject`という変数にプロジェクトの絶対パスを入れています。これによって、コードの変更なしにCIでのビルドができます。

``` app/CmakeLists.txt
add_library( lib_opencv SHARED IMPORTED )
include_directories(${pathToProject}/app/src/main/jniLibs/include)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${pathToProject}/app/src/main/jniLibs/${ANDROID_ABI}/libopencv_java3.so)
```
`add_library`コマンドで`lib_opencv`という名前でライブラリをインポートすることを宣言します。`include_directories`コマンドでヘッダーファイルを、`set_target_properties`でネイティブライブラリを指定します。ヘッダーファイルとネイティブライブラリの指定時に`app/build.gradle`で定義した`pathToProject`が使われています。また、`${ANDROID_ABI}`によってCPUの種類（arm64-v8a, ..., x86_64）ごとに読み込む`.so`ファイルを変えます。

# 動作確認
ここまでの設定でOpenCVのライブラリを利用することができるようになりました。さっそく動作確認をしてみましょう。

## C++コードから呼び出し
新規プロジェクト作成時に`native-lib.cpp`という`String`を返すメソッドが書かれたファイルがあるので、そのファイルを以下のように書き換えます。

``` app/src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>
#include <opencv2/core.hpp>

extern "C"
JNIEXPORT jstring JNICALL
Java_example_com_androidcmakeopencv_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    cv::Mat mat;
    std::ostringstream oss;
    oss << "rows: " << mat.rows << ", cols: " << mat.cols;
    std::string str = oss.str();
    return env->NewStringUTF(str.c_str());
}
```

`Mat`はOpenCVの基本的なクラスで、行列を表します。画像データを`Mat`に変換して処理することが多いです。この例では、`Mat`を初期化して行数と列数を表示できるようにしています。この初期化ではサイズ0の行列になるので、以下のような画面になれば成功です。

<img src="https://qiita-image-store.s3.amazonaws.com/0/98669/d8f6d478-ac57-f2ca-5dea-5e298c1ed2b3.png" width=300>

## Javaコードからの呼び出し
新規プロジェクト生成時からある`MainActivity.java`で動作確認をします。

``` MainActivity.java
package example.com.androidcmakeopencv;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import org.opencv.core.Mat;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Mat mat = new Mat();
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText("rows: " + mat.rows() + ", cols: " + mat.cols());
    }

    // C++コードからの呼び出しで利用。この例では未使用
    public native String stringFromJNI();
}
```

こちらも、`Mat`クラスの行数と列数を表示するコードですが、ネイティブライブラリではなくOpenCVのjavaモジュールが提供する`Mat`クラスを利用しています。

# 参考文献
- http://stackoverflow.com/questions/38958876/can-opencv-for-android-leverage-the-standard-c-support-to-get-native-build-sup/3900881
- https://developer.android.com/studio/projects/add-native-code.html
- http://blog.wagavulin.jp/entry/2011/11/27/222642
- http://stackoverflow.com/questions/38764787/android-studio-cmake-access-environement-variable