项目需要将tensorflow的pb模型移植到android,tensorflow官方给的移植例子是在java层面上的,有一个so和相应的jar包,不是很符合需要,因为实际项目里除了调用模型得到结果,中间还有很多的处理工作,并且在java层开发。我做的是一个手机人脸识别,从输入图像,mtcnn人脸检测和特征点检测,对齐,识别,对比,整个下来一个流程。另外,如果提供给客户sdk包含tensorflow的so和jar包,还有自己的so和jar包,势必显得很不优雅,也无法对算法保密。这篇帖子记录一下我将基于tf的人脸识别模型移植到android的过程。
tf源码编译方式一个是bazel,另外一个就是makefile了,在这里面 点击打开链接 有说明,makefile不仅支持android,还有linux,ios,甚至树莓派。与bazel编译脚本不同的是,这个自带的makefile android编译直接输出就是一个.a的静态库,这正好满足需要,但是调用这个.a的静态库还需要tensorflow的接口头文件,C API是调用不起来,估计没有编译在里面,C++ 的头文件又抽取不出来,里面各种依赖搞得人想死。另外发现这个makefile中自带了一个example 叫benchmark,这个benchmark是一个使用的是c++接口加载pb模型的例子,静态链接到tf的.a文件。我将这个benchmark push到android上面,发现可以运行,速度还挺快的。于是我就打算参考这个benchmark来新建自己的一个工程,在tensorflow/example目录下面新建一个自己文件夹,里面放我自己的代码,编译的脚本就直接往tensorflow/contrib/makefile里面添加就可以了。
1 开发环境
个人在Linux下开发c++一直用的是QtCreator,其实很多人误解,一提到QtCreator就认为是做界面的,其实他是一个超级开放的IDE,不仅仅支持cmake工程,还支持makefile工程(我是这一次才发现的),看下图。
菜单,新建文件或项目
选择tensorflow的源码导入后,选择要导入的文件
项目打开后,打开tools/benchmark里面的代码文件,发现按住Ctrl点击头文件发现无法跳转,也就是说无法找到头文件,正常的vector,string等标准库倒是没问题,最后发现QtCreator把要找的头文件路径都记录在项目根路径下"项目名.includes"的文件里面,像benchmark.cc这种头文件路径写法,看来只需要往这个include里面加个当前路径就好了,发现确实可以。下面前三条是我加的,第二条是opencv的头文件(从opencv的android sdk里面拷贝出来的,我的项目里面需要用到opencv),第三条是android jni相关的头文件目录(因为我要给我的so添加jni相关接口给java调用)
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/graph/algorithm.h"
#include "tensorflow/core/graph/graph.h"
接下来写代码就能自动提示,跳转都不成问题啦。接下来说下编译。
2 编译方法
主要根据makefile的readme里面的介绍,首先新建ndk环境变量,把里面的路径改成你的ndk开发包路径,推荐把他放到.bashrc里面,这样每次一打开终端就自动导出了
export NDK_ROOT=/absolute/path/to/NDK/android-ndk-rxxx
下载依赖库,这些依赖库放在tensorflow/contrib/makefile/downloads路径下,下载以后就不用下载了
tensorflow/contrib/makefile/download_dependencies.sh
编译protobuf,tensorflow里面用到了protobuf,这里编译android版本的,注意,默认是armv7的,如果想要armv8,加上 -a arm64-v8a,这点在想要编译出v8的tf库和自己的so库时尤其要注意
tensorflow/contrib/makefile/compile_android_protobuf.sh -c
导出两个环境变量,如果要编译v8,也把后面的armeabi-v7a改成arm64-v8a
export HOST_NSYNC_LIB=`tensorflow/contrib/makefile/compile_nsync.sh`
export TARGET_NSYNC_LIB=`CC_PREFIX="${CC_PREFIX}" NDK_ROOT="${NDK_ROOT}" \
tensorflow/contrib/makefile/compile_nsync.sh -t android -a armeabi-v7a`
最后编译tf的静态库,编译v8注意了,需要加上ANDROID_ARCH=arm64-v8a,这点readme里面没有提。
make -f tensorflow/contrib/makefile/Makefile TARGET=ANDROID
最后在tensorflow/contrib/makefile/gen/lib/android_armeabi-v7a下面就得到了tf的.a静态库了
那么,自己的项目该如何编译并链接到tf的静态库呢?参照benchmark,我的测试脚本如下,我是把opencv也静态链接进去了,上面make命令后面加上example就可以编译出一个可执行程序,再adb push到/data/local/tmp里面,再用adb shell执行。
####################################kface工程###################################
#$(error $(INCLUDES))
KFACE_DEMO := $(BINDIR)mtcnn
KFACE_SRCS := tensorflow/examples/kface/mtcnn/comm_lib.cc \
tensorflow/examples/kface/mtcnn/utils.cc \
tensorflow/examples/kface/mtcnn/main_raw.cc
INCLUDES += \
-Itensorflow/examples/kface/opencv_native/jni/include
LDFLAGS += \
-Ltensorflow/examples/kface/opencv_native/libs/$(ANDROID_ARCH)/ \
-Ltensorflow/examples/kface/opencv_native/3rdparty/libs/$(ANDROID_ARCH)/
LIBS += \
-lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_androidcamera -lopencv_video -ltbb -lIlmImf -llibjpeg -llibpng -llibjasper -llibtiff
KFACE_OBJS := $(addprefix $(OBJDIR), $(KFACE_SRCS:.cc=.o))
#$(error $(INCLUDES))
example:$(KFACE_DEMO)
$(KFACE_DEMO): $(KFACE_OBJS) $(LIB_PATH) $(CUDA_LIB_DEPS)
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(INCLUDES) \
-o $(KFACE_DEMO) $(KFACE_OBJS) \
$(LIBFLAGS) $(TEGRA_LIBS) $(LIB_PATH) $(LDFLAGS) $(LIBS) $(CUDA_LIBS)
####################################end kface工程###################################
那么,如果用qtcreator开发的化,该怎么设置,实现点一个按钮自动编译,首先在 项目根目录建立一个makefile的链接文件,然后,新建一个编译脚本
#!/bin/sh
export NDK_ROOT=/home/wayen/program/android-ndk-r15c
export HOST_NSYNC_LIB=`tensorflow/contrib/makefile/compile_nsync.sh`
export TARGET_NSYNC_LIB=`CC_PREFIX="${CC_PREFIX}" NDK_ROOT="${NDK_ROOT}" \
tensorflow/contrib/makefile/compile_nsync.sh -t android -a armeabi-v7a`
make -f tensorflow/contrib/makefile/Makefile TARGET=ANDROID demo_so
在qt里面设置构建步骤,自定义构建步骤
接下来一点左下角的锤子,就可以自动编译啦,如果想要切换编译目标,直接改上面这个脚本就可以了。
3 编译android中so
我的最终目标是输出一个so,这个so有包含jni的接口,然后在android studio里面的java直接loadlibrary就可以用了。那么该如何编译这个so呢,起初是直接加编译选项 -shared,发现出来的so没法用,坑。解决方法是要把编译选项中-fPIE和链接选择中-fPIE -pie去掉,就可以了。
以后手机上深度学习项目就可以愉快的使用tensorflow了,再也不用纠结移植问题了,一劳永逸。