Android Studio: JNI 使用小結
項目描述:在Android上部署KCF跟蹤算法,KCF 源碼為c++,且依賴OpenCV,為友善使用,在Android studio中将KCF打包成JNI類。實作過程涉及: JNI 調用 C++ 類對象/方法傳遞數組,c++ java Opencv 混合使用, cv::Mat和Bitmap的轉換等。
1. 添加OpenCV依賴
- Android OpenCV document: https://docs.opencv.org/2.4/doc/tutorials/introduction/android_binary_package/dev_with_OCV_on_Android.html#application-development-with-static-initialization
- 下載下傳
,在項目中:OpenCV-android-sdk
->file
->new
,選擇import module
,導入Android studio 後,module名為your_path/OpenCV-android-sdk/sdk/java
openCVLibrary320
- 修改
中的openCVLibrary320
build.gradle
compileSdkVersion buildToolsVersion "25.0.3" minSdkVersion // 至少是 21 否則opencv camera類報錯 targetSdkVersion
- 為
module添加依賴:project視圖中打開app
, 選擇app module ,右邊綠色小加号點選,添加mudle dependence: openCVLibrary320open module settings
-
OpenCV mixedprocessing: C++源碼也依賴opencv,在Android.mk 編譯c++庫的時候要添加opencv依賴,
Android.mk :
Application.mk :LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) ifdef OPENCV_ANDROID_SDK ifneq ("","$(wildcard $(OPENCV_ANDROID_SDK)/OpenCV.mk)") include ${OPENCV_ANDROID_SDK}/OpenCV.mk else include ${OPENCV_ANDROID_SDK}/sdk/native/jni/OpenCV.mk endif else include ../../sdk/native/jni/OpenCV.mk endif include /home/wurui/project/android/OpenCV-android-sdk/sdk/native/jni/OpenCV.mk LOCAL_MODULE := jniKCF LOCAL_SRC_FILES := ffttools.hpp \ fhog.cpp \ kcftracker.cpp \ labdata.hpp \ recttools.hpp \ kcfClass.cpp LOCAL_LDLIBS += -llog -ldl \ ## 在JNI中使用LOG -lm -llog -ljnigraphics LOCAL_LDFLAGS += -ljnigraphics ## mat/bitmap convert include $(BUILD_SHARED_LIBRARY)
APP_STL := gnustl_static APP_CPPFLAGS := -frtti -fexceptions APP_ABI := armeabi-v7a APP_PLATFORM := android-8
-
為Android.mk和cpp檔案添加關聯:
可以從圖形界面添加,也可以從build.gradle添加:
apply plugin: 'com.android.application' allprojects { repositories { jcenter() flatDir { dirs 'libs' } } } android { compileSdkVersion buildToolsVersion "25.0.3" defaultConfig { applicationId "camera.hj.cameracontroller" minSdkVersion targetSdkVersion versionCode versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk { moduleName "jniKCF" abiFilters "armeabi-v7a" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { assets.srcDirs = ['assets'] jni.srcDirs = [] //屏蔽gradle的jni生成過程 jniLibs.srcDirs = ['src/main/jniLibs'] } } externalNativeBuild { ndkBuild { path 'src/main/cpp/Android.mk' // 指定Android.mk } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' compile 'com.squareup.retrofit2:retrofit:2.3.0' //RxJava and RxAndroid compile 'io.reactivex.rxjava2:rxandroid:2.0.1' // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. compile 'io.reactivex.rxjava2:rxjava:2.1.0' testCompile 'junit:junit:4.12' // wrz compile(name: 'snpe-release', ext: 'aar') compile project(':openCVLibrary320') }
2. JNI将C++源碼打包
- JNI調用C++時,希望保留C++的類對象,通過傳位址實作。建立一個JNI類:jniKCF.java
public class jniKCF { // JNI static { System.loadLibrary("jniKCF"); } //儲存c++類的位址 long nativeKCF; //構造函數 public jniKCF(){ this.nativeKCF = createNativeObject(); } /**本地方法:建立c++對象并傳回位址*/ private native long createNativeObject(); public native void kcfInit(long kcfAddr, Object bmp, int[] rect); public native int[] kcfUpdate(long kcfAddr, Object bmp); // Functions public void init(Bitmap bitmap, int[] rect){ kcfInit(this.nativeKCF, bitmap, rect); } public int[] update(Bitmap bitmap){ int rect[] = new int[]; rect = kcfUpdate(this.nativeKCF, bitmap); return rect; } }
- 生成頭檔案:在 Terminal 中:
然後cd app/src/main/java
,就在java檔案夾下生成了頭檔案。把頭檔案拷貝到 app/src/main/cpp/下重命名為javah -jni com.xxx.xxx.jniKCF
kcfClass.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include <string> #include <opencv2/core.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/features2d.hpp> #include <vector> #include "test.h" #include<android/bitmap.h> /* Header for class com_example_wurui_kcf_ndk_jniKCF */ #ifndef _Included_com_example_wurui_kcf_ndk_jniKCF #define _Included_com_example_wurui_kcf_ndk_jniKCF #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_wurui_kcf_ndk_jniKCF * Method: createNativeObject * Signature: ()J */ JNIEXPORT jlong JNICALL Java_camera_hj_cameracontroller_decoder_jniKCF_createNativeObject (JNIEnv *, jobject); /* * Class: camera_hj_cameracontroller_decoder_jniKCF * Method: kcfInit * Signature: (JLjava/lang/Object;[I)V */ JNIEXPORT void JNICALL Java_camera_hj_cameracontroller_decoder_jniKCF_kcfInit (JNIEnv *, jobject, jlong, jobject, jintArray); /* * Class: camera_hj_cameracontroller_decoder_jniKCF * Method: kcfUpdate * Signature: (JLjava/lang/Object;)[I */ JNIEXPORT jintArray JNICALL Java_camera_hj_cameracontroller_decoder_jniKCF_kcfUpdate (JNIEnv *, jobject, jlong, jobject); #ifdef __cplusplus } #endif #endif
- 實作 kcfClass.cpp :
// // Created by wurui on 17-12-11. // #include "kcfClass.h" #include "kcftracker.hpp" #include <android/log.h> #include "eben_hpc_log.h" #include <math.h> using namespace cv; using namespace std; JNIEXPORT jlong JNICALL Java_camera_hj_cameracontroller_decoder_jniKCF_createNativeObject(JNIEnv *env, jobject obj){ jlong result; bool HOG = true; bool FIXEDWINDOW = false; bool MULTISCALE = false; bool LAB = false; result = (jlong) new KCFTracker(HOG, FIXEDWINDOW, MULTISCALE, LAB); return result; } /* * Class: com_example_wurui_kcf_ndk_jniKCF * Method: kcfInit * Signature: (Ljava/lang/Object;[I)V */ JNIEXPORT void JNICALL Java_camera_hj_cameracontroller_decoder_jniKCF_kcfInit (JNIEnv *env, jobject obj, jlong kcfClassAddr,jobject jbmp1, jintArray jrect){ // 擷取圖檔 AndroidBitmapInfo bmp1info; void* bmp1pixels; int height,width,ret,y,x; if ((ret = AndroidBitmap_getInfo(env, jbmp1, &bmp1info)) < ) { return ; } if (bmp1info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 || bmp1info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { LOGE("Bitmap format is not RGBA_8888!"); return ; } if ((ret = AndroidBitmap_lockPixels(env, jbmp1, &bmp1pixels)) < ) { LOGE("First Bitmap LockPixels Failed return=%d!", ret); return ; } AndroidBitmap_unlockPixels(env, jbmp1); height = bmp1info.height; width = bmp1info.width; // Bitmap 直接轉換得到的是4通道的 Mat cv::Mat image1(height,width,CV_8UC4,bmp1pixels); if(!(image1.data )){ LOGE("before bitmap failed convert to Mat return=%d!", ret); LOGD("------------------ CV image CV_8UC4 null ----------------------"); return ; } // 為了算法處理,需要得到 BGR 圖像 image1 cvtColor(image1,image1,CV_RGBA2BGR); if(!(image1.data)){ LOGE("after bitmap failed convert to Mat return=%d!", ret); LOGD("------------------ CV image CV_8UC3 null ----------------------"); return ; } // 讀取數組 ==== RectBuffer int* RectBuffer = new int[]; // result rect int* arrayPointer = (*env).GetIntArrayElements(jrect,NULL); for(int i=;i<;i++){ RectBuffer[i] = arrayPointer[i]; } cv::Rect rect = cv::Rect(RectBuffer[], RectBuffer[], RectBuffer[], RectBuffer[]); // 初始化 kcf tracker // (KCFTracker*)kcfClassAddr 為傳入的C++類對象的指針 ((KCFTracker*)kcfClassAddr)->init(rect, image1); (*env).ReleaseIntArrayElements(jrect,arrayPointer,); } /* * Class: com_example_wurui_kcf_ndk_jniKCF * Method: kcfUpdate * Signature: (Ljava/lang/Object;)[I */ JNIEXPORT jintArray JNICALL Java_camera_hj_cameracontroller_decoder_jniKCF_kcfUpdate (JNIEnv *env, jobject obj, jlong kcfClassAddr,jobject jbmp1){ // 擷取圖檔 AndroidBitmapInfo bmp1info; void* bmp1pixels; int height,width,ret,y,x; if ((ret = AndroidBitmap_getInfo(env, jbmp1, &bmp1info)) < ) { return NULL; } if (bmp1info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 || bmp1info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { LOGE("Bitmap format is not RGBA_8888!"); return NULL; } if ((ret = AndroidBitmap_lockPixels(env, jbmp1, &bmp1pixels)) < ) { LOGE("First Bitmap LockPixels Failed return=%d!", ret); return NULL; } AndroidBitmap_unlockPixels(env, jbmp1); height = bmp1info.height; width = bmp1info.width; cv::Mat image1(height,width,CV_8UC4,bmp1pixels); if(!(image1.data )){ LOGE("before bitmap failed convert to Mat return=%d!", ret); LOGD("------------------ CV image CV_8UC4 null ----------------------"); return NULL; } // 得到 BGR 圖像 ==== image1 cvtColor(image1,image1,CV_RGBA2BGR); if(!(image1.data)){ LOGE("after bitmap failed convert to Mat return=%d!", ret); LOGD("------------------ CV image CV_8UC3 null ----------------------"); return NULL; } // Update kcf cv::Rect rect; rect = ((KCFTracker*)kcfClassAddr)->update(image1); // 傳回結果 數組 int resultRect[]; resultRect[] = rect.x; resultRect[] = rect.y; resultRect[] = rect.width; resultRect[] = rect.height; jintArray array =(*env).NewIntArray(); (*env).SetIntArrayRegion(array,,,resultRect); return array; }