Android StudioでOpenCVを使えるようにする


Android Studioで、OpenCV (C++)を使う必要があったので、まとめてみました。
Android開発は、始めたばかりなので、間違っているところがありましたら、教えて頂けるとありがたいです。

試した環境は、以下の通り。

* Android Studio 1.4
* OSX 10.11
* OpenCV 3.0.0 (for Android)

※ OpenCV for Androidは、以下のフォルダにインストールされているものとす。

```
/Users/<name>/Library/Android/OpenCV-3.0.0-android-sdk/
```

## まず、OpenCVを使用するアプリケーションの作成
### 新規プロジェクトを作成する場合
「New Project...」で「My Application」という名前の「Blank Activity」を作成したとする。
「Import Module...」で以下のフォルダをインポートする。

```
/Users/<name>/Library/Android/OpenCV-3.0.0-android-sdk/sdk/java/
```

「openCVLibrary300」という名前でライブラリがインポートされる。
settings.gradle にopenCVの依存関係が追加される。

```groovy:settings.gradle
include ':app'
include ':openCVLibrary300' // <--- 追加される
```

/app/build.gradleを以下のように修正

```groovy:/app/build.gradle
...
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.0'
    compile 'com.android.support:design:23.1.0'
    compile project(':openCVLibrary300') // <--- 追加する
}
```

### OpenCVのSDKに付属のサンプルを試す場合
SDKに付属のサンプルは、以下の場所にある。

```
/Users/<name>/Library/Android/OpenCV-3.0.0-android-sdk/samples/
```

サンプルを開く場合、Android Studioの「Open...」で直接開かずに、「Import Project...」で開きたいサンプルのフォルダを選択し、インポート先をユーザフォルダの下などの別フォルダにする。
そうすると、同じフォルダにOpenCVのライブラリをインポートした状態のプロジェクトが作成される。

## OpenCVのライブラリのロードをどうする？
OpenCVは、C/C++で書かれたライブラリである。Android向けには、JavaからOpenCVを使用するためのJava Wrapperも提供されている。Java Wrapperを使用する場合でも、アプリが起動するためには、C/C++の共有ライブラリ(libopencv_java3.so)をロードする必要がある。

OpenCVの共有ライブラリをロードする方法として、以下の二つがある。
##### A: OpenCV Managerを使用する
* OpenCVの共有ライブラリを含まないため、配布するアプリのサイズは小さくなる。
* ただし、ユーザ環境にOpenCV Managerがインストールされている必要がある。
* アプリケーションの起動時に、OpenCV Managerがインストールされていない環境では、OpenCV Managerのダウンロード/インストールを要求される。
* OpenCV Managerは、必ずしも最新バージョンのOpenCVに対応していない。

##### B: 共有ライブラリをアプリケーションパッケージ(.apk)に含める
* 共有ライブラリを含む分、配布するアプリのサイズは大きくなる。
* OpenCV Managerは不要。
* 最新バージョンのOpenCVが使用できる。

### OpenCV Managerを使用する方法
ActivityクラスのonResumeで、OpenCVLoader.initDebug()を使って、OpenCV Managerを呼び出す。
詳細は、"/OpenCV-3.0.0-android-sdk/samples/image-manipulations/"などを参照。

``` java:ImageManipulationsActivity.java
public class ImageManipulationsActivity extends Activity implements CvCameraViewListener2 {
    
    @Override
    public void onResume()
    {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }
```

### 共有ライブラリを.apkに含める方法
プロジェクトの"app/src/main"の下の"jniLibs"フォルダを作成。
"/OpenCV-3.0.0-android-sdk/sdk/native/libs/"の下のディレクトリを"jniLibs"の下にコピー。
コピー先の各フォルダの"libopencv_java3.so"を残して、".a"ファイルは削除する。

``` perl:フォルダ構成
main/
    src/
        jniLibs/
            arm64-v8a/
                libopencv_java3.so
            armeabi/
                libopencv_java3.so
            armeabi-v7a/
                libopencv_java3.so
            mips/
                libopencv_java3.so
            mips64/
                libopencv_java3.so
            x86/
                libopencv_java3.so
            x86_64/
                libopencv_java3.so
```

プロジェクトのActivityクラスを以下のように修正。
OpenCVのサンプル、"image-manipulations"の例。

``` java:ImageManipulationsActivity.java
public class ImageManipulationsActivity extends Activity implements CvCameraViewListener2 {

    /* --- 追加 (共有ライブラリをロードする) --- */
    static {
        System.loadLibrary("opencv_java3");
    }
    
    @Override
    public void onResume()
    {
        super.onResume();
        /* --- OpenCV Managerの呼び出しをコメントアウト --- */
/*      if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else */ {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }
```

## NDKを使用して、C/C++からOpenCVを使用する
OpenCV Managerを使っても出来そうですが、調べていません。ここでは、共有ライブラリを.apk内に含めることとします。
上記の「共有ライブラリを.apkに含める方法」のところまでは同じ。

OpenCVのサンプル、"face-detection"を使用して説明する。

##### ./gradle/wrapper/gradle-wrapper.prpertiesを修正

``` perl:gradle-wrapper.properties
#Sun Oct 11 14:07:25 JST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
# 修正前
#distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
# 修正後
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
```

