本文介紹如何通過android studio通過jni調用openCV,不使用Opencv Manager,使用靜態編譯openCV的方式,生成單獨的一個so文件。
可先看上篇文章http://blog.csdn.net/cheng20150809/article/details/51348420,AndroidStudio基本的JNI編程。
1、新建一個空的Activity工程,添加Add->Folder->JNI Folder,並添加cpp文件(Windows下再多添加一個空的,只有一個cpp文件,ndk會報錯),同樣我們使用JNI動態註冊的方式,避免較長的函數名。代碼如下:
//
// Created by user on 16-5-4.
//
#include <jni.h>
#include <android/log.h>
#include <string.h>
#include <stdio.h>
#include <android/bitmap.h>
#include <assert.h>
#include <opencv2/core/core.hpp>
#include <opencv2/core/mat.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#ifndef LOG
#define LOG_TAG "imgprocess"
#define ALOGD(...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
#define ALOGE(...) \
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
#define ALOGV(...) \
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
#endif LOG
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
// 若這裏不加namespace cv,則下面所有openCV的數據類型都要加cv::限定
using namespace cv;
// 傳入一個bitmap對象,傳出一個包含邊緣信息的bitmap對象
// 注意前兩個參數是JNIEnv* env, jobject thiz
// JNIEnv標識當前NDK環境的對象指針,可以通過該參數訪問NDK中的內置成員,
// jobject表示調用當前NDK方法的Java對象,可以用該參數值訪問調用該方法的Java對象成員。
jobject getEdge(JNIEnv* env, jobject thiz, jobject bitmap){
AndroidBitmapInfo bitmapInfo;
uint32_t* storedBitmapPixels = NULL;
int pixelsCount;
int ret = -1;
// 讀取bitmap基本信息
if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
return NULL;
}
ALOGD("width:%d height:%d stride:%d", bitmapInfo.width, bitmapInfo.height, bitmapInfo.stride);
// 這裏只處理RGBA_888類型的bitmap
if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
return NULL;
}
// 提取像素值
void* bitmapPixels = NULL;
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) {
return NULL;
}
// 生成openCV Mat矩陣
Mat srcMat(Size(bitmapInfo.width, bitmapInfo.height), CV_8UC4);
pixelsCount = bitmapInfo.height * bitmapInfo.width;
memcpy(srcMat.data, bitmapPixels, sizeof(uint32_t) * pixelsCount);
AndroidBitmap_unlockPixels(env, bitmap);
// 處理求邊緣得到desMat
Mat desMat;
Canny(srcMat, desMat, 30.0, 90.0, 3, true);
// 通過JAVA層的Bitmap類,新建一個bitmap對象
jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jstring configName = env->NewStringUTF("ARGB_8888");
jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunction, configName);
jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapFunction, desMat.cols, desMat.rows, bitmapConfig);
// 獲取新bitmap的data數據指針
bitmapPixels = NULL;
if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0) {
return NULL;
}
// 將Mat數據寫入bitmap
uint32_t* newBitmapPixels = (uint32_t*)bitmapPixels;
pixelsCount = srcMat.cols * desMat.rows;
for (size_t i = 0; i < pixelsCount; i++) {
memset(&(newBitmapPixels[i]), srcMat.data[i], 3);
}
AndroidBitmap_unlockPixels(env, newBitmap);
return newBitmap;
}
// native函數所在的類
static const char *classPathName = "test/hc/cvtest/MainActivity";
//
static JNINativeMethod gMethods[] = {
{"getEdge", "(Ljava/lang/Object;)Ljava/lang/Object;", (void*)getEdge},
};
int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
int register_jni_methods(JNIEnv* env)
{
return registerNativeMethods(env, classPathName, gMethods, NELEM(gMethods));
}
jint JNI_OnLoad(JavaVM* vm, void*)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
if (register_jni_methods(env) < 0) {
ALOGE("ERROR: native registration failed\n");
goto bail;
}
ALOGE("SUCCESS: native registration successed\n");
result = JNI_VERSION_1_4;
bail:
return result;
}
2、由於需要使用opencv的mk文件,這裏我們不使用Android Studio的默認ndk編譯,使用我們自己的mk文件。在jni目錄中添加Android.mk和Application.mk文件。
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#opencv
OPENCVROOT:=/local/tools/OpenCV-2.4.10-android-sdk/OpenCV-2.4.10-android-sdk
OPENCV_CAMERA_MODULES:=off
OPENCV_INSTALL_MODULES:=on
#OPENCV_LIB_TYPE:=SHARED
#靜態編譯
OPENCV_LIB_TYPE:=STATIC
include ${OPENCVROOT}/sdk/native/jni/OpenCV.mk
LOCAL_SRC_FILES := ImgProcess.cpp
LOCAL_LDLIBS += -llog
LOCAL_LDLIBS += -ljnigraphics
LOCAL_MODULE := imgprocess
include $(BUILD_SHARED_LIBRARY)
Application.mk
# CPU架構,目前有arm64-v8a,需要64位ndk以及較新的opencv
APP_ABI := armeabi, armeabi-v7a, x86
APP_PLATFORM := android-16
APP_STL := gnustl_static
3、配置app的build.gradle,defaultConfig中添加一個編譯ndk的task,生成的so文件將會生成在src/main/jniLibs這個目錄中
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "test.hc.cvtest"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
// add begin for ndk
sourceSets.main.jni.srcDirs = []
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
commandLine "$ndkDir/ndk-build",
'NDK_PROJECT_PATH=build/intermediates/ndk',
'NDK_LIBS_OUT=src/main/jniLibs',
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
// add end for ndk
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
4、在gradle.properties中添加:
android.useDeprecatedNdk=true
ndkDir=/local/tools/android-ndk-r10
並在在local.properties中配置好ndk路徑
ndk.dir=/local/tools/android-ndk-r10
5、新建一個java類,load lib庫,並添加native函數即可