在Android Studio上使用OpenCV_Java接口和NDK_JNI开发

在Android Studio上使用OpenCV_Java接口和NDK_JNI开发

参考博客:
在Android Studio上利用NDK_JNI进行OpenCV开发

在Android Studio上调用OpenCV_Java接口开发,无需安装OpenCV Manager

在Android Studio上使用OpenCV_Java接口和NDK_JNI开发,无需安装OpenCV Manager

1.说明

在Android Studio(AS)上调用OpenCV开发有以下三种方法:
1. 在手机上安装OpenCV Manager,使用OpenCV_Java接口开发;
2. 使用C++开发,通过NDK和JNI给Java提供接口;
3. 同时使用以上两种方法;
对于比较熟悉C++接口而不大熟悉Java接口的同学来说,使用第一种方法非常麻烦,经常要查API,而且Java的效率没有C++高;而第二种方法在Java和C++间进行数据传递时比较麻烦,比如第一篇博客用的是int数组;所以我们使用第三种方法。另外,之前使用NDK和JNI调用OpenCV时用的是Android.mk,好长时间不搞忘了怎么操作了,比较麻烦,好在现在Android Studio支持使用cmake配置,使用起来非常方便。本篇博客主要参考前面列出的三篇博客,对一些不详细的地方进行补充。

2. Android Studio配置

参考第三篇博客,主要是安装cmake,NDK好像是在安装AS时就装好了。
这里写图片描述

3. 配置工程

首先是在创建工程的时候需要勾选Include C++ support
这里写图片描述
这里写图片描述
创建好之后,工程中会多了一个cpp文件夹,里面的native-lib.cpp文件里有个名为Java_com_tx_opencvdemo_MainActivity_stringFromJNI的函数,它和MainActivity.java中的public native String stringFromJNI();方法对应,方法名就是cpp函数最后一个_后面的内容。我们自己在创建新的函数时,使用这样的规则命名就会自动对应。在MainActivity.java中使用下面的代码加载本地库。

static {
    System.loadLibrary("native-lib");
}
4. 配置工程使用OpenCV本地库

我的工程放在D:\WorkSpace\AS目录下,工程文件夹为OpenCVDemo。
首先在OpenCVDemo\app\src\main目录下新建jniLibs文件夹,将解压出来的OpenCV-android-sdk\sdk\native\libs目录下的x86armeabi-v7a两个文件夹(对应CPU架构)拷到jniLibs文件夹下。因为我要在模拟器和手机上调试,所以我拷贝这两个文件夹,另外我之前建的模拟器选择的是x86_64镜像,实测无法运行,所以有重新创建了一个x86的镜像模拟器。
然后修改build.gradle(Module: app)中的defaultConfig

externalNativeBuild {
    cmake {
        cppFlags "-std=c++11 -frtti -fexceptions"
        abiFilters 'x86', 'armeabi-v7a'
    }
}

在android{}的最后添加

sourceSets {
    main {
        jniLibs.srcDirs = ['D:\\WorkSpace\\AS\\OpenCVDemo\\app\\src\\main\\jniLibs']
    }
}

接下来修改OpenCVDemo\app目录下的CMakeLists.txt文件,如下

cmake_minimum_required(VERSION 3.4.1)

#支持-std=gnu++11
set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

#工程路径
set(Project_Path D:/WorkSpace/AS/JNIDemo)

# OpenCV Android SDK Path
set(OpenCV_Path D:/opencv2.4.13/OpenCV-android-sdk/sdk/native)

include_directories(${OpenCV_Path}/jni/include)

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
             src/main/cpp/imageProcess.cpp)

#动态方式加载
add_library( lib_opencv SHARED IMPORTED )            

#引入libopencv_java.so文件
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${Project_Path}/app/src/main/jniLibs/${ANDROID_ABI}/libopencv_java.so)

find_library( 
              log-lib
              log )

target_link_libraries( # Specifies the target library.
                       native-lib

                       ${log-lib} 
                       lib_opencv)

其中src/main/cpp/imageProcess.cpp是我在对应目录下新建的文件,用于写OpenCV的相关代码,set_target_properties一行最后是libopencv_java.so,因为我用的是OpenCV2.x,如果是OpenCV3.x要改成libopencv_java3.so
修改完CMakeLists.txt需要更新一下工程,但是AS可能不会提示,点一下编译就会提示了。至此,我们可以使用C++调用OpenCV了,拷贝博客一中的代码:
imageProcess.cpp

#include <jni.h>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

