Saturday, March 28, 2015

Android Studio + Android + Gradle + NDK + OpenCV + SWIG + OSX

The purpose of the combination is to automatically compile OpenCV C/C++ code and use it in Android. The following solution refers to many other resources like how to execute NDK in gradle[1], how to execute SWIG [2].  The platform that I am talking about is Mac OS X.

 Part I, let me explain why we choose each tool in the tile.
(1) Intellij v.s. Android Studio v.s. Eclipse
   As a pure IDE, I like Intellij much more than eclipse considering the great prompt tips to assist you write code faster. Android Studio is actually a specific IDE based on Intellij. Unfortunately, only eclipse supports NDK, which is required to compile OpenCV android.

Even though, I prefer Intellij to Android Studio for some reasons like we can compile python module in the same project. However, I did not figure out how to import opencv-libary as a gradle module into the project, which works as a library to your Android opencv program.

(2) Gradle v.s. Maven v.s. Ant
Honestly, I just went through maven, which I find is very helpful to test and deploy project. However, in Android Studio, it is shipped with Gradle. There are many Q/A in stackoverflow to talk about gradle questions, instead of Maven. The other reason is that Gradle was developed to overcome disadvantages of Maven or Ant, like flat xml files. However, in Gradle, all the configuration is like coding, even in JAVA language, which makes it more flexible and efficient. Ant? I do not know much about it. Just a sense that it is replaced by Maven and then Gradle.

The mort important reason is that Gradle works like script, which can easily call system command like a shell script. This is key to embed NDK to Intellij. If executing the third party program like SWIG in next step, gradle is almost the top choice.

(3) SWIG
The way android calls OpenCV is through JNI code, which is a special format of function declaration. Trust me, you wouldn't like to convert all the C/C++ code to JNI code MANUALLY.

e.g., a JNI format C code
JNIEXPORT jlong JNICALL Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeCreateObject
  (JNIEnv *, jclass, jstring, jint);

Fortunately, SWIG is such a software to do this kind of dirty work for you. However, it is not convenient to cooperate with your IDE. You can manually execute commands in your shell every time. But with Gradle, you can easily write a task for SWIG. That is the other reason we choose Gradle over Maven.

 
Part II, let me explain each part of the code.

I. Preparation
    Download and Install the following packages.
    (1) Android Studio  //IDE
    (2) SWIG                //Generate JNI code for C/C++ code
    (3) Android SDK    //For developing Android
    (4) Android NDK   // For compile JNI code to the APK
    (5) OpenCV            //For developing OpenCV C code, see my other post.
    (6) OpenCV-android-sdk // For developing OpenCV Android code

II. Directory structure
 This is the direct structure of a Gradle module in Android Studio under the project view (NOT Android).

--> All the file are in the main/
--> All the JAVA code

--> These JAVA files are code for wrapping JNI code, which is generated automatically by SWIG. *_wrapper one which contains the related java method to each native one.

--> All your Android code are store under this directory.    
--> All the JNI related resource are stored in jni/ directory.  Android.mk is used to configure NDK. Application.mk is used to configure some STD c++ [3]
--> opencv_test.cpp and opencv_test.h are two normal c++ files which might be copied directly from you xcode. opencv_test.i is used to configure SWIG.  opencv_test_wrap.cpp is generated JNI code by SWIG.

--> jnilibs are used to store NDK generated files.

--> res/ stores all the android resource like layout file.
AndroidManifest.xml is an Android configuration file.
--> Your gradle file for this module.





III. Create two directories: main/jni and main/jnilibs

IV. Configure your SWIG in Gradle
In our sample project, C/C++ code are in two files: opencv_test.h and opencv_test.cpp. These two files are normal C/C++ code, which can be copied directly from your xcode.

To tell SWIG which files are to be converted, you need to prepare another file, opencv_test.i as the configuration, which will be passed to SWIG in the Gradle next.