Gradle-Wrapperに、他にどのバージョンが存在するかは、以下で確認可能
https://services.gradle.org/distributions
(2015.10.11現在、2.5しかダメ)

##### Gradle wrapperを使用するように変更
"preferences" > "Build, Execution, Deployment" > "Gradle" を開く。
"Project-level settings"を"Use local gradle distribution"から"Use default gradle wrapper (recommended)"に変更する。

##### ./build.gradleを修正

``` java:./build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // classpath 'com.android.tools.build:gradle:1.3.0' // 修正前
        classpath 'com.android.tools.build:gradle-experimental:0.2.0' // 修正後

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
```

他にどういうバージョンがあるかは、以下で確認可能
https://jcenter.bintray.com/com/android/tools/build/gradle-experimental/

##### ./app/build.gradleを修正

```groovy:./app/build.gradle[修正前]
apply plugin: 'com.android.application'

android {
    compileSdkVersion 14
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "org.opencv.samples.facedetect"
        minSdkVersion 8
        targetSdkVersion 8

        ndk {
            moduleName "detection_based_tracker"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile project(':openCVLibrary300')
}
```

```groovy:./app/build.gradle[修正後]
apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 14
        buildToolsVersion = "23.0.1"

        defaultConfig.with {
            applicationId = "org.opencv.samples.facedetect"
            minSdkVersion.apiLevel = 8
            targetSdkVersion.apiLevel = 8
            versionCode = 1
            versionName = "1.0"
        }
    }
    android.buildTypes {
        debug {
            ndk.with {
                debuggable = true
            }
        }
        release {
            minifyEnabled = false
            proguardFiles += file('proguard-rules.pro')
        }
    }
    
    compileOptions.with {
        sourceCompatibility=JavaVersion.VERSION_1_7
        targetCompatibility=JavaVersion.VERSION_1_7
    }

    android.ndk {
        moduleName = "detection_based_tracker"

        cppFlags += "-Werror"
        cppFlags += "--debug"
        cppFlags += "-frtti"
        cppFlags += "-fexceptions"
        cppFlags += "-I${file('/Users/<name>/Library/Android/OpenCV-3.0.0-android-sdk/sdk/native/jni/include')}".toString()
        cppFlags += "-I${file('src/main/jni')}".toString()

        ldLibs += ["android", "log", "stdc++", "dl", "z"]
        stl = "gnustl_static"
    }
    android.productFlavors {
        create("arm") {
            ndk.with {
                abiFilters += "armeabi"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "armeabi" + "/libopencv_java3.so"
            }
        }
        create("arm7") {
            ndk.with {
                abiFilters += "armeabi-v7a"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "armeabi-v7a" + "/libopencv_java3.so"
            }
        }
        create("arm8") {
            ndk.with {
                abiFilters += "arm64-v8a"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "arm64-v8a" + "/libopencv_java3.so"
            }
        }
        create("x86") {
            ndk.with {
                abiFilters += "x86"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "x86" + "/libopencv_java3.so"
            }
        }
        create("x86-64") {
            ndk.with {
                abiFilters += "x86_64"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "x86-64" + "/libopencv_java3.so"
            }
        }
        create("mips") {
            ndk.with {
                abiFilters += "mips"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "mips" + "/libopencv_java3.so"
            }
        }
        create("mips-64") {
            ndk.with {
                abiFilters += "mips64"

                File curDir = file('./')
                curDir = file(curDir.absolutePath)
                ldLibs += curDir.absolutePath + "/src/main/jniLibs/" + "mips64" + "/libopencv_java3.so"
            }
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':openCVLibrary300')
}
```

##### ./openCVLibrary300/build.gradleを修正

``` groovy:./openCVLibrary300/build.gradle[修正前]
apply plugin: 'com.android.library'

android {
    compileSdkVersion 14
    buildToolsVersion "23.0.1"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 8
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}
```

```groovy:./openCVLibrary300/build.gradle[修正前]
apply plugin: 'com.android.model.library'

model {
    android {
        compileSdkVersion = 14
        buildToolsVersion = "23.0.1"

        defaultConfig.with {
            minSdkVersion.apiLevel = 8
            targetSdkVersion.apiLevel = 8
        }
    }

    android.buildTypes {
        debug {
            ndk.with {
                debuggable = true
            }
        }
        release {
            minifyEnabled = false
            proguardFiles += file('proguard-rules.pro')
        }
    }

    compileOptions.with {
        sourceCompatibility=JavaVersion.VERSION_1_7
        targetCompatibility=JavaVersion.VERSION_1_7
    }
}
```

## C/C++コードのDebug
1. プロジェクトのC/C++のコードに、ブレークポイントを設定する。
1. ツールバーの"Select Run/Debug Configuration"をドロップダウン。
1. "<アプリケーション名>-native"を選択。
	* 無ければ、"Edit Configurations..."を選択。
	* 開いた画面(Run/Debug Configurations)の左上の[+]ボタンで、"Android Native"を追加する。
		* ⇒ 選択肢に"<アプリケーション名>-native"が追加される。
1. "Run"または"Debug"で、アプリケーションを実行。
	* ⇒ ブレークポイントで停止する。 

おしまい