10分钟内在GPU跑起目标检测应用(1)

NVIDIA
671 0 2019-06-29

目标检测(object detection)仍然是自动驾驶和智能视频分析等应用的主要驱动力,需要大量数据集进行大量训练,以实现高精度。 NVIDIA GPU在训练大型网络以生成用于目标检测推理数据集,所需的并行计算性能方面表现优异。本文介绍如何使用NVIDIA GPU快速高效地运行高性能目标检测。

这里的Python应用程序从实时视频流中获取帧,并在GPU上执行目标检测。使用结合 Inception V2的预训练SSD(Single Shot Detection)模型,然后利用TensorRT的优化流程,为GPU生成运行时(runtime),然后对视频源进行推断以获得标签和边界框。然后,应用程序使用这些边界框和类标签注释原始框架。生成的视频源具有覆盖在其上的目标检测网络的边界框预测,相同的方法可以扩展到其他任务,如分类(classification)与分割(segmentation)。

虽然GPU和NVIDIA软件知识并非必须的,您依然应该熟悉目标检测和python编程,来完成后续的步骤。接下来将会使用到一些软件工具,包括用NVIDIA GPU Cloud(NGC)的Docker容器来设置我们的环境、用OpenCV来获取摄像头的图像,以及TensorRT来加速我们的推理。您需要一台支持CUDA的GPU系统,和一台连接到机器的网络摄像头,来运行本文示例。可以使用命令nvidia-smi测试正常工作的GPU,您可能会发现此CUDA GPU列表很有帮助。

在本文结束时,您将了解设置端到端(end-to-end)目标检测推理工作流(pipeline)所需的组件,如何在GPU上应用不同的优化,以及如何在工作流执行FP16和INT8精度的推理。此示例中,我们使用带有Inception V2的SSD网络作为主干,本例中的所有代码都可以在NVIDIA GitHub上找到,包括所有安装内容的详细README。

执行范例!

我们使用docker容器来设置环境并将其打包以进行分发,使用容器的方式可以很快从冲突和崩溃中恢复,所以在尝试这个例子之前,请确保你的机器上有Docker和NVIDIA Docker。请进入到 object-detection-webcam 文件夹并运行以下部分以构建容器并运行应用程序

./setup_environment.sh
python SSD_Model/detect_objects_webcam.py

将显示一个窗口,显示来自网络摄像头的视频输入,边框和标签如图1所示。


图1所示。命令提示符上的输出显示了用于推断和目标类的Top-1预测的时间

安装NGC环境与TensorRT开源软件


回顾一下,安装的所有代码都可以在setup_environment.sh中找到。这里有4个关键步骤:

1.设置Docker查看webcam的环境变量
2.下载VOC数据集用于INT8校准(稍后我们将在博客中看到)
3.构建包含运行代码所需的所有库的Dockerfile
4.启动Dockerfile,以便我们可以在正确的环境中应用程序


由于使用Docker容器来管理环境,需要让容器访问主机中的所有硬件。除了手动添加的网络摄像头外,大部分都是由Docker自动处理的,我们需要设置Docker访问X11的权限,用于打开webcam提要的GUI。使用 docker run命令来启动修改过的环境变量。

xhost +local:docker
XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' |
xauth -f $XAUTH nmerge -

接下来,我们下载用于INT8校准的PASCAL VOC数据集,这将在后面的部分中介绍。这个数据集包含普通家庭用品和日常用品的图像。 

wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
tar -xf VOCtest_06-Nov-2007.tar

然后我们构建一个具有整个开发环境的Dockerfile。 Dockerfile安装以下组件:

1、TensorRT与相关的依赖库
2、TensorRT开源软件,替换TensorRT安装中的插件和解析器
3、整个应用程序的其他依赖项

NVIDIA NGC的TensorRT容器使得安装TensorRT非常简单。容器包含所需的库,如CUDA、cuDNN和NCCL。NGC是预构建容器的存储库,每月更新一次,并跨平台和云服务提供商进行测试。查看发布说明中的TensorRT容器中有什么。由于除了TensorRT之外,我们还需要组合其他多个库和包,因此我们将创建一个自定义Dockerfile,其中TensorRT容器作为基本映像。

