本篇是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版本
編譯
- 打開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替換上去
- 先編譯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