Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

Android Studio + MOVERIO + OpenCV #1

モチベーション

とあることをきっかけにEpson MOVERIO BT-300Androidアプリを開発することになった.Android Studioはほぼ初見だし,Javaにも親しみがないので,新たな発見や理解に苦労した点なども一緒に述べていきたい.

目標

  • OpenCVは画像処理に便利なので,とりあえずAndroid Studioで使えるようにする
  • OpenCVを使って,MOVERIOのカメラの画像を取得し,それをMOVERIOの画面に表示する

開発環境

  • Windows 10 Home
  • Android Studio 2.3.3
  • Android SDK 5.1 (Lollipop)
    • 自身が用いるデバイスに合ったバージョンを選択する
    • 以降の内容は,多分,極端に古くなければどのバージョンでも動作すると思う
  • OpenCV 3.3.0
  • BT300 SDK 1709
  • Epson MOVERIO BT-300

MOVERIOでの開発の難点

結構,色々と躓いた.重要なのでこの時点で書いておく.

  1. Google Play Storeアプリがインストールされていない&インストールできない
    • ハックする方法も色々とある様子だが,今回は特段ハックせずに進める
    • Google Play Storeアプリが使えない → MOVERIOをGoogle Play Storeに登録できない → OpenCV Managerをインストールできない
    • OpenCVAndroidアプリに引っ付けて,Androidバイスに送ることにした
  2. 結論から書くと,OpenCVのコールバックを使って画像を取得すると妙に画角が狭く表示されて使い物にならない
    • 画像を取得する段階でおかしいのか,表示の段階でおかしいのか分からないが,試行錯誤の結果,前者の可能性が高いというのが所感
    • この記事の最後にあるように,最終的には,Android SDKを使うことにした
    • しかし,OpenCVの設定という内容に関しては誰かの役に立つと思い,ここに記す
    • ちなみに,Vuforiaを使う方法もあるようだが試していない
      • できるだけコンパクトにいきたいのだ

Android StudioAndroid SDKのインストール

Android StudioAndroid SDKのインストール方法は公式サイトなどを参照のこと.色んな記事がWeb上に存在するけれど,公式の内容で十分だと思います.

Android StudioでのOpenCVの設定

結局のところ,以下の質問への回答の通りに設定するのが一番だった.英語なので,日本語で,かつもう少し写真や注意点を加えて以下に説明する.

stackoverflow.com

具体的な手順(全10ステップ)

以下の設定を行えば,OpenCV Managerなるものを入れなくても

  1. OpenCV公式サイトからダウンロード
    • 私はバージョン3.3.0を選択した
  2. zipファイルを解凍し,任意のパスへ
    • 解凍したフォルダまでのパスをPATHとする
  3. Android Studioを立ち上げてプロジェクトを新規作成
    • OpenCVTestというプロジェクト名にしました
  4. Android StudioFile>>New>>Import Module(下図)より,PATH/sdk/javaを選択 f:id:Mugichoko:20171005015049j:plain
    • 左のProjectメニュー内に "OpenCVLibrary330" というフォルダができる f:id:Mugichoko:20171005053625j:plain
  5. <プロジェクト名>/app/build.gradleにある以下の情報を<プロジェクト名>/OpenCVLibrary330/build.gradleにコピペ
    • compileSdkVersion
    • buildToolsVersion
    • minSdkVersion
    • targetSdkVersion f:id:Mugichoko:20171005053837j:plain
  6. Projectメニュー内のOpenCVTestプロジェクトを右クリックしてOpen Module Srttings (F4)をクリック
    • 【注意】デフォルトではOpenCVTestプロジェクトが見えていない
    • 下図の様にAndroidとあるタブをクリックしてProjectに変更しておく
    • 以降は使わないのでAndroidタブに戻しておくとよい f:id:Mugichoko:20171005054629j:plain
  7. 現れたProject Structureウィンドウでapp>>Dependenciesを開き,右上にある+ボタンを押してModule dependencyをクリックし,OpenCVLibrary330を選択 f:id:Mugichoko:20171005054728j:plain
  8. PATH/sdk/native/libsフォルダを丸々コピーして<プロジェクト名>/app/src/mainフォルダ内にペースト
  9. コピーしたlibsフォルダ名をjniLibsに変更
    • jniLibsフォルダ以下に多くのフォルダが存在するが,その各フォルダ内にある.soファイルだけが必要なので,その他のファイル(つまり,.aファイル)は削除しておく
  10. この記事にあるサンプルプログラムを実行
    • 実行するには,緑色の右向き三角ボタンf:id:Mugichoko:20171005070815j:plainを押す

