Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

Android Studio + MOVERIO + OpenCV #2

目標

前回の記事の最後に書いた通り,OpenCVCameraBridgeViewBase.CvCameraViewListener2を使うと,MOVERIOのSDKのサンプルで得られる画像よりも,画角が狭まりフレームレートが低くなる,といった問題があった.今回は,この問題を解決する.

mugichoko.hatenablog.com

開発環境

前回と同じです.

実装

参考サイトとそこからの追加

基本的な構造は,ここのサイトを参考にした.更に,カメラから得られた画像をOpenCVで処理したいので,それをできるようにした.

OpenCVで画像処理するための流れ

  1. onPreviewFrameでカメラのbyteデータを取得
  2. byteデータをOpenCVのMat形式に変換
  3. OpenCVを使ってYUV画像からRGB画像に変換
  4. OpenCVを使って画像処理
    • 次項のサンプルプログラムでは,一例として,カラー画像からグレースケール画像への変換を行った
  5. OpenCVのMat形式をBitmap形式に変換
  6. 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に関しては,画像形式の中身を知らないとちょっとトリッキー.以下を参考にした.

stackoverflow.com

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のサンプルと同じだし,フレームレートも(体感的に)問題ない.画像が画面の端っこに寄っているが,これはレイアウトの問題だと思う.しかし,まだレイアウトについてよく分かっていないのでこんな状態です.

f:id:Mugichoko:20171006075140j:plain