android opencv 平移,Android中使用Opencv自带JavaCameraView实现高帧率竖屏显示

最近学习openCv在android中的使用,首先就是拿官方的demo进行学习,在使用OpenCv自带的JavaCameraView中,发现在竖屏状态下相机的显示是横屏显示的,本来以为这个问题很好解决,但是后期为了调节到最高帧率还是花了很多心思!

对于相机为何会横屏显示,因为获取系统camera的帧数据就是横屏的,OpenCv并没有给我们经过处理。

传统我们使用SurfaceView获取系统相机是如下代码:

mCamera.setPreviewDisplay(mSurfaceViewHolder);

如果需要竖屏显示,则只需

mCamera.setDisplayOrientation(90);

这样,我们就能很流畅的显示系统的相机页面了。

但JavaCameraView没有选择这样,因为我们需要拿到相机的每一帧画面,并对画面进行处理后再绘制。如果只是人脸检测,贴图等简单操作,那可以使用上述方法,只需要在SurfaceView这一层上方再覆盖一层,将canvas背景透明化即可,但类似faceu这类相机画面实时变化的操作,那就达不到我们需要的效果。

阅读OpenCv自带的类CameraBridgeViewBase中的deliverAndDrawFrame方法即可发现,OpenCv是将每一帧处理后的Mat对象转化为Bitmap,然后将Bitmap对象绘制在SurfaceView的canvas之上,因此如何高效的进行绘制则是我们需要解决的目的。

查阅现有的解决办法,博文点击打开链接提供了三种方法,方法一的核心思想就是使用matrix对bitmap对象进行了旋转,方法二的核心思想就是对canvas进行了旋转,方法三的核心思想就是使用OpenCv自带的方法对Mat对象进行了旋转和缩放,然后再进行绘制。

在进行效率说明之前,我们先来看Camera.Parameters的setPreviewFpsRange方法,这个方法能控制相机帧数回调onPreviewFrame,通过getSupportedPreviewFpsRange方法我们可以获取到手机支持的帧数范围,我尝试了几个手机,发现最大的帧数是30000,即30帧,是否其他手机有支持30帧以上的有待考察,但暂定一般的手机相机数据的回调最大只支持30帧,那么,如果我们对每一帧的操作有一点点的耗时,SurfaceView绘制的帧数就会小于30,肉眼就会有可见的卡顿。

对于现有的方法,我全部进行了测试,发现耗时操作的地方有这几个:

1.Bitmap过大,我测试的机型为2K分辨率,如果每一帧的画面都是2K分辨率,那么即使不做任何其他操作,帧率都会下降10fps左右,因此canvas.drawBitmap中的bitmap对象不宜过大,参考了faceu的分辨率为720x1280,因此我对Camera.Parameters的PreViewSize进行了控制,使用了720P,具体使用还是需要调用getSupportedPreviewSizes()方法来选取一个最合适的分辨率,对于市面上主流的机型,720显示效果已经足以满足,因此对于下面的所有测试都在720P分辨率下进行;

2.对canvas进行旋转,此方法下降了10个左右的fps;

3.使用matrix对bitmap进行缩放旋转

matrix.preScale(2, 2);

matrix.setRotate(90, 720, 720);

canvas.drawBitmap(mCacheBitmap, matrix, null);

使用上述方法就能实现在2K分辨率下的全屏缩放,具体缩放的比率需要机型实际宽度除以720来确定,rotate的旋转中心也需要width/2。使用该方法下降8个左右的fps;

4.使用OpenCv自带的方法进行旋转缩放

在测试中发现canvas.drawBitmap(bitmap, null, new React(0, 0, width, height), null)能比较高效的绘制,但首先需要生成已经旋转过的                  bitmap,而且canvas对于bitmap的绘制效率对于byte[]的size并没有多大关系(将bitmap的type有ARGB_8888变成了RGB_565,size()减少了            大半,但帧数并未减少),似乎跟bitmap的分辨率有比较大的关系(只是猜测,有待考察),因此在尝试了上述博文的方法三后发现缩放会导致          效率下降,因此选择了先对Mat对象进行旋转,然后在drawBitmap的过程中进行放大,但遗憾的是该方法还会下降6个左右的fps;

旋转的方法:

Mat mGrayT = new Mat(mFrameHeight, mFrameWidth, CvType.CV_8UC1);

Core.transpose(modified, mGrayT);

Core.flip(mGrayT, mGrayT, 1);

上述方法需要将Bitmap的长宽颠倒过来,不然Mat无法转化成Bitmap;

因此,以上的所有尝试都无法满足。但发现,当删去上述方法4中的Core.flip方法,即不进行镜像翻转,这时候的画面就是全屏显示的,而且帧数接近30帧,只略微掉了一两帧,只是画面是左右反的,因此,如果能找到一种高效的旋转90度的方法对Mat对象进行旋转,那可能也是一种解决方法。