MOVERIO用SDKの設定

これは,公式のマニュアル通りに設定(特に「2.3. USBドライバの設定」と「2.5. Epson 提供SDKの組み込み」).

マニュアル

tech.moverio.epson.com

BT300 SDKのダウンロード

tech.moverio.epson.com

サンプルプログラム

MainActivity.java

Javaではクラス名とファイル名が紐づけられているのですね.つまり,1つのJavaファイルに1クラスということ.そういえば,昔に大学で習ったような...

このクラスが名前の通りメインです.OpenCVのカメラの画像取得及び表示用コールバック?であるCvCameraViewListener2を使って,お手軽にカメラからの画像を扱えそうです... と思っていたのですが,この記事の最後に書く通り,色々と問題があるので注意

20行目にあるOpenCVLoader.initDebug()OpenCVの諸々の初期化が行われる模様.C++では特段必要なかったので新鮮.OpenGLみたいですね.OpenCV Managerを利用する場合は,52行目のOpenCVLoader.initAsync()を呼ぶようだ.

package com.example.XXXXX.opencvtest;
// 上記のXXXXXには自身のユーザ名が入る

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;

public class MainActivity extends Activity implements CameraBridgeViewBase.CvCameraViewListener2 {
    private static final String TAG = "OpenCV::Activity";
    private CameraBridgeViewBase mOpenCvCameraView;

    static {
        if (OpenCVLoader.initDebug()) {
            Log.i(TAG, "OpenCV successfully initializated!");
        } else {
            Log.i(TAG, "OpenCV initialization failed!");
        }
    }

    private BaseLoaderCallback mLoaderCallBack = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            if (mOpenCvCameraView != null) {
                mOpenCvCameraView.enableView();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main);
        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.HelloOpenCvView);
        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
        mOpenCvCameraView.setCvCameraViewListener(this);
    }
    @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_3_0, this, mLoaderCallBack);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallBack.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }
    @Override
    public void onPause() {
        super.onPause();
        if (mOpenCvCameraView != null) {
            mOpenCvCameraView.disableView();
        }
    }

    public void onDestroy() {
        super.onDestroy();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    public void onCameraViewStarted(int width, int height) {
    }

    public void onCameraViewStopped() {
    }

    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        return inputFrame.rgba();
    }
}

activity_main.xml

Android端末に表示する際の画面のレイアウトがここに記述されている.このXMLファイルを開いた時に現れる,Android Studio画面内の下の方にあるDeisgnTextタブで切り替えるとXMLを理解しやすい.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/activity_main"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:opencv="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
tools:context="com.example.XXXXX.opencvtest.MainActivity">
<!--- 上記のXXXXXには自身のユーザ名が入る --->

<org.opencv.android.JavaCameraView
    android:id="@+id/HelloOpenCvView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="visible"
    opencv:camera_id="any"
    opencv:show_fps="true">
</org.opencv.android.JavaCameraView>

</RelativeLayout>

AndroidManifest.xml

そもそもの決まり事を書くところの様子.カメラを使うとか,使っちゃダメとか.OpenCVの公式ドキュメントのHello OpenCV Sampleにある様に,カメラを使えるように追記する.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.XXXXX.opencvtest">
    <!-- 上記のXXXXXには自身のユーザ名が入る -->

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
        >
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <!-- 以下を追記 -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera" android:required="true"/>
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>
</manifest>

結果

こんな感じで表示された.MOVERIOのグラス部分を通してiPhoneで撮影し,必要な部分だけクロップした.明るくなっている視覚の枠内にカメラで取得した画像が写っている.

f:id:Mugichoko:20171005071916j:plain

問題発覚

SDKについてきたサンプルプログラムのSampleCameraPreviewで見たときより,画角が妙に狭いぞ...??? これに気づいてから,色々と原因を探ってみた.例えば,JavaCameraViewからNativeCameraViewに変更を試みてみるも,そもそもMOVERIO内でアプリを起動できないし,他にもCompatible Screenとやらのせいか?と思いレイアウトを色々といじってみたが,どうにも上手くいかない...

解決方法

解決方法として,MOVERIOのサンプルプログラムの様にAndroid SDKを使って画像のByteデータを取得して,表示する方法に切り替えたが,これは次回に記すことにする.