How to include only OpenCV Android library that I need?

Viewed 195

I am trying to use OpenCV library for my Android app (OpenCV 4.6.0). But the size of my app is really big after I added the library. I want to only include the part of the library that I use, which is imgproc and core (specifically Sobel edge detection). I have tried to edit the CMakeLists.txt (which is located on libcxx_helper) to this:

cmake_minimum_required(VERSION 3.6)

# dummy target to bring libc++_shared.so into packages
add_library(opencv_jni_shared STATIC dummy.cpp)

set(OpenCV_DIR D:/Project/AndroidStudioProject/SobelProject/sdk/native/jni)

find_package(OpenCV REQUIRED core imgproc)
add_executable(opencv_sobel dummy.cpp)
target_link_libraries(opencv_sobel core imgproc ${EXTERNAL_LIBS})

I expect there will be a libopencv_sobel.so generated from the above CMakeLists.txt, but nothing is generated. So, I load the library with this:

init {
    System.loadLibrary('opencv_java4')
}

Then I run the app. But the size of my app is still the same. I am unfamiliar with C++ and NDK.

Any help would be very appreciated.

EDIT:

I follow exactly the same step from the documentation @Rohit Bhati gave.

This is my Android.mk

include $(CLEAR_VARS)

OPENCV_INSTALL_MODULES:=on

include D:\Project\AndroidStudioProject\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk

OPENCV_CAMERA_MODULES:=off
OPENCV_LIB_TYPE:=STATIC

and this is my Application.mk

APP_STL := c++_static # I changed it from gnustl_static because it is not supported anymore
APP_CPPFLAGS := -frtti -fexceptions

APP_ABI := arm64-v8a

APP_PLATFORM := android-21

both located at {myProject}/app/jni then I ran the ndk-build. After it completes, a libopencv_java4.so file is generated inside the libs/armv64-v8 folder, but the size is still the same.

I look into the documentation but I didn't found how to specify what library I want to include. Also, what is the purpose of this code

OPENCV_CAMERA_MODULES:=off

because it doesn't reduce the .so generated file size

EDIT 2

I followed the step from the related link by @Rohit Bhati with a slight different because the steps seems old.

/d/Project/AndroidStudioProject/OpenCV-android-sdk/sdk/native/staticlibs/arm64-v8a
$ D:/Android/Sdk/ndk/25.0.8775105/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang++ -shared -o libopencv_java4.so --sysroot=D:/Android/Sdk/ndk/25.0.8775105/toolchains/llvm/prebuilt/windows-x86_64/sysroot -Wl,--whole-archive libopencv_core.a libopencv_imgproc.a -Wl,--no-whole-archive

$ D:/Android/Sdk/ndk/25.0.8775105/toolchains/llvm/prebuilt/windows-x86_64/bin/llvm-strip libopencv_java4.so

A new libopencv_java4.so file then generated with smaller size. Then, I copy it to opencv-sdk/native/libs/armv64-v8a replacing the old file (which is bigger one). But, I get this error

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "_ZN3tbb4task13note_affinityEt" referenced by "/data/app/id.indevelopment.edgepaint-pG-fG4NFi7Mj7szExjH9ww==/lib/arm64/libopencv_java4.so"...
        at java.lang.Runtime.loadLibrary0(Runtime.java:1071)
        at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
        at java.lang.System.loadLibrary(System.java:1667)
        at id.indevelopment.edgepaint.detector.EdgeDetection.<init>(EdgeDetection.kt:17)
        at id.indevelopment.edgepaint.di.AppModuleKt$detectorModule$1$1.invoke(AppModule.kt:16)
        at id.indevelopment.edgepaint.di.AppModuleKt$detectorModule$1$1.invoke(AppModule.kt:16)
        at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:54)
        at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:38)
        at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:111)
        at org.koin.core.scope.Scope.resolveValue(Scope.kt:255)
        at org.koin.core.scope.Scope.resolveInstance(Scope.kt:242)
        at org.koin.core.scope.Scope.get(Scope.kt:205)
        at id.indevelopment.edgepaint.di.AppModuleKt$viewModelModule$1$1.invoke(AppModule.kt:22)
        at id.indevelopment.edgepaint.di.AppModuleKt$viewModelModule$1$1.invoke(AppModule.kt:11)
        at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:54)
        at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:38)
        at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:111)
        at org.koin.core.scope.Scope.resolveValue(Scope.kt:255)
        at org.koin.core.scope.Scope.resolveInstance(Scope.kt:242)
        at org.koin.core.scope.Scope.get(Scope.kt:205)
        at org.koin.androidx.viewmodel.factory.DefaultViewModelFactory.create(DefaultViewModelFactory.kt:13)
        at androidx.lifecycle.ViewModelProvider$Factory.create(ViewModelProvider.kt:83)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
        at id.indevelopment.edgepaint.ui.main.MainActivity.getViewModel(MainActivity.kt:77)
        at id.indevelopment.edgepaint.ui.main.MainActivity.setUpViewModel(MainActivity.kt:152)
        at id.indevelopment.edgepaint.ui.main.MainActivity.onCreate(MainActivity.kt:112)
        at android.app.Activity.performCreate(Activity.java:7893)
        at android.app.Activity.performCreate(Activity.java:7880)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1306)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3310)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3484)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2068)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7551)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995)
2 Answers

Finally, I'm able to find a decent solution to my problem, which is to build the OpenCV from sources with only the library that I needed. I will share the solution here in case anyone came across a similar situation.

Python is required for this solution. The step is:

  1. Get the OpenCV latest sources (in zip) from the GitHub.

  2. Extract the zip file and open the folder (you should be at the root folder). Skip step 3 if you use imgcodecs.

  3. Now, from the root folder, go to modules/java/generator/android/java/org/opencv/android and open Utils.java on any text editor. Comment all code that uses Imgcodecs class including the import statement. If you don't comment it, you will get an error when running the command at step 5 as it is trying to find an imgcodecs module which we don't include.

  4. Back to the root folder and open a command prompt.

  5. Run python platforms/android/build_sdk.py {output_path} ./ --ndk_path={your_ndk_path} --sdk_path={your_sdk_path} --modules_list="core,imgproc,java" --no_samples_build --config=ndk-22.config.py

    On the modules_list, java is required to generate the java wrapper. There are some other options available which you can look at the build_sdk.py code

  6. Wait for the process to complete and you will get the sdk at {output_path}/OpenCV-android-sdk/sdk

Now, the only problem with this solution is it runs 2-3x slower compared to the (all modules) sdk that OpenCV provided here (which is the sdk that I use before).

I will update it here if I found any solution regarding the speeds or if anyone have the solution, please kindly share it.

Use Static library linking which will only pull in the functionality you actually want to use in your project instead of the whole library. This link will be helpful Here

or else you can checkout the same sample project of static linking on Github. Hope it helps.

Related