はじめに
こんにちは、リコーの@roohii_3です。
以前、「THETAの中でOpenCVを動かす」という記事を書きました。
今回は、THETAからプレビュー映像を取得し、OpenCVでリアルタイムに処理してみたのでノウハウを紹介します。
下のような感じで、プレビュー画面を取得してリアルタイムで二値化してみるものを作ってみました。
コードは以下で公開しています。
https://github.com/theta-skunkworks/theta-plugin-opencv-preview-sample
※ 今回のコードでは、画像処理後のデータの保存は行っていません。
プレビュー画面を確認するには、Vysorを使ってください。
開発環境
- OpenCV Android pack ver. 3.4.5
- Android Studio ver. 3.3+
- gradle ver. 5.1.1
- RICOH THETA V Firmware ver. 2.50.1
事前準備
RICOH THETA V
以下のリンクを参考にして、THETAを開発者モードにしておきます。
THETA Plugin SDK (Android Studioプロジェクトファイル)
THETA Plugin SDKをベースに作っていきます。以下からダウンロードします。
https://github.com/ricohapi/theta-plugin-sdk
OpenCV Android pack
OpenCVのReleasesページから、ver3.xの最新版の"Android pack"をダウンロードします。
※ OpenCV4も出てますが、APIレベルがTHETAに合っていないため、THETA上で動かない可能性があります。そのためOpenCV3を入れました。
Vysor
プレビュー画面を確認するために、Vysorを利用します。
以下からインストールしておきます。
https://www.vysor.io/
プロジェクトファイルの準備
プロジェクトファイルを開く
Android Studioを起動し、"File > Open"から、ダウンロードしてきたTHETA Plugin SDKを開きます。
アプリケーションIDや、お好みでパッケージ名・プラグイン名を変更します(→参考)。
プロジェクトにOpenCVをインポートする
前回の記事では、NDK(C/C++)でOpenCVを使う方法を紹介しましたが、今回はjava上でOpenCVを使ってみます。
前回の記事に書いた、「1.2. ライブラリを静的リンクさせる方法」に当たります。
手順
-
"File > New > Import module"を選択し、"Source directory"に"
C:/(OpenCVを置いた場所)/sdk/java
"と入力
-
"app"ディレクトリ下に"jniLibs"ディレクトリを作成し、OpenCVライブラリ(.soファイル)をコピペ
コピー元: C:/(OpenCVを置いた場所)/sdk/native/libs/arm64-v8a コピー先: C:/(プロジェクトファイルがある場所)/app/src/main/jniLibs/arm64-v8a
-
"File > Project Structure"を開き、"app"を選択→"Dependencies"タブ→”+”→”Module dependency”で"openCVLibrary345"を選択→"OK"
本記事の方法でOpenCVを呼び出すには、OpenCVLoader.initDebug()
を使います。
protected void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Internal OpenCV library not found.");
} else {
Log.d(TAG, "OpenCV library found inside package.");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
OpenCV Managerを使う場合は、OpenCVLoader.initAsync()
を使います(→参考)。
実装
OpenCVをTHETAで使うときのポイント
OpenCVのAndroid packには、いくつかサンプルアプリが用意されています。
しかし、THETAでそのまま使うと以下のようなエラーが出てしまいます。
OpenCVのサンプルアプリ内ではCamera API
が使われており、それによってカメラの制御を行います。
しかしTHETAプラグインのデフォルトではCamera API
がロックされているために、このような表示が出てしまいます。
THETAでOpenCVを使うには、以下のポイントがあります。
プラグイン内でCamera APIを使えるようにする
THETAのプレビュー画面を取得するには、Camera API
を使うと便利です。
ドキュメントによると、Broadcast Intentで"com.theta360.plugin.ACTION_MAIN_CAMERA_CLOSE
"を使うと、プラグイン内でCamera API
を使えるようになります。
終了時には、"com.theta360.plugin.ACTION_MAIN_CAMERA_OPEN
"を使ってカメラリソースを開放する必要があります。
実際には、PluginActivity
を継承したクラス内で、開始時にはnotificationCameraClose()
を、終了時にはnotificationCameraOpen()
あるいはclose()
を使えばこれらのIntentを行ってくれます。
THETA独自のパラメータを使う
OpenCVにはJavaCameraView
というクラスが用意されており、内部ではCamera API
で諸々の設定や操作がされているようです。
しかしTHETAで使うには、THETA独自のパラメータを設定する必要があります。
THETA独自のCamera APIパラメータは、以下にまとまっています。
https://api.ricoh/docs/theta-plugin-reference/camera-api/
たとえばOpenCVのサンプルアプリでは、カメラ起動時に"RIC_SHOOTING_MODE
"というパラメータに"RicMoviePreview1920
"といった値を設定すると動くようになりました。
ソースコード
重要そうな部分だけ抜粋します。
大枠はOpenCVのサンプルコードの"tutorial-x-xxx
"を参考に作っています。
ソースコードの詳細はGitHubをご覧ください。
Camera APIの有効化・無効化
ポイントに書いたように、プラグイン起動時にnotificationCameraClose()
、終了時にclose()
を呼び、それぞれCamera APIの有効化と無効化を行っています。
※ onPause()
内でclose()
を呼ぶと失敗してしまうことがあり、「Modeボタン長押し(プラグイン終了の合図)」されたときにclose()
を呼ぶようにしました。
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...略...
// Set a callback when a button operation event is acquired.
setKeyCallback(new KeyCallback() {
@Override
public void onKeyDown(int keyCode, KeyEvent keyEvent) {}
@Override
public void onKeyUp(int keyCode, KeyEvent keyEvent) {}
// 終了時(Modeボタン長押し)に呼ばれる
@Override
public void onKeyLongPress(int keyCode, KeyEvent keyEvent) {
if (keyCode == KeyReceiver.KEYCODE_MEDIA_RECORD) {
if (!isEnded) {
// ...略...
// Camera APIの無効化
close();
isEnded = true;
}
}
}
});
// Camera APIの有効化
notificationCameraClose();
// ...略...
}
THETA独自のカメラ制御クラス
JavaCameraView
をそのままTHETA用にアレンジしたThetaView
というクラスを追加しました。
initializeCamera()
メソッド以外はほぼJavaCameraView
のままです。
しかしinitializeCamera()
内も、ポイントに書いたように「"RIC_SHOOTING_MODE
"というパラメータに"RicMoviePreview1920
"を設定する」ことと、不必要そうな記述を省いただけの変更です。
public class ThetaView extends CameraBridgeViewBase implements PreviewCallback {
// "RicMoviePreviewXXXX"パラメータの値に応じて、フレームサイズを定義する
private static final int PREVIEW_SIZE_WIDTH = 1920;
private static final int PREVIEW_SIZE_HEIGHT = 960;
private static final String PREVIEW_SIZE_PARAM = "RicMoviePreview1920";
protected boolean initializeCamera(int width, int height) {
boolean result = true;
synchronized (this) {
mCamera = null;
mCamera = Camera.open();
if (mCamera == null)
return false;
/* Now set camera parameters */
try {
Camera.Parameters params = mCamera.getParameters();
// フレームサイズを設定
Size frameSize = new Size(PREVIEW_SIZE_WIDTH, PREVIEW_SIZE_HEIGHT);
Log.d(TAG, "Set preview size to " + Integer.valueOf((int) frameSize.width) + "x" + Integer.valueOf((int) frameSize.height));
params.setPreviewSize((int) frameSize.width, (int) frameSize.height);
// 撮影モードを設定
params.set("RIC_SHOOTING_MODE", PREVIEW_SIZE_PARAM);
mCamera.setParameters(params);
// ...略...
/* Finally we are ready to start the preview */
Log.d(TAG, "startPreview");
mCamera.startPreview();
} catch (Exception e) {
result = false;
e.printStackTrace();
}
}
return result;
}
画像処理部分
CvCameraViewListener2
をimplements
するクラスの、onCameraFrame()
メソッドでプレビュー画面の1フレームを取得できます。
rgba()
でRGBAが、gray()
ではグレースケールで取得できました。
ここではグレースケースでフレームを取得し、OpenCVを使って2値化する処理を行っています。
public class MainActivity extends PluginActivity implements CvCameraViewListener2 {
private ThetaView mOpenCvCameraView;
private Mat mOutputFrame;
// ...略...
public void onCameraViewStarted(int width, int height) {
mOutputFrame = new Mat(height, width, CvType.CV_8UC1);
}
public void onCameraViewStopped() {
mOutputFrame.release();
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
// rgba()でRGBAが、gray()でグレースケールでプレビューの1フレームが取得できる
Mat gray = inputFrame.gray();
// do some image processing
// ここでは2値化処理だけ
Imgproc.threshold(gray, mOutputFrame, 127.0, 255.0, Imgproc.THRESH_BINARY);
return mOutputFrame;
}
実行
USBでTHETAを接続し、Vysorを起動して実行してください。
パーミッションの設定
一番始めにプラグインを起動したときは、「パーミッション」に許可が入っていないためすぐプラグインが落ちてしまいます。
Settingsアプリを開き、"OpenCV Preview Sample > Permissions"より、"Camera"の許可をONにしてください。
パーミッション許可を入れ、再度プラグインを実行すると、以下のような画面が出てきます。
おわりに
今回のように簡単な処理だったら、遅延なくサクサク動きました。
もうちょっと複雑な処理だとどうなるか見てみたいです。
また、今回はプレビュー画面を取得するだけだったので、撮影処理もチャレンジしてみたいです。
また何かできたら記事書きます。
続編書きました↓
THETAの中でOpenCVを動かす【プレビューフレーム応用編】(2019/06/05 追記)
RICOH THETAプラグインパートナープログラムについて
THETAプラグインに興味を持たれた方がいれば、以下の記事もぜひご覧ください。
RICOH THETAプラグイン開発者コミュニティでは、他にも記事を書いています。
RICOH THETAプラグインについてはこちら。興味を持たれた方はtwitterのフォローとTHETAプラグイン開発コミュニティ(slack)への参加もぜひどうぞ。