UE4 OpenCV+Dlib插件(支持Windows Android)人臉檢測(二)

本篇是Dlib插件的編寫,使用

難點主要在與Android端,PC端SDK打包好後基本就可以使用,Android端遇到了各種兼容之類的bug

參考

https://my.oschina.net/jjyuangu/blog/2243591
https://blog.csdn.net/u012234115/article/details/90642844

Dlib下載和編譯(針對Windows平臺和Android)

Dlib官網下載地址 工程裏(源碼在上一篇文章裏)使用的是19.7版本,只針對UE4的Android版本

修改後的UE4版本

編譯
  1. 打開dlib目錄下的CmakeLists.txt
if (DLIB_ISO_CPP_ONLY)
      option(DLIB_JPEG_SUPPORT ${DLIB_JPEG_SUPPORT_STR} OFF)
      option(DLIB_LINK_WITH_SQLITE3 ${DLIB_LINK_WITH_SQLITE3_STR} OFF)
      option(DLIB_USE_BLAS ${DLIB_USE_BLAS_STR} OFF)
      option(DLIB_USE_LAPACK ${DLIB_USE_LAPACK_STR} OFF)
      option(DLIB_USE_CUDA ${DLIB_USE_CUDA_STR} OFF)
      option(DLIB_PNG_SUPPORT ${DLIB_PNG_SUPPORT_STR} OFF)
      option(DLIB_GIF_SUPPORT ${DLIB_GIF_SUPPORT_STR} OFF)
      #option(DLIB_USE_FFTW ${DLIB_USE_FFTW_STR} OFF)
      option(DLIB_USE_MKL_FFT ${DLIB_USE_MKL_FFT_STR} OFF)
else()
      option(DLIB_JPEG_SUPPORT ${DLIB_JPEG_SUPPORT_STR} ON)
      option(DLIB_LINK_WITH_SQLITE3 ${DLIB_LINK_WITH_SQLITE3_STR} ON)
      option(DLIB_USE_BLAS ${DLIB_USE_BLAS_STR} ON)
      option(DLIB_USE_LAPACK ${DLIB_USE_LAPACK_STR} ON)
      option(DLIB_USE_CUDA ${DLIB_USE_CUDA_STR} ON)
      option(DLIB_PNG_SUPPORT ${DLIB_PNG_SUPPORT_STR} ON)
      option(DLIB_GIF_SUPPORT ${DLIB_GIF_SUPPORT_STR} ON)
      #option(DLIB_USE_FFTW ${DLIB_USE_FFTW_STR} ON)
      option(DLIB_USE_MKL_FFT ${DLIB_USE_MKL_FFT_STR} ON)
endif()

將DLIB_USE_CUDA_和DLIB_PNG_SUPPORT_STR 的ON改成OFF, 否則與UE4衝突
2. dlib目錄下的Config.h 添加一下代碼



// If you are compiling dlib as a shared library and installing it somewhere on your system
// then it is important that any programs that use dlib agree on the state of the
// DLIB_ASSERT statements (i.e. they are either always on or always off).  Therefore,
// uncomment one of the following lines to force all DLIB_ASSERTs to either always on or
// always off.  If you don't define one of these two macros then DLIB_ASSERT will toggle
// automatically depending on the state of certain other macros, which is not what you want
// when creating a shared library.
//#define ENABLE_ASSERTS       // asserts always enabled 
//#define DLIB_DISABLE_ASSERTS // asserts always disabled 

//#define DLIB_ISO_CPP_ONLY
//#define DLIB_NO_GUI_SUPPORT
//#define DLIB_ENABLE_STACK_TRACE

// You should also consider telling dlib to link against libjpeg, libpng, libgif, fftw, CUDA, 
// and a BLAS and LAPACK library.  To do this you need to uncomment the following #defines.
// #define DLIB_JPEG_SUPPORT
// #define DLIB_PNG_SUPPORT
// #define DLIB_GIF_SUPPORT
// #define DLIB_USE_FFTW
// #define DLIB_USE_BLAS
// #define DLIB_USE_LAPACK
// #define DLIB_USE_CUDA
#ifndef STDTOSTRING_H
#define STDTOSTRING_H
    #if defined(ANDROID)
    #ifdef ATOMIC_INT_LOCK_FREE
    #undef ATOMIC_INT_LOCK_FREE
    #endif
    #define ATOMIC_INT_LOCK_FREE 2
    #include <bits/exception_ptr.h>
    #include <bits/nested_exception.h>
    
    #include <string>
    #include <sstream>

    using namespace std;
    namespace std {
        template <typename T> std::string to_string(const T& n) {
            std::ostringstream stm;
            stm << n;
            return stm.str();
        }

        template <typename T> T round(T v) {
            return (v > 0) ? (v + 0.5) : (v - 0.5);
        }
    }
    #endif
