Amiya · 2022年04月20日

​硬件加速 3D 实时感知 (HARP-3D)

硬件加速 3D 实时感知 (HARP-3D)

使用在 ULTRA96V2 上运行的深度神经网络在 LiDAR 点云中进行 3D 对象检测的端到端演示。

0ab4c504d89463aed003dadbccb2ed50.png

本项目用到的东西

Ultra96-V2

c6a57c02353e88dace042238fc020a04.png

绪论

理念

--

感知是自动驾驶中的一项关键任务,并且可能是当今技术界讨论最多的话题之一。

简单来说,感知是理解数据的任务。这些数据通常由各种传感器生成,例如相机、雷达、激光雷达等。

最常用的感知任务是对象检测和语义分割。

对象检测是在传感器数据中定位和分类感兴趣的对象的任务。

语义分割是将传感器数据的每个像素/元素标记为属于一组感兴趣类别中的一个类别的任务。

深度神经网络 (DNN) 已经发展成为复杂感知算法的支柱,否则使用传统的计算机视觉算法几乎不可能进行设计。

任何类型的 DNN 的基本数学运算都是矩阵乘法。

事实证明,矩阵乘法是一种可以高度并行化的运算。这就是 GPU 对深度学习有用的根本原因。

这就是硬件加速可以作为加速深度学习算法的一种手段。

本项目旨在演示这一点,使用 Ultra96v2 开发板作为硬件加速平台。

Ultra96v2 基于 Xilinx Zynq MPSoC 平台,开发板具有除 FPGA 之外的其他几个外设。

该项目演示了如何使用 Ultra96v2 作为硬件加速平台和 Vitis-AI 作为软件平台在 LiDAR 点云中执行对象检测。它还以端到端的方式演示了带有对象检测模型的 Ultra96v2 如何作为边缘 AI 应用程序工作。

传感器数据


