27
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

THETAの中でOpenCVを動かす【プレビューフレーム取得編】

Last updated at Posted at 2019-05-13

はじめに

こんにちは、リコーの@roohii_3です。

以前、「THETAの中でOpenCVを動かす」という記事を書きました。
今回は、THETAからプレビュー映像を取得し、OpenCVでリアルタイムに処理してみたのでノウハウを紹介します。
下のような感じで、プレビュー画面を取得してリアルタイムで二値化してみるものを作ってみました。
rgb2binarization.jpg
コードは以下で公開しています。
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. ライブラリを静的リンクさせる方法」に当たります。

手順

  1. "File > New > Import module"を選択し、"Source directory"に"C:/(OpenCVを置いた場所)/sdk/java"と入力
    import-module.png

  2. "app"ディレクトリ下に"jniLibs"ディレクトリを作成し、OpenCVライブラリ(.soファイル)をコピペ

    コピー元: C:/(OpenCVを置いた場所)/sdk/native/libs/arm64-v8a
    コピー先: C:/(プロジェクトファイルがある場所)/app/src/main/jniLibs/arm64-v8a
    

    lib.png

  3. "File > Project Structure"を開き、"app"を選択→"Dependencies"タブ→”+”→”Module dependency”で"openCVLibrary345"を選択→"OK"
    module-dependency.png

本記事の方法でOpenCVを呼び出すには、OpenCVLoader.initDebug()を使います。

MainActivity.java
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でそのまま使うと以下のようなエラーが出てしまいます。
error.png
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()を呼ぶようにしました。

MainActivity.java
@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"を設定する」ことと、不必要そうな記述を省いただけの変更です。

ThetaView.java
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;
    }

画像処理部分

CvCameraViewListener2implementsするクラスの、onCameraFrame()メソッドでプレビュー画面の1フレームを取得できます。
rgba()でRGBAが、gray()ではグレースケールで取得できました。
ここではグレースケースでフレームを取得し、OpenCVを使って2値化する処理を行っています。

MainActivity.java
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にしてください。
permissions.jpg

パーミッション許可を入れ、再度プラグインを実行すると、以下のような画面が出てきます。
binarization.jpg

おわりに

今回のように簡単な処理だったら、遅延なくサクサク動きました。
もうちょっと複雑な処理だとどうなるか見てみたいです。
また、今回はプレビュー画面を取得するだけだったので、撮影処理もチャレンジしてみたいです。
また何かできたら記事書きます。

続編書きました↓
THETAの中でOpenCVを動かす【プレビューフレーム応用編】(2019/06/05 追記)

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

THETAプラグインに興味を持たれた方がいれば、以下の記事もぜひご覧ください。

RICOH THETAプラグイン開発者コミュニティでは、他にも記事を書いています。
RICOH THETAプラグインについてはこちら。興味を持たれた方はtwitterのフォローとTHETAプラグイン開発コミュニティ(slack)への参加もぜひどうぞ。

27
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?