OpenCV 4.9.0 カメラプレビュー OpenGLで高速化
(Android Studio)Camera2 API

2024.1.20 2020.6.6 Coskx Lab  

1 はじめに

OpenCVを使って,横位置でも縦位置でも正しく表示するデモを作成しましたが,イメージデータの回転が結構負荷が重く,回転を必要とするときには極端にFPSが低下します。
いくらネイティブのメソッドが行っているといってもCPUの限界が見えてしまいます。
そこでGPUを使って回転表示させると速くなることから,OpenGLを用いるアイディアがネット上で散見されました。
ここでは横位置でも縦位置でも正しく表示するデモにおいて回転・表示部にOpenGLを用いることにしました。
OpenGLのスクリーンで矩形にカメラ画像をテクスチャとして貼り付けたものを表示しています。
(OPenCVのライブラリ中にCameraGLRendererBase,CameraGLSurfaceViewがあるので,これらが利用できれば,簡単だと思いますが,検討中です。)
(OPenCLを使用したもう少しましな方法があるようです。)
まとめのところに書きましたが,最近のハードウエアの進歩のため,OpenGLの援用によるご利益は限定的です。

2 使用環境

3 準備

OpenCV4.9.0のカメラプレビュー」記事で「OpenCVモジュール」取り込みの手順はすでに終わっているものとします。

4 ダウンロードしたファイルをprojectに適正に配置

ダウンロード

FPS測定用特別版ダウンロード

(1)AndroidManifast.xmlにカメラpermission関連の3行を加えます(ダウンロードされたAndroidManifast.xml参照)。
(2)activity_main.xmlを置き換えます。
(3)MainActivity.javaをダウンロードしたファイルで置き換えます。パッケージ名は自動的に揃えられます。
(4)残りのjavaファイルをすべてMainActivity.javaと同じところにペーストします。パッケージ名は自動的に揃えられます。
(5)activity_main.xml内の「JavaCamera2ViewGL」のパッケージ名をこのプロジェクトのパッケージ名に変更します。

5 CameraBridgeViewBaseGL.javaなどは何をしているのか

カメラのフレームサイズやBitmapの大きさ,拡大率を適正なものにするための変更が
JavaCamera2ViewGL.java
で行われています。

CameraBridgeViewBaseGLでは,次のように流れを変更しています。
カメラが取得したイメージデータがdeliverAndDrawFrame()まで届けられるところまでは,CameraBridgeViewBaseと同じです。
その後の作業はこのdeliverAndDrawFrame()で行われます。
(1)MainActivityのonCameraFrame()に送って,その戻り値としてMat画像を受け取ります。
(デバイスの向きによって画像回転が必要な場合でも,まだ回転していないため, onCameraFrame()でのMatを作成するところでは向きがおかしなままの作業になります)
(2)受け取ったMatからBitmapを作った後,BitmapをGLRendererに送ります。 (Canvasに貼り付ければそのままOpenCVのビューに表示されてしまいますが,そうしません。)
(3)GLRendererでは,受け取ったBitmapを,四角形に貼り付けて表示します。
ゲームなどの用途では,表示メソッドのonDrawFrame()はOpenGLのタイミングで繰り返し実行されますが,ここでは CameraBridgeViewBaseGLがBitmapを送ったときにonDrawFrame()が起動するよう に,requestRender()で要求しています。
また,CameraBridgeViewBaseGLとGLRenderer間でBitmapを送るタイミングの調整をしています。

GLRenderer.javaの最初に,int texturemodeを決めるところがあります。
最近のデバイスではtexturemode=0で問題ないと思います。
イメージサイズが正方形で2のべき乗でなければ動かない古いデバイスでは,texturemode=1にします。
texturemode=1は,カメラ画像を正方形に変形させたイメージを作り,正方形のテクスチャに変換し,正方形をもとのイメージ比率に戻して表示しする動作モードです。

補足
CameraBridgeViewBaseGLはSurfaceViewでなくGLSurfaceViewを継承しています。そのため,
surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
surfaceCreated(SurfaceHolder holder)
surfaceDestroyed(SurfaceHolder holder)
の3つのメソッドでは,継承元のGLSurfaceViewの3つのメソッドをそれぞれ呼び出す必要があります。この3つのメソッドを呼び出すことででGLRendererの
onSurfaceCreated(GL10 gl10,EGLConfig eglConfig)
onSurfaceChanged(GL10 gl10,int w,int h)
onDrawFrame(GL10 glUnused)
が呼び出されます。

6 その性能は

デバイスを横向きにした場合は,OpenCVでそのまま表示したのと,OpenGLで表示させたものとでは,FPSはあまり変わりません。
デバイスを縦向きにしたとき,OpenCVでそのまま表示すると,表示速度(FPSで比較)が約半分に落ちたのですが, OpenGLを使って表示するとGPUの働きのため,横向きのときと表示速度は変わりませんでした。
例えばOpenGLを使うと,Nexus5xでは横向き縦向きともに30FPS,P10Liteでは横向き縦向きともに25FPSでした。


デバイス Nexus5x ランドスケープ
「pure OpenCV」27FPS @1280x768 「OpenCV+OpenGL」30FPS @1280x768
デバイス Nexus5x ポートレート
「pure OpenCV」14FPS @1080x1440 「OpenCV+OpenGL」30FPS @1080x1440

デバイス p10lite ランドスケープ
「pure OpenCV」25FPS @1280x720 「OpenCV+OpenGL」25FPS @1280x720

デバイス p10lite ポートレート
「pure OpenCV」15FPS @1080x1440 「OpenCV+OpenGL」25FPS @1080x1440

7 追加テスト

Pixel4aを入手したので,これもテストしてみました。

デバイス Pixel5a
   方向    画像サイズ ( W x H ) 方式 FPS
ランドスケープ 1280 x 720 「pure OpenCV」 30
ランドスケープ 1280 x 720 「OpenCV+OpenGL」 30
ポートレート 1080 x 1440 「pure OpenCV」 30
ポートレート 1080 x 1440 「OpenCV+OpenGL」 30
ランドスケープ 1920 x 960 「pure OpenCV」 30
ランドスケープ 1920 x 960 「OpenCV+OpenGL」 30
ポートレート 1080 x 1920 「pure OpenCV」 26
ポートレート 1080 x 1920 「OpenCV+OpenGL」 30

というわけで,ハードウエアの進歩のため,最大解像度の縦位置を除けば,OpenGLの援用は意味がないようでした。

8 まとめ

OpenCVカメラプレビューで,スクリーン表示部分をOpenGLにまかせるようにしたデモを作成しました。
非力なデバイスでは,デバイスを縦向きで使ってもOpenGLの援用によって表示速度が低下しないようになりました。
デバイスを縦向きで使うときは,どうしてもイメージを90度回転しなければならないため,この作業をCPUが行うのかGPUが行うのかで差が出てきた結果と思われます。
最近のハードウエアの進歩のため,OpenGLの援用は必要ではなくなったようです。