Ncnn使用详解(2)——Android端 - DmrfCoder的博客 - CSDN博客
摘要
本片文章基于你已经完成了 这篇文章的学习,主要介绍如何将写好的c代码应用到Android项目中。
环境说明
系统:Ubuntu16.04
软件:Android Studio
前期准备之 ndk安装
在正式开始前我们需要先下载安装ndk,这里介绍一种简单高效的方式,打开Android Studio,然后依次点击File->Settings->Appearance&Behavior->System Settings->Android SDK,然后在SDK Tools下找到ndk,然后选中,点击apply就可以自动下载安装了,如图:
完成之后在你的sdk目录下会多出一个ndk-bundle的包,这就是你ndk的路径,类似下图:
至此,ndk已经安装完毕了,下一步是配置ndk的环境变量:
首先打开profile:
sudo vim /etc/profile
- 1
打开后在profile文件的末尾加上:
export NDK_HOME=sdkroot/ndk-bundle
PATH=$NDK_HOME:$PATH
- 1
- 2
sdkroot是你的sdk目录,每个人的不一样,视情况而定,下面是我的配置 截图:
添加完成后保存退出,使用以下命令使配置的环境变量生效:
source /etc/profile
- 1
验证ndk是否配置成功:
ndk-build -v
- 1
出现类似以下输出即说明ndk配置成功:
编译ncnn sdk
我们需要将ncnn打包,这样我们才能在android ndk的代码中使用include
mkdir build-android
cd build-android
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON \
-DANDROID_PLATFORM=android-14 ..
make
make install
- 1
- 2
- 3
- 4
- 5
- 6
- 7
参数说明:
ANDROID_ABI 是架构名字,"armeabi-v7a" 支持绝大部分手机硬件
ANDROID_ARM_NEON 是否使用 NEON 指令集,设为 ON 支持绝大部分手机硬件
ANDROID_PLATFORM 指定最低系统版本,"android-14" 就是 android-4.0
- 1
- 2
- 3
- 4
- 5
你可以根据自己的需要设置自己的参数,详见 这里
完成后你就可以在ncnn/build-android下找到install了,大概如图:
install下有include和lib两个文件夹,这两个文件夹下的东西后面会用到。
进行ndk开发
Android可以通过ndk-build和cmake两种方式来编译c,而且官方比较推荐的是cmake的方式,但是我用cmake试了好长时间一直报各种诡异的错误,应该是我还没有学到ncnn in ndk with cmake的正确打开方式,所以这里介绍一下使用ndk-build这种方式编译c,步骤如下:
我这里新建了一个android 的demo项目,项目结构如下:
主要是assets文件夹下放置你的bin和param文件,jni文件夹下放置你的cpp和两个mk文件,具体内容下面会介绍(可以直接在对应位置新建这两个文件夹),然后要修改你的app gradle文件:
对应的内容你可以根据自己的情况修改,然后配置两个mk文件:
* Android.mk
LOCAL_PATH := $(call my-dir)
#把这个路径改成你自己刚才编译的install路径,用全路径!
NCNN_INSTALL_PATH := ncnn-master/build-android/install
include $(CLEAR_VARS)
LOCAL_MODULE := ncnn
LOCAL_SRC_FILES := $(NCNN_INSTALL_PATH)/lib/libncnn.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := demo
#这个是你的cpp文件
LOCAL_SRC_FILES := demo.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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- Application.mk
# APP_STL := stlport_static
APP_STL := gnustl_static
# APP_ABI := armeabi armeabi-v7a
APP_ABI := armeabi-v7a
APP_PLATFORM := android-9
#NDK_TOOLCHAIN_VERSION := 4.9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这两个.mk文件我的建议是复制粘贴到你的项目里,只改动必要的文件路径,其余的参数别动,除非你直到你改的意思是什么~
因为ndk的原理是使用 java接口调用c代码,所以我们需要进行java接口的编写,给出一个示例代码:
public class Ncnn
{
public native boolean InitNcnn(String gestureDetectionModelPath);
public native void Detect(float[] i,float[] q,float []scores,int[] a);
static {
System.loadLibrary("demo");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
一共两个方法,一个是初始化,一个是执行预测,需要注意的是初始化方法调用的时候需要传入一个二进制文件路径的参数,大概思路是把bin和param文件拷贝到手机上然后让c代码读取,这里给出模板代码:
private void IniteNcnn() throws IOException {
ncnn = new Ncnn();//实例化上面的java接口类
try {
copyBigDataToSD("demo.bin");
copyBigDataToSD("demo.param");
} catch (IOException e) {
e.printStackTrace();
}
//模型初始化
File sdDir = Environment.getExternalStorageDirectory();//获取跟目录
String sdPath = sdDir.toString() + "/gesturencnn/";
boolean b = ncnn.InitNcnn(sdPath);
}
private void copyBigDataToSD(String strOutFileName) throws IOException {
File sdDir = Environment.getExternalStorageDirectory();
File file = new File(sdDir.toString() + "/demo/");
if (!file.exists()) {
file.mkdir();
}
String tmpFile = sdDir.toString() + "/demo/" + strOutFileName;
File f = new File(tmpFile);
if (f.exists()) {
return;
}
InputStream myInput;
java.io.OutputStream myOutput = new FileOutputStream(sdDir.toString() + "/demo/" + strOutFileName);
myInput = context.getAssets().open(strOutFileName);
byte[] buffer = new byte[1024];
int length = myInput.read(buffer);
while (length > 0) {
myOutput.write(buffer, 0, length);
length = myInput.read(buffer);
}
myOutput.flush();
myInput.close();
myOutput.close();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
直接在你需要的地方调用IniteNcnn()就可以了,需要注意的是要 进行内存卡读取权限的申请哦
然后将你上一篇教程写的demo.cpp放在jni目录下,对里面的代码进行必要的修改,主要需要实现模型的初始化和执行预测两个函数,初始化这里给出一个模板,至于执行预测的函数直接写一个对应函数然后调用你之前写好的c代码就可以了:
- 初始化
这个函数是通用的,建议全部复制粘贴,具体对应的java接口代码后面会介绍
extern "C"{
#注意把这里的函数名改成你自己对应的,一定不能错!
JNIEXPORT jboolean JNICALL
Java_com_example_dmrf_JniClass_Ncnn_InitNcnn(JNIEnv *env, jobject instance,
jstring DetectionModelPath_) {
const char *DetectionModelPath = env->GetStringUTFChars(DetectionModelPath_, 0);
if (NULL == DetectionModelPath) {
return false;
}
string tModelDir = DetectionModelPath;
string tLastChar = tModelDir.substr(tModelDir.length() - 1, 1);
if ("\\" == tLastChar) {
tModelDir = tModelDir.substr(0, tModelDir.length() - 1) + "/";
} else if (tLastChar != "/") {
tModelDir += "/";
}
std::vector<std::string> param_files;
param_files.resize(1);
param_files[0] = tModelDir + "/demo.param";
std::vector<std::string> bin_files;
bin_files.resize(1);
bin_files[0] = tModelDir + "/demo.bin";
squeezenet.load_param(param_files[0].data());
squeezenet.load_model(bin_files[0].data());
env->ReleaseStringUTFChars(DetectionModelPath_, DetectionModelPath);
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
至此所有代码已经编写完毕,然后就可以build了,步骤如下:
cd到src/main/jni目录下,执行ndk-build,然后就会生成.so文件,然后你就可以干你的后序工作了。