在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
目录下的x86
和armeabi-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);
转换一下