在上述方法都失败之后,我又去查询了新的解决方法,查阅资料后发现TexuerView和GLSurfaceView可能可以解决我的困扰,但看了之后觉得有点复杂,对于相机返回的参数需要OpenGl ES进行绘制,网上也有很多使用这些实现的实时滤镜效果,但运行后发现帧数并不理想并且与OpenCv结合比较麻烦(GLSurfaceView绘制Bitmap查询资料好像有,但看起来好麻烦啊懒得看而且看不太懂),因此对于该思路有能力的各位可以考虑使用,实现后欢迎分享交流。

最后,就是我自己的方法,该方法的稳定性和适配性有待测试,在我的测试机上已经完美运行了。

首先,查看JavaCameraView中的内部类CameraWorker,发现它就是通过一个线程循环来访问缓存的两帧画面,但由于前一帧的画面处理时间慢,这就导致了两帧之间相隔了太多的处理时间,所以导致了帧数下降。

因此,我的方法的解决思路是新开一个线程,通过两个线程来分别处理两帧(A与B)的画面,帧与帧之间并不是串行关系而是并行关系,这样A帧就不会因为B帧还在处理而阻塞,当B帧显示完后,A帧也已经处理了一部分,就能加速A帧的显示。

对于实现,我贴个大概的实现,对于线程的安全以及会出现的问题暂时还未考虑:

//将原来的mCameraFrameReady参数修改为以下两个,分别对应两帧画面,具体的参数值定义与修改跟mCameraFrameReady相同

//private boolean mCameraFrameReady = false;

private boolean mCameraFrameReadyOne = false;

private boolean mCameraFrameReadyTwo = false;

@Override

public void onPreviewFrame(byte[] frame, Camera arg1) {

synchronized (this) {

if (mChainIdx == 0 && !mCameraFrameReadyOne) {

mFrameChain[0].put(0, 0, frame);

mCameraFrameReadyOne = true;

}

if (mChainIdx == 1 && !mCameraFrameReadyTwo) {

mFrameChain[1].put(0, 0, frame);

mCameraFrameReadyTwo = true;

}

this.notify();

}

if (mCamera != null)

mCamera.addCallbackBuffer(mBuffer);

}

private class CameraWorkerOne implements Runnable {

@Override

public void run() {

do {

boolean hasFrame = false;

synchronized (JavaCameraView.this) {

try {

while (!mCameraFrameReadyOne && !mStopThread) {

JavaCameraView.this.wait();

}

} catch (InterruptedException e) {

e.printStackTrace();

}

if (mCameraFrameReadyOne) {

mCameraFrameReadyOne = false;

hasFrame = true;

}

}

if (!mStopThread && hasFrame) {

if (!mFrameChain[0].empty()) {

mChainIdx = 1;

deliverAndDrawFrame(mCameraFrame[0]);

}

}

} while (!mStopThread);

Log.d(TAG, "Finish processing thread");

}

}

private class CameraWorkerTwo implements Runnable {

@Override

public void run() {

do {

boolean hasFrame = false;

synchronized (JavaCameraView.this) {

try {

while (!mCameraFrameReadyTwo && !mStopThread) {

JavaCameraView.this.wait();

}

} catch (InterruptedException e) {

e.printStackTrace();

}

if (mCameraFrameReadyTwo) {

mCameraFrameReadyTwo = false;

hasFrame = true;

}

}

if (!mStopThread && hasFrame) {

if (!mFrameChain[1].empty()) {

mChainIdx = 0;

deliverAndDrawFrame(mCameraFrame[1]);

}

}

} while (!mStopThread);

Log.d(TAG, "Finish processing thread");

}

}

修改CameraBridgeViewBase文件中的:

protected void deliverAndDrawFrame(CvCameraViewFrame frame) {

Mat modified;

if (mListener != null) {

modified = mListener.onCameraFrame(frame);

} else {

modified = frame.rgba();

}

boolean bmpValid = true;

if (modified != null) {

try {

Mat mGrayT = new Mat(mFrameHeight, mFrameWidth, CvType.CV_8UC1);

Core.transpose(modified, mGrayT);

Core.flip(mGrayT, mGrayT, 1);

Utils.matToBitmap(mGrayT, mCacheBitmap);

mGrayT.release();

} catch (Exception e) {

Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());

Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());

bmpValid = false;

}

}

if (bmpValid && mCacheBitmap != null) {

Canvas canvas = getHolder().lockCanvas();

if (canvas != null) {

canvas.drawBitmap(mCacheBitmap, null, new Rect(0, 0, canvas.getWidth(), canvas.getHeight()), null);

getHolder().unlockCanvasAndPost(canvas);

}

}

}

mCacheBitmap = Bitmap.createBitmap(mFrameHeight, mFrameWidth, Bitmap.Config.RGB_565);

基本实现就是以上代码,帧数已经稳稳停留在了30帧,如果能找到一个更加高效的旋转90度的方法,那就更稳了。还有别忘了修改camera的参数,适当的参数才能带来稳定的帧数。

上述代码没考虑到的就是A帧虽然是在B帧之前处理,万一有时对于算法的处理还是B帧更快,那么B帧就会在A帧之前显示,虽然该问题还没出现,但对于帧最后的显示没有控制到到百分百的先来后到。

你可能感兴趣的:(android,opencv,平移)