Opencv JNI Android(安卓)Camera 效率分析
16lz
2021-01-26
在将opencv移植到Android平台上,最让人纠结的地方就是:运行效率!!!
思路一: 获取Camera预览,保存为图片,C++读取图片地址。 摒弃,理由:camera预览保存图片以及C++操作成功之后再次保存图片,耗费大量时间,延迟高。
思路二: 将onPreviewFrame中获取到的预览byte[]格式的data传递到C++函数中
LOGE ( "---CV:1" ) ; jbyte *yuv = env->GetByteArrayElements(yuv_ , NULL ) ;
const char *path = env->GetStringUTFChars(path_ , 0 ) ;
// TODO Mat image(height+height/ 2 , width , CV_8UC1 , ( unsigned char *)yuv) ; Mat mBgr ; cvtColor(image , mBgr , CV_YUV2BGR_NV21 ) ; // Mat back; // cvtColor(mBgr,back,CV_BGR2YUV); if (mBgr.isContinuous()) yuv = ( jbyte *) mBgr. data ;
LOGE ( "---CV:4" ) ; env->ReleaseByteArrayElements(yuv_ , yuv , 0 ) ; env->ReleaseStringUTFChars(path_ , path) ;
可是,现在来看,也只有返回Mat可能解决这个问题了。偶然发现的一个思路让我很振奋。参考链接如下 http://answers.opencv.org/question/12090/returning-a-mat-from-native-jni-to-java/?answer=12097
这个方法确实是行得通的,但是这个方法也有一个坑在前面摆着。 http://blog.csdn.net/u012500046/article/details/53584971
因为需要在Java层为Mat赋予相应的地址,但是 n_Mat()是一个native方法,Mat对象是一个C++对象,而这个C++类的在 libopencv_java3.so包中,在加载完该.so包之前我们的Mat类是不能使用的。
于是出现了这样一个bug: No implementation found for long org.opencv.core.Mat.n_Mat() error Using OpenCV
解决方法也很简单,在使用mat前使用一个jni测试函数,这时候就导入 libopencv_java3.so包了。
再来看JNI部分的函数实现
哈,问题就是这样子,来看下用时:对时间做下分析。camera preview为获取到缓冲帧时间,同时,异步线程 LaneDetectTask启动。耗时15毫秒左右,完成缓冲帧data数据转换为opencv MAT格式处理对象。而后,在Java中进行Mat转BItmap处理,并绘制到相应显示区域,这部分耗时时间5-10毫秒。 因此,这种方式耗时平均约为30毫秒。在这30毫秒内处理了缓冲帧获取,帧数据传递,帧数据转Mat处理,Mat转bitmap,bitmap绘制这些操作。而前面的方法,耗时超过100毫秒,有着明显的提升。 注意到下图有一个细节。正常缓冲帧频应该是30帧每秒,但下图显示约为15帧每秒。但这并不影响。因为车道检测,人脸识别耗时操作并未进行。如果进行这些复杂操作,以已经测试过得车道检测来说,耗时达到300毫秒。因此,最理想的状态最好就是3帧一秒的操作速率。
思路一: 获取Camera预览,保存为图片,C++读取图片地址。 摒弃,理由:camera预览保存图片以及C++操作成功之后再次保存图片,耗费大量时间,延迟高。
思路二: 将onPreviewFrame中获取到的预览byte[]格式的data传递到C++函数中
public static native void readYuvTOBitmap(byte[] yuv,int height,int width,String path);而后,使用byte[]转mat方式,完成opencv图像读取的方式。这种方法在由JAVA层将数据传递到jni层,是行的通的。但同样有一个问题存在,同样要耗费大量的操作时间。
LOGE("---CV:1"); jbyte *yuv = env->GetByteArrayElements(yuv_, NULL); const char *path = env->GetStringUTFChars(path_, 0); // TODO Mat image(height+height/2,width,CV_8UC1,(unsigned char *)yuv); Mat mBgr; cvtColor(image,mBgr,CV_YUV2BGR_NV21); imwrite(path,mBgr); LOGE("---CV:4");思路三: 在前面的基础上,考虑直接返回byte[]给函数,然后使用byte[]转换成BItmap。但事实上,行不通。byte[]无法直接转换为bitmap,这是一个大坑,需要做YUV转换,才能够转换为bitmap,这同样耗费大量时间。
LOGE ( "---CV:1" ) ; jbyte *yuv = env->GetByteArrayElements(yuv_ , NULL ) ;
const char *path = env->GetStringUTFChars(path_ , 0 ) ;
// TODO Mat image(height+height/ 2 , width , CV_8UC1 , ( unsigned char *)yuv) ; Mat mBgr ; cvtColor(image , mBgr , CV_YUV2BGR_NV21 ) ; // Mat back; // cvtColor(mBgr,back,CV_BGR2YUV); if (mBgr.isContinuous()) yuv = ( jbyte *) mBgr. data ;
LOGE ( "---CV:4" ) ; env->ReleaseByteArrayElements(yuv_ , yuv , 0 ) ; env->ReleaseStringUTFChars(path_ , path) ;
byte[] result = OpenCVUtils.readYUVToBitmap(mData,h,w,resultPath); YuvImage yuvImage = new YuvImage(result,ImageFormat.NV21,w,h,null); ByteArrayOutputStream baos = new ByteArrayOutputStream(); yuvImage.compressToJpeg(new Rect(0,0,w,h),100,baos); byte[] rawImage = baos.toByteArray(); //将rawImage转换成bitmap BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_4444; final Bitmap fanzao = BitmapFactory.decodeByteArray(rawImage,0,rawImage.length,options);
这时候陷入了烦躁期,上天无路下地无门。 因此考虑直接返回Mat。但是JNI只支持返回特定类型的数据,比如说int,long ,string ,byte,其余的一概以Object处理。而C又对Object不那么友好。
可是,现在来看,也只有返回Mat可能解决这个问题了。偶然发现的一个思路让我很振奋。参考链接如下 http://answers.opencv.org/question/12090/returning-a-mat-from-native-jni-to-java/?answer=12097
// JavaMat m = new Mat();jni_func(m.getNativeObjAddr());// C++void jni_func(jlong matPtr){ Mat* mat = (Mat*) matPtr; mat->create(rows, cols, type); memcpy(mat->data, data, mat->step * mat->rows);}
这是传递进入Mat的地址参数进行操作,多么简单的方法啊,我怎么就没有考虑到!!! 这个方法确实是行得通的,但是这个方法也有一个坑在前面摆着。 http://blog.csdn.net/u012500046/article/details/53584971
因为需要在Java层为Mat赋予相应的地址,但是 n_Mat()是一个native方法,Mat对象是一个C++对象,而这个C++类的在 libopencv_java3.so包中,在加载完该.so包之前我们的Mat类是不能使用的。
于是出现了这样一个bug: No implementation found for long org.opencv.core.Mat.n_Mat() error Using OpenCV
解决方法也很简单,在使用mat前使用一个jni测试函数,这时候就导入 libopencv_java3.so包了。
//预导入libopencv_java3.so包 OpenCVUtils.test(); //初始化Mat地址 Mat seedImage = new Mat (w,h, CvType.CV_8UC1,new Scalar(4)); Mat tmp = new Mat (w,h, CvType.CV_8UC1,new Scalar(4)); //传递seedImage的地址给JNI OpenCVUtils.laneDisDetect(seedImage.getNativeObjAddr(),mData,h,w); //Mat转Bitmap final Bitmap bmp = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888); try{ Imgproc.cvtColor(seedImage,tmp,Imgproc.COLOR_BGR2RGBA); Utils.matToBitmap(tmp,bmp); }catch (CvException e){ Log.d("Exception",e.getMessage()); } //绘制bitmap到显示区域 runOnUiThread(new Runnable() { @Override public void run() { iv_takepic.setLayerType(View.LAYER_TYPE_SOFTWARE,null); iv_takepic.setImageBitmap(bmp); Log.e("time","stop");// handler.sendEmptyMessage(0); } });
再来看JNI部分的函数实现
extern "C"{JNIEXPORT void JNICALLJava_mulin_cjni_OpenCVUtils_laneDisDetect(JNIEnv *env, jclass type, jlong matPtr, jbyteArray yuv_, jint height, jint width) { jbyte *yuv = env->GetByteArrayElements(yuv_, NULL); // TODO Mat image(height+height/2,width,CV_8UC1,(unsigned char *)yuv); Mat transform; cvtColor(image,transform,CV_YUV2BGR_NV21); //这里是你对数据的处理逻辑,并将处理完的数据通过mat地址传递给Java ImageProcess(transform,matPtr); LOGE("---CV:存储到相应mat地址"); env->ReleaseByteArrayElements(yuv_, yuv, 0);}}
哈,问题就是这样子,来看下用时:对时间做下分析。camera preview为获取到缓冲帧时间,同时,异步线程 LaneDetectTask启动。耗时15毫秒左右,完成缓冲帧data数据转换为opencv MAT格式处理对象。而后,在Java中进行Mat转BItmap处理,并绘制到相应显示区域,这部分耗时时间5-10毫秒。 因此,这种方式耗时平均约为30毫秒。在这30毫秒内处理了缓冲帧获取,帧数据传递,帧数据转Mat处理,Mat转bitmap,bitmap绘制这些操作。而前面的方法,耗时超过100毫秒,有着明显的提升。 注意到下图有一个细节。正常缓冲帧频应该是30帧每秒,但下图显示约为15帧每秒。但这并不影响。因为车道检测,人脸识别耗时操作并未进行。如果进行这些复杂操作,以已经测试过得车道检测来说,耗时达到300毫秒。因此,最理想的状态最好就是3帧一秒的操作速率。
更多相关文章
- Android(安卓)NDK数据类型转换详解
- Android(安卓)studio使用adbwireless实现WiFi调试
- fir.im Weekly - 600个 Android(安卓)开源项目汇总
- Android文件操作总结
- Android多国语言使用须知
- Android(安卓)SearchView 实现一边输入一边搜索功能
- Android之ANR异常及解决方法
- 万字长文带你了解最常用的开源 Squid 代理服务器
- android实现截屏操作