AndroidでOpenCV + NDKでc++と連携してOpenCVを動かす

タイトル通り、AndroidOpenCVを導入し、c++と連携してOpenCVを動かす所まで書きます。
実現する状況は以下の通りです。

最後は個人的な都合でやってます。
OpenCV4Android SDKOpenCV Managerを介して利用することもできますが、今回はポータビリティに重点を置き、プロジェクトの共有ライブラリに.apkを含める形で進めます。

完全にネット上の知識のみの独学でやっているので、間違っている点やおかしな点があるかもしれません。
特にファイルの配置に関しては色々と無駄なことをやっていると思います。
アドバイスやコメント大歓迎です。

この記事の内容と、多少のサンプルコードを加えたプロジェクトをGitHub上で公開しています。
github.com

実行環境

環境は以下の通りです。

AndroidStudio 2.3.3
OpenCV4Android SDK 3.2.0
Gradle version 3.3
NDK version b06

プロジェクトの作成

今回の記事で実行する手順です。

  • (c++のコードを導入するよう設定しプロジェクトを開始)
  • OpenCV4Android SDKの導入
  • c++OpenCVを触れるように設定

プロジェクトの開始

今回はc++との連携を行うため、プロジェクト開始時にInclude C++ supportにチェックを入れます。f:id:wrongwrongwrongwrong163377:20170701165649p:plain
ついでにc++11を有効に。
f:id:wrongwrongwrongwrong163377:20170701165847p:plain
後は適当に選んでプロジェクトを開始します。

NDKのインストール

写真がありませんが、Include C++ supportにチェックを入れてプロジェクトを開始した場合、NDKのインストールが必要になります。
これはAndroidStudioからのメッセージに従ってインストールしていけば何も問題ありません。
以下の画面までたどり着いたら完了です。
f:id:wrongwrongwrongwrong163377:20170701165850p:plain

OpenCV4Android SDKの導入

qiita.com
この項目は、こちらの記事を参考に作成しました。

ダウンロード・解凍・配置

以下のページからバージョンを選びSDKをダウンロードしてきます。
sourceforge.net
今回はopencv-3.2.0-android-sdk.zipをダウンロードしてきました。
zipファイルは解凍したあとでできるだけ動かさない場所に置くと良いでしょう。

プロジェクトへモジュールを導入

AndroidStudioで、File > new > Import Moduleを選択します。f:id:wrongwrongwrongwrong163377:20170701181452p:plain
以下の画面に、「[解凍したものを配置した場所]/OpenCV-android-sdk/sdk/java」を入力し、ライブラリの追加を行います。
f:id:wrongwrongwrongwrong163377:20170701181631p:plain
この画面の後にももうひとつ画面が出ますが、特に弄る必要はありません。
実行後には、[プロジェクトディレクトリ]/import-summary.txtが開きます。
内容(抜粋)は以下のようになります。

ECLIPSE ANDROID PROJECT IMPORT SUMMARY
======================================

Ignored Files:
--------------
The following files were *not* copied into the new Gradle project; you
should evaluate whether these are still needed in your project and if
so manually move them:

* javadoc/
* javadoc/allclasses-frame.html
* javadoc/allclasses-noframe.html
* javadoc/constant-values.html
* javadoc/help-doc.html
* javadoc/index-all.html
* javadoc/index.html
* javadoc/org/
* javadoc/org/opencv/
* javadoc/org/opencv/android/
* javadoc/org/opencv/android/BaseLoaderCallback.html
* javadoc/org/opencv/android/CameraBridgeViewBase.CvCameraViewFrame.html
* javadoc/org/opencv/android/CameraBridgeViewBase.CvCameraViewListener.html
* javadoc/org/opencv/android/CameraBridgeViewBase.CvCameraViewListener2.html
︙

インポートしたものが確認できます。

ライブラリをプロジェクトに追加

プロジェクトのapp/src/mainに、jniLibsという名前のディレクトリを作成し、[解凍したものを配置した場所]/sdk/native/libs内のファイルを全てコピーペーストします。
以下のようになります(※opencv320というディレクトリが追加されているのは一旦無視して下さい)。
f:id:wrongwrongwrongwrong163377:20170701182926p:plain

モジュールの依存関係を設定