在示例中,我们使用了最新版本的TensorRT插件和解析器,因为它们是开源的。插件提供了在TensorRT模型中使用定制层的方法,并且已经包含在TensorRT容器中。例如,SSD模型使用来自插件库的flatconcat插件。严格地说,在这个例子中我们不需要使用插件的开源版本;使用TensorRT容器中提供的版本也可以。这很容易知道,使您能够扩展和自定义这些组件来支持模型中的自定义层。
为了获得开源插件,我们克隆了TensorRT github repo,使用cmake构建组件,并用新版本替换TensorRT容器中这些组件的现有版本。TensorRT应用程序将在此路径下搜索TensorRT核心库、解析器和插件。
最后,我们可以安装应用程序所需的其他依赖项,主要是OpenCV及其呈现库。OpenCV是一个计算机视觉库,我们使用它与我们的网络摄像头进行交互。

使用docker build命令构建Dockerfile中的所有组件:

docker build -t object_detection_webcam . # don’t forget the period at the end

启动容器以打开新的开发环境,如下所示。在这个命令中,我们将运行时设置为nvidia tolet。Docker知道我们的主机有gpu,然后我们将GitHub repo挂载到Docker容器中以访问其中的代码,最后通过后续挂载和环境变量转发关于如何与webcam交互的信息。 

docker run --runtime=nvidia -it -v `pwd`/:/mnt --device=/dev/video0 \
-e DISPLAY=$DISPLAY -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH object_detection_webcam

优化模型并建立推理引擎

应用程序detect_objects_webcam.py的伪代码如下,如图2所示:

# Download the frozen object detection model from TensorFlow Model Zoo
# Convert the frozen model (.pb file) to Universal Framework Format (UFF) 
# Build the TensorRT engine from the UFF version of the model 
# While True: 
	# Read in a frame from the webcam 
# Run inference on that frame using our TensorRT engine 
# Overlay the bounding boxes and class labels 
# Display that frame back to the user

(图2、本文涵盖了此工作流程中的所有步骤,从构建TensorRT引擎到将其插入到简单应用程序中)

第一个步骤是从 TensorFlow模型库下载已冻结的SSD物件检测模型

221 def prepare_ssd_model(model_name="ssd_inception_v2_coco_2017_11_17", silent=False):
222    """Downloads pretrained object detection model and converts it to UFF.
223
224    The model is downloaded from Tensorflow object detection model zoo.
225    Currently only ssd_inception_v2_coco_2017_11_17 model is supported
226    due to model_to_uff() using logic specific to that network when converting.
227
228    Args:
229        model_name (str): chosen object detection model
230        silent (bool): if True, writes progress messages to stdout
231    """
232    if model_name != "ssd_inception_v2_coco_2017_11_17":
233        raise NotImplementedError(
234            "Model {} is not supported yet".format(model_name))
235    download_model(model_name, silent)
236    ssd_pb_path = PATHS.get_model_pb_path(model_name)
237    ssd_uff_path = PATHS.get_model_uff_path(model_name)
238    model_to_uff(ssd_pb_path, ssd_uff_path, silent)
接着就是对该模型进行优化给推理计算使用,并且生成一个可在GPU上执行的运行时(runtime)。这里使用的 TersonRT,不仅是一种深度学习优化器,同时也是个运行时引擎。TensorRT会从这个应用去生成针对每个GPU的运行时引擎,你需要让这个应用产生最低的延迟,才有可能得到实时推理的效果。让我们看看TensorRT怎么运作。

使用model.py中提供的实用程序将冻结的TensorFlow图转换为通用框架格式(UFF)。现在,您可以使用解析器将UFF模型导入到Tensorrt中,应用优化,并生成运行时引擎。在构建过程中,优化被应用到引擎盖下,您不需要做任何事情来应用它们。例如,Tensorrt可以将多个层(如卷积、磁阻和偏压)融合到一个层中。这叫做层融合。另一种优化方法是张量融合或层聚合,在这种方法中,共享相同输入的层融合到单个内核中,然后将它们的结果取消连接。

