最近作了一個使用Andorid手機圖像識別的項目,須要在屏幕上實時顯示圖像處理後的效果。須要具有如下幾個特色:
一、使用Android手機攝像頭;
二、可以進行實時圖像識別、圖像處理;
三、最終手機屏幕上只實時顯示處理後的效果。html
調取Android攝像頭有兩種API: android.hardware.Camera (下面稱爲Camera1),android.hardware.Camera2(下面稱爲Camera2)。其獲取相機預覽圖像的方法分別爲:java
參考:http://blog.csdn.net/yanzi1225627/article/details/8605061
一、新建SurfaceView,獲取其SurfaceHolder,新建SurfaceHolder.addCallback(SurfaceView)回調。
二、使用camera.setPreviewDisplay(surfaceHolder),camera.setPreviewCallback(this),將相機預覽圖像與其綁定。
三、SurfaceHolder.Callback是用來預覽攝像頭視頻,一旦程序調用PreviewCallback接口,就會自動調用onPreviewFrame這個函數。
四、onPreviewFrame(byte[] data, Camera camera) 中的data 就是相機預覽的圖像數據, 可對其進行圖像識別、圖像處理。python
參考:http://blog.csdn.net/u010726057/article/details/24319781
一、可新建一個SurfaceTexture,SurfaceTexture可由TextureView/ SurfaceView 的 .getSurfaceTexture()/ onSurfaceTextureAvailable() 回調函數得到;
二、由new Surface(SurfaceTexture)得到其Surface;
三、由 Builder.addTarget(surface) 和 CameraDevice.createCaptureSession(…) 將camera2的預覽圖像數據輸出給該surface。
四、SurfaceTexture經常使用 OpenGLES20配合使用。在GLES的Render內部循環的onDrawFrame()中,SurfaceTexture使用.updateTexImage()方法來獲取最新的camera2預覽圖像,此時正好觸發SurfaceTexture.OnFrameAvailableListener() 的onFrameAvailable( SurfaceTexture surfaceTexture) 回調函數,此時"也可"在該回調函數中添加GLSurfaceView.requestRender()來加速GLSurfaceView的顯示。
五、處理部分是由OpenGLES的着色器Shader完成(主要爲圖像處理),顯示部分爲GLSurfaceView。android
綜上所述,因爲我須要作圖像識別,須要在圖像中添加相應線條、色塊,用GLES的着色器不方便,最好還有相應的算法模塊支持,故只能考慮Opencv。c++
肯定好須要使用Opencv來進行圖像識別後,就須要用Opencv來進行圖像處理。Android上使用opencv的方法也有三種:
一、Java法:調用opencv的SDK;
二、Native法:使用.mk文件的ndk-build方法;
三、Native法:使用Cmake方法。
參考: http://blog.csdn.net/u010677365/article/details/76922541
因爲java法速度效率最差,.mk方法不能調試,果斷選擇Cmake方法。
這樣即可經過Jni作橋樑將Android與Opencv聯繫起來。git
先用VS在本地用C++編寫好Opencv方法,封裝成類(.cpp/ .h),而後再設置CMakeLists.txt。github
cmake_minimum_required(VERSION 3.4.1) set(OpenCV_STATIC ON) set(OpenCV_DIR D:/OpenCV/cv33/opencv-3.3.0-android-sdk/OpenCV-android-sdk/sdk/native/jni) find_package(OpenCV REQUIRED) # 生成JNI部分的庫 add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp ) # 生成OpenCV部分的庫 add_library( opencvProcess SHARED src/main/cpp/imgProcess.cpp src/main/cpp/ExtractCase.cpp) target_link_libraries(opencvProcess ${OpenCV_LIBS}) find_library( log-lib log ) target_link_libraries( # Specifies the target library. native-lib # 將Opencv的庫連接到Jni庫上 opencvProcess ${log-lib} ${OpenCV_LIBS} )
點擊「Sync」同步刷新下項目,就會在目錄樹下面看到新增的兩個庫「native-lib」、「opencvProcess」。
在CMake的add_library中添加你本身的c++文件時,只用添加.cpp文件便可,CMake會自動去尋找其對應的.h文件。web
若你調用的Opencv的JNI庫的編譯庫 比 你的JNI庫的版本要低,此時能夠在app的build.gradle中設置一下。算法
android { compileSdkVersion 28 defaultConfig { applicationId "****************" minSdkVersion 22 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "-std=c++11 -frtti -fexceptions " abiFilters "armeabi-v7a", "arm64-v8a" arguments "-DANDROID_STL=gnustl_static" //該處添加gnustl_static,使得可讀取opencv JNI的庫文件。 } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { //該處可設置自定義的Lib路徑 main { jniLibs.srcDirs = ['libs'] } } externalNativeBuild { cmake { path "CMakeLists.txt" } } }
在JNI部分(native-lib.cpp文件)中,它是Opencv和Android內容溝通的橋樑。通常地,Android可使用多個形參,將多個變量傳遞給Opencv。但怎麼才能返回多個變量呢?這裏就得使用「結構體」——Android中新建一個類用來返回Jni中數據。
新建一個ResultFromJni2的類canvas
package opencv4unity.camera1gltest1; public class ResultFromJni2 { //返回的圖像 int [] resultInt; //邊框的左上角點 int x; int y; }
而後在Jni的被調用Native函數中添加
JNIEXPORT jobject JNICALL Java_opencv4unity_camera1gltest1_MyNDKOpencv_getScannerEffect (JNIEnv *env, jclass type,jintArray pixels_, jint w, jint h, jint model) { // 讀取"結構體"(自建的java類) jclass cSructInfo=env->FindClass("opencv4unity/camera1gltest1/ResultFromJni2"); jfieldID cXLoc=env->GetFieldID(cSructInfo,"x","I"); jfieldID cYLoc=env->GetFieldID(cSructInfo,"y","I"); jfieldID cResultInt=env->GetFieldID(cSructInfo,"resultInt","[I"); //新建Jni類對象 jobject oStructInfo=env->AllocObject(cSructInfo); jint *pixels = env->GetIntArrayElements(pixels_, false); if(pixels==NULL){ return NULL; } //獲得了Opencv熟悉的Mat變量 cv::Mat imgData(h, w, CV_8UC4, pixels); //imgData是獲取的Camera圖像,h/ w分別是height/ width,接下來用openCV作圖像處理 …… //將值賦給"結構體" Point leftPoint=mExtractImg.upLeftPt; int x=leftPoint.x; int y=leftPoint.y; env->SetIntField(oStructInfo,cXLoc,x); env->SetIntField(oStructInfo,cYLoc,y); int size = w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, pixels); env->SetObjectField(oStructInfo,cResultInt,result); //返回結構體 env->ReleaseIntArrayElements(pixels_, pixels, 0); return oStructInfo; }
#實時顯示圖像
若是是在VS用Opencv來作確定十分簡單,直接用cv::showimg
輕鬆搞定,但android是個變態。不管你是用TextureView仍是SurfaceView這個UI來獲取相機的預覽圖像,都必須先直接顯示了,才能引起onPreviewFrame()/ onFrameAvailable()這兩種方法。
因此以前爲了先驗證opencv效果,作過一個一半顯示預覽圖像,一半顯示處理後圖像的屏幕布局:
TextureView和 SurfaceView都是沿用 SurfaceTexture,故咱們能夠先本身隨便新建一個SurfaceTexture變量surfaceTexture=new SurfaceTexture(1)
,而後將Camera1的預覽圖層設定爲這個變量
camera.setPreviewTexture(surfaceTexture)
,這樣就能正常引起onPreviewFrame()/ onFrameAvailable()這兩種方法了。
若是須要將處理後的圖像再輸出到手機屏幕上,能夠經過GLSurfaceView。利用OpenGLES的Render,將處理後的圖像以紋理的形式貼出來。這樣就能實現 opencv 處理圖像的實時顯示了。
處理效果如圖:
個人Github:https://github.com/sd707589/Camera1GLTest1
原創不易,歡迎你們分享的同事點右上角的"star" _