10分钟内基于gpu的目标检测
10分钟内基于gpu的目标检测
Object Detection on GPUs in 10 Minutes
目标检测仍然是自动驾驶和智能视频分析等应用的主要驱动力。目标检测应用程序需要使用大量数据集进行大量训练,以实现高精度。NVIDIA gpu在训练大型网络以生成用于对象检测推断的数据集所需的并行计算性能方面表现优异。本文介绍了使用NVIDIA gpu快速高效地运行高性能目标检测管道所需的技术。
我们的python应用程序从实时视频流中获取帧,并在gpu上执行对象检测。我们使用带有Inception V2的预先训练的单点检测(SSD)模型,应用TensorRT的优化,为GPU生成一个运行时,然后对视频feed执行推断以获得标签和边界框。然后,应用程序使用这些边界框和类标签注释原始框架。生成的视频源上覆盖了来自我们的对象检测网络的边界框预测。同样的方法可以扩展到其他任务,如分类和分割。
虽然不需要了解GPUs和NVIDIA软件,但您应该熟悉对象检测和python编程。使用的一些软件工具包括NVIDIA GPU Cloud(NGC)的Docker容器来设置我们的环境,OpenCV来运行来自摄像机的feed,以及TensorRT来加速我们的推断。虽然您将受益于简单阅读这篇文章,您需要一个CUDA功能的GPU和一个网络摄像头连接到您的机器来运行这个例子。您可以使用命令nvidia smi测试工作的GPU。你可能会发现这个CUDA gpu列表很有用。
在本文结束时,您将了解设置端到端对象检测推断管道所需的组件,如何在gpu上应用不同的优化,以及如何在管道上执行FP16和INT8精度的推断。在本例中,我们使用以InceptionV2为骨干的单点检测网络。作为参考,所有代码(以及如何安装所有内容的详细自述文件)都可以在NVIDIA GitHub repo上找到。
Run the Sample!
我们使用docker容器来设置环境并将其打包以供分发。我们可以回忆许多使用容器的情况,在这些情况下,很容易从冲突和崩溃中恢复,因此在尝试此示例之前,请确保您的计算机上有Docker和NVIDIA Docker。
导航到“主要对象检测”网络摄像头文件夹并运行以下部分以生成容器并运行应用程序:
./setup_environment.sh
python SSD_Model/detect_objects_webcam.py
这将弹出一个窗口,显示来自您的网络摄像头的视频源,并覆盖边框和标签,如图1所示。
Figure 1. The output on the command prompt displays the time taken for inference and the Top-1 prediction of target classes
Setup with NGC and TensorRT open source software
让我们检查一下安装程序,安装程序中提供了安装程序的所有setup_environment.sh。有4个关键步骤:
设置Docker查看网络摄像头的环境变量
下载用于INT8校准的VOC数据集(我们稍后将在博客中看到)
构建包含运行代码所需的所有库的Dockerfile
启动Dockerfile以便在正确的环境中启动应用程序
因为我们使用Docker容器来管理我们的环境,所以我们需要让容器访问主机中的所有硬件。大部分是由Docker自动处理的,除了我们手动添加的摄像头。我们需要设置Docker访问X11的权限,X11用于打开网络摄像头源的图形用户界面。使用环境变量并通过设置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安装以下组件:
TensorRT和必需的库
TensorRT开源软件,在TensorRT安装中替换插件和解析器
我们应用程序的其他依赖项
NVIDIA NGC的TensorRT容器使安装TensorRT变得非常简单。容器包含必需的库,如CUDA、cuDNN和NCCL。NGC是一个预构建容器的存储库,每个月更新一次,并跨平台和云服务提供商进行测试。查看发行说明中TensorRT容器中的内容。由于除了TensorRT之外,我们还需要组合其他多个库和包,因此我们将创建一个自定义Dockerfile,并将TensorRT容器作为基本映像。
我们在示例中使用了TensorRT插件和解析器的最新版本,因为它们是开源的。插件提供了一种在TensorRT中的模型中使用自定义层的方法,并且已经包含在TensorRT容器中。例如,SSD模型使用插件库中的flattencat插件。严格地说,在本例中,我们不需要使用插件的开源版本;使用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
一旦容器启动,就可以使用detect_objects_webcam.py对象运行应用网络摄像头。
Optimize Model, Build Engine for Inference
探测目标的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
Figure 2. This post covers all the steps in this workflow, from building the TensorRT engine to plugging it into a simple application.
第一步是从TensorFlow model zoo下载冻结的SSD对象检测模型。
This is done in prepare_ssd_model
in model.py
:
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上执行的运行时。我们使用TensorRT,一个深度学习优化器和运行时引擎。TensorRT从这个应用程序为每个NVIDIA GPU生成运行时。您需要应用程序提供尽可能低的延迟来实时执行推断。让我们看看如何使用TensorRT。
使用中提供的实用程序将冻结的TensorFlow图转换为通用框架格式(UFF)model.py。现在,您可以使用解析器将UFF模型导入TensorRT,应用优化并生成运行时引擎。优化是在构建过程中在幕后应用的,您不需要做任何事情来应用它们。例如,TensorRT可以将卷积、ReLU和偏压等多个层融合到单个层中。这叫做层融合。另一种优化方法是张量融合或层聚合,在这种方法中,共享相同输入的层融合到一个内核中,然后将它们的结果分离。
要构建运行时引擎,需要指定4个参数:
模型的UFF文件路径
推理机精度(FP32、FP16或INT8)
校准数据集(仅在运行INT8时需要)
推断期间使用的批大小
见engine制造规范engine.py. 生成引擎的函数称为build_engine。
较低精度的推理(FP16和INT8)增加了吞吐量并提供较低的延迟。使用FP16精度在张量核上提供比FP32快几倍的性能,有效地不降低模型精度。INT8中的推理可以在模型精度下降不到1%的情况下进一步提高性能。TensorRT从FP32和您允许的任何精度中选择内核。启用FP16精度时,TensorRT从FP16和FP32精度中选择内核。要使用FP16和INT8精度,请启用两者以获得尽可能高的性能。
利用定标法确定图中张量的动态范围,可以有效地利用INT8精度的限制范围。稍后再谈。
最后一个参数batch size用于为推理工作负载选择最佳内核。您可以将引擎用于比创建期间指定的更小的批处理大小。不过,表现可能并不理想。我通常为我期望的最常见的批处理大小生成一些引擎,并在它们之间切换。在本例中,我们将一次从摄像头中抓取一帧,使批量大小为1。
还需要注意的是,TensorRT会自动检测GPU上的任何专用硬件。因此,如果你的GPU有张量核,它会自动检测并在这些张量核上运行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函数为生成器、解析器和网络创建一个对象。解析器以UFF格式导入SSD模型,并将转换后的图形放在network对象中。当我们使用UFF解析器导入转换后的TensorFlow模型时,TensorRT还包括用于Caffe和ONNX的解析器。两者都可以在TensorRT开源repo中找到。使用此模型的ONNX格式只意味着调用ONNXParser;其余代码将是相同的。
第71行指定TensorRT应用于应用优化的内存。这只是临时空间,您应该提供系统允许的最大大小;我提供2 GB。条件代码根据推理的精度设置参数。对于第一次运行,我们使用默认的FP32精度。
接下来的几行指定解析器的输入节点和输出节点的名称和形状。这个parser.parse分析实际使用上面指定的参数在UFF文件上执行解析器。最后,builder.build_cuda_engine对网络应用优化,并生成引擎对象。
剧本engine.py有两个附加的关键功能:保存引擎和加载引擎save_engine
and load_engine
。生成引擎后,可以将其保存到磁盘以备将来使用,这是一个称为序列化的过程。序列化生成一个计划文件,随后可以从磁盘加载该文件,通常比从头重新构建引擎快得多。这就是这些加载和保存函数的作用。如果您确实更改了用于构建引擎、使用的模型或GPU的参数,则需要重新生成引擎,因为TensorRT将选择不同的内核来构建引擎。
您可以从NGC模型下载预训练模型、参数和精度的多个组合的计划文件。如果我使用的是标准模型,我通常首先检查NGC上是否有可直接在我的应用程序中使用的计划文件。
Run Inference With TensorRT Engine
我们现在可以使用TensorRT引擎执行目标检测。我们的示例一次从网络摄像机获取一帧,并将其传递给TensorRT引擎推理inference.py -更具体地说,在功能推断网络摄像头infer_webcam。
166 def infer_webcam(self, arr):
167 """Infers model on given image.
168
169 Args:
170 arr (numpy array): image to run object detection model on
171 """
172
173 # Load image into CPU and do any pre-processing
174 img = self._load_img_webcam(arr)
175
176 # Copy it into appropriate place into memory
177 # (self.inputs was returned earlier by allocate_buffers())
178 np.copyto(self.inputs[0].host, img.ravel())
179
180 # When inferring on single image, we measure inference
181 # time to output it to the user
182 inference_start_time = time.time()
183
184 # Fetch output from the model
185 [detection_out, keepCount_out] = do_inference(
186 self.context, bindings=self.bindings, inputs=self.inputs,
187 outputs=self.outputs, stream=self.stream)
188
189 # Output inference time
190 print("TensorRT inference time: {} ms".format(
191 int(round((time.time() - inference_start_time) * 1000))))
192
193 # And return results
194 return detection_out, keepCount_out
此函数首先从网络摄像机加载图像(第174行),然后在函数load_img_webcam网络摄像机中执行几个预处理步骤。我们的示例将轴的顺序从HWC移动到CHW,对图像进行规格化,使所有值都在-1和+1之间,然后展平数组。您还可以在此函数中添加管道所需的任何其他预处理操作。
计时器从第182行开始测量TensorRT引擎执行推理所需的时间。这有助于理解整个推理管道的延迟。
我们调用do_inference来执行推理。此函数将数据发送到TensorRT引擎进行推理,并返回两个参数:detection_out和keepCount_out。detection_out函数包含每次检测的边界框坐标、置信度和类标签的所有信息。keepCount_out例程跟踪网络发现的检测总数。
Putting It All Together
到目前为止,我们已经研究了如何从TensorFlow model zoo导入一个预先训练的模型,将其转换为UFF格式,应用优化并生成TensorRT引擎,并使用该引擎对来自网络摄像机的单个图像执行推断。
让我们看看所有这些组件在检测对象时是如何组合在一起detect_objects_webcam.py:
166 def infer_webcam(self, arr):
167 """Infers model on given image.
168
169 Args:
170 arr (numpy array): image to run object detection model on
171 """
172
173 # Load image into CPU and do any pre-processing
174 img = self._load_img_webcam(arr)
175
176 # Copy it into appropriate place into memory
177 # (self.inputs was returned earlier by allocate_buffers())
178 np.copyto(self.inputs[0].host, img.ravel())
179
180 # When inferring on single image, we measure inference
181 # time to output it to the user
182 inference_start_time = time.time()
183
184 # Fetch output from the model
185 [detection_out, keepCount_out] = do_inference(
186 self.context, bindings=self.bindings, inputs=self.inputs,
187 outputs=self.outputs, stream=self.stream)
188
189 # Output inference time
190 print("TensorRT inference time: {} ms".format(
191 int(round((time.time() - inference_start_time) * 1000))))
192
193 # And return results
194 return detection_out, keepCount_out
解析命令行参数后,prepare_ssd_model使用model.py将冻结的TensorFlow图转换为UFF格式。然后在第153行初始化一个TensorRT推理对象,该对象在engine.py如上所述,实际构建TensorRT引擎。如果没有引擎文件保存在args.trt_引擎路径然后我们需要从头开始。UFF版本的模型也是如此。我们将在默认的FP32精度下运行,这样就不需要提供校准数据集。最后,由于我们只在一个网络摄像头feed上运行实时推断,因此我们将保持批大小为1。
现在让我们将其集成到操作网络摄像头的应用程序中。如果打开相机标志(默认),应用程序将使用OpenCV(第164行)启动视频流,并在第167行中输入主循环。如第169行所示,该循环不断从网络摄像机中拉入新帧,然后如第172行所示对该帧执行推断。
最后,我们将边界框结果覆盖到原始帧(第176-180行)上,并使用imshow将其显示给用户。
这就是我们的整个管道!
Inference in INT8 Precision With TensorRT
与框架内推理相比,应用程序在gpu上使用TensorRT执行推理的速度快了几倍。但是,你可以使它快几倍。到目前为止,我们使用单精度(FP32)进行推理,其中每个数字都使用32位表示。在FP32中,激活值可以在±3.4×1038的范围内,并且需要32位来存储每个数字。数量越大,执行时需要的存储空间就越大,这也会导致性能降低。当切换到使用精度较低的FP16时,大多数型号的精度几乎相同。使用NVIDIA提供的模型和技术,可以使用INT8 precision进行推理,从而获得尽可能高的性能。但是,请注意表1中可以用INT8精度表示的明显较低的动态范围。
Table 1. The dynamic range of values that can be represented at in FP32, FP16, and INT8 precision
使用INT8精度获得类似于FP32推断的精度意味着执行称为校准的附加步骤。在校准期间,您可以对与最终数据集类似的训练数据运行推断,并收集激活值的范围。然后,TensorRT计算一个比例因子,将INT8值的范围分布在每个节点的激活值范围内。图3显示,如果一个节点的激活范围在-6和+6之间,那么您希望可以用INT8表示的256个值只覆盖这个范围。
Figure 3. Calibration and quantization are critical steps for converting to INT8 precision.
使用下面的命令重新构建TensorRT引擎,以便在应用程序中使用INT8来提高精度,执行校准并运行推断。整个过程可能需要几分钟:
python detect_objects_webcam -p 8
您应该会看到与先前使用FP32精度获得的结果相比,性能更高的相同结果。
让我们看看如何在内置引擎中执行此操作engine.py. 条件块基于为推理启用的精度启用不同的生成器模式。默认情况下,TensorRT始终选择FP32内核。启用FP16模式意味着它还会尝试以FP16精度运行的内核;INT8也是如此。
然而,仅仅因为允许较低精度的内核并不意味着它们在性能上总是优于较高精度的内核。例如,即使我们将precision模式设置为INT8,一些FP16或FP32内核可能仍然存在,最终会运行得更快。TensorRT自动选择最佳优化。
TensorRT检测专用硬件(如Tensor内核)的存在,并将在其上使用FP16内核以获得尽可能高的性能。TensorRT自动选择最佳内核的能力称为内核自动调整。这使得在提供高性能的同时跨多种应用程序使用TensorRT成为可能。
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')
注意,INT8条件块使用函数SSDEntropyCalibrator。这个类在批量校准期间通过模型运行校准数据。因此,只需实现名为get_batch in的函数calibrator.py从校准数据集中获取下一批数据。请参阅中的SSDEntropyCalibrator代码calibrator.py下面。
14 class SSDEntropyCalibrator(trt.IInt8EntropyCalibrator2):
15 def __init__(self, data_dir, cache_file):
16 # Whenever you specify a custom constructor for a TensorRT class,
17 # you MUST call the constructor of the parent explicitly.
18 trt.IInt8EntropyCalibrator2.__init__(self)
19
20 self.num_calib_imgs = 100 # the number of images from the dataset to use for calibration
21 self.batch_size = 10
22 self.batch_shape = (self.batch_size, IMG_CH, IMG_H, IMG_W)
23 self.cache_file = cache_file
24
25 calib_imgs = [os.path.join(data_dir, f) for f in os.listdir(data_dir)]
26 self.calib_imgs = np.random.choice(calib_imgs, self.num_calib_imgs)
27 self.counter = 0 # for keeping track of how many files we have read
28
29 self.device_input = cuda.mem_alloc(trt.volume(self.batch_shape) * trt.float32.itemsize)
此函数将图像目录作为要校准的输入,并将其作为存储缓存文件的位置。此缓存文件包含网络激活所需的所有缩放因子。如果保存激活值,则只需对特定配置运行一次校准,并且只需为任何后续运行加载此缓存表。
这就是用TensorRT进行INT8校准所需要做的一切!
Next Steps
现在您已经基本了解了如何在GPU上快速设置和运行对象检测应用程序。我们已经覆盖了很多领域,包括设置、INT8 precision部署、使用TensorRT中最新的开源插件和解析器、连接到网络摄像头以及叠加结果。如果您在使用此应用程序时遇到问题,请确保检查此示例的GitHub repo中的问题以了解类似的问题和解决方案。
如果你想继续使用gpu进行目标检测和其他与AI相关的任务,请查看开发者博客中有关为gpu创建目标检测管道以及如何使用TensorRT加速推理的相关文章。我们还提供了一个关于如何为公共应用程序执行推理的网络研讨会,它使用了本文中介绍的相同的代码库。您还可以在TensorRT开源repo和TensorRT示例页面上找到TensorRT的其他资源,其中包括刚刚介绍的SSD示例。NVIDIA TensorRT开发者论坛提供了一个TensorRT用户社区,他们就最佳实践交换信息。
最后,如果你想加入免费的NVIDIA开发计划,以获得额外的技术资源和文件错误报告的能力,在我们的开发程序页注册。您将加入NVIDIA开发人员的庞大和不断增长的社区,为gpu创建新的和新颖的应用程序。
References
[Liu et al. 2016] Liu, Wei, et al. “SSD: Single shot multibox detector.” European Conference on Computer Vision. Springer, Cham, 2016.
[Szegedy et al. 2016] Szegedy, Christian, et al. “Rethinking the inception architecture for computer vision.” Proceedings of the IEEE conference on computer vision and pattern recognition. 2016.
[Lin et al. 2014] Lin, Tsung-Yi, et al. “Microsoft COCO: Common objects in context.” European conference on computer vision. Springer, Cham, 2014.