OpenCV for Androidを使ってみた

画像処理といえばOpenCVという感じなあれを使ってみました。

この記事ではプロジェクトの設定〜Thresholdによる二値化までをやります。

 

OpenCV For Androidの取得

まずは公式サイトからOpenCV For Androidをダウンロードします。

最下部のSourceForgeへ移動し、opencv-バージョン番号-android-sdk.zip をダウンロードしてきます。

ダウンロードが終わったらzipを解凍しておきます。それ以外に特別なインストール作業はいりません。

ちなみにOpenCVのライセンスは三項BSDです。この記事の手順で利用する場合は、アプリ内でOpenCV使ってますよ文をライセンス文と共に掲載する必要があります。

 

プロジェクトの設定

次にプロジェクトの設定を行います。

プロジェクトの新規作成

まずは普通にAndroidStudioで新規プロジェクトを作成します。

適当に名前を入れ、他は大体そのままでOKです。

 

OpenCVの参照

次に先ほどダウンロードしたOpenCVを設定します。

File → New → Import Moduleを選択します。

先ほどダウンロードした中のsdk → javaを選択します。

ModuleNameはそのままで問題ありません。

次のチェックもそのままで大丈夫です。

Importが終わったらおもむろにModule Settingsを開きます。

ModulesからopenCVLibraryを選択し、PropertiesのCompileSDKVersion21以上のものに変更します。

なぜかモジュールの設定とソースで利用されているAndroid SDKの内容が合致していないのです……

※カメラをつかわないので有れば21未満のAndroidでも動く感じはします。

 

以上でOpenCVのプロジェクト設定は大体終わりました。

ビルドを実行してみると成功すると思います。もし、エラーが出た場合はAndroidのビルド環境が正しくないか、古い等の問題があります。

 

OpenCV の初期化

OpenCVの初期化を行います。

ドキュメント通りであれば、OpenCVLoader.initDebug → OpenCVLoader.initAsyncをするわけですが、

initAsyncを呼び出したタイミングでGoogle Play Storeが開き、OpenCVManagerというアプリをダウンロードしようとします。

これには2点問題があります。

  1. 製品でよそのアプリをダウンロードしてもらうのは厳しい
  2. GooglePlayStoreにはOpenCVManagerがもはや存在しない

2は致命的ですので、今回はOpenCVManagerを利用しない方向で対応します。

 

.soをコピーしてくる

OpenCVManager内にはnative(c++)で実装されたコードが含まれており、アプリ内のOpenSDK for Androidではそれを利用するようになっています。

※おそらく容量の削減のために、複数のアプリから共用できるようにという設計かと思います。でもこれセキュリティもアレゲなのであんまりよろしくないのでは……

なのでこのOpenCVManager内のnativeコードをアプリ内に組み込むことでManagerのインストールを回避できます。

 

さて、実はダウンロードしてきたOpenCV For Androidにはこのnativeコードが付属しています。

このsdk/nativeフォルダ内のlibsフォルダに入っています。

libsフォルダの下にあるフォルダをコピーして、AndroidStudioのlibsフォルダへペーストします。

フォルダ名・ファイル名は絶対に変えないでください。

※なんだかAndroidStudioはmipsに対応していないみたいで、mipsはエラーが発生しましたので省いています。

ビルド設定

次にbuild.gradleに先ほど入れたファイルたちをアプリに組み込むように設定します。

    sourceSets {
        main {
            jniLibs.srcDirs 'libs'
        }
    }

これをandroid中に足します。

例としては以下のような感じです。

// 前略
android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.art.cfm.opencvsample"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs 'libs'
        }
    }
}
// 後略

プロジェクトの参照設定も足します。

 

改めて初期化処理

改めて初期化処理です。

まずは正規の手順通り、BaseLoaderCallbackを継承したクラスを用意します。

※コードはKotlinです

    class LoaderCallback(activity: AppCompatActivity) : BaseLoaderCallback(activity){
    }

