这几年我们在日常工作生活中看到越来越多的智能终端设备的出现,如智能家电、商城客服机器人、物流配送无人小车、智能监控等等,它们可以为我们生活带来各种各样的便利。因此,边缘智能与AIoT已成为不少国内外企业发展的一个重要方向。边缘智能是一项以嵌入式设备应用开发为基础的前沿技术,我们需要在一些资源紧张的嵌入式设备,如MCU、SOC,部署如人脸识别、物体检测、音频分类等智能应用。
然而,我们又看到现实的嵌入式智能应用开发面正临着一些软硬件生态兼容方面的挑战。例如,芯片厂商提供推荐的板载系统往往是定制的,使用不同的编译工具,而且大多不会有Python解释器。所以,当我们打算将自己的智能应用部署到嵌入式设备时,我们绕不开AI推理框架跨平台的问题。
TensorFlowLite应用C++++作为框架底层的基础实现可以天然保证跨平台扩展特性,但由于它的这项技术的比较前沿,在嵌入式Linux设备上以Python接口为主,有些开发者不太适应,认为不易上手。为此,我们开发EdgeBrain方便开发者以其熟悉的交叉编译方式部署TensorFlowLite智能应用,让他们的嵌入式应用走向智能化。
交叉编译
交叉编译是指,一种在某个系统平台下可以产生另一个系统平台的可执行文件的编译方式。这种方式的优点是,当程序在目标运行系统平台进行编译比较困难时,它通过解耦编译和运行两个过程来实现更高效的程序调试。比如,在资源紧张的LinuxARM嵌入式系统平台调试应用程序,其编译过程往往有着很高的CPU占用率,更不用说我们其实希望程序的编译与运行调试工作能并行开展。因此,交叉编译在嵌入式智能开发有着重要的应用场景。
后面的篇幅,我们将参考官方文档以跨平台交叉编译树莓派的TensorFlowLiteC++应用为例,介绍如何实现跨平台部署嵌入式智能应用的部署。因为,树莓派是LinuxARM嵌入式系统平台的其中一种,所以我们希望本文能够起到抛砖引玉的效果,读者未来遇到类似的问题时,能举一反三完成业务平台的部署,甚至分享心得与我们一起为开源社区做贡献。
准备Docker编译环境
本文选用的交叉编译工具为Google开源推出的Bazel。其由于具有易用性的特点,已经在大量开源AI项目中得到应用。在本章节,我们将手把手的带领您一步一步搭建编译环境。首先,我们不希望开发者由于环境安装的兼容性问题,遇到系统软件版本冲突的状况。所以,我们建议大家将程序的编译环境配置在docker中。这样不仅可以保证本地环境的安全,还能方便后续环境迁移。
本文选用ubuntu04作为我们的基础镜像,并在其中采用Bazel官网中BinaryInstaller的安装方式。具体步骤如下:
1.创建Dockerfile内容如下
Fromubuntu:18.04
RUNaptupdate-y
&&aptinstall-ycurlgnupggitvimpythonpython3python3-distutilspython3-pipg++unzipzipopenjdk-11-jdkwgetcmakemake-y
&&pip3installnumpy
&&wgethttps://github.com/bazelbuild/bazelisk/releases/download/v1.7.5/bazelisk-linux-amd64
&&chmod+xbazelisk-linux-amd64
&&mvbazelisk-linux-amd64/usr/bin/bazel
&&echo‘exportPATH=$PATH:$HOME/bin’》》~/.bashrc
&&apt-getpurge-y--auto-remove
2.在Dockerfile所在目录中执行下面的命令生成我们需要的Docker镜像实现编译环境的配置。
~$dockerbuild-tbazel-build-env:v0.01。
BazelTensorFlowLite
Bazel可以轻松完成交叉编译,互联网有许多教程介绍toolchain的配置原理,我们不再赘述。这里我们主要介绍交叉编译TensorFlowLite的实战步骤。因为我们希望最终程序在树莓派上使用,所以我们直接使用TensorFlow的toolchain配置即可。具体步骤如下:
1.导入TensorFlow库
TensorFlow的toolchain以及TFLite相关的源码均存在github的仓库之中,于是我们需要使用Bazel将其自动下载下来,并继承其配置文件。Bazel提供了非常简单的实现方式,即在项目根目录下配置WORKSPACE文件中追加如下内容即可:
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”)
#NeededbyTensorFlow
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”)
可以看到上述内容中,我们不仅仅制定了TensorFlow仓库,而且Bazel还允许我们通过tag来选择特定版本的内容。除此之外,在配置好TensorFlow仓库之后,还能使用@org_tensorflow来进行额外的配置,如继承仓库中的WORKSPACE配置。
2.修改.bazelrc文件
我们参考TensorFlow库中的配置,修改项目路径中的edge-brain/.bazelrc如下:
#TFsettings
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
经过第一步的配置,我们已经使得Bazel不仅知道从何处下载什么版本的TensorFlow源码,还加载了TF仓库中已有的相关配置。这样当我们使用--configelinux_armhf时,bazel将知道应使用TF库中@local_config_embedded_arm//:toolchain来编译代码,至此便轻松的完成了交叉编译的环境配置工作,接下来让我们来测试下编译环境。
3.验证TFLite的Bazel配置
我们的EdgeBrain仓库已经为你提前完成上述的相关环境配置。现在,我们可以执行下面的指令尝试编译TFLite提供的minial.cc程序验证编译环境。
edge-brain$bazelbuild--configelinux_armhf//examples/hello_world:hello_world--experimental_repo_remote_exec
BazelOpenCV
OpenCV是一个轻量高效的计算机视觉和机器学习软件库,可以跨平台运行在Linux、Windows、Android和MacOS的操作系统上,而且集成许多图像处理和计算机视觉方面的通用优秀算法。再之,计算机视觉作为最先引入卷积神经网络的前沿领域,它的智能算法相对成熟并且已被广泛应用于各种生活场景,如安防常用的人脸识别与目标跟踪等都属于这一领域。
但是,如前文所述,我们现有的嵌入式系统平台的种类繁多而且硬件资源特别有限。所以,OpenCV团队难以支持各式各样系统平台的库文件预编译(binaryprebuilt),而我们也不愿意忍受嵌入式系统上编译OpenCV库的漫长过程。因此,我们基于Bazel工具搭建OpenCV智能应用的交叉编译环境,希望它帮助一些计算机视觉领域同学快速构建他们自己的嵌入式视觉应用。
下面我们将简单介绍Bazel搭建OpenCV编译环境的解决思路。我们了解到OpenCV主要构建工具是CMake,所以它的所有编译配置都写在CMakeList.txt文件中。CMake是现在开源项目的主流编译工具,过去如Caffe、Tesseract以及Boost等开源项目都是用CMake编译的。因此,Bazel为了兼容CMake的编译规则扩展提供一个名为cmak_external函数接口,实现对第三方库编译参数的控制。
cmak_external函数接口有两个控制编译参数的关键变量:cache_entries与make_commands。Bazel会根据这两个变量的参数自动编写一个适合的CMake运行脚本并执行得到理想的编译结果。简单来说,我们可以认为cmak_external就是让Bazel通过Shell脚本控制本地终端完成CMake的编译过程。下面我们展示edge-brain/third_party/BUILD如何配置OpenCV的静态库编译。
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”ifOPENCV_SHARED_LIBSelse“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”ifnotOPENCV_SHARED_LIBSelse“OFF”,
“OPENCV_SKIP_PYTHON_LOADER”:“ON”,
“BUILD_opencv_python”:“OFF”,
“ENABLE_CCACHE”:“OFF”,
},
make_commands=[“make-j4”,“makeinstall”]+[“cp{}/share/OpenCV/3rdparty/lib/*.a{}/lib/”.format(OPENCV_DEPS_PATH,OPENCV_DEPS_PATH)],
lib_source=“@opencv//:all”,
linkopts=[]ifOPENCV_SHARED_LIBSelse[
“-ldl”,
“-lm”,
“-lpthread”,
“-lrt”,
],
shared_libraries=select({
“@bazel_tools//src/conditions:darwin”:[“libopencv_%s.%s.dylib”%(module,OPENCV_SO_VERSION)formoduleinOPENCV_MODULES],
“//conditions:default”:[“libopencv_%s.so.%s”%(module,OPENCV_SO_VERSION)formoduleinOPENCV_MODULES],
})ifOPENCV_SHARED_LIBSelseNone,
static_libraries=[“libopencv_%s.a”%moduleformoduleinOPENCV_MODULES]
+[moduleformoduleinOPENCV_THIRD_PARTY_DEPS]ifnotOPENCV_SHARED_LIBSelseNone,
alwayslink=True,
)
最后,我们在edge-brain目录运行下面的指令编译测试程序,并将测试程序拷贝到树莓派上运行,从而验证Bazel搭建OpenCV编译环境正确性。
edge-brain$bazelbuild--configelinux_armhf//examples/hello_opencv:hello-opencv--experimental_repo_remote_exec
应用实践
下面我们将介绍如何利用EdgeBrain的编译环境完成实际的智能应用在嵌入式平台的部署。
低照度图像增强
MIRNet是LearningEnrichedFeaturesforRealImageRestorationandEnhancement提出的一种图像增强网络模型。该模型学习了一组丰富的特征,这些特征结合了来自多个尺度的上下文信息,同时保留了高分辨率的空间细节。其算法的核心是:并行多分辨率卷积流,用于提取多尺度特征;跨多分辨率流的信息交换;空间和通道注意力机制来捕获上下文信息;基于注意力的多尺度特征聚合。下面是MIRNet的一些原理图示。
MIRNet整体框架
选择核心特征融合模块(SelectiveKernelFeatureFusion,SKFF)
对偶注意力机制单元(DualAttentionUnit,DAU)
下采样模块(DownsamplingModule)
上采样模块(UpsamplingModule)
LearningEnrichedFeaturesforRealImageRestorationandEnhancement
https://arxiv.org/pdf/2003.06792v2.pdf
基于sayakpaul/MIRNet-TFLite-TRT提供的MIRNet模型可以实现图像照度的恢复,其运行效果如图所示。
MIRNet-TFLite-TRT的展示效果
最后,我们简单介绍在树莓派上部署这个MIRNet模型的实际操作。
1.交叉编译MIRNet应用。
edge-brain$bazelbuild--configelinux_armhf//examples/mir_net:mir_net--experimental_repo_remote_exec
2.将编译出来的可执行文件mir_net与它的模型文件lite-model_mirnet-fixed_dr_1.tflite和测试图片data/test.jpg上传至树莓派,其中192.168.1.2是树莓派的IP。
edge-brain$scpbazel-bin/examples/mir_netpi@192.168.1.2:~
edge-brain$scplite-model_mirnet-fixed_dr_1.tflitepi@192.168.1.2:~
edge-brain$scpdata/test.jpgpi@192.168.1.2:~
3.在树莓派的终端运行MIRNet应用。
~$。/mir_net-i=test.jpg-m=lite-model_mirnet-fixed_dr_1.tflite-o=output.jpg
4.查看output.jpg,可以看到运行后的结果。
使用入门
为了让读者能够相当轻松地应用我们的EdgeBrain环境入门嵌入式智能应用部署,我们介绍两种简单的程序编译方式,供读者参考完成自己的AI业务部署。同时,我们也欢迎各位小伙伴为开源社区贡献你们的应用案例与实践反馈。
编译外部GitHub工程
我们以SunAriesCN/image-classifier的图像分类应用为例,详细介绍如何两步完成外部GitHub工程的交叉编译,还能为我们EdgeBrain贡献新案例。
1.在edge-brain/WORKSPACE工程环境配置文件导入外部image-classifier工程。
load(“@bazel_tools//tools/build_defs/repo:git.bzl”,“git_repository”)
#Customotherthirdpartyapplicationsintorepoasexamples.
git_repository(
name=“image-classifier”,
remote=“https://github.com/SunAriesCN/image-classifier.git”,
commit=“72d80543f1887375abb565988c12af1960fd311f”,
)
上述代码很清晰地告诉我们,Bazel将从远程仓库image-classifier中拉取特定commit版本的代码到本地,并以@image-classifier代表其路径。我们未来可以直接使用@image-classifier//XXX访问该外部工程配置的编译文件。这样我们不仅仅可以获得对应的代码文件,还能轻松的进行版本控制。
2.在example文件夹中新建对应文件夹,并配置BUILD编译配置描述。
SunAriesCN/image-classifier工程项目提供了一些图像分类的模型应用。我们可以分别将它们配置到EdgeBrain对应的example文件夹中。比如,我们在edge-brain/examples下创建一个image_benchmark案例目录,再添加相应的BUILD编译配置描述。我们将得到目录结构如下:
├──examples
│├──BUILD
│├──hello_opencv
││├──BUILD
││└──hello-opencv.cc
│├──hello_world
││├──BUILD
││└──minimal.cc
│├──image_benchmark
││└──BUILD
其中,examples下的每个目录代表一个应用案例。而且,所有案例目录都有一个BUILD文件描述对应案例项目的编译配置。比如,image_benchmark对应SunAriesCN/image-classifier的图像分类基准测试应用。它的BUILD描述如下:
alias(
name=“image_benchmark”,
actual=“@image-classifier//image_classifier/apps/raspberry_pi:image_classifier_benchmark”
)
我们可以看到其内容非常易懂,即给第三方仓库@image-classifier中对应的image_classifier_benchmark应用创建别名为image_benchmark。
完成上述外部工程导入操作后,我们可以使用下面的指令轻松完成应用的交叉编译:
edge-brain$bazelbuild--configelinux_armhf//examples/image_benchmark:image_benchmark--experimental_repo_remote_exec
为了便于EdgeBrain项目的长期维护,同时,我们也希望能为每位开源贡献者带来项目成功的荣誉。我们更加推荐这种编译外部GitHub工程的应用方式,毕竟它能实现我们项目间协同开发。只要你的项目工程也使用Bazel工具进行编译,你便可以在edge-brain的WORKSPACE中添加简单的几行代码配置完成嵌入式智能应用的部署。
直接添加examples案例
该方式也特别简单,参考“BazelTensorFlowLite”部分内容或edge-brain/examples/hello_world案例,我们在examples目录下创建案例目录,编写BUILD描述文件以及相应的智能应用代码,再回到edge-brain目录执行bazelbuild。编译成功后,我们从bazel-bin/examples/hello_world中将测试程序与相关模型文件上传到树莓派上运行即完成部署。
最后,如果你愿意为我们的项目贡献代码案例,请你在完成程序调试后,向EdgeBrain项目提交PullRequest,我们将继续完善后面代码审核和README文档更新工作,最后会予以署名致谢。
原文标题:社区分享|TensorFlowLite边缘智能快速入门
文章出处:【微信公众号:TensorFlow】欢迎添加关注!文章转载请注明出处。
责任编辑:haq