這幾年我們?cè)谌粘9ぷ魃钪锌吹皆絹?lái)越多的智能終端設(shè)備的出現(xiàn),如智能家電、商城客服機(jī)器人、物流配送無(wú)人小車(chē)、智能監(jiān)控等等,它們可以為我們生活帶來(lái)各種各樣的便利。因此,邊緣智能與 AIoT 已成為不少?lài)?guó)內(nèi)外企業(yè)發(fā)展的一個(gè)重要方向。邊緣智能是一項(xiàng)以嵌入式設(shè)備應(yīng)用開(kāi)發(fā)為基礎(chǔ)的前沿技術(shù),我們需要在一些資源緊張的嵌入式設(shè)備,如 MCU、SOC,部署如人臉識(shí)別、物體檢測(cè)、音頻分類(lèi)等智能應(yīng)用。
然而,我們又看到現(xiàn)實(shí)的嵌入式智能應(yīng)用開(kāi)發(fā)面正臨著一些軟硬件生態(tài)兼容方面的挑戰(zhàn)。例如,芯片廠(chǎng)商提供推薦的板載系統(tǒng)往往是定制的,使用不同的編譯工具,而且大多不會(huì)有 Python 解釋器。所以,當(dāng)我們打算將自己的智能應(yīng)用部署到嵌入式設(shè)備時(shí),我們繞不開(kāi) AI 推理框架跨平臺(tái)的問(wèn)題。
TensorFlow Lite 應(yīng)用 C++ 作為框架底層的基礎(chǔ)實(shí)現(xiàn)可以天然保證跨平臺(tái)擴(kuò)展特性,但由于它的這項(xiàng)技術(shù)的比較前沿,在嵌入式 Linux 設(shè)備上以 Python 接口為主,有些開(kāi)發(fā)者不太適應(yīng),認(rèn)為不易上手。為此,我們開(kāi)發(fā) Edge Brain 方便開(kāi)發(fā)者以其熟悉的交叉編譯方式部署 TensorFlow Lite 智能應(yīng)用,讓他們的嵌入式應(yīng)用走向智能化。
交叉編譯
交叉編譯是指,一種在某個(gè)系統(tǒng)平臺(tái)下可以產(chǎn)生另一個(gè)系統(tǒng)平臺(tái)的可執(zhí)行文件的編譯方式。這種方式的優(yōu)點(diǎn)是,當(dāng)程序在目標(biāo)運(yùn)行系統(tǒng)平臺(tái)進(jìn)行編譯比較困難時(shí),它通過(guò)解耦編譯和運(yùn)行兩個(gè)過(guò)程來(lái)實(shí)現(xiàn)更高效的程序調(diào)試。比如,在資源緊張的 Linux ARM 嵌入式系統(tǒng)平臺(tái)調(diào)試應(yīng)用程序,其編譯過(guò)程往往有著很高的 CPU 占用率,更不用說(shuō)我們其實(shí)希望程序的編譯與運(yùn)行調(diào)試工作能并行開(kāi)展。因此,交叉編譯在嵌入式智能開(kāi)發(fā)有著重要的應(yīng)用場(chǎng)景。
后面的篇幅,我們將參考官方文檔以跨平臺(tái)交叉編譯樹(shù)莓派的 TensorFlow Lite C++ 應(yīng)用為例,介紹如何實(shí)現(xiàn)跨平臺(tái)部署嵌入式智能應(yīng)用的部署。因?yàn)?,?shù)莓派是 Linux ARM 嵌入式系統(tǒng)平臺(tái)的其中一種,所以我們希望本文能夠起到拋磚引玉的效果,讀者未來(lái)遇到類(lèi)似的問(wèn)題時(shí),能舉一反三完成業(yè)務(wù)平臺(tái)的部署,甚至分享心得與我們一起為開(kāi)源社區(qū)做貢獻(xiàn)。
準(zhǔn)備 Docker 編譯環(huán)境
本文選用的交叉編譯工具為 Google 開(kāi)源推出的 Bazel。其由于具有易用性的特點(diǎn),已經(jīng)在大量開(kāi)源 AI 項(xiàng)目中得到應(yīng)用。在本章節(jié),我們將手把手的帶領(lǐng)您一步一步搭建編譯環(huán)境。首先,我們不希望開(kāi)發(fā)者由于環(huán)境安裝的兼容性問(wèn)題,遇到系統(tǒng)軟件版本沖突的狀況。所以,我們建議大家將程序的編譯環(huán)境配置在 docker 中。這樣不僅可以保證本地環(huán)境的安全,還能方便后續(xù)環(huán)境遷移。
本文選用 ubuntu04 作為我們的基礎(chǔ)鏡像,并在其中采用 Bazel 官網(wǎng)中 Binary Installer 的安裝方式。具體步驟如下:
1. 創(chuàng)建 Dockerfile 內(nèi)容如下
From ubuntu:18.04
RUN apt update -y
&& apt install -y curl gnupg git vim python python3 python3-distutils python3-pip g++ unzip zip openjdk-11-jdk wget cmake make -y
&& pip3 install numpy
&& wget https://github.com/bazelbuild/bazelisk/releases/download/v1.7.5/bazelisk-linux-amd64
&& chmod +x bazelisk-linux-amd64
&& mv bazelisk-linux-amd64 /usr/bin/bazel
&& echo ‘export PATH=$PATH:$HOME/bin’ 》》 ~/.bashrc
&& apt-get purge -y --auto-remove
2. 在 Dockerfile 所在目錄中執(zhí)行下面的命令生成我們需要的 Docker 鏡像實(shí)現(xiàn)編譯環(huán)境的配置。
~$ docker build -t bazel-build-env:v0.01 。
Bazel TensorFlow Lite
Bazel 可以輕松完成交叉編譯,互聯(lián)網(wǎng)有許多教程介紹 toolchain 的配置原理,我們不再贅述。這里我們主要介紹交叉編譯 TensorFlow Lite 的實(shí)戰(zhàn)步驟。因?yàn)槲覀兿M罱K程序在樹(shù)莓派上使用,所以我們直接使用 TensorFlow 的 toolchain 配置即可。具體步驟如下:
1. 導(dǎo)入 TensorFlow 庫(kù)
TensorFlow 的 toolchain 以及 TFLite 相關(guān)的源碼均存在 github 的倉(cāng)庫(kù)之中,于是我們需要使用 Bazel 將其自動(dòng)下載下來(lái),并繼承其配置文件。Bazel 提供了非常簡(jiǎn)單的實(shí)現(xiàn)方式,即在項(xiàng)目根目錄下配置 WORKSPACE 文件中追加如下內(nèi)容即可:
load(“@bazel_tools//tools/build_defs/repo:http.bzl”, “http_archive”)
load(“@bazel_tools//tools/build_defs/repo:git.bzl”, “git_repository”, “new_git_repository”)
# Needed by TensorFlow
http_archive(
name = “io_bazel_rules_closure”,
sha256 = “e0a111000aeed2051f29fcc7a3f83be3ad8c6c93c186e64beb1ad313f0c7f9f9”,
strip_prefix = “rules_closure-cf1e44edb908e9616030cc83d085989b8e6cd6df”,
urls = [
“http://mirror.tensorflow.org/github.com/bazelbuild/rules_closure/archive/cf1e44edb908e9616030cc83d085989b8e6cd6df.tar.gz”,
“https://github.com/bazelbuild/rules_closure/archive/cf1e44edb908e9616030cc83d085989b8e6cd6df.tar.gz”, # 2019-04-04
],
)
git_repository(
name = “org_tensorflow”,
remote = “https://github.com.cnpmjs.org/tensorflow/tensorflow.git”,
tag = “v2.4.0”
)
load(“@org_tensorflow//tensorflow:workspace.bzl”, “tf_workspace”)
tf_workspace(tf_repo_name = “org_tensorflow”)
可以看到上述內(nèi)容中,我們不僅僅制定了 TensorFlow 倉(cāng)庫(kù),而且 Bazel 還允許我們通過(guò) tag 來(lái)選擇特定版本的內(nèi)容。除此之外,在配置好 TensorFlow 倉(cāng)庫(kù)之后,還能使用 @org_tensorflow 來(lái)進(jìn)行額外的配置,如繼承倉(cāng)庫(kù)中的 WORKSPACE 配置。
2. 修改 .bazelrc 文件
我們參考 TensorFlow 庫(kù)中的配置, 修改項(xiàng)目路徑中的 edge-brain/.bazelrc 如下:
# TF settings
build:elinux --crosstool_top=@local_config_embedded_arm//:toolchain
build:elinux --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
build:elinux_armhf --config=elinux
build:elinux_armhf --cpu=armhf
經(jīng)過(guò)第一步的配置,我們已經(jīng)使得 Bazel 不僅知道從何處下載什么版本的 TensorFlow 源碼,還加載了 TF 倉(cāng)庫(kù)中已有的相關(guān)配置。這樣當(dāng)我們使用 --config elinux_armhf 時(shí),bazel 將知道應(yīng)使用 TF 庫(kù)中 @local_config_embedded_arm//:toolchain 來(lái)編譯代碼,至此便輕松的完成了交叉編譯的環(huán)境配置工作,接下來(lái)讓我們來(lái)測(cè)試下編譯環(huán)境。
3.驗(yàn)證 TFLite 的 Bazel 配置
我們的 Edge Brain 倉(cāng)庫(kù)已經(jīng)為你提前完成上述的相關(guān)環(huán)境配置。現(xiàn)在,我們可以執(zhí)行下面的指令嘗試編譯 TFLite 提供的 minial.cc 程序驗(yàn)證編譯環(huán)境。
edge-brain$ bazel build --config elinux_armhf //examples/hello_world:hello_world --experimental_repo_remote_exec
Bazel OpenCV
OpenCV 是一個(gè)輕量高效的計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)軟件庫(kù),可以跨平臺(tái)運(yùn)行在 Linux、Windows、Android 和 MacOS 的操作系統(tǒng)上,而且集成許多圖像處理和計(jì)算機(jī)視覺(jué)方面的通用優(yōu)秀算法。再之,計(jì)算機(jī)視覺(jué)作為最先引入卷積神經(jīng)網(wǎng)絡(luò)的前沿領(lǐng)域,它的智能算法相對(duì)成熟并且已被廣泛應(yīng)用于各種生活場(chǎng)景,如安防常用的人臉識(shí)別與目標(biāo)跟蹤等都屬于這一領(lǐng)域。
但是,如前文所述,我們現(xiàn)有的嵌入式系統(tǒng)平臺(tái)的種類(lèi)繁多而且硬件資源特別有限。所以, OpenCV 團(tuán)隊(duì)難以支持各式各樣系統(tǒng)平臺(tái)的庫(kù)文件預(yù)編譯(binary prebuilt),而我們也不愿意忍受嵌入式系統(tǒng)上編譯 OpenCV 庫(kù)的漫長(zhǎng)過(guò)程。因此,我們基于 Bazel 工具搭建 OpenCV 智能應(yīng)用的交叉編譯環(huán)境,希望它幫助一些計(jì)算機(jī)視覺(jué)領(lǐng)域同學(xué)快速構(gòu)建他們自己的嵌入式視覺(jué)應(yīng)用。
下面我們將簡(jiǎn)單介紹 Bazel 搭建 OpenCV 編譯環(huán)境的解決思路。我們了解到 OpenCV 主要構(gòu)建工具是 CMake,所以它的所有編譯配置都寫(xiě)在 CMakeList.txt 文件中。CMake 是現(xiàn)在開(kāi)源項(xiàng)目的主流編譯工具,過(guò)去如 Caffe、Tesseract 以及 Boost 等開(kāi)源項(xiàng)目都是用 CMake 編譯的。因此,Bazel 為了兼容 CMake 的編譯規(guī)則擴(kuò)展提供一個(gè)名為 cmak_external 函數(shù)接口,實(shí)現(xiàn)對(duì)第三方庫(kù)編譯參數(shù)的控制。
cmak_external 函數(shù)接口有兩個(gè)控制編譯參數(shù)的關(guān)鍵變量:cache_entries 與 make_commands。Bazel 會(huì)根據(jù)這兩個(gè)變量的參數(shù)自動(dòng)編寫(xiě)一個(gè)適合的 CMake 運(yùn)行腳本并執(zhí)行得到理想的編譯結(jié)果。簡(jiǎn)單來(lái)說(shuō),我們可以認(rèn)為 cmak_external 就是讓 Bazel 通過(guò) Shell 腳本控制本地終端完成 CMake 的編譯過(guò)程。下面我們展示 edge-brain/third_party/BUILD 如何配置 OpenCV 的靜態(tài)庫(kù)編譯。
load(“@rules_foreign_cc//tools/build_defs:cmake.bzl”, “cmake_external”)
load(“//third_party:opencv_configs.bzl”,
“OPENCV_SO_VERSION”,
“OPENCV_MODULES”,
“OPENCV_THIRD_PARTY_DEPS”,
“OPENCV_SHARED_LIBS”)
exports_files([“LICENSE”])
package(default_visibility = [“//visibility:public”])
alias(
name = “opencv”,
actual = select({
“//conditions:default”: “:opencv_cmake”,
}),
visibility = [“//visibility:public”],
)
OPENCV_DEPS_PATH = “$BUILD_TMPDIR/$INSTALL_PREFIX”
cmake_external(
name = “opencv_cmake”,
cache_entries = {
“CMAKE_BUILD_TYPE”: “Release”,
“CMAKE_TOOLCHAIN_FILE”: “$EXT_BUILD_ROOT/external/opencv/platforms/linux/arm-gnueabi.toolchain.cmake”,
“BUILD_LIST”: “,”.join(sorted(OPENCV_MODULES)),
“BUILD_TESTS”: “OFF”,
“BUILD_PERF_TESTS”: “OFF”,
“BUILD_EXAMPLES”: “OFF”,
“BUILD_SHARED_LIBS”: “ON” if OPENCV_SHARED_LIBS else “OFF”,
“WITH_ITT”: “OFF”,
“WITH_TIFF”: “OFF”,
“WITH_JASPER”: “OFF”,
“WITH_WEBP”: “OFF”,
“BUILD_PNG”: “ON”,
“BUILD_JPEG”: “ON”,
“BUILD_ZLIB”: “ON”,
“OPENCV_SKIP_VISIBILITY_HIDDEN”: “ON” if not OPENCV_SHARED_LIBS else “OFF”,
“OPENCV_SKIP_PYTHON_LOADER”: “ON”,
“BUILD_opencv_python”: “OFF”,
“ENABLE_CCACHE”: “OFF”,
},
make_commands = [“make -j4”, “make install”] + [“cp {}/share/OpenCV/3rdparty/lib/*.a {}/lib/”.format(OPENCV_DEPS_PATH, OPENCV_DEPS_PATH)],
lib_source = “@opencv//:all”,
linkopts = [] if OPENCV_SHARED_LIBS else [
“-ldl”,
“-lm”,
“-lpthread”,
“-lrt”,
],
shared_libraries = select({
“@bazel_tools//src/conditions:darwin”: [“l(fā)ibopencv_%s.%s.dylib” % (module, OPENCV_SO_VERSION) for module in OPENCV_MODULES],
“//conditions:default”: [“l(fā)ibopencv_%s.so.%s” % (module, OPENCV_SO_VERSION) for module in OPENCV_MODULES],
}) if OPENCV_SHARED_LIBS else None,
static_libraries = [“l(fā)ibopencv_%s.a” % module for module in OPENCV_MODULES]
+ [module for module in OPENCV_THIRD_PARTY_DEPS] if not OPENCV_SHARED_LIBS else None,
alwayslink=True,
)
最后,我們?cè)?edge-brain 目錄運(yùn)行下面的指令編譯測(cè)試程序,并將測(cè)試程序拷貝到樹(shù)莓派上運(yùn)行,從而驗(yàn)證 Bazel 搭建 OpenCV 編譯環(huán)境正確性。
edge-brain$ bazel build --config elinux_armhf //examples/hello_opencv:hello-opencv --experimental_repo_remote_exec
應(yīng)用實(shí)踐
下面我們將介紹如何利用 Edge Brain 的編譯環(huán)境完成實(shí)際的智能應(yīng)用在嵌入式平臺(tái)的部署。
低照度圖像增強(qiáng)
MIRNet 是 Learning Enriched Features for Real Image Restoration and Enhancement 提出的一種圖像增強(qiáng)網(wǎng)絡(luò)模型。該模型學(xué)習(xí)了一組豐富的特征,這些特征結(jié)合了來(lái)自多個(gè)尺度的上下文信息,同時(shí)保留了高分辨率的空間細(xì)節(jié)。其算法的核心是:并行多分辨率卷積流,用于提取多尺度特征;跨多分辨率流的信息交換;空間和通道注意力機(jī)制來(lái)捕獲上下文信息;基于注意力的多尺度特征聚合。下面是 MIRNet 的一些原理圖示。
MIRNet 整體框架
選擇核心特征融合模塊 (Selective Kernel Feature Fusion, SKFF)
對(duì)偶注意力機(jī)制單元 (Dual Attention Unit, DAU)
下采樣模塊 (Downsampling Module)
上采樣模塊 (Upsampling Module)
Learning Enriched Features for Real Image Restoration and Enhancement
https://arxiv.org/pdf/2003.06792v2.pdf
基于 sayakpaul/MIRNet-TFLite-TRT 提供的 MIRNet 模型可以實(shí)現(xiàn)圖像照度的恢復(fù),其運(yùn)行效果如圖所示。
MIRNet-TFLite-TRT 的展示效果
最后,我們簡(jiǎn)單介紹在樹(shù)莓派上部署這個(gè) MIRNet 模型的實(shí)際操作。
1. 交叉編譯 MIRNet 應(yīng)用。
edge-brain$ bazel build --config elinux_armhf //examples/mir_net:mir_net --experimental_repo_remote_exec
2. 將編譯出來(lái)的可執(zhí)行文件 mir_net 與它的模型文件 lite-model_mirnet-fixed_dr_1.tflite 和測(cè)試圖片 data/test.jpg 上傳至樹(shù)莓派,其中 192.168.1.2 是樹(shù)莓派的 IP。
edge-brain$ scp bazel-bin/examples/mir_net pi@192.168.1.2:~
edge-brain$ scp lite-model_mirnet-fixed_dr_1.tflite pi@192.168.1.2:~
edge-brain$ scp data/test.jpg pi@192.168.1.2:~
3. 在樹(shù)莓派的終端運(yùn)行 MIRNet 應(yīng)用。
~$ 。/mir_net -i=test.jpg -m=lite-model_mirnet-fixed_dr_1.tflite -o=output.jpg
4. 查看 output.jpg,可以看到運(yùn)行后的結(jié)果。
使用入門(mén)
為了讓讀者能夠相當(dāng)輕松地應(yīng)用我們的 Edge Brain 環(huán)境入門(mén)嵌入式智能應(yīng)用部署,我們介紹兩種簡(jiǎn)單的程序編譯方式,供讀者參考完成自己的 AI 業(yè)務(wù)部署。同時(shí),我們也歡迎各位小伙伴為開(kāi)源社區(qū)貢獻(xiàn)你們的應(yīng)用案例與實(shí)踐反饋。
編譯外部 GitHub 工程
我們以 SunAriesCN/image-classifier 的圖像分類(lèi)應(yīng)用為例,詳細(xì)介紹如何兩步完成外部 GitHub 工程的交叉編譯,還能為我們 Edge Brain 貢獻(xiàn)新案例。
1. 在 edge-brain/WORKSPACE 工程環(huán)境配置文件導(dǎo)入外部 image-classifier 工程。
load(“@bazel_tools//tools/build_defs/repo:git.bzl”, “git_repository”)
# Custom other thirdparty applications into repo as examples.
git_repository(
name = “image-classifier”,
remote = “https://github.com/SunAriesCN/image-classifier.git”,
commit= “72d80543f1887375abb565988c12af1960fd311f”,
)
上述代碼很清晰地告訴我們,Bazel 將從遠(yuǎn)程倉(cāng)庫(kù) image-classifier 中拉取特定 commit 版本的代碼到本地,并以 @image-classifier 代表其路徑。我們未來(lái)可以直接使用 @image-classifier//XXX 訪(fǎng)問(wèn)該外部工程配置的編譯文件。這樣我們不僅僅可以獲得對(duì)應(yīng)的代碼文件,還能輕松的進(jìn)行版本控制。
2. 在 example 文件夾中新建對(duì)應(yīng)文件夾,并配置 BUILD 編譯配置描述。
SunAriesCN/image-classifier 工程項(xiàng)目提供了一些圖像分類(lèi)的模型應(yīng)用。我們可以分別將它們配置到Edge Brain 對(duì)應(yīng)的 example 文件夾中。比如,我們?cè)?edge-brain/examples 下創(chuàng)建一個(gè) image_benchmark 案例目錄,再添加相應(yīng)的 BUILD 編譯配置描述。我們將得到目錄結(jié)構(gòu)如下:
├── examples
│ ├── BUILD
│ ├── hello_opencv
│ │ ├── BUILD
│ │ └── hello-opencv.cc
│ ├── hello_world
│ │ ├── BUILD
│ │ └── minimal.cc
│ ├── image_benchmark
│ │ └── BUILD
其中,examples 下的每個(gè)目錄代表一個(gè)應(yīng)用案例。而且,所有案例目錄都有一個(gè) BUILD 文件描述對(duì)應(yīng)案例項(xiàng)目的編譯配置。比如, image_benchmark 對(duì)應(yīng) SunAriesCN/image-classifier 的圖像分類(lèi)基準(zhǔn)測(cè)試應(yīng)用。它的 BUILD 描述如下:
alias(
name=“image_benchmark”,
actual=“@image-classifier//image_classifier/apps/raspberry_pi:image_classifier_benchmark”
)
我們可以看到其內(nèi)容非常易懂,即給第三方倉(cāng)庫(kù) @image-classifier 中對(duì)應(yīng)的 image_classifier_benchmark 應(yīng)用創(chuàng)建別名為 image_benchmark。
完成上述外部工程導(dǎo)入操作后,我們可以使用下面的指令輕松完成應(yīng)用的交叉編譯:
edge-brain$ bazel build --config elinux_armhf //examples/image_benchmark:image_benchmark --experimental_repo_remote_exec
為了便于 Edge Brain 項(xiàng)目的長(zhǎng)期維護(hù),同時(shí),我們也希望能為每位開(kāi)源貢獻(xiàn)者帶來(lái)項(xiàng)目成功的榮譽(yù)。我們更加推薦這種編譯外部 GitHub 工程的應(yīng)用方式,畢竟它能實(shí)現(xiàn)我們項(xiàng)目間協(xié)同開(kāi)發(fā)。只要你的項(xiàng)目工程也使用 Bazel 工具進(jìn)行編譯,你便可以在 edge-brain 的 WORKSPACE 中添加簡(jiǎn)單的幾行代碼配置完成嵌入式智能應(yīng)用的部署。
直接添加 examples 案例
該方式也特別簡(jiǎn)單,參考 “Bazel TensorFlow Lite” 部分內(nèi)容或 edge-brain/examples/hello_world 案例,我們?cè)?examples 目錄下創(chuàng)建案例目錄,編寫(xiě) BUILD 描述文件以及相應(yīng)的智能應(yīng)用代碼,再回到 edge-brain 目錄執(zhí)行 bazel build。編譯成功后,我們從 bazel-bin/examples/hello_world 中將測(cè)試程序與相關(guān)模型文件上傳到樹(shù)莓派上運(yùn)行即完成部署。
最后,如果你愿意為我們的項(xiàng)目貢獻(xiàn)代碼案例,請(qǐng)你在完成程序調(diào)試后,向 Edge Brain 項(xiàng)目提交 Pull Request,我們將繼續(xù)完善后面代碼審核和 README 文檔更新工作,最后會(huì)予以署名致謝。
原文標(biāo)題:社區(qū)分享 | TensorFlow Lite 邊緣智能快速入門(mén)
文章出處:【微信公眾號(hào):TensorFlow】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
責(zé)任編輯:haq
-
智能終端
+關(guān)注
關(guān)注
6文章
886瀏覽量
34824 -
邊緣計(jì)算
+關(guān)注
關(guān)注
22文章
3119瀏覽量
49321
原文標(biāo)題:社區(qū)分享 | TensorFlow Lite 邊緣智能快速入門(mén)
文章出處:【微信號(hào):tensorflowers,微信公眾號(hào):Tensorflowers】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論