使用MACE在Android上实现图像分类¶
前言¶
在之前的文章中,我们介绍了如何使用PaddleMobile在Android设备上实现图像分类。本章将介绍使用小米开源的手机深度学习框架MACE来实现同样的功能。
MACE的GitHub地址:https://github.com/XiaoMi/mace
编译MACE库和模型¶
编译MACE库和模型有两种方式:Ubuntu本地编译和Docker编译。
使用Ubuntu编译¶
环境依赖¶
必备依赖
| 软件 | 安装命令 | 测试版本 |
|------|----------|----------|
| Python | - | 2.7 |
| Bazel | bazel安装指南 | 0.13.0 |
| CMake | apt-get install cmake | ≥3.11.3 |
| Jinja2 | pip install -I jinja2==2.10 | 2.10 |
| PyYaml | pip install -I pyyaml==3.12 | 3.12.0 |
| sh | pip install -I sh==1.12.14 | 1.12.14 |
| Numpy | pip install -I numpy==1.14.0 | 模型验证所需 |
| six | pip install -I six==1.11.0 | Python 2和3兼容性 |
可选依赖
| 软件 | 安装命令 | 备注 |
|------|----------|------|
| Android NDK | NDK安装指南 | Android构建需要,r15b/r15c/r16b/r17b |
| ADB | apt-get install android-tools-adb | Android运行需要,≥1.0.32 |
| TensorFlow | pip install -I tensorflow==1.6.0 | TensorFlow模型需要 |
| Docker | Docker安装指南 | Caffe模型的Docker模式 |
| Scipy | pip install -I scipy==1.0.0 | 模型验证需要 |
| FileLock | pip install -I filelock==3.0.0 | Android运行需要 |
安装依赖环境¶
安装Bazel
export BAZEL_VERSION=0.13.1
mkdir /bazel && \
cd /bazel && \
wget https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-installer-linux-x86_64.sh && \
chmod +x bazel-*.sh && \
./bazel-$BAZEL_VERSION-installer-linux-x86_64.sh && \
cd / && \
rm -f /bazel/bazel-$BAZEL_VERSION-installer-linux-x86_64.sh
安装Android NDK
# 下载NDK r15c
cd /opt/ && \
wget -q https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip && \
unzip -q android-ndk-r15c-linux-x86_64.zip && \
rm -f android-ndk-r15c-linux-x86_64.zip
export ANDROID_NDK_VERSION=r15c
export ANDROID_NDK=/opt/android-ndk-${ANDROID_NDK_VERSION}
export ANDROID_NDK_HOME=${ANDROID_NDK}
# 添加到PATH
export PATH=${PATH}:${ANDROID_NDK_HOME}
安装其他工具
apt-get install -y --no-install-recommends \
cmake \
android-tools-adb
pip install -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com setuptools
pip install -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com \
"numpy>=1.14.0" \
scipy \
jinja2 \
pyyaml \
sh==1.12.14 \
pycodestyle==2.4.0 \
filelock
安装TensorFlow
pip install -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com tensorflow==1.6.0
编译库和模型¶
- 克隆MACE源码
git clone https://github.com/XiaoMi/mace.git
- 进入Android Demo目录
cd mace/mace/examples/android/
- 修改build.sh
#!/usr/bin/env bash
set -e -u -o pipefail
pushd ../../../
TARGET_ABI=armeabi-v7a
LIBRARY_DIR=mace/examples/android/macelibrary/src/main/cpp/
INCLUDE_DIR=$LIBRARY_DIR/include/mace/public/
LIBMACE_DIR=$LIBRARY_DIR/lib/$TARGET_ABI/
rm -rf $LIBRARY_DIR/include/
mkdir -p $INCLUDE_DIR
rm -rf $LIBRARY_DIR/lib/
mkdir -p $LIBMACE_DIR
rm -rf $LIBRARY_DIR/model/
python tools/converter.py convert --config=mace/examples/android/mobilenet.yml --target_abis=$TARGET_ABI
cp -rf builds/mobilenet/include/mace/public/*.h $INCLUDE_DIR
cp -rf builds/mobilenet/model $LIBRARY_DIR
bazel build --config android --config optimization mace/libmace:libmace_static --define neon=true --define openmp=true --define opencl=true --cpu=$TARGET_ABI
cp -rf mace/public/*.h $INCLUDE_DIR
cp -rf bazel-genfiles/mace/libmace/libmace.a $LIBMACE_DIR
popd
- 修改模型配置文件mobilenet.yml
library_name: mobilenet
target_abis: [armeabi-v7a]
model_graph_format: code
model_data_format: code
models:
mobilenet_v2:
platform: tensorflow
model_file_path: https://cnbj1.fds.api.xiaomi.com/mace/miai-models/mobilenet-v2/mobilenet-v2-1.0.pb
model_sha256_checksum: 369f9a5f38f3c15b4311c1c84c032ce868da9f371b5f78c13d3ea3c537389bb4
subgraphs:
- input_tensors:
- input
input_shapes:
- 1,224,224,3
output_tensors:
- MobilenetV2/Predictions/Reshape_1
output_shapes:
- 1,1001
runtime: cpu+gpu
limit_opencl_kernel_time: 0
nnlib_graph_mode: 0
obfuscate: 0
winograd: 0
- 执行编译
./build.sh
- 编译结果
编译完成后,在mace/mace/examples/android/macelibrary/src/main/cpp/目录下会生成三个文件:
-include: 包含调用MACE接口和模型配置的头文件
-lib: 存放编译好的MACE库
-model: 存放模型文件(如MobileNet V2模型)
使用Docker编译¶
- 安装Docker
apt-get install docker.io
- 拉取MACE镜像
docker pull registry.cn-hangzhou.aliyuncs.com/xiaomimace/mace-dev
- 获取MACE源码并修改配置文件
git clone https://github.com/XiaoMi/mace.git
cd mace/mace/examples/android/
# 修改build.sh和mobilenet.yml
- 启动Docker容器
docker run -it -v $PWD:/mace registry.cn-hangzhou.aliyuncs.com/xiaomimace/mace-dev
- 编译
cd mace/mace/examples/android/
./build.sh
开发Android项目¶
创建Android项目¶
创建项目时需注意:
- 选择C++支持
- 最低支持Android 5.0
- 使用C++11标准
复制编译生成的文件¶
将编译生成的三个目录(include、lib、model)复制到Android项目的cpp目录下。
修改CMakeLists.txt¶
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
include_directories(${CMAKE_SOURCE_DIR}/)
include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)
set(mace_lib ${CMAKE_SOURCE_DIR}/src/main/cpp/lib/armeabi-v7a/libmace.a)
set(mobilenet_lib ${CMAKE_SOURCE_DIR}/src/main/cpp/model/armeabi-v7a/mobilenet.a)
add_library (mace_lib STATIC IMPORTED)
set_target_properties(mace_lib PROPERTIES IMPORTED_LOCATION ${mace_lib})
add_library (mobilenet_lib STATIC IMPORTED)
set_target_properties(mobilenet_lib PROPERTIES IMPORTED_LOCATION ${mobilenet_lib})
add_library( # Sets the name of the library.
mace_mobile_jni
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/image_classify.cc )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
mace_mobile_jni
mace_lib
mobilenet_lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
修改build.gradle¶
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -fopenmp"
abiFilters "armeabi-v7a"
}
}
android {
...
sourceSets {
main {
jniLibs.srcDirs = ["src/main/jniLibs"]
jni.srcDirs = ['src/cpp']
}
}
}
修改NDK版本¶
在Android Studio中设置NDK版本为r15c。
创建Java类¶
- JniMaceUtils.java
package com.xiaomi.mace;
public class JniMaceUtils {
static {
System.loadLibrary("mace_mobile_jni");
}
public static native int maceMobilenetSetAttrs(int ompNumThreads, int cpuAffinityPolicy, int gpuPerfHint, int gpuPriorityHint, String kernelPath);
public static native int maceMobilenetCreateEngine(String model, String device);
public static native float[] maceMobilenetClassify(float[] input);
}
- InitData.java - 配置MACE参数
package com.example.myapplication;
import android.os.Environment;
import java.io.File;
public class InitData {
public static final String[] DEVICES = new String[]{"CPU", "GPU"};
public static final String[] MODELS = new String[]{"mobilenet_v1", "mobilenet_v2"};
private String model;
private String device = "";
private int ompNumThreads = 4;
private int cpuAffinityPolicy = 0;
private int gpuPerfHint = 3;
private int gpuPriorityHint = 3;
private String kernelPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mace";
// Getters and setters
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public String getDevice() { return device; }
public void setDevice(String device) { this.device = device; }
public int getOmpNumThreads() { return ompNumThreads; }
public void setOmpNumThreads(int ompNumThreads) { this.ompNumThreads = ompNumThreads; }
public int getCpuAffinityPolicy() { return cpuAffinityPolicy; }
public void setCpuAffinityPolicy(int cpuAffinityPolicy) { this.cpuAffinityPolicy = cpuAffinityPolicy; }
public int getGpuPerfHint() { return gpuPerfHint; }
public void setGpuPerfHint(int gpuPerfHint) { this.gpuPerfHint = gpuPerfHint; }
public int getGpuPriorityHint() { return gpuPriorityHint; }
public void setGpuPriorityHint(int gpuPriorityHint) { this.gpuPriorityHint = gpuPriorityHint; }
public String getKernelPath() { return kernelPath; }
public void setKernelPath(String kernelPath) { this.kernelPath = kernelPath; }
public InitData() {
model = MODELS[1];
File file = new File(kernelPath);
if (!file.exists()) file.mkdir();
}
}
- PhotoUtil.java - 图片处理工具类
```java
package com.example.myapplication;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;
public class PhotoUtil {
// 启动相机
public static Uri start_camera(Activity activity, int requestCode) {
Uri imageUri;
File outputImage = new File(activity.getExternalCacheDir(), “out_image.jpg”);
try {
if (outputImage.exists()) outputImage.delete();
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri = FileProvider.getUriForFile(activity, “com.example.myapplication”, outputImage);
} else {
imageUri = Uri.fromFile(outputImage);
}
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
activity.startActivityForResult(intent, requestCode);
return imageUri;
}
// 从相册选择图片
public static void use_photo(Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
activity.startActivityForResult(intent, requestCode);
}
// 获取图片路径
public static String get_path_from_URI(Context context, Uri uri) {
String result;
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor == null) {
result = uri.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
// 压缩图片并转换为输入数据格式
public static float[] getScaledMatrix(Bitmap bitmap, int desWidth, int desHeight) {
float[] floatValues = new float[desWidth * desHeight * 3];
FloatBuffer floatBuffer = FloatBuffer.wrap(floatValues);
int[] pixels = new int[desWidth * desHeight];
Bitmap bm = Bitmap.createScaledBitmap(bitmap, desWidth, desHeight, false);
bm.getPixels(pixels, 0, bm.getWidth(), 0, 0, desWidth, desHeight);
for (int clr : pixels) {
floatBuffer.put(((clr >> 16 & 0xFF) - 128f) / 128f);
floatBuffer.put(((clr >> 8 & 0xFF) - 128f) / 128f);
floatBuffer.put(((clr & 0xFF) - 128f) / 128f);
}
if (!bm.isRecycled()) bm.recycle();
return floatBuffer.array();
}
// 按比例缩放图片
public static Bitmap getScaleBitmap(String filePath) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath