目標
前回の記事の最後に書いた通り,OpenCVのCameraBridgeViewBase.CvCameraViewListener2を使うと,MOVERIOのSDKのサンプルで得られる画像よりも,画角が狭まりフレームレートが低くなる,といった問題があった.今回は,この問題を解決する.
開発環境
※ 前回と同じです.
- Windows 10 Home
- Android Studio 2.3.3
- Android SDK 5.1 (Lollipop)
- OpenCV 3.3.0
- BT300 SDK 1709
- Epson MOVERIO BT-300
実装
参考サイトとそこからの追加
基本的な構造は,ここのサイトを参考にした.更に,カメラから得られた画像をOpenCVで処理したいので,それをできるようにした.
OpenCVで画像処理するための流れ
- onPreviewFrameでカメラのbyteデータを取得
- byteデータをOpenCVのMat形式に変換
- OpenCVを使ってYUV画像からRGB画像に変換
- OpenCVを使って画像処理
- 次項のサンプルプログラムでは,一例として,カラー画像からグレースケール画像への変換を行った
- OpenCVのMat形式をBitmap形式に変換
- ImageViewkを使って表示
サンプルプログラム
以下のサンプルプログラム内で,実装に示した6つのステップに対応する箇所にStep XXとコメントを入れた.
MainActivity.java
package com.example.XXXXX.moveriocamera; // 上記のXXXXXには自身のユーザ名が入る import android.app.Activity; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup.LayoutParams; import android.util.Size; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; public class MainActivity extends Activity { private CameraPreview mCamPrev; private ImageView mImgPrev = null; private FrameLayout mMainLayout; private Size mPrevSize = new Size(640, 480); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); mImgPrev = new ImageView(this); SurfaceView surfaceView = new SurfaceView(this); SurfaceHolder surfaceHolder = surfaceView.getHolder(); mCamPrev = new CameraPreview(mPrevSize.getWidth(), mPrevSize.getHeight(), mImgPrev); surfaceHolder.addCallback(mCamPrev); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mMainLayout = (FrameLayout) findViewById(R.id.frameLayout); mMainLayout.addView(surfaceView, new LayoutParams(mPrevSize.getWidth(), mPrevSize.getHeight())); mMainLayout.addView(mImgPrev, new LayoutParams(mPrevSize.getWidth(), mPrevSize.getHeight())); } protected void onPause() { if (mCamPrev != null) { mCamPrev.onPause(); } super.onPause(); } }
CameraPreview.java
このコードの一番最後にあるimageProc関数内が主に画像を受け取るところ.Step 4に関しては,画像形式の中身を知らないとちょっとトリッキー.以下を参考にした.
package com.example.XXXXX.moveriocamera; // 上記のXXXXXには自身のユーザ名が入る import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.hardware.Camera; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.util.Size; import android.view.SurfaceHolder; import android.widget.ImageView; import org.opencv.android.Utils; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.imgproc.Imgproc; import java.io.IOException; public class CameraPreview implements SurfaceHolder.Callback, Camera.PreviewCallback { private Camera mCamera = null; private ImageView mImagePreview = null; private Bitmap mBitmap = null; private byte[] mFrameData = null; private int mImageFormat; private Size mPreviewSize; private boolean bProcessing = false; Handler mHandler = new Handler(Looper.getMainLooper()); public CameraPreview(int previewLayoutWidth, int previewLayoutHeight, ImageView imagePreview) { mPreviewSize = new Size(previewLayoutWidth, previewLayoutHeight); mImagePreview = imagePreview; mBitmap = Bitmap.createBitmap(mPreviewSize.getWidth(), mPreviewSize.getHeight(), Bitmap.Config.ARGB_8888); } @Override public void onPreviewFrame(byte[] data, Camera camera) { if (mImageFormat == ImageFormat.NV21) { if (!bProcessing) { // --> Step 1: onPreviewFrameでカメラのbyteデータを取得 mFrameData = data; mHandler.post(imageProc); } } } public void onPause() { mCamera.stopPreview(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Camera.Parameters params; params = mCamera.getParameters(); params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); params.setAutoWhiteBalanceLock(false); params.setAutoExposureLock(false); params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); mCamera.setParameters(params); mImageFormat = params.getPreviewFormat(); mCamera.startPreview(); } @Override public void surfaceCreated(SurfaceHolder holder) { mCamera = Camera.open(); try { mCamera.setPreviewDisplay(holder); mCamera.setPreviewCallback(this); } catch (IOException e) { mCamera.release(); mCamera = null; } } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } private Runnable imageProc = new Runnable() { @Override public void run() { Log.i(this.getClass().getName(), "Processing image"); bProcessing = true; Mat imgRgb = ImageProcUtil.byteToRGBMat(mPreviewSize.getWidth(), mPreviewSize.getHeight(), mFrameData); // --> Step 4: OpenCVを使って画像処理 Mat imgGray = new Mat(); Imgproc.cvtColor(imgRgb, imgGray, Imgproc.COLOR_RGB2GRAY); imgGray.convertTo(imgGray, CvType.CV_8UC3); // --> Step 5: OpenCVのMat形式をBitmap形式に変換 Utils.matToBitmap(imgGray, mBitmap); // --> Step 6: ImageViewkを使って表示 mImagePreview.setImageBitmap(mBitmap); bProcessing = false; } }; }
ImageProcUtil.java
OpenCVによる画像処理に関するコードを放り込むために作成した.別に,上記のCameraPreview.java内のimageProc()関数内に書き込んでもよいです.
package com.example.XXXXX.moveriocamera; // 上記のXXXXXには自身のユーザ名が入る import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.imgproc.Imgproc; public class ImageProcUtil { static public Mat byteToRGBMat(int w, int h, byte[] data) { // --> Step 2: byteデータをOpenCVのMat形式に変換 Mat matRgb = new Mat(); Mat matYuv = new Mat(h + h / 2, w, CvType.CV_8UC1); matYuv.put(0, 0, data); // --> Step 3: OpenCVを使ってYUV画像からRGB画像に変換 Imgproc.cvtColor(matYuv, matRgb, Imgproc.COLOR_YUV420sp2RGB, 3); return matRgb; } }
AndroidManifest.xml
カメラが使えるように設定(6行目).
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.XXXXX.moveriocamera"> <!-- 上記のXXXXXには自身のユーザ名が入る --> <uses-permission android:name="android.permission.CAMERA"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:orientation="horizontal"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
activity_main.xml
MOVERIOに写す画面のレイアウト.カメラの画像を表示する部分だけで構成されている.7~12行目のFrameLayoutに映像が書き込まれる.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/frameLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> </FrameLayout> </LinearLayout>
結果
画像の様にグレースケール画像が表示された.画角もMOVERIOのサンプルと同じだし,フレームレートも(体感的に)問題ない.画像が画面の端っこに寄っているが,これはレイアウトの問題だと思う.しかし,まだレイアウトについてよく分かっていないのでこんな状態です.