1. 程式人生 > >Caffe轉NCNN並移植Android配置記錄

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.mkApplication.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中的函式就可以直接呼叫來實現對應的功能了