OpenCV4.10.0のポートレート(縦向き)カメラプレビュー
(Android Studio)Camera2 API

2024.1.17 2020.5.3 Coskx Lab  

1 はじめに

OpenCV4.9.0 までは,android-sdkを使って,カメラプレビューするアプリを作ると,縦位置の表示に不具合がありました。
OpenCV4.10.0のandroid-sdkでカメラビューアプリを作成すると,デバイスが縦位置でも横位置でも正しくカメラプレビューできるようになりました。
この文書は不要になりましたが,OpenCV4.9.0までの縦位置でも横位置でも正しくカメラプレビューできるアプリの保守のため,この文書を残します。

OpenCV4.10.0のandroid-sdkでは,
 CameraBridgeViewBase.java
 JavaCamera2View.java
 JavaCameraView.java
の3つのjavaファイルが更新され,カメラプレビューの縦位置表示に不具合を修正しています。
これまでこのページでは,android-sdk中のJavaCamera2View.javaを使わずに修正版のJavaCamera2ViewPlus.javaを使った方法を提示してきました。
JavaCamera2ViewPlus.javaでは,CameraBridgeViewBase.javaはandroid-sdk内のものを使用していました。

OpenCV4.10.0にてJavaCamera2ViewPlus.javaを使うためには,以前のCameraBridgeViewBase.javaを使う必要があります。
そのため,OpenCV4.9.0のCameraBridgeViewBase.javaをCameraBridgeViewBase49.javaと改名して,必要な名前の付け替えをして使うことにしました。
OpenCV android-sdkのソースファイルには手を付けません。

2 使用環境

3 準備

まず,新規プロジェクトを「OCVprePlus2」の名前でEmpty Views Activityとして作ることにします。
・AppおよびOPenCVモジュールでjavaを使用するので,
   Language → Java
   Build configuration language → Groovy DSL(build.gradle)
 とします。
パッケージ名が「jp.gr.java_conf.coskx.OCVprePlus2」になったとします。
OpenCV4.10.0のカメラプレビュー」記事の手順で次の作業を行います。
 (1)OpenCVモジュールを取り込みます。
 (2)AndroidManifast.xmlを修正します。
その続きからの作業になります。

4 追加作業

4.1 OCVprePlus2.zipのダウンロード

次のところから,OCVprePlus2.zipをダウンロードします。

OCVprePlus2.zipのダウンロード

zipを展開すると次のファイルが現れます。
・AndroidManifest.xml
・CameraBridgeViewBase49.java
・JavaCamera2ViewPlus.java
・MainActivity.java
・activity_main.xml

4.2 projectに新しいファイルを差し替え

Projectのところで,JavaCamera2ViewPlus.javaをapp/java/「パッケージ名」のMainActivity.javaと同じフォルダに入れます。

app > manifests 内のAndroidManifest.xmlをダウンロードしたファイルAndroidManifest.xmlに差し替え
app > java > 「パッケージ名」のMainActivity.javaをダウンロードしたファイルMainActivity.javaに差し替え
app > java > 「パッケージ名」にダウンロードしたファイルCameraBridgeViewBase49.javaを挿入
app > java > 「パッケージ名」にダウンロードしたファイルJavaCamera2ViewPlus.javaを挿入
app > res > layout 内のactivity_main.xmlをダウンロードしたファイルactivity_main.xmlに差し替え

5 何をしているのか

5.1 幅・高さの概念と,画像データ

説明の準備として,4つの「幅×高さ」の概念を使います。
デバイススクリーンの幅・高さの概念はデバイスの向きによって変化するはずです。
しかしカメラの幅・高さはカメラに固定した概念になっていて,変化しません。
JavaCamera2View.javaとJavaCamera2ViewPlusの持つ4つのサイズ概念について見てみます。

また,カメラ画像データはpreviewsizeの横長長方形で得られるので,デバイスを横向き(landscape)の時にはそのまま使えますが, 縦向き(portrait)の時には,90度回転した画像データを受け取ることになり,画像データを90度回転させないと正しく表示できません。

5.2 JavaCamera2View.java(OpenCV4.9.0以前)での作業
    (OpenCVモジュールに付属,横位置のみで正しく動作)


「5.1」の4つのサイズに関する作業はすべてconnectCamera()で行われています。
connectCameraはconnectCamera(int width, int height)のように呼び出されますが,このwidth,heightがavailablesizeです。
calcPreviewSize()でavailablesizeをもとに,カメラにとって都合の良いpreviewsizeを設定してもらいます。(ここに問題があります)
そして,previewsizeをそのままframesizeにしています。(ここにも問題があります)
その後,framesizeをdisplaysizeにするための倍率mScaleを求めています。
CameraBridgeViewBase中でmScale倍されてdisplaysizeに変換されてスクリーンのユーザー領域に表示されます。

この操作においては,デバイスを横向きにした場合にはデバイスの幅・高さとカメラの幅・高さの概念は共通ですが, デバイスを縦にしたときは,デバイスでの幅・高さの概念とカメラの幅・高さの概念がずれてしまうので,正しいpreviewsizeを選ぶことができず, また受け取った画像データも90度回転したものを受け取るため,うまく動作しません。


5.3 JavaCamera2ViewPlus.javaでの作業
    (横位置・縦位置で正しく動作するように修正したもの)


