目前深度学习在终端部署上很多高质量的开源框架,例如,百度的PaddlePaddle-lite,阿里的MNN,腾讯的ncnn。不过看了很多评测,我最终选择了阿里的MNN进行学习。但是,对于阿里的提供的相应的工具链并不是特别满意。我估计最纠结的是那些使用windows的用户吧。(强烈建议官方使用统一脚本语言实现对应的辅助脚本。)这篇文章,我将介绍如何修改源码开开心心的在windows进行编译mnn(其实,修改后不用做任何操作,也是可以适用于其它操作系统 的)。
另外,请参考原文档:https://www.yuque.com/mnn/cn/build_windows
进行教程前,请先确认自己的操作系统有至少有以下环境:
- Microsoft Visual Studio (2017或以上)注,笔者这里是使用的vs2015,需将3rd_party/flatbuffers/CMakeLists.txt里的/WX 换成/WX-
- cmake(建议使用3.10或以上版本)
- android sdk(如果需要在win上编译android sdk的话)
1. 首先,在schema下,使用python实现了一份generate.py代码。注,我是使用python3.x。
#-*-coding:utf-8-*-
#coding by: yuangu(lifulinghan@aol.com)
import os
import sys
import shutil
import platform
def p():
frozen = "not"
if getattr(sys, 'frozen',False):
frozen = "ever so"
return os.path.dirname(sys.executable)
return os.path.split(os.path.realpath(__file__))[0]
currentWorkPath = p()
os.chdir(currentWorkPath)
if '-lazy' in sys.argv and os.path.isdir("current"):
print("*** done ***")
exit(0)
# check is flatbuffer installed or not
FLATC = '../3rd_party/flatbuffers/tmp/flatc' + ('.exe' if "Windows" == platform.system() else '')
FLATC = os.path.realpath(FLATC)
if not os.path.isfile(FLATC):
print("*** building flatc ***")
tmpDir = os.path.realpath('../3rd_party/flatbuffers/tmp')
if os.path.isdir(tmpDir):
shutil.rmtree(tmpDir)
os.mkdir(tmpDir)
os.chdir(tmpDir)
os.system('cmake -DCMAKE_BUILD_TYPE=Release ..')
if "Windows" == platform.system():
os.system('cmake --build . --target flatc --config Release')
if os.path.isfile( os.path.join(tmpDir, 'Release/flatc.exe') ):
shutil.move(os.path.join(tmpDir, 'Release/flatc.exe'), FLATC)
else:
os.system('cmake --build . --target flatc')
# dir recover
os.chdir(currentWorkPath)
# determine directory to use
DIR='default'
if os.path.isdir('private'):
DIR = 'private'
DIR = os.path.realpath(DIR)
# clean up
print('*** cleaning up ***')
if os.path.isdir('current'):
shutil.rmtree('current')
os.mkdir('current')
# flatc all fbs
os.chdir('current')
listFile = os.listdir(DIR)
for fileName in listFile:
tmpFileName = os.path.join(DIR, fileName)
cmd = "%s -c -b --gen-object-api --reflect-names %s" %(FLATC, tmpFileName)
os.system(cmd)
os.chdir(currentWorkPath)
print( "*** done ***")
将原有的步骤
powershell ./schema/generate.ps1
换成
python ./schema/generate.py
事例:
如果顺利的话,你们将会看到如下的结果。
2. 接下来,改造cmake文件。
位于MNN/CMakeLists.txt 中的
# schema generator
if(WIN32)
add_custom_target( MNN_SCHEMA ALL
COMMAND powershell ${CMAKE_CURRENT_SOURCE_DIR}/schema/generate.ps1 -lazy
)
else()
add_custom_target( MNN_SCHEMA ALL
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/schema/generate.sh -lazy
)
endif()
换成如下代码。
find_program(PYTHON_BIN python3)
if (NOT PYTHON_BIN)
find_program(PYTHON_BIN python)
endif()
if (NOT PYTHON_BIN)
message(FATAL_ERROR "please install python and add it's path to system path")
endif()
# schema generator
add_custom_target( MNN_SCHEMA ALL
COMMAND ${PYTHON_BIN} ${CMAKE_CURRENT_SOURCE_DIR}/schema/generate.py -lazy
)
3、正式编译源码
然后你可以官网所说的。(注意咯,笔者是实践证明vs2015有些语法支持程度不够,在编译阶段是会报些错误的哟。如果没有精修代码的能力,建议还是老实按官网说的,使用vs2017以上,另外,可以不使用官方推荐的Ninja也是可以的,这个问题不大。)
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
如图所示:
最后使用
cmake --build .
4、最后保姆级的提示:
如果您偏爱mingw32或者其它的clang编译器,或ide,可以更改-G里的内容。至于-G如何填写。可以执行如下命例进行提示
5、以下是写给有需要编译Android的so库的同学。(请您做完上述的1和2)
原文档:https://www.yuque.com/mnn/cn/build_android
强烈吐槽一下,文档和所使用的工具。文档默认了用户会使用cmake,以及工具只能在类unix的环境下使用。
^^很多用户都不知道文档说的编译选项是什么鬼。 ^^。不过,官方的源码中demo下的android是有相应的so库的。也免去了很多用户的编译过程。
1、编译前,请查看您的android sdk是否装有以下组件
这两个sdk组件的安装方式可以如上图所示,在android studio中使用GUI进安装。
另命令党可参考(最新的android SDK没有进行验证,注:%ANDROID_HOME%为android sdk的根目录):
mkdir "%ANDROID_HOME%/licenses"
echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "%ANDROID_HOME%/licenses/android-sdk-license"
echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "%ANDROID_HOME%/licenses/android-sdk-preview-license"
"%ANDROID_HOME%/tools/bin/sdkmanager.bat" cmake;3.10.2.4988404 ndk-bundle
2、 cd 至 project\android下 (注:%ANDROID_HOME%为android sdk的根目录)如果您不太喜欢敲命令的可以直接跳过这一步。
mkdir build && cd build
%ANDROID_HOME%/cmake/3.10.2.4988404/bin/cmake.exe -DANDROID_ABI=arm64-v8a \
-DCMAKE_BUILD_TYPE=Release \
-DANDROID_NDK=%ANDROID_HOME%/ndk-bundle \
-DANDROID_STL=c++_static \
-DCMAKE_CXX_FLAGS=-std=c++11 -frtti -fexceptions \
-DCMAKE_TOOLCHAIN_FILE=%ANDROID_HOME%/ndk-bundle\build\cmake/android.toolchain.cmake \
-DCMAKE_MAKE_PROGRAM=%ANDROID_HOME%/cmake/3.10.2.4988404/bin/ninja.exe -G "Ninja" \
-DMNN_BUILD_FOR_ANDROID_COMMAND=true \
-DMNN_DEBUG=false -DNATIVE_LIBRARY_OUTPUT=. ../../..
cmake --build .
注:
上述命令中,ANDROID_ABI可以选择的参数除了arm64-v8a以外。还有 armeabi-v7a,x86,x86_64。如果您需要支持armeabi,mips, mips64,那就需要通过降低相应的ndk版本以及sdk版本来实现。因为google官方已经移除了这几个平台的支持。
3、下文是笔者对2步骤实现的脚本。(其实本来还有andrid SDK和NDK检测与安装功能,但估计使脚本变得更加复杂,故我都移除掉了) 请您将它放置 project/android 目录下并执行它
#-*-coding:utf-8-*
# code by yuangu(lifulinghan@aol.com)
import os
import platform
import shutil
import sys
import getopt
def p():
frozen = "not"
if getattr(sys, 'frozen',False):
frozen = "ever so"
return os.path.dirname(sys.executable)
return os.path.split(os.path.realpath(__file__))[0]
def checkPath(path):
if not os.path.isdir(path):
parent = os.path.dirname(path)
if os.path.isdir(parent):
os.mkdir(path)
else:
checkPath(parent)
SUPPER_ABI_LIST = [
'armeabi-v7a',
"arm64-v8a",
"x86",
'x86_64'
]
class MNN_Builder:
def __init__(self, argv):
try:
opts, args = getopt.getopt(argv,"hs:o:a:",['help', 'sdk=','out=','abi='])
except getopt.GetoptError:
self.usage()
sys.exit(-1)
androidSDKPath = None
outDir = os.path.join(p(), 'out')
abiList = [abi for abi in SUPPER_ABI_LIST]
extOption = []
for opt, arg in opts:
if opt in ("-h","--help"):
self.usage()
sys.exit()
elif opt in ("-s","--sdk"):
androidSDKPath = arg
elif opt in ("-o","--out"):
outDir = arg
elif opt in ('-a', '--abi'):
if arg == 'all':
pass
elif arg in SUPPER_ABI_LIST:
abiList.append(arg)
else:
print('abi not support')
self.usage()
sys.exit(-1)
elif opt in ('-c', '--core'):
if arg == 'opencl':
extOption.append("-DMNN_OPENCL=true")
extOption.append("-DANDROID_PLATFORM=android-16")
elif arg == 'opengl':
extOption.append("-DMNN_OPENGL=true")
extOption.append("-DANDROID_PLATFORM=android-21")
elif arg == 'vulkan':
extOption.append("-DMNN_VULKAN=true")
extOption.append("-DANDROID_PLATFORM=android-21")
elif arg == 'cpu':
pass
else:
print('the core not support')
self.usage()
sys.exit(-1)
elif opt in ('-d', '--enable_debug'):
extOption.append('-DMNN_DEBUG=true')
if not '-DMNN_DEBUG=true' in extOption:
extOption.append('-DMNN_DEBUG=false')
if androidSDKPath == None:
androidSDKPath = self.getAndroidSDKPath()
#check android sdk
if androidSDKPath == None or not os.path.isdir(androidSDKPath):
print("not found android sdk")
sys.exit(-1)
androidNDKPath = self.getNDKPath(androidSDKPath)
# check android ndk
if androidNDKPath == None:
print('not found android ndk')
sys.exit(-1)
cmakeDir = self. getCmakeDir(androidSDKPath)
if cmakeDir == None:
print("please install cmake in android sdk")
exit(-1)
outDir = os.path.realpath(outDir)
checkPath(outDir)
cmakeBin = os.path.join(cmakeDir,'bin/cmake') + ( '.exe' if 'Windows' == platform.system() else '' )
ninjaBin = os.path.join(cmakeDir,'bin/ninja') + ( '.exe' if 'Windows' == platform.system() else '' )
print(abiList)
for abi in abiList:
build_path = self.build(abi, androidNDKPath, cmakeBin, ninjaBin, extOption)
self.copySoToOut(build_path, abi, outDir)
print('****done****')
def usage(self):
print('usage: python build_mnn.py [-s <Android SDK>] [-o <*.so out dir>] [-a <aib name>]')
print("-h, --help print this message")
print("-s, -sdk Android SDK dir path, default from system variables of ANDROID_HOME or ANDROID_SDK_ROOT ")
print("-o, --out *.so out dir default './out' ")
print("-a, --abi all,armeabi-v7a,arm64-v8a,x86,x86_64, default all")
print("-c, --core cpu, opencl,opengl,vulkan, default cpu")
print("-d, --enable_debug, default close")
def getAndroidSDKPath(self):
environ_names = [
'ANDROID_HOME',
'ANDROID_SDK_ROOT'
]
for name in environ_names:
#环境变量里不存在
if name not in os.environ.keys():
continue
android_sdk_path = os.environ[name]
#验证如果不存在此目录
if not os.path.isdir(android_sdk_path):
continue
return android_sdk_path
#没有找到相应的sdk路径
return None
def getCmakeDir(self, androidSDKPath):
ndk_cmake_dir = os.path.join(androidSDKPath, "cmake")
if not os.path.isdir(ndk_cmake_dir):
return None
cmake_dir_list = os.listdir(ndk_cmake_dir)
list_len = len(cmake_dir_list)
if list_len <= 0:
return None
return os.path.join(ndk_cmake_dir, cmake_dir_list[0] )
# get ndk path from android sdk or NDK_ROOT or ANDROID_NDK
def getNDKPath(self, androidSDKPath):
#通过系统变量来寻找
environ_names = [
'NDK_ROOT',
'ANDROID_NDK'
]
for name in environ_names:
#环境变量里不存在
if name not in os.environ.keys():
continue
android_ndk_path = os.environ[name]
#验证如果不存在此目录
if not os.path.isdir(android_ndk_path):
continue
return android_ndk_path
ndk_bundle_dir = os.path.join(androidSDKPath, "ndk-bundle/toolchains")
if os.path.isdir(ndk_bundle_dir):
return os.path.join(androidSDKPath, "ndk-bundle")
def build(self, abi, androidNDKPath,cmakeBin ,ninjaBin, extOption):
rootPath = p()
build_path = os.path.join(rootPath, 'build/' + abi)
checkPath(build_path)
if os.path.isdir(build_path):
shutil.rmtree(build_path)
os.mkdir(build_path)
os.chdir(build_path)
cmd = '''%s -DANDROID_ABI=%s \
%s \
-DCMAKE_BUILD_TYPE=Release \
-DANDROID_NDK=%s \
-DANDROID_STL=c++_static \
-DCMAKE_CXX_FLAGS=-std=c++11 -frtti -fexceptions \
-DCMAKE_TOOLCHAIN_FILE=%s/build/cmake/android.toolchain.cmake \
-DCMAKE_MAKE_PROGRAM=%s -G "Ninja" \
-DMNN_BUILD_FOR_ANDROID_COMMAND=true \
-DMNN_DEBUG=false -DNATIVE_LIBRARY_OUTPUT=. ../../../../'''%(cmakeBin,abi, ' '.join(extOption), androidNDKPath,androidNDKPath,ninjaBin)
if (os.system(cmd) != 0 or os.system("%s --build ."%(cmakeBin, )) != 0):
print("build failed")
sys.exit(-1)
os.chdir(rootPath)
return build_path
def copySoToOut(self, build_path, abi, outDir):
copyList = ['libMNN.so']
for v in copyList:
if os.path.exists(os.path.join( build_path, v )):
checkPath(os.path.join(outDir, abi))
shutil.copy( os.path.join( build_path, v ), os.path.join(outDir, abi, v ))
if __name__ == "__main__":
MNN_Builder(sys.argv[1:])
4、关于文档中的编译选项开启。 以编译OpenCL部分的MNN_OPENCL编译选项为例。 只需要在上述的cmake命令中,加入 -DMNN_OPENCL=true 或 -DMNN_OPENCL=1 即可。有此需求且使用笔者的python脚本,请自行修改。 以2中命令做为完整例子如下图。:
mkdir build && cd build
%ANDROID_HOME%/cmake/3.10.2.4988404/bin/cmake.exe -DANDROID_ABI=arm64-v8a \
-DCMAKE_BUILD_TYPE=Release \
-DANDROID_NDK=%ANDROID_HOME%/ndk-bundle \
-DANDROID_STL=c++_static \
-DCMAKE_CXX_FLAGS=-std=c++11 -frtti -fexceptions \
-DCMAKE_TOOLCHAIN_FILE=%ANDROID_HOME%/ndk-bundle\build\cmake/android.toolchain.cmake \
-DCMAKE_MAKE_PROGRAM=%ANDROID_HOME%/cmake/3.10.2.4988404/bin/ninja.exe -G "Ninja" \
-DMNN_BUILD_FOR_ANDROID_COMMAND=true \
-DMNN_DEBUG=false -DMNN_OPENCL=true -DNATIVE_LIBRARY_OUTPUT=. ../../..
cmake --build .
5、另,tools/script的命令还没有做python的改造。但是至此已经基本上不影响大家的使用了。
如果大家对框架底层原理感兴趣的话,我可以写一些我对这套框架代码阅读的心得。
*******************************************完*******************************************
广告时间:
1、如果您对深度学习算法无比热爱,亦或从事相应的工作与研究。您可以加我微信 yyguzi 共同研究,共同进步。加微信,请标注:深度学习
2、本人擅长跨平台c++开发以及python。 如果有深度学习相应的工作,不嫌弃我非名校(985,211)及无SCI也可以加上述微信号,与我联系。