Here is a sample of opencv_test.i.  We name the module "opencv_test_wrapper", which will be the name of generated JAVA file. Also between %{ and %}, we tell SWIG to convert "opencv_test.h", which defines all the methods. For detailed tutorial, please refer to SWIG tutorial.

%module opencv_test_wrapper

%{
#include "opencv_test.h"
%}

%include "opencv_test.h"

Here is a sample code for calling SWIG in Gradle. The basic code is from [2].

def coreWrapperDir=new File("${projectDir}/src/main/java/com/example/opencvswig/core")
//Create the directory for storing SWIG generated JAVA wrapped JNI files.
task createCoreWrapperDir{
    coreWrapperDir.mkdirs()
}
//Call SWIG to wrap your C/C++ code to a JAVA code
task runSwig(type:Exec, dependsOn:['createCoreWrapperDir']){
    commandLine '/usr/local/bin/swig'
    args '-c++', '-java', '-package', 'com.example.opencvswig.core', '-outdir', coreWrapperDir.absolutePath, '-o',"${projectDir}/src/main/jni/opencv_test_wrap.cpp", "${projectDir}/src/main/jni/opencv_test.i"
}
(1) commandLine is a command to refer to swig, unlike [2], it must be an ABSOLUTE path to swig. Otherwise, gradle probably fails to find SWIG.
(2) args is for defining arguments of SWIG. Note that the last argument is the configuration file opencv_test.i .

V. Configure your NDK in Gradle
The basic purpose of NDK is to compile all your C/C++ codes to a *.so file, which can be further loaded in your android code. Two files are supposed to be configured NDK: Android.mk and Application.mk

There is a example for Android.mk.
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

OPENCV_ROOT := path to your andorid sdk/OpenCV-android-sdk
#OPENCV_CAMERA_MODULES:=on
#OPENCV_INSTALL_MODULES:=on
include ${OPENCV_ROOT}/sdk/native/jni/OpenCV.mk

LOCAL_SRC_FILES  := opencv_test_wrap.cpp opencv_test.cpp
LOCAL_C_INCLUDES += $(LOCAL_PATH)
LOCAL_LDLIBS     += -llog -ldl
LOCAL_MODULE     := opencv_test
 
include $(BUILD_SHARED_LIBRARY)
(1) You have to include the path to OpenCV.mk, which usually locates in the OpenCV SDK for Android.
(2) In addition to your source C/C++ file, you need to add the JNI files generated by SWIG, like open_test_wrapp.cpp in our sample.
(3) opencv_test is the library name, which you can refer from your Android code.


VI. Import OpenCV JAVA library to your project
To write your OpenCV android code, you have to refer to the library provided by OpenCV. It is distributed as the normal project. You can use Android Studio (NOT Intellij) to import it to your project and set your module to depends on the library. This solution is the first four steps of answer [5].

VII. Call you native code

(1) Before calling the native code, you need to load the library.
System.loadLibrary("opencv_test");

(2) Since all the C/C++ methods that wrapped in JAVA are static ones in the class, you just use the classname to refer them.
In our sample, the generated JAVA class file opencv_test_wrapper.java, so just use opencv_test_wrapper to refer each method.

NOTE: if some methods are not visible to your code, please check the generated java code. Some of them might be set as protected.

(3) Make sure your OpenCV-android is the same version as your OpenCV OSX. If they do not match, you would probably cannot pass data like Mat from the Java code to C/C++ code.


Then, you are done. I will upload my project to github later.

References
[1] http://hujiaweibujidao.github.io/blog/2014/10/22/android-ndk-and-opencv-development-with-android-studio/
[2] http://www.sureshjoshi.com/mobile/android-ndk-in-android-studio-with-swig/
[3] http://docs.opencv.org/doc/tutorials/introduction/android_binary_package/android_dev_intro.html
[4] http://www.swig.org/tutorial.html
[5] http://stackoverflow.com/a/27356635

No comments:

Post a Comment