这一节的主要内容是OpenCV在Android NDK开发中的应用,包括下面几个方面的内容:html
实现Static Initialization就是指将OpenCV Library添加到app package中,不须要安装OpenCV Manager这个app就能运行,官方文档有介绍,可是不详细,尤为是最后那句代码到底要放在什么地方不少人都不清楚,其实并不须要像官方文档中介绍的那样配置,我想在这里介绍下如何修改FaceDetection项目的源码来作到这点。(最好是找一个包含jni代码的项目进行修改)java
off
设置为on
,并设置OpenCV_LIB_TYPE
为SHARED
,结果以下: OpenCV_CAMERA_MODULES:=on OpenCV_INSTALL_MODULES:=on OpenCV_LIB_TYPE:=SHARED include ${OpenCVROOT}/sdk/native/jni/OpenCV.mk
OpenCV_java
库的,因为FaceDetection中还用了另外一个库detection_based_tracker(用于人脸跟踪),因此要在else
子句中加载进来:static { Log.i(TAG, "OpenCV library load!"); if (!OpenCVLoader.initDebug()) { Log.i(TAG, "OpenCV load not successfully"); } else { System.loadLibrary("detection_based_tracker");// load other libraries } }
@Override public void onResume() { super.onResume(); //OpenCVLoader.initAsync(OpenCVLoader.OpenCV_VERSION_2_4_3, this, mLoaderCallback);// }
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this)
代码块中拷贝try-catch
块放到OnCreate的setContentView()
以后,而后拷贝mOpenCVCameraView.enableView();
放到mOpenCVCameraView = (CameraBridgeViewBase) findViewById(R.id.fd_activity_surface_view);
以后,修改后的OnCreate()方法以下:public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "called onCreate"); super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.face_detect_surface_view); // try { // load cascade file from application resources InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface); File cascadeDir = getDir("cascade", Context.MODE_PRIVATE); mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml"); FileOutputStream os = new FileOutputStream(mCascadeFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } is.close(); os.close(); mJavaDetector = new CascadeClassifier(mCascadeFile.getAbsolutePath()); if (mJavaDetector.empty()) { Log.e(TAG, "Failed to load cascade classifier"); mJavaDetector = null; } else Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath()); mNativeDetector = new DetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);// hujiawei cascadeDir.delete(); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "Failed to load cascade. Exception thrown: " + e); } // mOpenCVCameraView = (CameraBridgeViewBase) findViewById(R.id.fd_activity_surface_view); mOpenCVCameraView.enableView();// mOpenCVCameraView.setCvCameraViewListener(this); }
这10篇文献大部分[百度网盘下载地址]都仍是停留如何在Android开发中使用OpenCV library,没有牵涉到具体的实现领域。具体总结以下:android
本文主要介绍了如何在底层经过OpenCV来对人脸部分进行检测,获得的人脸位置数据经过JNI传递给Java层,详细介绍了其中的JNI代码和共享库的构建过程,对图片是经过图片的路径来进行传递的,由于这里的检测只是对单张静态的图片进行检测。git
本文主要是介绍了OpenCV和Android NDK开发环境的搭建,以及基于示例程序Face-Detection的演示。使用的方式是将OpenCV Library Project做为库,而后调用OpenCV Android API。github
这是一份详细的项目介绍,实现了几种基于Android平台的人脸检测和识别,包括Google API和OpenCV的,可是OpenCV的因为须要Library Project,并且算法过于复杂,做者便自行开发了人脸检测库,有6大特性,其中包括了眼镜和嘴巴的检测。算法
这份报告写得精简可是内容丰富,有几个重要点:
(1) 使用OpenCV的Android应用开发方式,对应不一样的开发人群:Java developer / Native developer
(2) OpenCV4Android目前的局限性,以及开发过程当中对于提升性能和开发效率须要注意的事项canvas
本文设计的内容都很基础,涉及到OpenCV和Android开发的环境搭建,亮点是最后的Using C++ OpenCV code,这里是在Android ndk中使用OpenCV本地代码的重要配置项。数组
这份报告讲述了不少OpenCV的相关知识,另外还详细讲述了一我的脸检测的算法app
这份报告内容也比较多,可是都很基础。ide
这份报告讲的是OpenCV在嵌入式设备中的应用,其中介绍了OpenCV在Android上的开发,须要注意的是OpenCV2.4开始提供了native Android camera support!
这篇论文介绍了利用OpenCV对实时的视频进行处理和纯Android library进行处理的比较,发现利用OpenCV处理的结果更加准确,效率更快,并且更加省电。比较时使用的都是基本图像处理操做,例如灰度化,高斯模糊,Sobel边缘检测等等。
这篇文章比较有意思,大体看了下,介绍了OpenCV在移动终端的应用。
关于如何使用Android的摄像头:Android设备通常有两个摄像头,前置摄像头和后置摄像头,在进行和摄像头相关的应用开发的时候很容易遇到各类问题,推荐如下几篇文章:
Android Developer中有对应的文档:Camera
这位做者的总结:Android相机
StackOverflow上关于如何调用前置摄像头
如何在Android中后台开启摄像头默默拍照
关于Camera的三种Callback
关于保存预览图片:Android中的BitmapFactory.decodeByteArray
只支持必定的格式,Camara默认的previewformat格式为NV21
(对于大多数的Android设备,即便修改CameraParameters的设置也仍是不行),因此在得到bitmap时,须要进行转换,经过YuvImage类来转换成JPEG格式,而后再保存到文件中。
Google Group上的讨论
关于如何在预览界面上添加一个矩形框,相似二维码扫描那样,原理很简单,一个使用SurfaceView,另外一个使用ImageVIew(或者SurfaceView也行),推荐文章:
Android摄像头中预览界面添加矩形框
关于如何进行和OpenCV有关的摄像头开发:有了OpenCV的library以后,关于摄像头的开发可谓是简单了不少,能够参见OpenCV for Android中的三个Tutorial(CameraPreview, MixingProcessing和CameraControl),源码都在OpenCV-Android sdk的samples目录下,这里简单介绍下:
OpenCV Library中提供了两种摄像头,一种是Java摄像头-org.OpenCV.Android.JavaCameraView
,另外一种是Native摄像头-org.OpenCV.Android.NativeCameraView
(能够运行CameraPreview这个项目来体验下二者的不一样,其实差很少)。二者都继承自CameraBridgeViewBase
这个抽象类,可是JavaCamera使用的就是Android SDK中的Camera
,而NativeCamera使用的是OpenCV中的VideoCapture
。
关于OpenCV的Camera在Layout文件中的配置:OpenCV:show_fps
在layout中若是设置为true
的话显示界面中会出现当前摄像头帧率的信息以及图片的大小,OpenCV:camera_id
的配置有三种front
,back
,any
分别对应前置摄像头,后置摄像头和默认的摄像头(其实也就是后置摄像头)。
关于CvCameraViewListener2
接口:它能够方便的处理和摄像头的交互,该接口只有三个函数,分别在Camera打开(onCameraViewStarted
),关闭(onCameraViewStopped
)和预览的图片帧到了的时候(onCameraFrame
)调用。其中OnCameraFrame
这个方法很重要,若是要对图片进行处理的话通常都是在这里面处理的,这个函数的输入参数是CvCameraViewFrame
,须要注意的是,不要在这个方法的外面使用这个变量,由于这个对象没有它本身的状态,在回调方法的外面它的行为是不可预测的!它提供了两个有用的方法rgba()
和gray()
分别获得图像帧的RGBA格式和灰度图,OnCameraFrame
的返回值是RGBA格式的图像,这个很重要!必定要保证处理了以后的图像是RGBA格式的Android系统才能正常显示!来自OpenCV文档:Android Development with Android
Note Do not save or use CvCameraViewFrame object out of onCameraFrame callback. This object does not have its own state and its behavior out of callback is unpredictable!
①传递图片路径:这是最差的方式,我使用过,速度很慢,主要用于前期开发的时候进行测试,测试Java层和Native层的互调是否正常。
②传递预览图像的字节数组到Native层,而后将字节数组处理成RGB或者RGBA的格式[具体哪一种格式要看你的图像处理函数可否处理RGBA格式的,若是能够的话推荐转换成RGBA格式,由于返回的也是RGBA格式的。网上有不少的文章讨论如何转换:一种方式是使用一个自定义的函数进行编码转换(能够搜索到这个函数),另外一个种方式是使用OpenCV中的Mat和cvtColor函数进行转换,接着调用图像处理函数,处理完成以后,将处理的结果保存在一个整形数组中(实际上就是RGB或者RGBA格式的图像数据),最后调用Bitmap的方法将其转换成bitmap返回。这种方法速度也比较慢,可是比第一种方案要快了很多,具体实现过程能够看下面的推荐书籍。
③使用OpenCV的摄像头:JavaCamera或者NativeCamera都行,好处是它进行了不少的封装,能够直接将预览图像的Mat结构传递给Native层,这种传递是使用Mat的内存地址(long型),Native层只要根据这个地址将其封装成Mat就能够进行处理了,另外,它的回调函数的返回值也是Mat,很是方便!这种方式速度较快。详细过程能够查看OpenCV-Android sdk的samples项目中的Tutorial2-MixedProcessing。
portrait
模式以后)在调用了OpenCV的Camera以后,出现预览内容倒置了90度的现象,缘由是OpenCV的Camera默认状况下是以landscape
模式运行的,一个可行可是不是很好的解决方案是修改OpenCV库中的org.opencv.android.CameraBridgeViewBase
类中的deliverAndDrawFrame
方法,问题参考连接 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 { Utils.matToBitmap(modified, mCacheBitmap); } catch(Exception e) { Log.e(TAG, "Mat type: " + modified); 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.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR); // Log.d(TAG, "mStretch value: " + mScale); // // if (mScale != 0) { // canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()), // new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2), // (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2), // (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()), // (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null); // } else { // canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()), // new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2, // (canvas.getHeight() - mCacheBitmap.getHeight()) / 2, // (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(), // (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null); // } //ABC : Fixed for image rotation //TODO Why portrait is not opening in fulls creen Matrix matrix = new Matrix(); int height_Canvas = canvas.getHeight(); int width_Canvas = canvas.getWidth(); int width = mCacheBitmap.getWidth(); int height = mCacheBitmap.getHeight(); float f1 = (width_Canvas - width) / 2; float f2 = (height_Canvas - height) / 2; matrix.preTranslate(f1, f2); if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) matrix.postRotate(270f,(width_Canvas) / 2,(height_Canvas) / 2); canvas.drawBitmap(mCacheBitmap, matrix, new Paint()); if (mFpsMeter != null) { mFpsMeter.measure(); mFpsMeter.draw(canvas, 20, 30); } getHolder().unlockCanvasAndPost(canvas); } } }
在进行这类开发的时候,须要考虑如何在Android中使用OpenCV,而且若是须要调用摄像头的话,要考虑如下内容:
首先,是不是在原有的C/C++代码上进行移植,若是是的话,那么尽可能考虑使用ndk开发,不然使用OpenCV for Android编写Java代码进行开发,效率不会比native代码低多少;
其次,若是是须要OpenCV library,是否可以容忍运行应用还须要安装OpenCV Manager,若是不能的话,则在开发时要考虑将OpenCV binaries添加到应用中进行static initialization,但其实使用OpenCV Manager是有不少好处的,上面的论文和OpenCV官网都有相应的文档介绍它的好处和使用方式;
接着,是否须要调用摄像头,若是须要的话,是使用原生Android的Camera仍是使用OpenCV的Camera,若是是OpenCV Camera的话,是使用Java调用摄像头仍是Native调用摄像头;
最后,图片如何进行传递,若是是单张静态图片进行处理的话,只须要路径就好了,可是若是是在视频状态下对图片进行处理的话,那么就只能传递图像数据了,这里涉及到了Android中如何获取预览的图像数据以及如何将其传递到底层,又如何进行转换(通常是YUV转成RGB)使得OpenCV能够进行处理,处理完了以后,又如何将处理获得的图片传递给Java层。
推荐一本书籍《Mastering OpenCV with Practical Computer Vision Projects》,电子书能够在皮皮书屋下载,原书源码在Github上。该书第一章介绍如何开发一个使用OpenCV的Android项目-Cartoonifer and Skin Changer for Android
,这个项目涉及到了OpenCV在Android中的方方面面,采用的是第二种图像数据传递方式,其中他提出了不少能够优化的地方,包括: ①尽可能使用Mat而不要使用IplImage ②尽可能保证你的图像处理函数可以处理RGBA格式的图像 ③若是能够先压缩图像大小再对图像进行处理 ④使用noise filter下降图像中的噪声。