实时显示 Opencv处理后的Camera图像 AndroidStudio NDK方法

目标

最近做了一个使用Andorid手机图像识别的项目,需要在屏幕上实时显示图像处理后的效果。需要具备以下几个特点:
1、使用Android手机摄像头;
2、能够进行实时图像识别、图像处理;
3、最终手机屏幕上只实时显示处理后的效果。

获取图像方法分析

调取Android摄像头有两种API: android.hardware.Camera (下面称为Camera1),android.hardware.Camera2(下面称为Camera2)。其获取相机预览图像的方法分别为:

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

参考:http://blog.csdn.net/yanzi1225627/article/details/8605061
1、新建SurfaceView,获取其SurfaceHolder,新建SurfaceHolder.addCallback(SurfaceView)回调。
2、使用camera.setPreviewDisplay(surfaceHolder),camera.setPreviewCallback(this),将相机预览图像与其绑定。
3、SurfaceHolder.Callback是用来预览摄像头视频,一旦程序调用PreviewCallback接口,就会自动调用onPreviewFrame这个函数。
4、onPreviewFrame(byte[] data, Camera camera) 中的data 就是相机预览的图像数据, 可对其进行图像识别、图像处理。

Camera2——onFrameAvailable( SurfaceTexture surfaceTexture) 方法。

参考:http://blog.csdn.net/u010726057/article/details/24319781
1、可新建一个SurfaceTexture,SurfaceTexture可由TextureView/ SurfaceView 的 .getSurfaceTexture()/ onSurfaceTextureAvailable() 回调函数获得;
2、由new Surface(SurfaceTexture)获得其Surface;
3、由 Builder.addTarget(surface) 和 CameraDevice.createCaptureSession(…) 将camera2的预览图像数据输出给该surface。
4、SurfaceTexture常用 OpenGLES20配合使用。在GLES的Render内部循环的onDrawFrame()中,SurfaceTexture使用.updateTexImage()方法来获取最新的camera2预览图像,此时正好触发SurfaceTexture.OnFrameAvailableListener() 的onFrameAvailable( SurfaceTexture surfaceTexture) 回调函数,此时"也可"在该回调函数中添加GLSurfaceView.requestRender()来加速GLSurfaceView的显示。
5、处理部分是由OpenGLES的着色器Shader完成(主要为图像处理),显示部分为GLSurfaceView。

其他方法:

Camera2中使用Allocation存储预览图像,使用RenderScript来处理图像。

通过getHolder().lockCanvas()获得canvas,在canvas上进行绘画(添加图形)再通过canvas.drawBitmap将bitmap绘制在屏幕当中。

综上所述,由于我需要做图像识别,需要在图像中添加相应线条、色块,用GLES的着色器不方便,最好还有相应的算法模块支持,故只能考虑Opencv。

图像处理方法分析

opencv方法选择

确定好需要使用Opencv来进行图像识别后,就需要用Opencv来进行图像处理。Android上使用opencv的方法也有三种:
1、Java法:调用opencv的SDK;
2、Native法:使用.mk文件的ndk-build方法;
3、Native法:使用Cmake方法。
参考: http://blog.csdn.net/u010677365/article/details/76922541
由于java法速度效率最差,.mk方法不能调试,果断选择Cmake方法
这样便可通过Jni做桥梁将Android与Opencv联系起来。

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

CMake设置

先用VS在本地用C++编写好Opencv方法,封装成类(.cpp/ .h),然后再设置CMakeLists.txt。

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”。
实时显示 Opencv处理后的Camera图像 AndroidStudio NDK方法_第1张图片
在CMake的add_library中添加你自己的c++文件时,只用添加.cpp文件即可,CMake会自动去寻找其对应的.h文件。

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的类

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效果,做过一个一半显示预览图像,一半显示处理后图像的屏幕布局:
实时显示 Opencv处理后的Camera图像 AndroidStudio NDK方法_第2张图片

预览图层使用SurfaceTexture

TextureView和 SurfaceView都是沿用 SurfaceTexture,故我们可以先自己随便新建一个SurfaceTexture变量surfaceTexture=new SurfaceTexture(1) ,然后将Camera1的预览图层设定为这个变量
camera.setPreviewTexture(surfaceTexture) ,这样就能正常引发onPreviewFrame()/ onFrameAvailable()这两种方法了。

输出图层使用GLSurfaceView

如果需要将处理后的图像再输出到手机屏幕上,可以通过GLSurfaceView。利用OpenGLES的Render,将处理后的图像以纹理的形式贴出来。这样就能实现 opencv 处理图像的实时显示了。
处理效果如图:
实时显示 Opencv处理后的Camera图像 AndroidStudio NDK方法_第3张图片

我的Github:https://github.com/sd707589/Camera1GLTest1
原创不易,欢迎大家分享的同事点右上角的"star" _

你可能感兴趣的:(使用心得,OpenCV,ndk学习)