THETAプラグインで画像処理【FastCV導入編】

こんにちは、リコーの@roohii_3です。
最近は Python+OpenCV で画像処理をしていることが多いです。
昨年までは C++ メインで、とあるシステムの設計・開発をしてました。

**※本記事の続編は以下に書きました↓**
[THETAプラグインで画像処理【FastCV画像処理編】](https://qiita.com/roohii_3/items/988e96a251d132f466de)
**※[2019/01/08 追記] OpenCV編は以下↓**
[THETAの中でOpenCVを動かす](https://qiita.com/roohii_3/items/63a546169981f41967df)


## はじめに
2018年11月現在の[RICOH THETA](https://theta360.com/ja/)の最新機種、**RICOH THETA V**では「**プラグイン**」機能を使ってTHETAをカスタマイズすることができます。
さらに、「**RICOH THETAプラグインパートナープログラム**」に登録するとプラグインを作ることもできます。
パートナープログラムについては、[記事の一番最後](#ricoh-thetaプラグインパートナープログラムについて)をご覧ください。

というわけで、 **Android開発は初めて・”NDK”というキーワードも初耳** な私が、プラグイン開発にチャレンジしてみました。
日頃画像処理をすることが多いので、何らかの画像処理をするプラグインを作ってみたいと思います。

タイトルにあるように、画像処理ライブラリには **FastCV** を使いました。
内容が盛りだくさんなので、今回は **「FastCVの導入～簡単なAPIを使うまで」** を扱います。
実際にFastCVで画像処理する際の手順は、次回記事にまとめたいと思います。

## [FastCV](https://developer.qualcomm.com/software/fastcv-sdk)とは

**FastCV** は、**Qualcomm社** が提供している **Computer Visionライブラリ** です。
Qualcomm社が独自のライセンスで提供しています[^1]。
画像処理に関わる演算がARMプロセッサ向けに最適化されており、Qualcomm製の **Snapdragon** プロセッサにおいては特に効果を発揮します。
FastCVを使うには、[Android NDK（Native Development Kit）](https://developer.android.com/ndk/)を用いてC/C++でプログラミングする必要があります。

### なぜFastCV？
画像処理ライブラリといえば **OpenCV** を思い浮かべる方が多いかと思います。
"OpenCV for Android SDK"を用いればOpenCVをプラグインで使えないこともないのですが、RICOH THETA Vではメインプロセッサとして **Snapdragon** を採用しているため、ここでは **FastCV** を使ってみたいと思います。

**※[2019/01/08 追記]** OpenCV編を書きました↓
[THETAの中でOpenCVを動かす](https://qiita.com/roohii_3/items/63a546169981f41967df)

### APIリファレンス
ドキュメントは [Qualcomm FastCV Library](https://developer.qualcomm.com/docs/fastcv/api/index.html) にあります。
基本的な処理をはじめ、**特徴点抽出** や **クラスタリング**、**機械学習** などのモジュールも用意されているようですね。
![modules.png](https://qiita-image-store.s3.amazonaws.com/0/304709/eefa2dc4-8695-4cb7-aea7-d8da45a69d08.png)


## 環境
本記事で紹介する手順は、以下の環境で確認しています。

* RICOH THETA V firmware ver. 2.50.1
* RICOH THETA Plug-in SDK ver. 1.0.1
* Windows 7 Professional 64bit
* AndroidStudio ver. 3.2.1
* NDK ver. 18.1.5063045
* FastCV ver. 1.7.1


## 下準備
NDK開発環境を整え、FastCV SDKをインストールします。

### 開発環境の構築
下記リンク等を参考に、AndroidStudioの導入とプラグイン開発環境を整えておいてください。

* [【THETAプラグイン開発】THETAを開発者モードにする手順](https://qiita.com/ueue/items/7c4027db3af463956e26)
* [【THETAプラグイン開発】THETAでRICOH THETA Plug-in SDKを動かす方法](https://qiita.com/yokazuya/items/cda20420ee0b1b58e03f)
* [Ricoh THETA V Plugin Application Development Community Guide](http://theta360.guide/plugin-guide/)
* [AndroidStudio](https://developer.android.com/studio/)


### NDKのインストール
1. AndroidStudioを起動し、メニューから”Tools > SDK Manager”を選択
1. ”SDK Tools”タブを選択し、**”NDK”** にチェックを入れて"OK"をクリック
![ndk_install.png](https://qiita-image-store.s3.amazonaws.com/0/304709/7ee9d9c8-85a6-7500-fc67-e3016a6a2cf5.png)
1. 画面に従って進め、インストールが終わったら"Finish"をクリック

### FastCV SDKのインストール
1. [Qualcomm developer network](https://developer.qualcomm.com/)の”Register”からアカウントを登録
1. アカウント取得後、[FastCV Computer Vision SDK](https://developer.qualcomm.com/software/fastcv-sdk)から **”FastCV SDK Installer”** をダウンロード
1. ダウンロードしたInstallerを実行

私の場合、FastCV SDKは以下の場所にインストールされました。

```
C:/Android/Development/fastcv-android-1-7-1
```

<details>
<summary>インストーラ実行時に下記のエラーが出た場合</summary><div>
![fastcv_install_error.png](https://qiita-image-store.s3.amazonaws.com/0/304709/09129ae0-d8f4-abf4-b6a0-54e929845db1.png)

コマンドラインでJava VMを指定すれば、無事インストールできました[^2]。コマンドラインへは以下のように入力します。

```bash
> fastcv-installer-android-1-7-1/fastcv-installer-android-1-7-1.exe LAX_VM "C:/Program Files(x86)/Java/jre1.8.0_171/bin/java.exe"
```
</div></details>


## プロジェクトファイルの準備
RICOH THETA Plug-in SDKをベースにする方法を紹介します。
もちろん、一からプロジェクトファイルを作っても構いません。

なお、[2. 名称変更](#2-名称変更)は最悪行わなくても動きます。プラグインストアに出す場合は、必ず行うようにしてください。
※本記事で紹介している[実装例](#2-実装)では、プロジェクト名は"theta-plugin-fastcv-simple-sample"、パッケージ名は"fastcvsimplesample"にしています。

### 1. RICOH THETA Plug-in SDKのダウンロード
1. [RICOH THETA Plug-in SDK](https://github.com/ricohapi/theta-plugin-sdk)（AndroidStudioプロジェクト）をダウンロード

### 2. 名称変更
1. **プロジェクトファイル名の変更**<br>
ダウンロードしたAndroidStudioプロジェクトの名称（ディレクトリ名）を任意のものにリネーム
1. **アプリケーションID・パッケージ名の変更**<br>
[アプリケーションIDの設定](https://developer.android.com/studio/build/application-id?hl=ja)等を参考に変更する
1. **プラグイン名の変更**<br>
プロジェクトツリーより **"app > res > values > strings.xml"** を開き、任意の名称に変更する

```
例）<string name="app_name">FastCV Simple Sample</string>
```


## FastCVのインポートとビルド
作成したプロジェクトファイルにFastCVをインポートし、ビルドファイルを修正した後、プロジェクトをビルドします。

### 1. ライブラリファイルのコピー
FastCVのライブラリファイルを、今回作ったプロジェクトにコピー＆ペーストする

ファイルのコピー元・先を以下にまとめる
コピー先のディレクトリは適宜作成する

| ファイル名           | コピー元 | コピー先 |
|:------------------- |:---------|:--------|
| fastcv.h            | ~/(FastCV SDK path)/inc/fastcv.h | ~/(Project path)/app/src/main/jni/fastcv/fastcv.h |
| libfastcv.a (32bit) | ~/(FastCV SDK path)/lib/Android/lib32/libfastcv.a | ~/(Project path)/app/src/main/libs/armeabi-v7a/libfastcv.a |
| libfastcv.a (64bit) | ~/(FastCV SDK path)/lib/Android/lib64/libfastcv.a | ~/(Project path)/app/src/main/libs/arm64-v8a/libfastcv.a |

### 2.  Android.mkの編集
NDKのビルドファイルを修正する
ここでは、FastCVのサンプルプロジェクトのものを流用し、適宜修正していく

1. Android.mkのコピー
下記表を参考に、Android.mkをコピーしてプロジェクトに配置

    | ファイル名  | コピー元 | コピー先 |
    |:-----------|:---------|:--------|
    | Android.mk | ~/(FastCV SDK path)/samples/fastcorner/jni/Android.mk | ~/(Project path)/app/src/main/jni/Android.mk |

1. AndroidStudioのプロジェクトツリーを **”Android”** ビューにし、 **”app > cpp > Android.mk”** が追加されたことを確認する
![AS_Androidmk.png](https://qiita-image-store.s3.amazonaws.com/0/304709/1b9abb46-6d20-6d83-25ee-e4e6e1448f9d.png)

1. AndroidStudioでAndroid.mkを開き、以下の修正を加える[^3] [^4]
    * `include $(CLEAR_VARS)` を追加
    * `LOCAL_MODULE := libfastcv` を追加
    * `LOCAL_SRC_FILES := ../libs/$(TARGET_ARCH_ABI)/libfastcv.a` を追加
    * `include $(PREBUILT_STATIC_LIBRARY)` を追加
    * `LOCAL_LDLIBSから-lfastcv` をコメントアウト
    * `LOCAL_LDFLAGS := -Wl, --no-fix,cortex-a8` をコメントアウト
    * `LOCAL_MODULE :=` を任意の名称に変更
    * `LOCAL_SRC_FILES :=` の値を削除
    * `LOCAL_SHARED_LIBRARIES := liblog libGLESv2` をコメントアウト
    * `LOCAL_MODULE_OWNER :=` を任意の値に変更
    * `LOCAL_JNI_SHARED_LIBRARIES :=` を任意の値に変更
    * `LOCAL_PACKAGE_NAME :=` を任意の値に変更

    <details>
    <summary>記述例</summary><div>

    ```Android.mk

    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := libfastcv
    LOCAL_SRC_FILES := ../libs/$(TARGET_ARCH_ABI)/libfastcv.a

    include $(PREBUILT_STATIC_LIBRARY)

    include $(CLEAR_VARS)
    LOCAL_PRELINK_MODULE:= false

    # This variable determines the OpenGL ES API version to use:
    # If set to true, OpenGL ES 1.1 is used, otherwise OpenGL ES 2.0.

    USE_OPENGL_ES_1_1 := false

    # Set OpenGL ES version-specific settings.

    ifeq ($(USE_OPENGL_ES_1_1), true)
        OPENGLES_LIB  := -lGLESv1_CM
        OPENGLES_DEF  := -DUSE_OPENGL_ES_1_1
    else
        OPENGLES_LIB  := -lGLESv2
        OPENGLES_DEF  := -DUSE_OPENGL_ES_2_0
    endif

    # An optional set of compiler flags that will be passed when building
    # C ***AND*** C++ source files.
    #
    # NOTE: flag "-Wno-write-strings" removes warning about deprecated conversion
    #       from string constant to 'char*'

    LOCAL_CFLAGS := -Wno-write-strings $(OPENGLES_DEF)

    # The list of additional linker flags to be used when building your
    # module. This is useful to pass the name of specific system libraries
    # with the "-l" prefix.

    LOCAL_LDLIBS := \
        -llog $(OPENGLES_LIB)
    #      -lfastcv
    # LOCAL_LDFLAGS:= -Wl,--no-fix-cortex-a8

    LOCAL_MODULE    := fastcvsimplesample
    LOCAL_CFLAGS    := -Werror
    LOCAL_SRC_FILES := \

    LOCAL_STATIC_LIBRARIES := libfastcv
    # LOCAL_SHARED_LIBRARIES := liblog libGLESv2
    LOCAL_C_INCLUDES += $(TARGET_OUT_HEADERS)/fastcv/inc \
    LOCAL_C_INCLUDES += $(TARGET_OUT_HEADERS)/fastcv


    LOCAL_MODULE_OWNER := theta360
    LOCAL_PROPRIETARY_MODULE := true

    include $(BUILD_SHARED_LIBRARY)


    include $(CLEAR_VARS)

    LOCAL_STATIC_JAVA_LIBRARIES :=
    LOCAL_JNI_SHARED_LIBRARIES := libfastcvsimplesample
    LOCAL_SRC_FILES := $(call all-subdir-java-files)
    LOCAL_PACKAGE_NAME := pluginapplication

    include $(BUILD_PACKAGE)
    ```

    </div></details>


### 3. build.gradle(Module:app)の編集
1. **"Gradle Scripts > build.gradle(Module: app)"** を開く
1. **"android > defaultConfig"** に、以下のように **"ndk"** の値を追記する

    ```build.gradle
    android {
        ...
        defaultConfig {
            ...
            ndk {
                moduleName "libfastcvsimplesample"
                abiFilters 'armeabi-v7a', 'arm64-v8a'
            }
        }
    }
    ```

1. エディタウィンドウ上部に表示された **"Sync Now"** をクリック

### 4. ネイティブライブラリとGradleのリンク
1. プロジェクトツリーで **”app”** を右クリックし、 **"Link C++ Project with Gradle"** を選択
1. 開いたウィンドウのBuild Systemに **"ndk-build"** 、Project Pathに **"Android.mk"** のパスを指定し、OKをクリック<br>
![AS_ndkLink.png](https://qiita-image-store.s3.amazonaws.com/0/304709/279e1182-7300-6e4c-4811-ca03c26c563f.png)

### 5. ビルド
1. メニューから **"Build > Build APK(s)"** をクリック
1. ツールウィンドウの **”Build”** より、ビルドが成功したことを確認する
![AS_build.png](https://qiita-image-store.s3.amazonaws.com/0/304709/3ff28d32-d863-c8b9-3f07-0bc2bf712374.png)


## FastCVアプリケーションの作成
実際にプロジェクト内でFastCV APIを使ってみます。

### 1. NDKソースファイルの追加
1. ツールウィンドウバーから **”Project”** を選択し、ビューを **”Project”** モードにする
![AS_ProjectView.png](https://qiita-image-store.s3.amazonaws.com/0/304709/64b3f3e1-368c-6eae-60f3-20ad4ecfd0ee.png)
1. プロジェクトツリーから”(project) > app > src > main > jni”をたどって右クリックし、**”New > C/C++ Source File”** をクリック
1. 適当なC++ファイル名を入力し、OKをクリック
1. 同様に、**”New > C/C++ Header File”** からヘッダーファイルを作成する<br>
"jni"下に作成したファイルが追加される<br>
![AS_AddCFile.png](https://qiita-image-store.s3.amazonaws.com/0/304709/8d7f79a9-a7f9-4d88-924e-48488b3e0c03.png)
1. ”Android.mk”を開き、`LOCAL_SRC_FILES`に追加した.cppファイル名を追記する

    ```Android.mk
    LOCAL_SRC_FILES := \
        FastCVSample.cpp
    ```

1. エディタウィンドウ上部に表示された **”Sync Now”** をクリック

### 2. 実装
#### Javaとネイティブ関数のやりとり
NDKについてはまだ勉強中なので詳細は触れませんが、Java ↔ ネイティブ(C++)のやり取りで用いる関数には、特殊なルールがあるようです。

戻り値や引数の型も特殊のものを使うようです[^6]。
詳しくは、[JNIの型とデータ構造](https://docs.oracle.com/javase/jp/1.5.0/guide/jni/spec/types.html)等を参考にしてください。

##### ネイティブ側関数の命名規則[^5]

```cpp
JNIEXPORT [戻り値の型] JNICALL Java_[最上位ソースディレクトリからのJavaソースの相対パス]_[呼び出し元のJavaのクラス名]_[関数名](JNIEnv *, jobject, [引数], ...))
```

```cpp
// 例
JNIEXPORT jstring Java_com_theta360_fastcvsimplesample_MainActivity_getFastCVVersion(JNIEnv * env, jobject obj);
```

##### Javaからのネイティブ関数の呼び出し
以下を行う必要があるようです[^5]

* 起動時に`System.loadLibrary("[.soファイル名]")`を呼ぶ
* `native`キーワードを付けて関数を宣言

```MainActivity.java
// 例

static {
    // Load JNI Library
    Log.i(TAG, "Load JNI Library");
    System.loadLibrary("fastcvsimplesample");   // <--
}
...

private void foo(){

    // ネイティブ関数を呼ぶ
    String versionText = "FastCV Version: " + getFastCVVersion();   // <--
    Log.i(TAG, versionText);
    }
}
...

// ネイティブ関数の定義
public native String getFastCVVersion();    // <--
```


#### FastCVの基本API
基本APIは[Miscellaneous](https://developer.qualcomm.com/docs/fastcv/api/group__misc.html)モジュールに定義されています。
FastCVを使う際には、以下の初期化・終了処理APIを呼ぶ必要があります。

* 初期化処理： `fcvSetOperationMode( fcvOperationMode mode );`
    * 引数にパフォーマンスレベルを設定するようです。<br>
[ドキュメント(fcvOperationMode項)](https://developer.qualcomm.com/docs/fastcv/api/fastcv_8h.html#a6b170923796860c68e491eb2af466058)にあるように、5段階の設定値が定義されています。
* 終了処理 : `fcvCleanUp( void );`
* バージョン取得: `fcvGetVersion( char *version, unsigned int versionLength);`
    * 引数に、バージョン情報を格納するための`ポインタ`、`文字数`を渡します。


### 3. コード記述例

<details><summary>ネイティブ ヘッダファイル</summary><div>

```FastCVSample.h
#ifndef THETA_PLUGIN_FASTCV_SIMPLE_SAMPLE_FASTCVSAMPLE_H
#define THETA_PLUGIN_FASTCV_SIMPLE_SAMPLE_FASTCVSAMPLE_H

#include <jni.h>

// Declarations
extern "C" {
JNIEXPORT void JNICALL Java_com_theta360_fastcvsimplesample_MainActivity_initFastCV
        (
                JNIEnv* env,
                jobject obj
        );
JNIEXPORT void JNICALL Java_com_theta360_fastcvsimplesample_MainActivity_cleanupFastCV
        (
                JNIEnv * env,
                jobject obj
        );
JNIEXPORT jstring JNICALL Java_com_theta360_fastcvsimplesample_MainActivity_getFastCVVersion
        (
                JNIEnv* env,
                jobject obj
        );
};

#endif //THETA_PLUGIN_FASTCV_SIMPLE_SAMPLE_FASTCVSAMPLE_H
```

</div></details>

<details><summary>ネイティブ C++ファイル</summary><div>

```FastCVSample.cpp
#include <fastcv/fastcv.h>
#include "FastCVSample.h"

JNIEXPORT void JNICALL Java_com_theta360_fastcvsimplesample_MainActivity_initFastCV
(
        JNIEnv* env,
        jobject obj
)
{
    fcvSetOperationMode( (fcvOperationMode) FASTCV_OP_PERFORMANCE );
    return;
}

JNIEXPORT void JNICALL Java_com_theta360_fastcvsimplesample_MainActivity_cleanupFastCV
(
        JNIEnv * env,
        jobject obj
)
{
    fcvCleanUp();
}

JNIEXPORT jstring JNICALL Java_com_theta360_fastcvsimplesample_MainActivity_getFastCVVersion
        (
                JNIEnv * env,
                jobject obj
        )
{
    char sVersion[32];
    fcvGetVersion(sVersion, 32);

    return env->NewStringUTF(sVersion);
}
```

</div></details>

<details><summary>Javaファイル</summary><div>

[RICOH THETA Plug-in SDK](https://github.com/ricohapi/theta-plugin-sdk)の **"MainActivity.java"** をベースに、[[2. 実装]](#2-実装)通りに実装していきます。
`getFastCVVersion()` は `onKeyDown()` 内で呼ぶようにしています。これにより、THETAのシャッターボタンを押したときにバージョンが表示されるようにしています。

```MainActivity.java

(省略) 

public class MainActivity extends PluginActivity {
    private static final String TAG = "FastCVSimpleSample";

    static {
        // Load JNI Library
        Log.i(TAG, "Load JNI Library");
        System.loadLibrary("fastcvsimplesample");   // <--
    }

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

        // Set enable to close by pluginlibrary, If you set false, please call close() after finishing your end processing.
        setAutoClose(true);

        // Set a callback when a button operation event is acquired.
        setKeyCallback(new KeyCallback() {
            @Override
            public void onKeyDown(int keyCode, KeyEvent event) {
                if (keyCode == KeyReceiver.KEYCODE_CAMERA) {

                    // ネイティブ関数を呼ぶ
                    String versionText = "FastCV Version: " + getFastCVVersion();   // <--
                    Log.i(TAG, versionText);
                }
            }

            @Override
            public void onKeyUp(int keyCode, KeyEvent event) {

            }

            @Override
            public void onKeyLongPress(int keyCode, KeyEvent event) {

            }
        });
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        initFastCV();   // <--
    }

    @Override
    protected void onPause() {
        cleanupFastCV();    // <--
        super.onPause();
    }

    // ネイティブ関数の定義
    public native void initFastCV();            // <--
    public native void cleanupFastCV();         // <--
    public native String getFastCVVersion();    // <--
}

```

</div></details>


### 4. ビルド・実行
1. メニューから **"Run > Run'app'"** をクリックしてビルド・実行する
1. "Select Deployment Target"ウィンドウが出たらTHETA Vを選択し、OKをクリック
1. ツールウィンドウの **”Build”** より、ビルドが成功したことを確認する

[[3. コード記述例]](#3-コード記述例)の通りにコード記述すると、シャッターボタンを押したときにFastCVのバージョンがLogcatに表示される。
※ `Log.i()`で設定した`TAG名`でフィルタリングすると見やすい
![logcat.png](https://qiita-image-store.s3.amazonaws.com/0/304709/06389d58-73b3-ab9d-55b3-603898035a0c.png)


## おわりに
今回は出来上がるものが地味な割に、結構大変な作業だったかと思います…。
次回記事では、実際に画像処理をする手順をご紹介する予定です。
**※本記事の続編は以下です↓**
[THETAプラグインで画像処理【FastCV画像処理編】](https://qiita.com/roohii_3/items/988e96a251d132f466de)


### RICOH THETAプラグインパートナープログラムについて

RICOH THETAプラグインをご存じない方は[こちら](https://api.ricoh/products/theta-plugin/)をご覧ください。
[RICOH THETAプラグイン開発者コミュニティ](https://qiita.com/organizations/theta-plugin)の以下の記事もぜひご覧ください。

* [THETAプラグインパートナープログラムはじめました](https://qiita.com/mrtakada/items/8f3bbf8d283c26899473)
* [【THETAプラグイン開発】プラグインパートナープログラムに登録してみよう](https://qiita.com/mShiiina/items/55d98f366e650ca42251)
* [【THETAプラグイン開発】THETAを開発者モードにする手順](https://qiita.com/ueue/items/7c4027db3af463956e26)

興味を持たれた方は[twitter](https://twitter.com/thetaplugin)のフォローと[THETAプラグイン開発コミュニティ(slack)](https://join.slack.com/t/thetadeveloper/shared_invite/enQtNzcxODI2MTk2ODg0LWY5YmU2ZTIxNGYwNzlmNWZjMWMyMWFlMzY3ZmQ3NGU3ZjZiZjI0Njk4MGIxMDU5NDJiMzQ4Y2FkNDEzYmFlNGU)への参加もよろしくおねがいします｡



[^1]: [Forums - fastcv license](https://developer.qualcomm.com/forum/qdevnet-forums/mobile-technologies/computer-vision-fastcv/26815)
[^2]: [Java VM の読み込み中にWindowsのエラー2が発生しました](http://09.hatenadiary.jp/entry/2016/07/27/070000)
[^3]: [FastCVをAndroid Studioでビルドする](https://qiita.com/mktshhr/items/59f911f386b801fdf9e0)
[^4]: [Forums - FastCV in Android Studio](https://developer.qualcomm.com/forum/qdn-forums/software/fastcv-computer-vision-sdk/33111)
[^5]: [Developers > NDK > サンプル > hello-jni](https://developer.android.com/ndk/samples/sample_hellojni)
[^6]: [JNIの型とデータ構造](https://docs.oracle.com/javase/jp/1.5.0/guide/jni/spec/types.html)