本記事はOpenCV Advent Calendar 2018の17日目の記事です。
RICOH THETAというカメラの中で、OpenCVを動かしてみました。
Android Studio + NDK (ndk-build)でOpenCVを使ったアプリをビルドしたい、という方にも参考になれば幸いです。
はじめに
こんにちは、リコーの@roohii_3です。
弊社でRICOH THETAという全周囲360度撮れるカメラを出してるのですが、このカメラの最新機種(2018年12月現在)のRICOH THETA Vは、Androidで動いています。
Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます。
THETAプラグインでOpenCV+クラウドなどを組み合わせば、何かを認識して撮影・クラウドに送信、のようなIoT的な使い方もできそうです。
ということで、そのことはじめとしてTHETAプラグインでOpenCVを動かしてみました。
開発環境
- OpenCV Android pack ver. 3.4.4
- Android Studio ver. 3.2.1
- JDK ver. 1.8.0_191
- NDK ver. 18.1.5063045
- gradle ver. 4.6
- RICOH THETA V Firmware ver. 2.50.1
(Android ver. 7.1.2 / API level 25)
事前準備
OpenCV Android pack
- OpenCVのReleasesページから"Android pack"の最新版をダウンロード
- ダウンロードしたファイルを解凍後、任意の場所に配置
私は ”OpenCV-android-sdk” → ”OpenCV-3.4.4-android-sdk” にリネームし、以下の場所に置きました。
C:/opencv/OpenCV-3.4.4-android-sdk
Android開発環境
Android Studio
Android Developersより、Android Studioをダウンロード・インストール
Android SDK・Tools
- Android Studioのメニューより、”Tools > SDK Manager”を開く
- ”SDK Platforms”タブより、Androidのバージョンに応じて必要なものにチェックを入れる
- ”SDK Tools”タブより、以下のものにチェックが入っていなかったら入れておく
- Android SDK Build-Tools
- Android SDK Platform-Tools
- Android SDK Tools
- Google USB Driver
- NDK
- "OK"をクリック
JDK
以下より、”Java SE Developer Kit 8”をダウンロード・インストール
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
RICOH THETA V
THETAを使う場合は、以下のリンクを参考にして開発者モードにしておく
【THETAプラグイン開発】プラグインパートナープログラムに登録してみよう
【THETAプラグイン開発】THETAを開発者モードにする手順
OpenCV for Android
OpenCVの公式ドキュメントにある"Android Development with OpenCV"によると、AndroidでOpenCVを使うにはいくつか方法があるようです。
-
Javaで使う方法
1.1. OpenCV Managerを使う方法
1.2. ライブラリを静的リンクさせる方法 -
NDKによってC/C++で使う方法
※ビルドに、"CMake"を使う方法、"ndk-build"を使う方法の2種類がある模様。
OpenCV公式では「1.1. OpenCV Managerを使う方法」を推奨しているみたいです1 2。
しかし、THETAは常時ネットワークに繋がっていないため、OpenCV Managerの特徴を生かせられません。
そのため、1.2.の方法か2.の方法を使うことになります。
今回は、「2. NDKによってC/C++で使う方法」で実装することにします。
ビルドには”ndk-build”を使います。
プロジェクトファイル・NDKビルド環境の準備
1. Android Studioプロジェクトの新規作成
Android Studioを開き、メニューの"File > New Project"を選択します。画面に従って"Empty Activity"を作成します。
※ **"Include C++ support"のチェックは不要です。**これにチェックを入れるとCMakeList.txtが作成されるようですが、今回はndk-buildでビルドするのでチェックの必要はありません。
2. ビルドファイルの作成
"app"ディレクトリの下に"jni"ディレクトリを作成し、"Android.mk"と"Application.mk"を作成します。
それぞれのファイルには、以下のように記入します。
【Android.mk】
- 7行目の
include
に、OpenCV Android packの中にある"OpenCV.mk"のパスを記入 -
LOCAL_MODULE
に、任意の名称(NDKで生成されるライブラリ名)を設定 -
LOCAL_SRC_FILES
に、C/C++(Nativeコード)のソース名を設定
※Nativeコードが複数ある場合は、空白で区切るようです3。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
OPENCV_INSTALL_MODULES:=on
OPENCV_LIB_TYPE:=SHARED
include C:\opencv\OpenCV-3.4.4-android-sdk\sdk\native\jni\OpenCV.mk
LOCAL_MODULE := opencvsample
LOCAL_SRC_FILES := sample.cpp
include $(BUILD_SHARED_LIBRARY)
【Application.mk】
-
APP_ABI
には、プラットフォームに応じて適当なものを設定
APP_STL := c++_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a
3. OpenCVライブラリ(.soファイル)のコピー
"app"ディレクトリ下に"jniLibs"ディレクトリを作成し、OpenCVライブラリ(.soファイル)をコピー&ペーストします。
プラットフォームに応じて、必要なライブラリファイルをディレクトリごとコピペします。
コピー元: C:/(OpenCV Android packがある場所)/sdk/native/libs/armeabi-v7a
コピー先: C:/(プロジェクトファイルがある場所)/app/jniLibs/armeabi-v7a
4. build.gradleの編集
build.gradle(Module:app)を開き、下記のようにNDK設定を追記します。
-
moduleName
には、"Android.mk"で設定したLOCAL_MODULE
の名前の先頭に"lib"をつけたものを設定する -
abiFilters
の設定値は、プラットフォームに応じて適当なものを設定する
android {
...
defaultConfig {
...
ndk {
moduleName "libopencvsample"
abiFilters 'armeabi-v7a'
}
}
}
エディタウィンドウ上部に"Sync Now"が出たら、それをクリック
5. ビルドシステムの設定
プロジェクトツリーで"app"を右クリックし、"Link C++ Project with Gradle"を選択します。
開いたウィンドウのBuild Systemに "ndk-build" 、Project Pathに "Android.mk" のパスを指定し、OKをクリックします。
実装
"drawable"に置いた"lena.jpg"を開き、Nativeコード内でOpenCVを動かして色空間をRGB→BGRに変換、それをImageView上に表示させる、というものを作ってみます。
layout
バージョンを表示させるTextView
と、処理後の画像を表示させるImageView
を配置しています。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
Javaコード
"Android.mk"でLOCAL_MODULE
に設定したライブラリ名の先頭に"lib"を付け、System.loadLibrary()
で読み込みます。
Nativeコード関数は、public native String version();
のように宣言しておきます。
package com.theta360.opencvsample;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import java.nio.ByteBuffer;
public class MainActivity extends AppCompatActivity {
// load native library
static {
System.loadLibrary("opencvsample");
}
private TextView mTextView;
private ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// OpenCV version
mTextView = (TextView) findViewById(R.id.textView);
mTextView.setText("OpenCV version: " + version());
// load the picture from the drawable resource
Bitmap img = BitmapFactory.decodeResource(getResources(), R.drawable.lena);
// get the byte array from the Bitmap instance
ByteBuffer byteBuffer = ByteBuffer.allocate(img.getByteCount());
img.copyPixelsToBuffer(byteBuffer);
// call the process from the native library
byte[] dst = rgba2bgra(img.getWidth(), img.getHeight(), byteBuffer.array());
// set the output image on an ImageView
Bitmap bmp = Bitmap.createBitmap(img.getWidth(), img.getHeight(), Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(ByteBuffer.wrap(dst));
mImageView = (ImageView) findViewById(R.id.imageView);
mImageView.setImageBitmap(bmp);
}
// native functions
public native String version();
public native byte[] rgba2bgra(int width, int height, byte[] src);
}
Nativeコード
OpenCVの処理はC/C++で行います。
"app > jni"下に任意の名前でC/C++ファイルを作成します。
"Android.mk"のLOCAL_SRC_FILES
には、作成したファイルの名称を設定しておきます。
Java ↔ Native(C/C++)のやり取りで用いる関数には、特殊なルールがあるようです。
以下のように、定義します。
JNIEXPORT [戻り値の型] JNICALL Java_[最上位ソースディレクトリからのJavaソースの相対パス]_[呼び出し元のJavaのクラス名]_[関数名](JNIEnv *, jobject, [引数], ...))
戻り値や引数の型も特殊のものを使うようです。
詳しくは、JNIの型とデータ構造等を参考にしてください。
#include <jni.h>
#include <string>
#include <opencv2/core.hpp>
#include <cv.hpp>
extern "C"
{
JNIEXPORT jstring JNICALL
Java_com_theta360_opencvsample_MainActivity_version(
JNIEnv *env,
jobject) {
std::string version = cv::getVersionString();
return env->NewStringUTF(version.c_str());
}
JNIEXPORT jbyteArray
JNICALL Java_com_theta360_opencvsample_MainActivity_rgba2bgra
(
JNIEnv *env,
jobject obj,
jint w,
jint h,
jbyteArray src
) {
// 要素列の取得
// 最後に開放する必要がある
jbyte *p_src = env->GetByteArrayElements(src, NULL);
if (p_src == NULL) {
return NULL;
}
// 配列をcv::Matに変換する
cv::Mat m_src(h, w, CV_8UC4, (u_char *) p_src);
cv::Mat m_dst(h, w, CV_8UC4);
// OpenCV process
cv::cvtColor(m_src, m_dst, CV_RGBA2BGRA);
// 配列をcv::Matから取り出す
u_char *p_dst = m_dst.data;
// 戻り値用に要素を割り当てる
jbyteArray dst = env->NewByteArray(w * h * 4);
if (dst == NULL) {
env->ReleaseByteArrayElements(src, p_src, 0);
return NULL;
}
env->SetByteArrayRegion(dst, 0, w * h * 4, (jbyte *) p_dst);
// release
env->ReleaseByteArrayElements(src, p_src, 0);
return dst;
}
}
実行
実行すると、下記のように表示されます。
※THETAには画面がないので、Vysorを使っています。
おわりに
THETA内でOpenCVの動作確認をしてみただけなので、もうちょっと360度映像の特性を活かしたものを作ってみたいです。
何かできたらまた書こうと思います。
続編書きました↓
THETAの中でOpenCVを動かす【プレビューフレーム取得編】(2019/05/16 追記)
THETAの中でOpenCVを動かす【プレビューフレーム応用編】(2019/06/05 追記)
明日は@n_uehさんの**「OpenCV×VUIについて」**の記事です。
RICOH THETAプラグインパートナープログラムについて
THETAプラグインに興味を持たれた方がいれば、以下の記事もぜひご覧ください。
RICOH THETAプラグイン開発者コミュニティでは、他にも記事を書いています。
RICOH THETAプラグインについてはこちら。興味を持たれた方はtwitterのフォローとTHETAプラグイン開発コミュニティ(slack)への参加もぜひどうぞ。
-
ドキュメントに、”Using async initialization is a recommended way for application development. It uses the OpenCV Manager to access OpenCV libraries externally installed in the target system.”とあります。 ↩
-
OpenCV Managerについては → [Android OpenCV Manager]に詳細が書いてありました。ver.2.4のドキュメントなので情報が古いかもですが。 ↩