该项目使用 LiDAR 传感器数据作为输入。现在,由于传感器的状态非常昂贵,该项目使用来自最先进的 LiDAR 传感器的预先记录的数据。这是KITTI数据集(http://www.cvlibs.net/dataset...)。

传感器的典型扫描,称为点云如下所示。

点云中的每个点都由至少 3 个数字表示,这些数字对应于 3D 空间中该点相对于传感器的 x、y 和 z 坐标。

bf1fefa850b2de8fb6ff4f7afc09afd4.png

目标

--

我们试图确定汽车和其他物体在上述点云中的位置,并在它们周围放置一个边界框。

这如下所示。

04132fb144178e3f7693a4d494dd3479.png

神经网络


使用的神经网络是称为U-Net的语义分割网络。

https://arxiv.org/abs/1505.04597

它被修改为预测关键点,其中关键点是范围图像中的对象中心。

一旦我们有了关键点,一个简单的后处理步骤将它们转换回 3D 坐标 - x、y、z。

根据每个关键点的类别完成基于模型的框拟合。

这是通过获取 KITTI 训练数据集中不同对象类别的框的平均尺寸来发现的。

例如,汽车通常具有长度、宽度和高度分别为 3.6、1.5、1.8 的盒子尺寸。

下图显示了网络的架构。

15aadfe2e2fc82a2354b6f98bd60d3bd.png

数据预处理


3D 点云首先被转换为称为距离图像的 2D 表示,然后使用上面显示的 U-Net 架构进行处理。

7270120ca62dec80355e4bd180050045.png

转换过程的详细信息可以在SqueezeSeg论文中(https://arxiv.org/abs/1710.07368)找到。

作者还提供了一个预先转换的数据集(https://www.dropbox.com/s/pnz...\_2d.tgz?dl=0)。

更多详细信息,请参考他们的github页面(https://github.com/BichenWuUC...)。

预测

--

为每个对象预测关键点,其中关键点是对象在其分割掩码中的中心。下图展示了一些预测的关键点。

90e40cb8218fddf6bf03044e8b282533.png

一旦检测到关键点并将每个对象过滤为 1 个关键点,然后将球坐标转换回 3D 笛卡尔坐标以获得关键点在 3D 空间中的位置(在 3D PCL 中)。

公式可以在这里找到(https://mathinsight.org/spher...\_coordinates)。

上文中附上了将 2D 关键点转换为 3D 的代码。

f140243772880bdaf326f126285e5ac6.png

上图显示了预测的 2D 关键点如何映射到点云中的 3D 位置。

一旦知道了对象位置,就完成了基于模型的框拟合,即,对于每个对象类,从训练集中找到的平均框尺寸用于放置 3D 边界框,如下所示。

a589ea028d6c4f00be2f9a947903621b.png

61115938cfa8f802eab5ff7bdccc3611.png

8a18bd83be15f217d891cce22134168e.png

使用 Vitis-AI-1.2 将训练好的模型转换为.elf

原始模型是使用 keras 训练的。

可以在附件中找到经过训练的模型。

第 1 步:获取 Ultra96V2 的系统镜像

https://www.hackster.io/Alber...)教程中的系统镜像可以使用。也可以为 Ultra96v2 构建自己的板映像。

镜像中包含实例化 DPU IP 所需的所有组件。

将镜像中文件安装到 SD 卡。

然后,将 .dcf 文件从 SD 卡复制到本地文件系统。

下一步在 github 代码库中创建了一个文件夹 - Ultra96v2,并将 dcf 文件复制到其中。

然后在同一文件夹中创建一个arch.json文件(http://arch.json/)。

两者都可以在附件中找到。

给开发板加电,连接到 Web 服务器并配置 WiFi。

SSH 进入开发板并从根文件夹验证 DPU 确实存在

dexporer --whoami  

应该看到类似于以下内容的输出:

[DPU Core Configuration List]  
DPU Core                : #0  
DPU Enabled             : Yes  
DPU Arch                : B4096  
DPU Target Version      : v1.4.1  
DPU Freqency            : 300 MHz  
Ram Usage               : High  
DepthwiseConv           : Enabled  
DepthwiseConv+Relu6     : Enabled  
Conv+Leakyrelu          : Enabled  
Conv+Relu6              : Enabled  
Channel Augmentation    : Enabled  
Average Pool            : Enabled  
  
DPU Core                : #1  
DPU Enabled             : Yes  
DPU Arch                : B4096  
DPU Target Version      : v1.4.1  
DPU Freqency            : 300 MHz  
Ram Usage               : High  
DepthwiseConv           : Enabled  
DepthwiseConv+Relu6     : Enabled  
Conv+Leakyrelu          : Enabled  
Conv+Relu6              : Enabled  
Channel Augmentation    : Enabled  
Average Pool            : Enabled  
  
[DPU Extension List]  
Extension Softmax  
Enabled                 : Yes

`

第二步:将.h5模型量化编译为.elf

使用 Vitis-AI 设置为本地系统
从github 的 Vitis-AI 克隆 DenseNet 示例
https://github.com/Xilinx/Vit...

c284a19fc79ec525d6eeb4522dba6262.png

本教程包含将.h5 训练的 keras 模型转换、量化和编译为要部署在 Ultra96V2 上的.elf 所需的所有文件。

克隆后,将 files/build/keras_model 文件夹中的 k_model.h5 文件替换为附加的训练模型。或者,替换为自己的自定义模型。名称必须为 k_model.h5。这确保了在后续步骤中所需的更改更少。

现在,根据您的本地系统更改 0_setenv.sh 脚本中的以下参数。

# network parameters  
export INPUT_HEIGHT=64  
export INPUT_WIDTH=256  
export INPUT_CHAN=1  
export INPUT_SHAPE=?,${INPUT_HEIGHT},${INPUT_WIDTH},${INPUT_CHAN}  
export INPUT_NODE=input_1  
export OUTPUT_NODE=conv2d_23/BiasAdd  
export NET_NAME=u3d_kp  
  
# training parameters  
export EPOCHS=200  
export BATCHSIZE=150  
export LEARNRATE=0.001  
  
  
# target board  
sudo mkdir /opt/vitis_ai/compiler/arch/DPUCZDX8G/ULTRA96V2  
sudo cp ./ULTRA96V2/arch.json /opt/vitis_ai/compiler/arch/DPUCZDX8G/ULTRA96V2  
sudo cp ./ULTRA96V2/ULTRA96V2.dcf /opt/vitis_ai/compiler/arch/DPUCZDX8G/ULTRA96V2  
export ARCH=/opt/vitis_ai/compiler/arch/DPUCZDX8G/ULTRA96V2/arch.json  
  
#export ARCH=/opt/vitis_ai/compiler/arch/DPUCZDX8G/ZCU102/arch.json  
# DPU mode - best performance with DPU_MODE = normal  
export DPU_MODE=normal  
#export DPU_MODE=debug 

上述更改会在我们启动 tensorflow 工作区时自动将 Ultra96 板文件复制到工作区中。Ultra96 板文件应与此脚本位于同一文件夹中。

对于输出节点,如果是自定义模型,最好的方法是继续量化,然后查看错误消息并相应地修改参数。

继续获取有关如何获取输出节点名称的示例。

导航到克隆存储库的文件夹并键入:

`sh -x docker_run.sh xilinx/vitis-ai:latest
`

应该看到:

3c2d63aa30db86d54631ae40736da21f.png

现在,在本地更改量化文件,如下所示,替换为自己的校准数据生成功能。这个函数基本上是一个简单的 python 脚本,它返回成批的训练数据,预处理方式与推理完全相同。

附上用于该项目的功能。

#!/bin/bash  
  
# Copyright 2020 Xilinx Inc.  
#   
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#   
#     http://www.apache.org/licenses/LICENSE-2.0  
#   
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
  
  
  
# quantize  
quantize() {  
    
  echo "Making calibration images.."    
  
  # python tf_gen_images.py  \  
  #     --dataset=train \  
  #     --image_dir=${QUANT}/images \  
  #     --calib_list=calib_list.txt \  
  #     --max_images=${CALIB_IMAGES}  
  
  # log the quantizer version being used  
  vai_q_tensorflow --version  
  
  # quantize  
  # vai_q_tensorflow quantize \  
  #   --input_frozen_graph ${FREEZE}/${FROZEN_GRAPH} \  
 #  --input_fn           image_input_fn.calib_input \  
 #  --output_dir         ${QUANT} \  
 #   --input_nodes        ${INPUT_NODE} \  
 #  --output_nodes       ${OUTPUT_NODE} \  
 #  --input_shapes       ${INPUT_SHAPE} \  
 #  --calib_iter         10  
  
  vai_q_tensorflow quantize \  
    --input_frozen_graph ${FREEZE}/${FROZEN_GRAPH} \  
  --input_fn           datagen.get_calib_data \  
  --output_dir         ${QUANT} \  
   --input_nodes        ${INPUT_NODE} \  
  --output_nodes       ${OUTPUT_NODE} \  
  --input_shapes       ${INPUT_SHAPE} \  
  --calib_iter         10  
}  
  
echo "-----------------------------------------"  
echo "QUANTIZE STARTED.."  
echo "-----------------------------------------"  
  
rm -rf ${QUANT}   
mkdir -p ${QUANT}/images  
quantize 2>&1 | tee ${LOG}/${QUANT_LOG}  
rm -rf ${QUANT}/images  
  
echo "-----------------------------------------"  
echo "QUANTIZE COMPLETED"  
echo "-----------------------------------------"  
  

在上面,datagen.py是 python 文件,get_calib_data 是函数。

键入以下命令并验证输出:

`source ./0_setenv.sh
source ./2_keras2tf.sh
`

输出的一部分应显示:

keras_2_tf command line arguments:  
 --keras_json:   
 --keras_hdf5: ./build/keras_model/k_model.h5  
 --tf_ckpt   : ./build/tf_chkpt/tf_float.ckpt  
-------------------------------------  
Keras model information:  
 Input names : [<tf.Tensor 'input_1:0' shape=(?, 64, 256, 1) dtype=float32>]  
 Output names: [<tf.Tensor 'conv2d_23/BiasAdd:0' shape=(?, 64, 256, 2) dtype=float32>]  
 
 Checkpoint created : ./build/tf_chkpt/tf_float.ckpt  

在上面,我们可以看到输出节点名称是:conv2d_23/BiasAdd。

注意:不要添加 ':0'

回到 0_setenv.sh 脚本,填写输出节点名称。

在这个阶段,一个名为 -frozen_graph.pb 的冻结图在 build/freeze 中生成

现在使用以下命令从工作区中执行量化脚本:

`source ./4_quant.sh
`

成功的量化应该产生:

221da65740222fc61f5214559889ed8a.png

我们可以看到使用的部分校准图像。

最后,编译

`source ./6_compile.sh
`

成功的编译应该产生:

bb3433c027a6e106838573b1b195d529.png

现在应该在 /build/compile 中看到 .elf 文件

Ultra96v2 上的部署和应用执行

现在我们有了模型,下一步是将模型复制到 Ultra96,然后编写使用该模型的应用程序。

将.elf 复制到 /root 文件夹中选择的任何文件夹中。

下图显示了我如何放置它:

aa0a6ab27cef8662145a17f099f6a5f8.png
在上面,我使用了 VSCode 来 ssh 进入板子。它使应用程序开发变得非常简单。

虽然有多种方法可以设计用于推理的 IoT 应用程序,但对于该项目,使用了模拟传感器,因为获得产生数据的 Velodyne 传感器是不切实际的。

因此,再次使用 KITTI 测试/验证数据集,主机作为模拟传感器,通过 FTP 将数据发送到 Ultra96 进行推理。

整体应用架构如下图所示。

23b9f8386dbb4d71c7665cf0110edb9a.png

一个简单的 TCP 客户端/服务器套接字应用程序用于同步,Ultra96 上的一个共享文件夹用于数据交换。

具体来说,主机打开一个TCP套接字作为服务器,等待Ultra96客户端连接。

客户端是整个推理应用程序的一部分。

客户端然后通过套接字发送一个启动消息,主机 PC 通过 FTP 将第一批测试数据复制到 Ultra96 上的共享文件夹。

以下代码段显示了数据传输。

from ftpretty import ftpretty  
import numpy as np  
import os  
import socket  
  
def place_test_file_in_fpga(local_file_path, host_addr, remote_path):  
    """  
    File copies the file at local_file_path to the remote_file_path on U96      
    """  
    # Supply the credentisals  
    f = ftpretty(host_addr, "root", "root")  
    file = np.load(file_path)  # read in the numpy file  
    # Get a file, save it locally  
    # f.get('someremote/file/on/server.txt', '/tmp/localcopy/server.txt')  
    # Put a local file to a remote location  
    # non-existent subdirectories will be created automatically  
    f.put(file_path, remote_file_path+'/')  

主应用程序作为 jupyter notebook 附加。

检测到的关键点的一些预测结果如下所示。

271e9423e2a05a62848975bfd9201c71.png

下面显示了在批量大小为 1 的 50 帧上进行推理的帧速率。

72fafde24786ce55b522d48e652d8a06.png

正如我们所看到的,它实现了令人印象深刻的 109 帧/秒的帧速率。

然而,高帧率的一部分也归因于使用的网络非常小,准确性不是这里的关键问题。因此,在这个项目中,准确性不是基准。

第一部分中解释的最终后处理是在主机上完成的,因为这不是推理的一部分。

附上一些测试文件。

链接:https://pan.baidu.com/s/1cBse7zkCWwK7idnx9qZhtw?pwd=open提取码:open

结论

对我来说,这是一个非常有趣的机会,可以在硬件上尝试加速深度学习算法。一些预处理和后处理也可以作为硬件 IP 来实现。这将使推理更快。

项目参考地址:

https://www.hackster.io/sm\_1991/hardware-accelerated-real-time-perception-in-3d-harp-3d-fde0c0

END

原文链接: https://mp.weixin.qq.com/s/aKD46ZokKpXgTiezRxBeHg
微信公众号:
hack电子.jpg
推荐阅读
更多IC设计技术干货请关注IC设计技术专栏。
推荐阅读
关注数
19608
内容数
1303
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息