如何在windows上开心的编译阿里的MNN

我的未来我决定 提交于 2019-12-08 08:47:32

目前深度学习在终端部署上很多高质量的开源框架,例如,百度的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也可以加上述微信号,与我联系。

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!