OpenCV for Androidのlibopencv_java4.soのバイナリサイズを減らし、APKのサイズを減らす

# libopencv_java4.soは（多くの場合）無駄を含んでいる

OpenCVはコンパイル済みのAndroid用ビルドが公式で配布されています。
https://github.com/opencv/opencv/releases

opencv-4.0.1-android-sdk.zipをダウンロードし、共有ライブラリlibopencv_java4.soとjavaファイル群をAndroidプロジェクトの所定の位置に置くと、AndroidでOpenCVの処理をJava/Kotlinから使うことができます。

しかし共有ライブラリlibopencv_java4.soは多くの場合無駄を含んでいます。

nmコマンドでJNI関数のシンボルを確認してみます。

```bash
wget https://github.com/opencv/opencv/releases/download/4.0.1/opencv-4.0.1-android-sdk.zip
unzip opencv-4.0.1-android-sdk.zip
cd OpenCV-android-sdk/
nm -D sdk/native/libs/arm64-v8a/libopencv_java4.so  | less
```

シンボル名のJava_org_opencv_の次に注目してください。

```
前略
00000000001f0918 T Java_org_opencv_calib3d_StereoSGBM_setUniquenessRatio_10
中略
000000000018e4bc T Java_org_opencv_core_Algorithm_clear_10
中略
00000000001c9828 T Java_org_opencv_dnn_DictValue_DictValue_10
中略
00000000001e8830 T Java_org_opencv_features2d_AKAZE_create_10
中略
00000000001d746c T Java_org_opencv_imgcodecs_Imgcodecs_haveImageReader_10
中略
00000000001a50d8 T Java_org_opencv_imgproc_CLAHE_apply_10
中略
00000000001bb888 T Java_org_opencv_ml_ANN_1MLP_create_10
中略
000000000020fb5c T Java_org_opencv_objdetect_BaseCascadeClassifier_delete
中略
00000000001c7034 T Java_org_opencv_photo_AlignExposures_delete
中略
000000000021db0c T Java_org_opencv_video_BackgroundSubtractorKNN_delete
中略
00000000001da178 T Java_org_opencv_videoio_VideoCapture_VideoCapture_10
以下略
```

それら、calib3d、core、dnn、features2d等はそれぞれOpenCVのモジュールに相当します。多くの場合、持っているモジュールをすべて使うことは無いと思います。私はcore、imgcodecs、imgprocしか使いません。今回はその３つだけを持つlibopencv_java4.soを自分でビルドします。

# 2022年版を書きました。

この記事のやり方は2022年の最新のOpenCV、Android SDK、Android NDKではできなかったので、新たにDockerを使用したビルド方法の記事を書きました。

[【2022年版】OpenCV for Androidのlibopencv_java4.soのバイナリサイズを減らし、APKのサイズを減らす](https://qiita.com/tfandkusu/items/d43f3adce0f60e294f43)

以降は2022年版をご参照ください。

# ビルド環境の構築

Android NDKをAndroid Studioからインストールします。

Android SDK、Android NDKのパスを環境変数に設定します。

```bash:.bash_profile
export ANDROID_SDK=$HOME/Library/Android/sdk
export ANDROID_NDK=$ANDROID_SDK/ndk-bundle/
```

Python3をpyenv等でセットアップします。

cmake、ninja、ccacheをインストールします。

```bash
# Macの場合
brew install cmake ninja ccache
# Ubuntuの場合はaptコマンド
```
# ビルドスクリプトの編集

ソースコードをダウンロード、展開し、ビルドスクリプトを編集します。

```bash
wget https://github.com/opencv/opencv/archive/4.0.1.tar.gz
tar -xvzf 4.0.1.tar.gz
cd opencv-4.0.1/platforms/
# エディタで android/build_sdk.py を開く
```

build_library関数のcmake_varsを編集します。このスクリプトは内部でcmakeコマンドを呼んでいて、この編集はcmakeの引数を変更するものになります。

```python:android/build_sdk.py
    def build_library(self, abi, do_install):
        cmd = ["cmake", "-GNinja"]
        cmake_vars = dict(
            CMAKE_TOOLCHAIN_FILE=self.get_toolchain_file(),
            INSTALL_CREATE_DISTRIB="ON",
            WITH_OPENCL="OFF",
            WITH_IPP=("ON" if abi.haveIPP() else "OFF"),
            WITH_TBB="ON",
            BUILD_EXAMPLES="OFF",
            BUILD_TESTS="OFF",
            BUILD_PERF_TESTS="OFF",
            BUILD_DOCS="OFF",
            BUILD_ANDROID_EXAMPLES="OFF", # ビルドに失敗するのでOFF
            INSTALL_ANDROID_EXAMPLES="OFF",  # ビルドに失敗するのでOFF
            BUILD_LIST="core,imgcodecs,imgproc,java" # 追加する
        )
```

用途に応じてBUILD_LISTを変更します。例えば局所特徴量が必要な場合はfeatures2dを追加します。

あとはこのスクリプトを実行するだけです。ビルドが始まります。

```bash
android/build_sdk.py
```

# 生成物

OpenCV-android-sdkディレクトリにlibopencv_java4.soとjavaファイル群が作成されました。

```bash
find OpenCV-android-sdk/sdk/native/libs -type f
```
```
OpenCV-android-sdk/sdk/native/libs/armeabi-v7a/libopencv_java4.so
OpenCV-android-sdk/sdk/native/libs/x86/libopencv_java4.so
OpenCV-android-sdk/sdk/native/libs/arm64-v8a/libopencv_java4.so
OpenCV-android-sdk/sdk/native/libs/x86_64/libopencv_java4.so
```

```bash
find OpenCV-android-sdk/sdk/java/src -type f
```

```
OpenCV-android-sdk/sdk/java/src/org/opencv/core/Scalar.java
OpenCV-android-sdk/sdk/java/src/org/opencv/core/MatOfFloat6.java
OpenCV-android-sdk/sdk/java/src/org/opencv/core/Size.java
以下略
```

シンボルを確認すると、確かにcore、imgcodecs、imgprocしか含まれていません。

```bash
nm -D OpenCV-android-sdk/sdk/native/libs/arm64-v8a/libopencv_java4.so | less
```

```
前略
00000000000fc2c4 T Java_org_opencv_core_Algorithm_clear_10
中略
0000000000116cb8 T Java_org_opencv_imgcodecs_Imgcodecs_haveImageReader_10
中略
0000000000114044 T Java_org_opencv_imgproc_CLAHE_apply_10
以下略
```

# 削減した容量

libopencv_java4.soに含めるモジュールをcore,imgcodecs,imgprocだけにすると、以下のようにバイナリサイズを削減できました。

|ABI|元のファイルサイズ|今回のファイルサイズ|
|---|---:|---:|
|armeabi-v7a|9MB|5MB|
|arm64-v8a|17MB|9MB|


# 追記 ABIがx86とx86_64のsoファイルの容量を削減する

build_sdk.pyをもう1行変更しないと、ABIがx86とx86_64の容量が大きいままでした。

```android/build_sdk.py
def haveIPP(self):
    return False #self.name == "x86" or self.name == "x86_64"
```

|ABI| haveIPP関数そのまま | haveIPP関数の戻り値False |
|---|---:|---:|
|x86|25M|10M|
|x86_64|39M|11M|