https://blog.csdn.net/qq_33200967/article/details/82421089
原文博客:Doi技术团队
链接地址:https://blog.doiduoyi.com/authors/1584446358138
初心:记录优秀的Doi技术团队学习经历
前言
在之前笔者有介绍过《在Android设备上使用PaddleMobile实现图像分类》,使用的框架是百度开源的PaddleMobile。在本章中,笔者将会介绍使用腾讯的开源手机深度学习框架ncnn来实现在Android手机实现图像分类,这个框架开源时间比较长,相对稳定很多。
ncnn的GitHub地址:https://github.com/Tencent/ncnn
使用Ubuntu编译ncnn库
1、首先要下载和解压NDK。
wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip
unzip android-ndk-r17b-linux-x86_64.zip
- 1
- 2
2、设置NDK环境变量,目录是NDK的解压目录。
export ANDROID_NDK="/home/test/paddlepaddle/android-ndk-r17b"
- 1
设置好之后,可以使用以下的命令查看配置情况。
root@test:/home/test/paddlepaddle# echo $NDK_ROOT
/home/test/paddlepaddle/android-ndk-r17b
- 1
- 2
3、安装cmake,需要安装较高版本的,笔者的cmake版本是3.11.2。
下载cmake源码
wget https://cmake.org/files/v3.11/cmake-3.11.2.tar.gz
- 1
解压cmake源码
tar -zxvf cmake-3.11.2.tar.gz
- 1
进入到cmake源码根目录,并执行bootstrap。
cd cmake-3.11.2
./bootstrap
- 1
- 2
最后执行以下两条命令开始安装cmake。
make
make install
- 1
- 2
安装完成之后,可以使用cmake --version是否安装成功。
root@test:/home/test/paddlepaddle# cmake --version
cmake version 3.11.2
CMake suite maintained and supported by Kitware (kitware.com/cmake).
- 1
- 2
- 3
- 4
4、克隆ncnn源码。
git clone https://github.com/Tencent/ncnn.git
- 1
5、编译源码。
# 进入到ncnn源码根目录下
cd ncnn
# 创建一个新的文件夹
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 ..
# 这里笔者使用4个行程并行编译
make -j4
make install
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
6、编译完成,会在build-android-armv7
目录下生成一个install
目录,我们编译得到的文件都在该文件夹下:
include
调用ncnn所需的头文件,该文件夹会存放在Android项目的src/main/cpp
目录下;lib
编译得到的ncnn库libncnn.a
,之后会存放在Android项目的src/main/jniLibs/armeabi-v7a/libncnn.a
转换预测模型
1、克隆Caffe源码。
git clone https://github.com/BVLC/caffe.git
- 1
2、编译Caffe源码。
# 切换到Caffe目录
cd caffe
# 在当前目录执行cmake
cmake .
# 使用4个线程编译
make -j4
make install
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3、升级Caffe模型。
# 把需要转换的模型复制到caffe/tools,并切入到该目录
cd tools
# 升级Caffe模型
./upgrade_net_proto_text mobilenet_v2_deploy.prototxt mobilenet_v2_deploy_new.prototxt
./upgrade_net_proto_binary mobilenet_v2.caffemodel mobilenet_v2_new.caffemodel
- 1
- 2
- 3
- 4
- 5
4、检查模型配置文件,因为只能一张一张图片预测,所以输入要设置为dim: 1
。
name: "MOBILENET_V2"
layer {
name: "input"
type: "Input"
top: "data"
input_param {
shape {
dim: 1
dim: 3
dim: 224
dim: 224
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
5、切换到ncnn的根目录,就是我们上一部分克隆的ncnn源码。
cd ncnn/
- 1
6、在根目录下编译ncnn源码。
mkdir -p build
cd build
cmake ..
make -j4
make install
- 1
- 2
- 3
- 4
- 5
7、把新的Caffe模型转换成NCNN模型。
# 经过上一步,会生产一个tools,我们进入到以下目录
cd tools/caffe/
# 把已经升级的网络定义文件和权重文件复制到当目录,并执行以下命令
./caffe2ncnn mobilenet_v2_deploy_new.prototxt mobilenet_v2_new.caffemodel mobilenet_v2.param mobilenet_v2.bin
- 1
- 2
- 3
- 4
8、对象模型参数进行加密,这样就算别人反编译我们的apk也用不了我们的模型文件。把上一步获得的mobilenet_v2.param
、mobilenet_v2.bin
复制到该目录的上一个目录,也就是tools目录。
# 切换到上一个目录
cd ../
# 执行命令之后会生成mobilenet_v2.param、mobilenet_v2.id.h、mobilenet_v2.mem.h
./ncnn2mem mobilenet_v2.param mobilenet_v2.bin mobilenet_v2.id.h mobilenet_v2.mem.h
- 1
- 2
- 3
- 4
经过上面的步骤,得到的文件中,以下文件时需要的:
mobilenet_v2.param.bin
网络的模型参数;mobilenet_v2.bin
网络的权重;mobilenet_v2.id.h
在预测图片的时候使用到。
开发Android项目
- 我们在Android Studio上创建一个NCNN1的项目,别忘了选择C++支持。
其他的可以直接默认就可以了,在这里要注意选择C++11支持。
-
在
main
目录下创建assets
目录,并复制以下目录到该目录: -
mobilenet_v2.param.bin
上一步获取网络的模型参数; -
mobilenet_v2.bin
上一步获取网络的权重; -
synset.txt
label对应的名称,下载地址:https://github.com/shicai/MobileNet-Caffe/blob/master/synset.txt。 -
在
cpp
目录下复制在使用Ubuntu编译NCNN库
部分编译得到的include
文件夹,包括里面的C++头文件。 -
把
mobilenet_v2.id.h
复制到cpp
目录下。 -
在main目录下创建
jniLibs/armeabi-v7a/
目录,并把使用Ubuntu编译NCNN库
部分编译得到的libncnn.a
复制到该目录。 -
在
cpp
目录下创建一个C++文件,并编写以下代码,这段代码是用于加载模型和预测图片的:
#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <string>
#include <vector>
// ncnn
#include "include/net.h"
#include "mobilenet_v2.id.h"
#include <sys/time.h>
#include <unistd.h>
static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
static ncnn::PoolAllocator g_workspace_pool_allocator;
static ncnn::Mat ncnn_param;
static ncnn::Mat ncnn_bin;
static ncnn::Net ncnn_net;
extern "C" {
// public native boolean Init(byte[] param, byte[] bin, byte[] words);
JNIEXPORT jboolean JNICALL
Java_com_example_ncnn1_NcnnJni_Init(JNIEnv *env, jobject thiz, jbyteArray param, jbyteArray bin) {
// init param
{
int len = env->GetArrayLength(param);
ncnn_param.create(len, (size_t) 1u);
env->GetByteArrayRegion(param, 0, len, (jbyte *) ncnn_param);
int ret = ncnn_net.load_param((const unsigned char *) ncnn_param);
__android_log_print(ANDROID_LOG_DEBUG, "NcnnJni", "load_param %d %d", ret, len);
}
// init bin
{
int len = env->GetArrayLength(bin);
ncnn_bin.create(len, (size_t) 1u);
env->GetByteArrayRegion(bin, 0, len, (jbyte *) ncnn_bin);
int ret = ncnn_net.load_model((const unsigned char *) ncnn_bin);
__android_log_print(ANDROID_LOG_DEBUG, "NcnnJni", "load_model %d %d", ret, len);
}
ncnn::Option opt;
opt.lightmode = true;
opt.num_threads = 4;
opt.blob_allocator = &g_blob_pool_allocator;
opt.workspace_allocator = &g_workspace_pool_allocator;
ncnn::set_default_option(opt);
return JNI_TRUE;
}
// public native String Detect(Bitmap bitmap);
JNIEXPORT jfloatArray JNICALL Java_com_example_ncnn1_NcnnJni_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
{
// ncnn from bitmap
ncnn::Mat in;
{
AndroidBitmapInfo info;
AndroidBitmap_getInfo(env, bitmap, &info);
int width = info.width;
int height = info.height;
if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
return NULL;
void* indata;
AndroidBitmap_lockPixels(env, bitmap, &indata);
// 把像素转换成data,并指定通道顺序
in = ncnn::Mat::from_pixels