這一節的主要內容是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下降圖像中的噪聲。