#endif

  • 這個的目的是Android NDK中沒有對應的sdk函數,需要我們手動添加上。 注意我們後面編好後, 需要將這個Config.h替換上去
  1. 先編譯Android端的,後編譯Windows端的
    Android:
    • 首先你需要在WIndows的環境變量中添加ANDROID_SDK_ROOT和NDK_ROOT,這裏名字必須是這兩個,後面的腳本根據名字來找android sdk ndk。
    • 在這裏插入圖片描述
    • 創建ndk編譯腳本
#-*-coding:utf-8-*-

import os
import shutil
import zipfile
import hashlib
import sys
import platform
import requests
import urllib
import subprocess

#工具類
class Utils():
    #如果目錄不存,則創建。
    @staticmethod
    def mkDir(dirPath):
        if os.path.exists(dirPath) and os.path.isdir(dirPath):
            return
        parent = os.path.dirname(dirPath)
        if not (os.path.exists(parent) and os.path.isdir(parent)):
            Utils.mkDir(parent)
        
        os.mkdir(dirPath)
    
    #獲取某個目錄是否含有某個文件, extList獲取指定的文件後綴
    @staticmethod
    def getAllDirFiles(dirPath, extList = None):
        ret = []    
        for file in os.listdir( dirPath):
            if os.path.isfile(os.path.join(dirPath, file)):
               ret.append(os.path.join(dirPath, file))
            else:
                ret += Utils.getAllDirFiles(os.path.join(dirPath, file))
        
        #需要過濾某些文件
        if extList != None:             
            extList = [tmp.lower() for tmp in extList]           
            ret = [path for path in ret if os.path.splitext(path)[1].lower()  in extList]    
        return ret

    #清理掉某個數據
    @staticmethod
    def cleanFile(path):
        if not os.path.exists(path):
            return
        if os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.isfile(path):
            os.remove(path)

    #將一個文件夾壓縮成zip文件
    @staticmethod
    def makeZipFile(fileName, fromDir):        
        fileList = Utils.getAllDirFiles(fromDir)
        with zipfile.ZipFile(fileName , 'w')  as zip:
            for file in fileList:
                zip.write(file, os.path.relpath(file, fromDir))
    
    @staticmethod
    def extractZipFile(fileName, toDir = "."):
        file_zip = zipfile.ZipFile(fileName, 'r')
        for file in file_zip.namelist():
            file_zip.extract(file, toDir)
        file_zip.close()
            

    @staticmethod
    def sha256_checksum(filename, block_size=65536):
        sha256 = hashlib.sha256()
        with open(filename, 'rb') as f:
            for block in iter(lambda: f.read(block_size), b''):
                sha256.update(block)
        return sha256.hexdigest()

#獲取python文件所在的路徑
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 callbackfunc(blocknum, blocksize, totalsize):
    '''回調函數
    @blocknum: 已經下載的數據塊
    @blocksize: 數據塊的大小
    @totalsize: 遠程文件的大小
    '''
    percent = 100.0 * blocknum * blocksize / totalsize
    if percent > 100:
        percent = 100
    
    max_arrow = 50 #進度條的長度
    num_arrow = int(percent * max_arrow/100.0) 

    process_bar = '\r[' + '>' * num_arrow + '#' * (max_arrow -  num_arrow) + ']'\
                      + '%.2f%%' % percent  #帶輸出的字符串,'\r'表示不換行回到最左邊
    sys.stdout.write(process_bar) #這兩句打印字符到終端
    sys.stdout.flush()

