Jetson Nano是NVIDIA出品的一款小型AI计算机,与Raspberry PI这种开发板相比,它上面安装了NVIDIA Maxwell GPU,显然更适合于运行计算机视觉方面的AI应用。根据我之前的一篇博客,也可以很轻松地配置好一台具有计算机视觉AI开发环境的Jetson Nano。
但Jetson Nano毕竟也只有信用卡大小,直接运行一些很成熟的CV模型,如YOLO、OpenPose等,都会遇到诸如too many resources requested for launch
的问题。NVIDIA论坛上也有好多提问指出了类似的问题,有大佬指出这种嵌入式系统中的4G内存是不够用的,运行深度学习的模型也都必须是优化后的轻量级模型。
此时了解到Google前两年就推出了一款称为MediaPipe的开源机器学习框架,该框架主要就提供了处理视频流的跨平台深度学习优化功能。其核心使用C++开发,可以通过自定义计算图,实现数据预处理、后处理以及组合应用各种TensorFlow Lite模型进行推断等。使用MediaPipe框架运行计算图的同时,会自动进行合适的优化,使生成的应用能够最大化地利用各种计算资源,并运行在轻量级的设备上。同时MediaPipe官方已经提供了十几个常用的案例程序,如物体识别、手势识别、面部识别、身体姿态识别等。于是就尝试在Jetson Nano上安装运行MediaPipe的示例程序,记录在这篇博客中。
首先克隆下来MediaPipe的GitHub库,之后按照官方的说明文档中的Installation部分安装配置MediaPipe运行所需要的环境。
For Nvidia Jetson and Raspberry Pi devices with aarch64 Linux, Bazel needs to be built from source:
既然MediaPipe是Google开发的,那构建工具自然还是要用Google自家的——Bazel。谷歌应该也考虑到了Jetson玩家对MediaPipe的需求,安装说明中就指明了如果是Jetson和Raspberry Pi设备,其架构为ARM64位(aarch64),因此需要从源码编译安装。从GitHub上下载Bazel源码,然后依次运行如下命令:
# 安装依赖包
> sudo apt-get install build-essential openjdk-8-jdk zip unzip
# 解压源码
> mkdir $HOME/bazel-3.7.2
> cd $HOME/bazel-3.7.2
> unzip bazel-3.7.2-dist.zip
# 编译
> env EXTRA_BAZEL_ARGS="--host_javabase=@local_jdk//:jdk" bash ./compile.sh
# 将生成的bazel文件放在PATH目录中
> sudo cp output/bazel /usr/local/bin/
# 确认安装结果以及版本号
> bazel --version
OpenCV和FFMpeg都已经在Jetson上安装过,由于MediaPipe是Bazel项目,因此重要的也就是将MediaPipe中第三方库的路径配置修改一下就好。在MediaPipe官网上说明只需要使用如下命令:
> sed -i "s/x86_64-linux-gnu/aarch64-linux-gnu/g" third_party/opencv_linux.BUILD
> sed -i "s/x86_64-linux-gnu/aarch64-linux-gnu/g" third_party/ffmpeg_linux.BUILD
也就是将third_party/opencv_linux.BUILD
和third_party/ffmpeg_linux.BUILD
文件中所有的x86_64-linux-gnu
更换为aarch64-linux-gnu
,即将OpenCV和FFMpeg的库路径定义在/usr/lib/aarch64-linux-gnu
下。在我的Jetson上,FFMpeg的库已预装,在/usr/lib/aarch64-linux-gnu
目录下确实都有相关的.so
文件,运行上述命令的第二句就行。但OpenCV是从源码编译安装的,编译安装后的lib文件和头文件都拷贝至/usr/local
下,因此需要结合修改WORKSPACE
文件以及third_party/opencv_linux.BUILD
两个文件以定位OpenCV库路径:
# WORKSPACE文件
new_local_repository(
name = "linux_opencv",
build_file = "@//third_party:opencv_linux.BUILD",
path = "/usr/local",
)
# third_party/opencv_linux.BUILD文件
cc_library(
name = "opencv",
srcs = glob(
[
"lib/libopencv_core.so",
"lib/libopencv_calib3d.so",
"lib/libopencv_features2d.so",
"lib/libopencv_highgui.so",
"lib/libopencv_imgcodecs.so",
"lib/libopencv_imgproc.so",
"lib/libopencv_video.so",
"lib/libopencv_videoio.so",
],
),
hdrs = glob([
# For OpenCV 3.x
"include/opencv2/**/*.h*",
# For OpenCV 4.x
# "include/opencv4/opencv2/**/*.h*",
]),
includes = [
# For OpenCV 3.x
"include/",
# For OpenCV 4.x
# "include/opencv4/",
],
linkstatic = 1,
visibility = ["//visibility:public"],
)
> sudo apt-get install mesa-common-dev libegl1-mesa-dev libgles2-mesa-dev
> sudo apt-get install mesa-utils
# 检验安装情况
> glxinfo | grep -i opengl
OpenGL vendor string: NVIDIA Corporation
OpenGL renderer string: NVIDIA Tegra X1 (nvgpu)/integrated
OpenGL core profile version string: 4.6.0 NVIDIA 32.4.4
OpenGL core profile shading language version string: 4.60 NVIDIA
OpenGL core profile context flags: (none)
OpenGL core profile profile mask: core profile
OpenGL core profile extensions:
OpenGL ES profile version string: OpenGL ES 3.2 NVIDIA 32.4.4
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.20
OpenGL ES profile extensions:
注意使用glxinfo
检验OpenGL安装情况时,若通过ssh连接,则运行该命令时会报错libGL error: MESA-LOADER: failed to open swrast (search paths /usr/lib/aarch64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
,而通过直连Jetson的显示器运行该命令则不会有该错误。因此若之后通过ssh连接Jetson构建或运行出错,应该考虑通过直连显示器进行尝试。
之后在构建或运行时就需要将命令行中的参数--define MEDIAPIPE_DISABLE_GPU=1
替换为--copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11
。如下面尝试构建运行GPU版本的"Hello World":
> export GLOG_logtostderr=1
> bazel run --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 mediapipe/examples/desktop/hello_world:hello_world
首先确认各种库(CUPTI、CUDA、CUDNN、NVCC)的安装情况:
> ls /usr/local/cuda-10.2/targets/aarch64-linux/lib | grep libcupti*
libcupti.so
libcupti.so.10.2
libcupti.so.10.2.75
> ls /usr/local/cuda-10.2
bin extras lib64 nvml nvvmx samples targets version.txt
doc include LICENSE nvvm README share tools
> nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2019 NVIDIA Corporation
Built on Wed_Oct_23_21:14:42_PDT_2019
Cuda compilation tools, release 10.2, V10.2.89
> ls /usr/lib/aarch64-linux-gnu/ | grep libcudnn.so
libcudnn.so
libcudnn.so.8
libcudnn.so.8.0.0
将CUDA库的路径通过环境变量TF_CUDA_PATHS
配置给TensorFlow:
> vi /etc/profile
export TF_CUDA_PATHS=/usr/local/cuda-10.2,/usr/lib/aarch64-linux-gnu,/usr/include
为了让bazel编译时能找到CUDA,还需要将TensorFlow官方.bazelrc文件中的build:using_cuda
和build:cuda
部分添加到MediaPipe文件夹下的.bazelrc
文件中:
# This config refers to building with CUDA available. It does not necessarily
# mean that we build CUDA op kernels.
build:using_cuda --define=using_cuda=true
build:using_cuda --action_env TF_NEED_CUDA=1
build:using_cuda --crosstool_top=@local_config_cuda//crosstool:toolchain
# This config refers to building CUDA op kernels with nvcc.
build:cuda --config=using_cuda
build:cuda --define=using_cuda_nvcc=true
最后在编译的时候,就要在命令中再加两个参数--config=cuda
和--spawn_strategy=local
。
在MediaPipe文件夹下,运行如下命令编译Holistic GPU版:
> bazel build -c opt --config=cuda --spawn_strategy=local --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 mediapipe/examples/desktop/holistic_tracking:holistic_tracking_gpu
编译过程中报错ERROR: /home/victor/mediapipe/mediapipe/framework/tool/BUILD:66:10: Linking of rule '//mediapipe/framework/tool:encode_as_c_string' failed (Exit 1)
,GitHub上有Issue提出类似问题,但解决方案貌似是需要修改一个中间文件。注意到有评论说目前MediaPipe用到CUDA的仅仅在Object Detection中,因此暂时放弃加上CUDA选项。修改编译命令重新编译:
> bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 mediapipe/examples/desktop/holistic_tracking:holistic_tracking_gpu
编译时间较长,花费了30多分钟,需要耐心等待。再运行如下命令运行生成的程序:
> export GLOG_logtostderr=1
> ./bazel-bin/mediapipe/examples/desktop/holistic_tracking/holistic_tracking_gpu --calculator_graph_config_file=mediapipe/graphs/holistic_tracking/holistic_tracking_gpu.pbtxt
运行状态较好,帧数较为稳定,CPU利用率在60%左右,GPU利用率在80-90%浮动,可见应该是成功利用到了GPU资源。注意连接CSI摄像头时,需要修改mediapipe\examples\desktop\demo_run_graph_main_gpu.cc
中设置正确的GStream Pipeline,否则无法打开摄像头,连接USB摄像头则可以直接使用。