要生成运行时引擎,需要指定4个参数:

    1、模型的UFF文件路径
    2、推理机精度(FP32、FP16或INT8)
    3、校准数据集(仅在运行INT8时需要)
    4、推理期间使用的 batch 大小

参考 engine.py 中引擎生成的代码,生成引擎的函数称为build_engine

在推理计算时,采用低精度(fp16和int8)方式将会提高了吞吐量,并提供了较低的延迟。使用FP16精度张量核的性能比FP32快几倍,而且模型精度没有下降;INT8的推理性能会进一步提高,模型精度下降不到1%。TensorRT可以选择从FP32到该设备能允许的任何精度的内核。例如启用FP16精度时,TensorRT会在FP16与FP32之间选择内核。如果要使用FP16和INT8精度,最好两者都启用,这样有机会得到最佳的性能。

校准数据集是用于确定图中张量的动态范围,因此您可以有效地使用限制范围的INT8精度,稍后会详细介绍。

最后一个参数batch大小用于为推理工作负载选择最佳内核,调用引擎是可选择小于创建期间指定的批量大小,但是性能可能并不理想。我通常将预期的最通用 batch 生成一些引擎,然后在它们之间切换。在这个例子中,我们将从网络摄像头一次抓取一帧,将batch设为1。

同样重要的是要关注 TensorRT 会自动检测各种 GPU 硬件,如果您的 GPU 具有Tensor Core,它将自动检测并在这些 Tensor Core 上运行FP16内核。

我们来看看engine.py,探索这些参数是如何工作的。 

69 def build_engine(uff_model_path, trt_logger, trt_engine_datatype=trt.DataType.FLOAT, calib_dataset=None, batch_size=1, silent=False):
70    with trt.Builder(trt_logger) as builder, builder.create_network() as network, trt.UffParser() as parser:
71        builder.max_workspace_size = 2 << 30
72        builder.max_batch_size = batch_size
73        if trt_engine_datatype == trt.DataType.HALF:
74            builder.fp16_mode = True
75        elif trt_engine_datatype == trt.DataType.INT8:
76            builder.fp16_mode = True
77            builder.int8_mode = True
78            builder.int8_calibrator = calibrator.SSDEntropyCalibrator(data_dir=calib_dataset, cache_file='INT8CacheFile')
79
80        parser.register_input(ModelData.INPUT_NAME, ModelData.INPUT_SHAPE)
81        parser.register_output("MarkOutput_0")
82        parser.parse(uff_model_path, network)
83
84        if not silent:
85            print("Building TensorRT engine. This may take few minutes.")
86
87        return builder.build_cuda_engine(network)

build_engine函数为构建器(builder)、解析器(parser)和网络创建一个对象,解析器以UFF格式导入SSD模型,并将转换后的图放在网络对象中。当我们使用UFF解析器导入转换后的 TensorFlow 模型时,其实 TensorRT 也提供 Caffe 和 ONNX 解析器,这两个都可以在TensorRT 开源 repo 中找到,只要调用 ONNXParser 就可以使用该模型的 ONNX 格式,代码的其余部分是相同的。

第71行指定 TensorRT 应用优化时应该使用的内存,这只是一个工作空间,可以给定您系统允许的最大尺寸,这里我给定2 GB大小。下面的条件代码根据推理的精度设置参数,第一次运行时使用默认的FP32精度。

接下来的几行指定解析器的输入节点和输出节点的名称和形状,paser.parse 实际上使用前面所指定的参数,在UFF文件上执行解析器。最后的 builder.build_cuda_engine 对网络进行优化,并生成engine对象。

engine.py脚本有两个额外的关键函数:save_engineload_engine,一旦生成了引擎,就可以将其保存到磁盘以备将来使用,这个过程称为序列化(serialization)。序列化生成一个计划文件,可以随后从磁盘加载进来,通常比从头构建引擎快得多。这就是 load 和 save 函数的作用。如果更改用于构建引擎的参数、使用的模型或GPU,则需要重新生成引擎,因为TensorRT将选择不同的内核来构建引擎。

您可以从NGC模型下载几个预训练模型、参数和精度的组合的计划文件。如果我使用的是标准模型,我通常首先检查的是NGC上是否有可用的计划文件可以直接在我的应用程序中使用。