Caffe轉NCNN並移植Android配置記錄
實驗目的:
實驗環境:
1、系統環境
2、軟體
實驗過程
1、實驗準備
- tools是後面需要用到的一些工具程式碼,包含了將各種網路轉換到NCNN的程式碼
2、編譯好的caffe原始碼用於後面轉換模型使用
2、編譯NCNN
(1)參照:https://github.com/Tencent/ncnn/wiki/how-to-build
中選擇一個需要的環境編譯,因為我需要在Android上面使用,所以選擇了“Build for Android”:
這裡首先需要安裝NDK來編譯Android專案,配置NDK環境有以下兩種方式:
-
使用Android Studio來直接安裝:
在偏好設定中進行如上圖所示的配置,就可以配置NDK編譯環境以及相關工具,安裝好後NDK存放在上面的sdk目錄下的ndk-bundle資料夾中 -
自己到網站上面下載的方式:
下載網址為:http://developer.android.com/ndk/downloads/index.html
選擇合適的版本下載(因為上面的第一種方法雖然簡單,但是預設下載最新的NDK,在編譯的時候可能會出現後面我會講到的一些問題,所以這種方式可以根據實際需要選擇合適的版本)
解壓上面下載的NDK壓縮包
使用下面的命令配置環境變數:
vim ~/.bash_profile # 在.bash_profile檔案的最後新增上(路徑根據自己的進行修改): export PATH=$PATH:/Users/camlin_z/Data/Project/AndroidStudioProjects/android-ndk-r10e # 或者想把環境變數新增成Android Studio配置的NDK的話: export ANDROID_SDK="/Users/camlin_z/Library/Android/sdk" export ANDROID_NDK="/Users/camlin_z/Library/Android/sdk/ndk-bundle" export PATH="$PATH:$ANDROID_SDK/tools:$ANDROID_SDK/platform-tools:$ANDROID_NDK" source ~/.bash_profile
或者想要替換Android Studio中的NDK環境為自己下的版本的話將上面下載的NDK壓縮包重新命名為ndk-bundle後放到sdk目錄下即可
(2)編譯libncnn.a
根據上面ncnn的github下的教程有:
$ cd <ncnn-root-dir> $ mkdir -p build-android-armv7 $ cd build-android-armv7 $ cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \ -DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON \ -DANDROID_PLATFORM=android-14 .. $ make -j4 $ make install
即可“build armv7 library”,之後便會在build-android-armv7/install/lib目錄下生成libncnn.a,這樣ncnn的編譯工作就完成了
3、使用NCNN將caffemodel轉換成NCNN中需要的格式
train.prototxt
deploy.prototxt
snapshot_10000.caffemodel
然後使用之前編譯好的caffe中build/tools資料夾下的upgrade_net_proto_text和upgrade_net_proto_binary兩個檔案分別處理模型以及權重檔案:
upgrade_net_proto_text [old prototxt] [new prototxt]
upgrade_net_proto_binary [old caffemodel] [new caffemodel]
同時要更改資料層的batchsize大小為1:
layer {
name: "data"
type: "Input"
top: "data"
input_param { shape: { dim: 1 dim: 3 dim: 227 dim: 227 } }
}
經過上面的步驟就準備好了需要轉換的模型和權重檔案。
接下來進入之前clone的 ncnn工程檔案:
cd tools/caffe
mkdir build
cmake ..
make -j4
就可以在build資料夾中生成caffe2ncnn.cpp對應的可執行檔案caffe2ncnn,最後執行:
caffe2ncnn deploy.prototxt bvlc_alexnet.caffemodel alexnet.param alexnet.bin
就可以得到最後轉化的權重以及模型檔案:alexnet.param alexnet.bin
4、編譯jni生成了.so庫檔案
進入剛剛ncnn工程下的examples中,這是一個用squeeze net作為例子來生成動態連結庫的例子,可以看到examples下面有已經按照3中步驟生成好的squeeze net對應的權重和模型檔案,
進入的squeezencnn/jni資料夾中,可以看到如下檔案架結構:
其中的cpp和h就是我們需要編寫的C++檔案和標頭檔案,其中包含以下幾個部分:
- 我們需要的C++功能函式以及對應的標頭檔案
- C++和java之間的jni介面函式,用於兩者之間的資訊互通
然後在終端使用
ndk-build
命令就可以將上面的檔案打包成一個 .so動態連結庫供Android呼叫,可以參考:
https://blog.csdn.net/CrazyMo_/article/details/52804896 中的講解,下面我以squeeze net這個例子簡單說明一下安卓呼叫的過程:
首先是Android Studio工程中的結構為:
實際上上圖中的工程順序也就是我們建立我們工程的順序:
- 按照上面3中的步驟轉換的模型就放在assets目錄下
- 然後我們除了MainActivity.java,就可以定義一個自己需要的函式介面類程式碼,比如這裡的SqueezeNcnn.java,裡面的內容為:
package com.tencent.squeezencnn;
import android.graphics.Bitmap;
import android.content.Context;
public class SqueezeNcnn
{
// 我們自己定義的類方法,用於實現我們自己的功能(這裡可以看到是java)
public native boolean Init(byte[] param, byte[] bin, byte[] words);
public native String Detect(Bitmap bitmap);
static {
System.loadLibrary("squeezencnn");
}
}
- 接著就是JNI程式碼了,這個部分實際上包含了實現功能的C/C++程式碼以及jni介面函式兩部分,通過上面的生成,我們得到了squeezenet_v1.1.id.h和squeezencnn_jni.cpp,對應於上面SqueezeNcnn.java中的類方法,squeezencnn_jni.cpp中有對應的JNI介面函式:
(函式具體內容大家可以到ncnn工程中檢視,這裡為了說明方便隱去內容)
可以看到jni介面函式是在java類函式的前面加上了
Java_com_tencent_squeezencnn_SqueezeNcnn_
部分,將java的native方法轉換成C函式宣告的規則是這樣的:Java_{package_and_classname}_{function_name}(JNI arguments)。包名中的點換成單下劃線。需要說明的是生成函式中的兩個引數:
JNIEnv *:這是一個指向JNI執行環境的指標,後面我們會看到,我們通過這個指標訪問JNI函式
jobject:這裡指代java中的this物件
而對於一些不是介面的功能函式,我們就可以使用C++或者C來編寫,而不需要考慮jni
- 最後就是將上面的程式碼編譯成libsqueezencnn.so動態庫
這裡我們首先需要編寫jni目錄下的編譯配置檔案 Android.mk 和 Application.mk ,類似於C++編譯中的CMakeLists.txt:
Android. mk :
LOCAL_PATH := $(call my-dir)
# change this folder path to yours
NCNN_INSTALL_PATH := /Users/camlin_z/Data/Project/AndroidStudioProjects/ncnn-master/build-android-armv7/install
include $(CLEAR_VARS)
LOCAL_MODULE := ncnn
# LOCAL_SRC_FILES := $(NCNN_INSTALL_PATH)/$(TARGET_ARCH_ABI)/libncnn.a
LOCAL_SRC_FILES := $(NCNN_INSTALL_PATH)/lib/libncnn.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := squeezencnn
LOCAL_SRC_FILES := squeezencnn_jni.cpp
LOCAL_C_INCLUDES := $(NCNN_INSTALL_PATH)/include
LOCAL_STATIC_LIBRARIES := ncnn
LOCAL_CFLAGS := -O2 -fvisibility=hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math
LOCAL_CPPFLAGS := -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math
LOCAL_LDFLAGS += -Wl,--gc-sections
LOCAL_CFLAGS += -fopenmp
LOCAL_CPPFLAGS += -fopenmp
LOCAL_LDFLAGS += -fopenmp
LOCAL_LDLIBS := -lz -llog -ljnigraphics
include $(BUILD_SHARED_LIBRARY)
Application. mk:
# APP_STL := stlport_static
APP_STL := gnustl_static
# APP_ABI := armeabi armeabi-v7a
# 注意此處喲啊對應你之前編譯ncnn時的版本,比如我之前用的就是armeabi-v7a
# 下面就要指定為armeabi-v7a,不能再有後面的arm64-v8a
APP_ABI := armeabi-v7a #arm64-v8a
APP_PLATFORM := android-14
# NDK_TOOLCHAIN_VERSION := 4.9
寫好上面的各個配置檔案之後就可以在終端進入jni資料夾輸入:
ndk-build
命令進行編譯生成 libsqueezencnn. so動態連結庫,經過了以上的所有步驟得到最後的動態連結庫,Android中的函式就可以直接呼叫來實現對應的功能了