「5.1」の4つのサイズに関する修正作業はすべてconnectCamera()内で行われています。
connectCameraはconnectCamera(int width, int height)のように呼び出されますが,このwidth,heightがavailablesizeです。
width<heightの値がえられたら,デバイスが縦位置にあることがわかります。
デバイスが縦位置のときは,calcPreviewSize()に与える幅・高さは幅>高さでなければならないので,availablesizeの幅・高さを longside,shortsideとしてcalcPreviewSize()に与える準備をします。

    isportrait = width<height;
    int longside, shortside;
    if (isportrait) {
        longside = height;
        shortside = width;
    } else {
        longside = width;
        shortside = height;
    }

そして,longside,shortsideをもとに,カメラにとって都合の良いpreviewsizeをcalcPreviewSize(longside, shortside);で設定してもらいます。
ただし,previewsize(CameraBridgeViewBaseの変数)を設定しているところはここでは見えません。

        boolean needReconfig = calcPreviewSize(longside, shortside);

求められたpreviewsize(CameraBridgeViewBaseの変数)は必ず幅>高さなので,デバイスが横位置のときは,framesizeにはpreviewsizeの値をそのまま使い, デバイスが縦位置のときは,framesizeにはpreviewsizeを幅・高さを逆にして使います。

        if (isportrait) {
            mFrameWidth = mPreviewSize.getHeight();
            mFrameHeight = mPreviewSize.getWidth();
        } else {
            mFrameWidth = mPreviewSize.getWidth();
            mFrameHeight = mPreviewSize.getHeight();
        }

その後,availablesizeとframesizeから,framesizeをdisplaysizeにするための倍率mScaleを求めています。
ここはJavaCamera2View.javaと同じ記述になります。

        if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT))
            mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);
        else
            mScale = 0;

デバイスが縦位置のときは,得られた画像データを90度回転して表示します。(回転すると幅・高さがframesizeに一致します。)
画像の回転は,JavaCamera2ViewPlus.javaのclass JavaCamera2Frame内で行います。
画像がgrayscaleのときは,Mat gray()で,rgbaのときはMat rgba()で画像の回転を行っています。
CameraBridgeViewBase中でmScale倍されてdisplaysizeに変換されてスクリーンのユーザー領域に表示されます。

<

6 画面の余白(余黒?)をなくして,全画面に表示したい

スクリーンのユーザ領域の縦横比とカメラの制約によるプレビューサイズの縦横比が異なるため, 表示に余白が生じます。画像処理用途では,「画面の余白(余黒?)をなくして,全画面に表示したい」 という要求はないと思いますが,見かけはきれいな方がよいと思う場合もあるでしょう。 その場合は 表示倍率を大きくして,プレビューの一部を捨てると全画面表示にすることができます。
JavaCamera2ViewPlus.javaのメソッドconnectCamera()で,
Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);

Math.max(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);
に変更することで実現できます。表示倍率を大きくして,縦横で長い側に合うようにして表示画像を画面からはみ出させています。
これは表示が変わるだけで,MainActivityのonCameraFrame()で扱っている元のMatの画像情報は変化しません。



7 常に正しい向きで表示させる仕組み

デバイスを縦位置にして使っても横位置にして使っても,常に正しい向きで表示させるには,「 デバイスの回転角」,「カメラ光軸の方向(カメラの正面方向)」,「カメラ(イメージセンサ)の設置角 (デバイスに対してどの向きにカメラがついているか)」の3つの値が必要になります。

デバイスの回転角
 通常の使い方では,デバイスを縦(portrait)に構えるので,それが初期位置で0度になります。 デバイスを時計回りに倒して横向き(landscape)にしたときが90度,初期位置から反時計回りに倒して横向きにしたときが270度になります。

カメラ光軸の方向
 バックカメラの向きを0度としたとき,フロントカメラの向きは180度と考えるのが都合がよいでしょう。

カメラ(イメージセンサ)の設置角
 イメージセンサは横向き(landscape)についているので, 横長カメラの上辺がデバイスの3時方向のときカメラの設置角は90度,デバイスの9時方向のとき270度になります。
この3つの情報を得て,画像を回転させる角度を求めているのが,クラスJavaCamera2ViewPlusのメソッドgetRotationIndex()になります。 この関数の返す値は,角度を90で除した値が使われています。
そしてこの「回転させる角度」を使って,rgb()あるいはgray()のメソッド中で, 画像(Mat形式の画像)を正しく回転させて表示に使えるようにしています。

8 終わりに

OpenCV4.9.0-sdk以前を利用していた縦位置でも横位置でも正しくカメラプレビューできるアプリを作っていました。 OpenCV4.10.0-sdkの元でも利用できるアプリに修正しました。これは,以前のアプリの保守のための説明です。

補足

非推奨になった記述が,
・CameraBridgeViewBase.java
・JavaCamera2View.java
にあり,それらは
・CameraBridgeViewBase49.java
・JavaCamera2ViewPlus.java
で修正されています。

結局OpenCV4.10.0-sdkの OpenCV > java\src > org > opencv > androidの中で使用しているのは,
・FpsMeter.java
・OpenCVLoader.java
・StaticHelper.java
・Utils.java
のみであり,
・Camera2Renderer.java
・CameraActivity.java
・CameraBridgeViewBase.java
・CameraGLRendererBase.java
・CameraGLSurfaceView.java
・CameraRenderer.java
・JavaCamera2View.java
・JavaCameraView.java
・NativeCameraView.java
は使用していません。