實時顯示 Opencv處理後的Camera圖像 AndroidStudio NDK方法

目標

最近作了一個使用Andorid手機圖像識別的項目,須要在屏幕上實時顯示圖像處理後的效果。須要具有如下幾個特色:
一、使用Android手機攝像頭;
二、可以進行實時圖像識別、圖像處理;
三、最終手機屏幕上只實時顯示處理後的效果。html

獲取圖像方法分析

調取Android攝像頭有兩種API: android.hardware.Camera (下面稱爲Camera1),android.hardware.Camera2(下面稱爲Camera2)。其獲取相機預覽圖像的方法分別爲:java

Camera1——onPreviewFrame(byte[] data, Camera camera)方法。

參考: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

Camera2——onFrameAvailable( SurfaceTexture surfaceTexture) 方法。

參考: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

其餘方法:

Camera2中使用Allocation存儲預覽圖像,使用RenderScript來處理圖像。

經過getHolder().lockCanvas()得到canvas,在canvas上進行繪畫(添加圖形)再經過canvas.drawBitmap將bitmap繪製在屏幕當中。

綜上所述,因爲我須要作圖像識別,須要在圖像中添加相應線條、色塊,用GLES的着色器不方便,最好還有相應的算法模塊支持,故只能考慮Opencv。c++

圖像處理方法分析

opencv方法選擇

肯定好須要使用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

Created with Raphaël 2.2.0 Android (java) Jni (C/C++) opencv (C++)

CMake設置

先用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

Gradle設置(可選)

若你調用的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部分設置

在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效果,作過一個一半顯示預覽圖像,一半顯示處理後圖像的屏幕布局:
這裏寫圖片描述

預覽圖層使用SurfaceTexture

TextureView和 SurfaceView都是沿用 SurfaceTexture,故咱們能夠先本身隨便新建一個SurfaceTexture變量surfaceTexture=new SurfaceTexture(1) ,而後將Camera1的預覽圖層設定爲這個變量
camera.setPreviewTexture(surfaceTexture) ,這樣就能正常引起onPreviewFrame()/ onFrameAvailable()這兩種方法了。

輸出圖層使用GLSurfaceView

若是須要將處理後的圖像再輸出到手機屏幕上,能夠經過GLSurfaceView。利用OpenGLES的Render,將處理後的圖像以紋理的形式貼出來。這樣就能實現 opencv 處理圖像的實時顯示了。
處理效果如圖:
實時OpenCV處理後的圖像

個人Github:https://github.com/sd707589/Camera1GLTest1
原創不易,歡迎你們分享的同事點右上角的"star" _