天天看點

Android Studio: JNI 使用小結Android Studio: JNI 使用小結

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

    ,選擇

    your_path/OpenCV-android-sdk/sdk/java

    ,導入Android studio 後,module名為

    openCVLibrary320

  • 修改

    openCVLibrary320

    中的

    build.gradle

    compileSdkVersion   
    buildToolsVersion "25.0.3"
    minSdkVersion  // 至少是 21 否則opencv camera類報錯
    targetSdkVersion 
               
  • app

    module添加依賴:project視圖中打開

    open module settings

    , 選擇app module ,右邊綠色小加号點選,添加mudle dependence: openCVLibrary320
  • OpenCV mixedprocessing: C++源碼也依賴opencv,在Android.mk 編譯c++庫的時候要添加opencv依賴,

    Android.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)
               
    Application.mk :
    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

    然後

    javah -jni com.xxx.xxx.jniKCF

    ,就在java檔案夾下生成了頭檔案。把頭檔案拷貝到 app/src/main/cpp/下重命名為

    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;
    }
               

繼續閱讀