AndroidStudioで、File > Project Structureからappを選択し、Dependenciesタブの緑の'+'マークからModule depencencyを選択します。
f:id:wrongwrongwrongwrong163377:20170701183802p:plain
f:id:wrongwrongwrongwrong163377:20170701183808p:plain
すると先ほど取り込んだモジュールが表示されるので、それを選択しOKを押します。
f:id:wrongwrongwrongwrong163377:20170701183827p:plain
以下のようになります。f:id:wrongwrongwrongwrong163377:20170701183950p:plain

gradleの設定

素の状態では、OpenCVコンパイルに関する設定のAPIレベル等が低い状態です。
例えばcamera2 APIを利用しようと思った場合、これはAPIレベル21以降の実装であるため、build.gradleの設定無しではビルドができなくなります。
これを解消するため、build.gradleの設定を変更します。

app/build.gradleと、openCVLibrary320/build.gradleを開きます(320はバージョン名)。
f:id:wrongwrongwrongwrong163377:20170701184419p:plain
編集前の内容は、それぞれ以下のように(抜粋)なっています。

app/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.wrongwrong.opencv4androidsample"
        minSdkVersion 22
        targetSdkVersion 25
        versionCode 1

openCVLibrary320/build.gradle(編集前)

apply plugin: 'com.android.library'

android {
    compileSdkVersion 14
    buildToolsVersion "25.0.0"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 21
    }
︙

このうちopenCVLibrary320/build.gradleのcompileSdkVersion, buildToolsVersion, minSdkVersion, targetSdkVersionの4項目を、app/build.gradleに合わせて書き換えます。

openCVLibrary320/build.gradle(編集後)

apply plugin: 'com.android.library'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"

    defaultConfig {
        minSdkVersion 22
        targetSdkVersion 25
    }
︙

ここまでの設定で、JavaからOpenCVを利用することが可能になります。

c++からOpenCVを利用する

次に、Androidc++からOpenCVを利用するための設定を行います。
正直この辺りのことは全く理解できていません。
以下のリポジトリから、設定をコピペする形で設定を行いました。
github.com

([解凍したものを配置した場所]/OpenCV-android-sdk/sdkの配置)

持ち運びのためにやっていることなので、やらなくても良い手順です。
app直下にopencv320という名前のフォルダを作成し、[解凍したものを配置した場所]/OpenCV-android-sdk/sdkをコピペします。

app/build.gradleの設定

app/build.gradleに設定を追加します。

app/build.gradle(編集前)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.wrongwrong.opencv4androidsample"
        minSdkVersion 22
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
︙

追加した内容は、cmake内と、buildTypesの手前の内容です。

追加した部分1

cmake {
    cppFlags "-std=c++11"
    abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips', 'mips64'
}

追加した部分2

sourceSets {
    main {
        jniLibs.srcDirs = ['src/main/jniLibs']
    }
}

app/build.gradle(編集後)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.wrongwrong.opencv4androidsample"
        minSdkVersion 22
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
                abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips', 'mips64'
            }
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
︙
app/CMakeLists.txtの設定

初期状態CMakeLists.txtは以下の通りです(最初から入ってるコメントは邪魔なので消しました)。
app/CMakeLists.txt(編集前)

cmake_minimum_required(VERSION 3.4.1)

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

find_library( log-lib log )

target_link_libraries( native-lib ${log-lib} )

これを以下のように編集します

app/CMakeLists.txt(編集後)

cmake_minimum_required(VERSION 3.4.1)

# 以下3行追加
include_directories(./opencv320/sdk/native/jni/include) #置いた場所で変わる
add_library( lib_opencv SHARED IMPORTED )
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java3.so)

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

find_library( log-lib log )

# lib_opencv追加
target_link_libraries( native-lib lib_opencv ${log-lib} )

注意点として、include_directories(./opencv320/sdk/native/jni/include)の部分では、自分がsdk/jni/includeを置いた場所を指定する必要があります。
ここまでの設定で、Androidc++からOpenCVを利用することができるようになります。

疑問

以下の点が疑問として残っていますので、追々調べていきます。

  • 例えばg++などに渡す最適化のフラグはどのようにして渡すのか(そもそも渡せるのか)
  • 無駄な手順が含まれていないか(全部やってから動くことは確認したが、途中でどう動かないか記録していない)

おまけ

掲載したサンプルプロジェクトの解説を書きました。
wrongwrong163377.hatenablog.com