BaseLoaderCallbackを書いた時点でAndroidStudioが気を効かせてimportを足すようにおすすめしてきますので、その通りにします。

 

次にスタティックライブラリ(.so/apkの中にあったやつ)の読み込みです。

    companion object {
        init {
            System.loadLibrary("opencv_java3")
        }
    }

クラスロード時に読み込むように指定しました。

次に、正規であればinitDebug → initAsyncするところですが、initAsync相当が上記のloadLibraryなので、initAsyncは行いません。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        OpenCVLoader.initDebug();
        LoaderCallback(this).onManagerConnected(LoaderCallbackInterface.SUCCESS)
    }

onCreateでinitDebugとLoaderCallbackのonManagerConnectedを直接呼び出します。

※本来はinitAsyncでOpenCVManagerの読み込みが完了した時点で呼び出されます。

これでOpenCVの準備完了です。

OpenCVの基本サンプル

では、画像を読み込んでOpenCVで二値化します。

まず適当な画像読込

ここはまだ、OpenCVではなくAndroidの話です。

まずは適当な画像を用意してdrawableに配置します。

 

BitmapFactoryを使って読み込んでImageViewに設定します。

なんでも良いのですが、とりあえずImageViewを1つ用意し色は透明にしました。

プログラム中でdrawableに追加した画像で上書きするので適当で大丈夫です。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        OpenCVLoader.initDebug();
        LoaderCallback(this).onManagerConnected(LoaderCallbackInterface.SUCCESS)

        // リソースから読み込み
        var bmp = BitmapFactory.decodeResource(resources, R.drawable.eto_inu_hone)

        // todo: OpenCVで二値化する

        // ImageViewの画像を差し替え
        var imageView = findViewById<ImageView>(R.id.imageView)
        imageView.setImageBitmap(bmp)
    }

R.drawable.とかは各自適当に書き換えて下さい。

これでdrawableに足した画像がひとまず表示されます。

 

BMPをOpenCVの形式へ

変換し、戻す処理を書きます。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        OpenCVLoader.initDebug();
        LoaderCallback(this).onManagerConnected(LoaderCallbackInterface.SUCCESS)

        // リソースから読み込み
        var bmp = BitmapFactory.decodeResource(resources, R.drawable.eto_inu_hone)

        // todo: OpenCVで二値化する
        var mat = Mat()
        Utils.bitmapToMat(bmp, mat) // OpenCVの行列へ

        var output = Bitmap.createBitmap(bmp.width, bmp.height, Bitmap.Config.ARGB_8888)
        Utils.matToBitmap(mat, output)

        // ImageViewの画像を差し替え
        var imageView = findViewById<ImageView>(R.id.imageView)
        imageView.setImageBitmap(output)
    }

ユーティリティにまさにそのものが用意されているのでそれを利用します。

 

二値化

これもOpenCVの関数を呼ぶだけです。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        OpenCVLoader.initDebug();
        LoaderCallback(this).onManagerConnected(LoaderCallbackInterface.SUCCESS)

        // リソースから読み込み
        var bmp = BitmapFactory.decodeResource(resources, R.drawable.eto_inu_hone)

        // bmp → OpenCVへ
        var mat = Mat()
        Utils.bitmapToMat(bmp, mat) // OpenCVの行列へ

        Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY)  // まずグレースケールへ(明るさだけの形式)
        Imgproc.threshold(mat, mat, 128.0, 255.0, Imgproc.THRESH_BINARY)  // 明るさが128を境に白と黒へ変換

        // OpenCV → bmpへ
        var output = Bitmap.createBitmap(bmp.width, bmp.height, Bitmap.Config.ARGB_8888)
        Utils.matToBitmap(mat, output)

        // ImageViewの画像を差し替え
        var imageView = findViewById<ImageView>(R.id.imageView)
        imageView.setImageBitmap(output)
    }

簡単ですね。

タイトルとURLをコピーしました