extern "C" {

JNIEXPORT jintArray JNICALL
Java_com_tx_opencvdemo_MainActivity_getGrayImage(
        JNIEnv *env,
        jclass type,
        jintArray pixels_,
        jint w, jint h) {
    jint *pixels = env->GetIntArrayElements(pixels_, NULL);
    // TODO
    if(pixels==NULL){
        return NULL;
    }
    cv::Mat imgData(h, w, CV_8UC4, pixels);
    uchar *ptr = imgData.ptr(0);
    for (int i = 0; i < w * h; i++) {
        int grayScale = (int) (ptr[4 * i + 2] * 0.299 + ptr[4 * i + 1] * 0.587
                               + ptr[4 * i + 0] * 0.114);
        ptr[4 * i + 1] = (uchar) grayScale;
        ptr[4 * i + 2] = (uchar) grayScale;
        ptr[4 * i + 0] = (uchar) grayScale;
    }

    int size = w * h;
    jintArray result = env->NewIntArray(size);
    env->SetIntArrayRegion(result, 0, size, pixels);
    env->ReleaseIntArrayElements(pixels_, pixels, 0);
    return result;
}

}

MainActivity.java
声明
public native int[] getGrayImage(int[] pixels, int w, int h);
添加

static {
    System.loadLibrary("native-lib");
    System.loadLibrary("opencv_java");
}
iv = (ImageView) findViewById(R.id.iv);

Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
int w = bmp.getWidth();
int h = bmp.getHeight();
int[] pixels = new int[w*h];
bmp.getPixels(pixels, 0, w, 0, 0, w, h);
//recall JNI
int[] resultInt = getGrayImage(pixels, w, h);
Bitmap resultImg = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
iv.setImageBitmap(resultImg);

代码使用int数组传递图片,写起来比较麻烦。

5. 配置工程调用OpenCV Java接口

参考博客二,点击File->New->Import Module…
需要说明的是:在build.gradle(Module: app)的最后添加

task nativeLibsToJar(type: Jar, description: 'create a jar archive of the native libs') {
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    from fileTree(dir: 'libs', include: '**/*.so')
    into 'lib/'
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn(nativeLibsToJar)
}

在dependencies{}中的最后添加

compile fileTree(dir: "$buildDir/native-libs", include: 'native-libs.jar')

还需要修改build.gradle(Module: openCVLibrariy24136中对应的版本号。
这样就可以在Java程序中调用OpenCV接口了。下面给出C++程序中检测角点,将结果返回的代码:
imageProcess.cpp

JNIEXPORT void JNICALL
Java_com_tx_OpenCVdemo_MainActivity_detectCorner(
        JNIEnv *env,
        jclass type,
        jlong srcMatAddr,
        jlong dstMatAddr) {

    cv::Mat *srcMat = (Mat*)srcMatAddr;
    cv::Mat *dstMat = (Mat*)dstMatAddr;

    vector<KeyPoint> keyPoints;
    OrbFeatureDetector detector(100);
    detector.detect(*srcMat, keyPoints);
    drawKeypoints(*srcMat, keyPoints, *dstMat);

//    OpenCV3 code
//    Ptr<SIFT> detector = SIFT::create(100);
//    detector->detect(*srcMat, Keypoints);
//    detector->compute(*srcMat, Keypoints, *descriptors);
}

使用cv::Mat *srcMat = (Mat*)srcMatAddr;就能得到Mat*
MainActivity.java中申明

public native void detectCorner(long srcMatAddr,long dstMatAddr);

调用

Mat src = new Mat();
Mat bgr = new Mat();

Utils.bitmapToMat(bmp, src);
Imgproc.cvtColor(src, bgr, Imgproc.COLOR_BGRA2BGR);

detectCorner(bgr.getNativeObjAddr(), bgr.getNativeObjAddr());   // 检测角点

Utils.matToBitmap(bgr, bmpShow);
iv.setImageBitmap(bmpShow);

运行结果:
这里写图片描述

6. 其他说明

上面的代码,使用
Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.mipmap.cat);
解码图片,OpenCV提供

Utils.bitmapToMat(bmp, bgr);
Utils.matToBitmap(dst, bmpShow);

用于在Bitmap和Mat之间转换。
需要注意的是,似乎不管你的原图片是三通道还是四通道,使用Utils.bitmapToMat(bmp, src);得到的都是BGRA四通道的Mat,所以必须要用Imgproc.cvtColor(src, bgr, Imgproc.COLOR_BGRA2BGR);转换一下

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值