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

NVIDIA
316 0 2019-06-29

使用Tensorrt引擎运行推理

我们现在可以使用 Tensorrt 引擎执行对象检测。示例中一次从网络摄像机中提取一帧图像,并将其传递给 inference.py 代码里的 TensorRT 引擎,更具体地说是在 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 则跟踪网络找到的检测总数。

合并在一起处理

到目前为止,我们已经研究了如何从TensorFlow Model Zoo导入一个预先培训过的模型,将其转换为UFF格式,应用优化并生成一个Tensorrt引擎,并使用该引擎对来自网络摄像头的单个图像执行推理。

让我们看看所有这些组件是如何在 detect_objects_webcam.py 中结合在一起的:

141 def main():
142
143    # Parse command line arguments
144    args = parse_commandline_arguments()
145
146    # Fetch .uff model path, convert from .pb
147    # if needed, using prepare_ssd_model
148    ssd_model_uff_path = PATHS.get_model_uff_path(MODEL_NAME)
149    if not os.path.exists(ssd_model_uff_path):
150        model_utils.prepare_ssd_model(MODEL_NAME)
151
152    # Set up all TensorRT data structures needed for inference
153    trt_inference_wrapper = inference_utils.TRTInference(
154        args.trt_engine_path, ssd_model_uff_path,
155        trt_engine_datatype=args.trt_engine_datatype,
156        calib_dataset = args.calib_dataset,
157        batch_size=args.max_batch_size)
158
159    print("TRT ENGINE PATH", args.trt_engine_path)
160
161    if args.camera == True:
162        print('Running webcam:', args.camera)
163        # Define the video stream
164        cap = cv2.VideoCapture(0)  # Change only if you have more than one webcams
165
166        # Loop for running inference on frames from the webcam
167        while True:
168            # Read frame from camera (and expand its dimensions to fit)
169            ret, image_np = cap.read()
170
171            # Actually run inference
172            detection_out, keep_count_out = trt_inference_wrapper.infer_webcam(image_np)
173
174            # Overlay the bounding boxes on the image
175            # let analyze_prediction() draw them based on model output
176            img_pil = Image.fromarray(image_np)
177            prediction_fields = len(TRT_PREDICTION_LAYOUT)
178            for det in range(int(keep_count_out[0])):
179                analyze_prediction(detection_out, det * prediction_fields, img_pil)
180            final_img = np.asarray(img_pil)
181
182            # Display output
183            cv2.imshow('object detection', final_img) 
184
185            if cv2.waitKey(25) & 0xFF == ord('q'):
186                cv2.destroyAllWindows()
187                break

在解析命令行参数之后,prepare_ssd_model 使用 model.py 将冻结的 TensorFlow 图形转换为 UFF格式,然后在153行中调用 engine.py 里的 build_engine 来初始化 TensorRT的推理对象,实际上这就是前面提到构建 TensorRT引擎。如果 args.trt_engine_path 上没有保存任何引擎文件,那么我们需要从头构建一个。同样适用于 UFF 格式的模型。我们将以默认的fp32精度运行,这样就不需要提供校准数据集。最后,因为我们只在一个网络摄像头提要上运行实时推断,所以我们将保持批处理大小为1。
现在让我们把它集成到操作网络摄像头的应用程序中。如果相机标志打开(默认),应用程序将使用opencv(第164行)启动视频流,并在第167行中输入主循环。如第169行所示,这个循环不断地从网络摄像机拉入新的帧,然后对该帧执行推理,如第172行所示。
最后,我们将边界框结果覆盖到原始帧(第176-180行)上,并使用imshow将其显示回用户。

这就是我们的整个管道!



与框架内推理相比,应用程序在GPU上使用tensorrt进行推理的速度快了几倍。但是,您可以使它快几倍。到目前为止,我们已经使用单精度(fp32)进行推理,其中每个数字都用32位表示。在fp32中,激活值可以在+/-3.4×1038的范围内,并且需要32位来存储每个数字。数量越大,执行所需的存储空间就越大,性能也就越差。当切换到使用较低精度的fp16时,大多数模型的执行精度几乎相同。使用Nvidia提供的模型和技术,您可以使用int8精度进行推理,从而获得尽可能高的性能。但是,请注意,表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个值只覆盖这个范围。


使用下面的命令重新构建Tensorrt引擎,以便在应用程序中使用Int8实现精度、执行校准和运行推断。整个过程可能需要几分钟:


python detect_objects_webcam -p 8

您应该看到相同的结果,其性能比之前使用fp32精度所获得的性能更高。

让我们看看如何在engine.py中构建引擎。条件块根据为推理启用的精度启用不同的生成器模式。默认情况下,tensorrt始终选择fp32内核。启用fp16模式意味着它还尝试以fp16精度运行内核;对于int8也是如此。

然而,仅仅因为允许使用低精度内核并不意味着这些内核在性能上总是优于高精度内核。例如,即使我们将精度模式设置为int8,一些fp16或fp32内核可能仍然存在,最终运行速度更快。Tensorrt自动选择最佳优化。

Tensorrt检测到特定硬件(如Tensor内核)的存在,并将在其上使用fp16内核以获得尽可能高的性能。Tensorrt自动选择最佳内核的能力称为内核自动调整。这使得在提供高性能的同时,可以跨多种应用程序使用tensort。

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。此类在批量校准期间通过模型运行校准数据。因此,您只需实现calibrator.py中名为get_batch的函数,即可从您的校准数据集中获取下一批数据。请参见下面calibrator.py中的ssdentropycalibrator代码。



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精度中部署、使用Tensorrt中新打开的源插件和解析器、连接到网络摄像头以及覆盖结果。如果您在使用此应用程序时遇到问题,请确保检查此示例的Github repo中的问题以了解类似的问题和解决方案。

如果您希望进一步使用GPU进行对象检测和其他与人工智能相关的任务,请查看相关的开发人员博客文章,了解如何为GPU创建对象检测管道以及如何使用Tensorrt加快推理。我们还提供了一个关于如何为公共应用程序执行推理的网络研讨会,该研讨会使用本文中介绍的相同代码库。您还可以在Tensorrt开源报告和Tensorrt示例页面上找到Tensorrt的其他资源,其中包括刚刚介绍的SSD示例。Nvidia Tensorrt开发者论坛为Tensorrt用户提供了一个社区,交流有关最佳实践的信息。

最后,如果您想加入免费的Nvidia开发人员程序以获得更多的技术资源和提交错误报告的能力,请登录我们的开发人员程序页面。您将加入Nvidia开发人员的庞大和不断增长的社区,为GPU创建新的和新颖的应用程序。如果您对其他GPU加速应用程序有很酷的想法或对本文有疑问,请发表评论。