#andoird sdk 的操作sdk路徑 
class  AndroidSDK():
    def __init__(self):
        self.ANDROID_SDK  =  self.getAndroidSDKPath()
        if self.ANDROID_SDK == None:
            self.ANDROID_SDK = self.installAndroidSDK()
            #更新android  sdk 
            self.updateSDK(['platforms;android-16'])
        
        self.cmakeDir = self.getCmakeDir()
        if self.cmakeDir == None:
            self.updateSDK(['cmake;3.6.4111459'])
            self.cmakeDir = self.getCmakeDir()

        self.NDKPath = self.getNDKPath()
        if self.NDKPath == None:
            self.updateSDK(['ndk-bundle'])
            self.NDKPath = self.getNDKPath()

        
    def installAndroidSDK(self):
        sysstr = platform.system().lower()
        
        SHA_256 = {
            "windows":'7e81d69c303e47a4f0e748a6352d85cd0c8fd90a5a95ae4e076b5e5f960d3c7a',
            'darwin':'ecb29358bc0f13d7c2fa0f9290135a5b608e38434aad9bf7067d0252c160853e',
            'linux':'92ffee5a1d98d856634e8b71132e8a95d96c83a63fde1099be3d86df3106def9',
        }
        
        #是否需要下載包
        needDownload  = True
        android_sdk_zip = "android_sdk.zip"
        if os.path.isfile(android_sdk_zip):
            sha256 = Utils.sha256_checksum(android_sdk_zip)
            if sha256.lower() == SHA_256[sysstr]:
                needDownload =  False
        
        print u"下載Android_sdk"
        #下載包
        if needDownload:
            sdk_download_url = 'https://dl.google.com/android/repository/sdk-tools-%s-4333796.zip'%(sysstr, )     
            urllib.urlretrieve(sdk_download_url, android_sdk_zip, callbackfunc)

        #解壓文件
        Utils.extractZipFile(android_sdk_zip,  "./android_sdk")
        os.environ['ANDROID_HOME'] = os.path.realpath("android_sdk")
        return os.environ['ANDROID_HOME']

    def updateSDK(self, package = [ 'platforms;android-16', 'cmake;3.6.4111459', 'ndk-bundle' ]):
        sdkmanager = os.path.join(self.ANDROID_SDK, 'tools/bin/sdkmanager')
        if "windows" ==  platform.system().lower():
            sdkmanager =  sdkmanager + '.bat'
        else:
            cmd = 'chmod +x %s' %(sdkmanager,)
            os.system(cmd)

        args = ['"%s"' %(key) for key in package]

        args.insert(0, sdkmanager)
        cmd = 'echo y|' +  " ".join(args)
        os.system(cmd)
       

    #獲取sdk裏的 cmake 信息 
    def getCmakeDir(self):
        ndk_cmake_dir  = os.path.join(self.ANDROID_SDK,  "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[list_len - 1] )
       
        
    def  getNDKPath(self):
        #通過系統變量來尋找
        environ_names = [
           'NDK_ROOT', 
        ]

        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(self.ANDROID_SDK,  "ndk-bundle")
        ndk_bundle_list = os.listdir( ndk_bundle_dir)
        
        ndk_bundle_list_len = len(ndk_bundle_list)
        if ndk_bundle_list_len <= 0 :
            return None

        #取最後一個高版本的使用
        return  os.path.join(self.ANDROID_SDK,  "ndk-bundle/" + ndk_bundle_dir[ndk_bundle_list_len - 1] )

    # 根據系統變量android sdk的路徑
    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


if '__main__' == __name__:
    android_sdk = AndroidSDK()
    ANDROID_SDK = android_sdk.getAndroidSDKPath()
    ANDROID_NDK =android_sdk.getNDKPath()

    ANDROID_CMAKE = os.path.join(android_sdk.getCmakeDir(), 'bin/cmake')
    ANDROID_NINJA=os.path.join(android_sdk.getCmakeDir(),'bin/ninja')

    if "windows" ==  platform.system().lower():
        ANDROID_CMAKE =  ANDROID_CMAKE + '.exe'
        ANDROID_NINJA = ANDROID_NINJA + '.exe'

    pyPath = p()
    buildDir = os.path.join(pyPath, "build")
    outDir = os.path.join(pyPath, "out")


    Utils().cleanFile(outDir)
    Utils().mkDir(outDir)
    
    #需要打包的abi
    abiList = [
        'armeabi',
        'armeabi-v7a',
        "arm64-v8a",
        "x86",
        'x86_64',
        'mips',
        'mips64',
    ]
    for abi in abiList:
        os.chdir(pyPath)
        Utils().cleanFile(buildDir)
        Utils().mkDir(buildDir)
        os.chdir(buildDir)
        
        #導出目錄
        outSoPath = os.path.join(outDir, "" + abi)
        Utils().cleanFile(outSoPath)
        Utils().mkDir(outSoPath)

        cmd = '''%s -DANDROID_ABI=%s   \
        -DANDROID_PLATFORM=android-16  \
        -DCMAKE_BUILD_TYPE=Release   \
        -DANDROID_NDK=%s    \
        -DCMAKE_CXX_FLAGS=-std=c++11 -frtti -fexceptions   \
        -DCMAKE_TOOLCHAIN_FILE=%s/build/cmake/android.toolchain.cmake    \
        -DCMAKE_MAKE_PROGRAM=%s -G "Ninja"    \
        -DDLIB_NO_GUI_SUPPORT=1 \
        -DCMAKE_INSTALL_PREFIX=%s \
        ..'''%(ANDROID_CMAKE,abi,ANDROID_NDK,ANDROID_NDK,ANDROID_NINJA, outSoPath ) 
        print cmd
        os.system(cmd)
        os.system("%s --build ."%(ANDROID_CMAKE, ))
        os.system("%s -P cmake_install.cmake"%(ANDROID_CMAKE, ))

執行 Python ./ndk_build.py,成功的話 會在sdk更目錄創建出Out
在這裏插入圖片描述
裏面有對應的Include文件和.a庫文件,將Config.h覆蓋這裏,然後拷到插件對應的目錄中
Windows:
安裝CMake GUI,生成vs工程後就是傳統的打包lib了,將lib拷貝到插件目錄中
在這裏插入圖片描述
4. Dlib插件編寫

  • Dlib中有C++ throw的寫法,在NDK中默認是不支持的,需要在.build.cs中添加 bEnableExceptions = true;
  • Dlib 有一些局部變量覆蓋全局變量的情況,會引起C4458 C4459警告,xx聲明隱藏了全局聲明,
    但是在UE4中這些警告會被認爲是錯誤,無法編譯錯誤,解決方法添加 ShadowVariableWarningLevel = WarningLevel.Off;
  • 其次就是正常添加上dlib.lib 還有libdlib.a的庫,具體看工程代碼,跟OpenCV差不多

5.Dlib測試使用

  • 這裏參考TestDlibActor.cpp中的測試代碼,如果log中有對應的結果,就是添加成功了
  • 這裏單獨說一下Dlib中加載保存訓練數據的函數接口 serialize(const std::string FileName&)還有deserialize(const std::string FileName&)
    這兩個函數在Windows端使用是沒問題的,但是在Android端就會崩潰,看android logcat猜測原因是NDK中對智能指針調用ifstream析構函數時有問題,這裏我們就麻煩一點,使用下面的代碼進行處理
			UE_LOG(LogTemp, Warning, TEXT("Begin Serialize file TempPath = %s"), *TempPath );
			std::string strTemp(TCHAR_TO_UTF8(*TempPath));
			std::ofstream StreamOut(strTemp.c_str(), std::ios::binary);
			
			UE_LOG(LogTemp, Warning, TEXT("custom serialize begin, TempPath = %s"), *TempPath );
			serialize(learned_pfunct, StreamOut);
			StreamOut.clear();
			StreamOut.close();
			UE_LOG(LogTemp, Warning, TEXT("custom serialize end, TempPath = %s"), *TempPath );

			std::ifstream StreamIn(strTemp.c_str(), std::ios::binary);
			UE_LOG(LogTemp, Warning, TEXT("custom deserialize begin, begin deserialize TempPath = %s"), *TempPath );
			deserialize(learned_pfunct, StreamIn);
			UE_LOG(LogTemp, Warning, TEXT("custom deserialize end, begin deserialize TempPath = %s"), *TempPath );

PS:

  • 找到dlib\lzp_buffer\lzp_buffer_kernel_2.h 修改 inline bool vefrify()這個函數的函數名和下面的調用,否者會報意外的類型bool,因爲和UE4衝突
  • 錯誤 “UpdateResourceW”: 不是“UTexture2D”的成員 在頭文件中添加#define UpdateResource UpdateResource
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章