OpenCV4Androidのカメラプレビューをクロッピングします.
ちなみにOpenCV4Androidのカメラの使い方は
http://rest-term.com/archives/3010/
などが参考になります.
この記事でも,こんなコードを想定します.
JavaCameraViewを使います.
問題
OpenCV4androidを使ったandroidアプリでカメラプレビューを正方形にクロッピングしたくなりました.
安直にonCameraFrame()
で画像をクロップして返せばいいやと思ってやってみたところ,
@Override
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
mRgba = inputFrame.rgba();
return new Mat(mRgba, mROI);
}
以下のようなエラーを吐き続けてプレビューが表示されなくなりました.
08-10 23:08:14.137 30863-31993/com.example E/cv::error()﹕ OpenCV Error: Assertion failed (src.dims == 2 && info.height == (uint32_t)src.rows && info.width == (uint32_t)src.cols) in void Java_org_opencv_android_Utils_nMatToBitmap2(JNIEnv*, jclass, jlong, jobject, jboolean), file /hdd2/buildbot/slaves/slave_ardbeg1/50-SDK/opencv/modules/java/generator/src/cpp/utils.cpp, line 97
08-10 23:08:14.137 30863-31993/com.example E/org.opencv.android.Utils﹕ nMatToBitmap catched cv::Exception: /hdd2/buildbot/slaves/slave_ardbeg1/50-SDK/opencv/modules/java/generator/src/cpp/utils.cpp:97: error: (-215) src.dims == 2 && info.height == (uint32_t)src.rows && info.width == (uint32_t)src.cols in function void Java_org_opencv_android_Utils_nMatToBitmap2(JNIEnv*, jclass, jlong, jobject, jboolean)
08-10 23:08:14.137 30863-31993/com.example E/CameraBridge﹕ Mat type: Mat [ 480*480*CV_8UC4, isCont=false, isSubmat=true, nativeObj=0x78cfb440, dataAddr=0x7a91a1f0 ]
08-10 23:08:14.137 30863-31993/com.example E/CameraBridge﹕ Bitmap type: 720*480
08-10 23:08:14.137 30863-31993/com.example E/CameraBridge﹕ Utils.matToBitmap() throws an exception: /hdd2/buildbot/slaves/slave_ardbeg1/50-SDK/opencv/modules/java/generator/src/cpp/utils.cpp:97: error: (-215) src.dims == 2 && info.height == (uint32_t)src.rows && info.width == (uint32_t)src.cols in function void Java_org_opencv_android_Utils_nMatToBitmap2(JNIEnv*, jclass, jlong, jobject, jboolean)
JavaCameraView
の親クラスCameraBridgeViewBase
は表示用バッファとしてBitmap mCacheBitmap
を持っていて,初期化時にこれをカメラのフレームサイズで確保します.
そしてonCameraFrame()
から返るMat
をUtils.matToBitmap()
でmCacheBitmap
に入れてプレビュー表示処理に回します.
しかし,onCameraFrame()
からサイズを変えたMat
を返すと,Utils.matToBitmap()
で変換元と変換先のサイズが合わずに上記のエラーになります.
対策
JavaCameraViewを継承したビュークラスを作り,クロッピングを行いました.
package com.example;
import android.content.Context;
import android.util.AttributeSet;
import org.opencv.android.JavaCameraView;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
public class CameraView extends JavaCameraView {
private Rect mROI;
private int mSquareFrameWidth;
public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void AllocateCache()
{
int frameWidth = mFrameWidth, frameHeight = mFrameHeight;
mSquareFrameWidth = Math.min(mFrameWidth, mFrameHeight);
mFrameWidth = mSquareFrameWidth;
mFrameHeight = mSquareFrameWidth;
super.AllocateCache();
mFrameWidth = frameWidth;
mFrameHeight = frameHeight;
mROI = new Rect((mFrameWidth - mSquareFrameWidth)/2, (mFrameHeight - mSquareFrameWidth)/2, mSquareFrameWidth, mSquareFrameWidth);
}
@Override
protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
super.deliverAndDrawFrame(new CroppedCameraViewFrame(frame, mROI));
}
private class CroppedCameraViewFrame implements CvCameraViewFrame {
private Rect mROI;
private CvCameraViewFrame mOriginalFrame;
@Override
public Mat gray() {
return new Mat(mOriginalFrame.gray(), mROI);
}
@Override
public Mat rgba() {
return new Mat(mOriginalFrame.rgba(), mROI);
}
public CroppedCameraViewFrame(CvCameraViewFrame original, Rect ROI) {
mOriginalFrame = original;
mROI = ROI;
}
}
}
そして,レイアウトファイルで使うビューをこれに差し替えます
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:opencv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.CameraView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/camera_preview"
opencv:camera_id="any" />
</LinearLayout>
説明
上述したとおり,カメラのフレームサイズでBitmap mCacheBitmap
が初期化されますが,それを行っているのがJavaCameraView.AllocateCache()
です.
継承したクラスでは,これを呼ぶ前にmFrameWidth
,mFrameHeight
を書き換えて初期化するサイズをごまかします.ここでは正方形にクロッピングするようにしました.
AllocateCache()
を呼んだあとは元の値に戻します.
また,クロッピングに使うRect
オブジェクトmROI
を作っておきます.
@Override
protected void AllocateCache()
{
int frameWidth = mFrameWidth, frameHeight = mFrameHeight;
mSquareFrameWidth = Math.min(mFrameWidth, mFrameHeight);
mFrameWidth = mSquareFrameWidth;
mFrameHeight = mSquareFrameWidth;
super.AllocateCache();
mFrameWidth = frameWidth;
mFrameHeight = frameHeight;
mROI = new Rect((mFrameWidth - mSquareFrameWidth)/2, (mFrameHeight - mSquareFrameWidth)/2, mSquareFrameWidth, mSquareFrameWidth);
}
JavaCameraView.deliverAndDrawFrame()
がonCameraFrame()
を呼び,返ってきたMat
をUtils.matToBitmap()
でmCacheBitmap
に入れています.
そこで,子クラスでdeliverAndDrawFrame()
をオーバーライドし,JavaCameraView.deliverAndDrawFrame()
に渡るCvCameraViewFrame
を実装したオブジェクトをCroppedCameraViewFrame
に差し替えています.
CroppedCameraViewFrame
は元々のCvCameraViewFrame
オブジェクトをラップし,rgba()
とgray()
で元のオブジェクトのrgba()
,gray()
の返り値をクロッピングして返すことでクロッピングを実現しています.
@Override
protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
super.deliverAndDrawFrame(new CroppedCameraViewFrame(frame, mROI));
}
private class CroppedCameraViewFrame implements CvCameraViewFrame {
private Rect mROI;
private CvCameraViewFrame mOriginalFrame;
@Override
public Mat gray() {
return new Mat(mOriginalFrame.gray(), mROI);
}
@Override
public Mat rgba() {
return new Mat(mOriginalFrame.rgba(), mROI);
}
public CroppedCameraViewFrame(CvCameraViewFrame original, Rect ROI) {
mOriginalFrame = original;
mROI = ROI;
}
}