赞
踩
如何证明我们自己写的部署程序比官方推理程序快?总不可能我们写的程序放GPU上,官方放CPU上吧,这样太不公平了,要对比就放同一设备上。本机使用RTX 3060显卡,我们就来测量一下在GPU上的时间。
在命令行输入:
python detect.py --source uav_bird_training/data/images/train/20220318_01.jpg --weights runs/train/exp2/weights/best.pt --data uav_bird_training/dataset.yaml --device 0
输出:
可以看到,使用GPU的情况下,预处理+正向推理+后处理,三步只需要20ms,已经是非常快了。
ONNX Runtime(ONNX Runtime,简称ORT)是微软推出的用于深度学习模型推理(inference)的高性能开源推理引擎,本节将介绍如何通过ONNXRUNTIME,将我们训练好的yolov5模型部署到GPU中。
在yolov5-6.1目录下创建一个名为inference_openvino.py的文件,文件内部创建一个名为Inference_Onnxruntime的类,该类的结构与上一篇文章中的Inference_Opencv基本一样,改变的只有__init__和pred_img两个类内函数,并且也只是稍微改动了一点而已。这两个函数的代码如下:
import os import cv2 import time import yaml import numpy as np import onnxruntime class Inference_Onnxruntime(): # 全局设置(也可以在__init__中将它们设置成实例属性) INPUT_WIDTH = 640 INPUT_HEIGHT = 640 def __init__(self, onnx_path, yaml_path, score_threshold=0.25, nms_threshold=0.45, out_dir='out'): """ 初始化方法 Args: onnx_path: onnx文件路径 yaml_path: 数据集配置文件路径,这里主要是通过它来获取数据集有哪些类别 score_threshold: 置信度得分阈值 nms_threshold: NMS时的IOU阈值 out_dir: 检测结果保存目录,暂时只能保存图像,摄像头/视频后续可以加 """ # 获取类列表 with open(yaml_path, "r", errors='ignore') as f: self.class_list = yaml.safe_load(f)['names'] # 创建推理会话 self.session = onnxruntime.InferenceSession(onnx_path, providers=['CUDAExecutionProvider']) # 绘制预测框、文字所用的颜色 self.colors = [(255, 255, 0), (0, 255, 0), (0, 255, 255), (255, 0, 0)] # 预测框过滤相关的阈值设置 self.score_threshold = score_threshold self.nms_threshold = nms_threshold # 检测结果保存目录 self.out_dir = out_dir if not os.path.exists(self.out_dir): os.makedirs(self.out_dir) def pred_img(self, img_path): """ 预测图像 Args: img_path: 图像路径 """ start = time.time() # 读取图像并预处理 image = cv2.imread(img_path) time1 = time.time() print('read:', time1 - start) inputImage, factor, (dh, dw) = self.preprocess(image, (Inference_Onnxruntime.INPUT_HEIGHT, Inference_Onnxruntime.INPUT_WIDTH)) time2 = time.time() print('preprocess:', time2 - time1) # 模型正向推理 ort_inputs = {self.session.get_inputs()[0].name: inputImage} outs = self.session.run(None, ort_inputs)[0] time3 = time.time() print('refer:', time3 - time2) # 解析推理结果(后处理) class_ids, scores, boxes = self.wrap_detection2(outs[0]) # self.wrap_detection2内部使用numpy广播机制 time4 = time.time() print('wrap_detection:', time4 - time3) # 绘图 image = self.draw_boxes(image, factor, (dh, dw), class_ids, scores, boxes) time5 = time.time() print('draw boxes:', time5 - time4) # 计算fps end = time.time() inf_end = end - start fps = 1 / inf_end fps_label = "FPS: %.2f" % fps cv2.putText(image, fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) time6 = time.time() print('compute fps:', time6 - time5) # 保存 basename = os.path.basename(img_path) cv2.imwrite(os.path.join(self.out_dir, basename), image) time7 = time.time() print('save:', time7 - time6)
其他关于前处理(preprocess)、后处理(wrap_detection2)、画框(draw_boxes)的函数,与Inference_Opencv类完全一致,这里不再赘述。
测试程序如下:
if __name__ == '__main__':
onnx_path = "runs/train/exp2/weights/best.onnx"
yaml_path = "uav_bird_training/dataset.yaml"
inference_Model = Inference_Onnxruntime(onnx_path, yaml_path)
# 对保存在磁盘上的图片进行推理
start = time.time()
inference_Model.pred_img('uav_bird_training/data/images/train/20220318_01.jpg')
print(time.time() - start)
输出:
read: 0.002992391586303711
preprocess: 0.00997304916381836
refer: 1.2430415153503418
wrap_detection: 0.000997781753540039
draw boxes: 0.27842187881469727
compute fps: 0.0
save: 0.00498652458190918
1.5404131412506104
这里有两个蹊跷的地方,首先,这里推理时间(refer)比之前所有的方案都慢,按理说都用GPU了,不应该慢才对;此外,画框的程序与之前一模一样但这里画框的时间却远远少于之前。
这是由于ONNXRUNTIME需要在第一次正向传播时建图,因此refer占用的时间很长。为了科学地统计时间,我们这里用第二次推理的时间来评估ONNXRUNTIME的推理速度,相关的测试代码如下:
if __name__ == '__main__':
onnx_path = "runs/train/exp2/weights/best.onnx"
yaml_path = "uav_bird_training/dataset.yaml"
inference_Model = Inference_Onnxruntime(onnx_path, yaml_path)
# 对保存在磁盘上的图片进行推理
start = time.time()
inference_Model.pred_img('uav_bird_training/data/images/train/20220318_01.jpg')
print(time.time() - start)
print('--------------------------------')
# 第二次推理
start = time.time()
inference_Model.pred_img('uav_bird_training/data/images/train/20220318_01.jpg')
print(time.time() - start)
输出:
read: 0.002992391586303711 preprocess: 0.009973287582397461 refer: 1.261014699935913 wrap_detection: 0.0 draw boxes: 0.26511549949645996 compute fps: 0.0 save: 0.002991914749145508 1.5430963039398193 -------------------------------- read: 0.001983642578125 preprocess: 0.00498652458190918 refer: 0.008976221084594727 wrap_detection: 0.0 draw boxes: 0.0009975433349609375 compute fps: 0.0 save: 0.003988027572631836 0.02093195915222168
好的,从上面的结果来看,第二次检测图像时,refer所花的时间明显减少,我们因此看到了onnxruntime的性能。预处理、模型前向传播、后处理三步合计耗时为14ms,已经低于官方程序的GPU推理用时。
当然,我们同样可以用第二次推理的时间来衡量OpenCV DNN和OpenVINO这两个框架的推理用时,最后的结果和第一次推理的用时差不多,感兴趣的小伙伴自己可以尝试。
我们刚刚通过ONNXRUNTIME实现了模型的GPU部署模型,并且做到了比官方程序更快的速度,但我们希望推理速度能再快一点,此时可以考虑使用TensorRT。
TensorRT是nvidia家的一款高性能深度学习推理SDK。此SDK包含深度学习推理优化器和运行环境,可为深度学习推理应用提供低延迟和高吞吐量,在推理过程中,基于TensorRT的应用程序比仅仅使用CPU作为平台的应用程序要快40倍。
前面介绍的OpenCV DNN、OpenVINO和ONNXRUNTIME,它们既可以实现模型的CPU部署,也可以实现GPU,TensorRT与这些框架不同的是,它只能GPU部署,毕竟英伟达公司的主业不是CPU。
篇幅有限,这里只介绍Windows上的安装,如果用的Linux(比如用的事前面介绍过的AutoDL、AutoLn等),则可以跳过这一节,直接看这篇文章。
首先要查看CUDA版本,在命令行中输入如下命令:
nvcc --version
假如我们还想看自己装的cuDNN什么版本,可以先查看CUDA的安装路径:
where nvcc
可以看到本机的CUDA版本为11.1,安装目录为:C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1
我们进到这个目录里,打开名为include的文件夹,然后在下面找到名为cudnn.h的文件:
用notepad++或者记事本打开,滑到最后,可以看到几行C++的宏定义:
从上面画框的信息来看,本机的cuDNN版本为8.2.1。
接下来是下载TensorRT,进入TensorRT的下载页面
在弹出的窗口中,输入自己的邮箱:
在TensorRT的列表中,我们选择TensorRT8
在需要同意的地方打钩
选择TensorRT 8.6 GA,如果CUDA是10.X,那么必须选8.5或者8.5以下的TensorRT,因为自8.6开始已经不再支持CUDA10.X了。
根据自己的平台选择程序包(我当前的电脑是Windows10,而且CUDA版本是11.0,因此选择红色画框部分):
下载之后解压,然后在解压后的目录下找到python:
进去之后根据本地的python环境找到对应的whl文件,这里我们选择完整的安装包(dispatch和lean都是不完整的):
随后在这个目录下打开终端、激活环境、安装whl文件:
pip install tensorrt-8.6.1-cp36-none-win_amd64.whl
接下来安装安装onnx python sdk支持,相关的whl文件在解压文件的onnx_graphsuigeon中
在命令行中返回到上一级目录,随后进入到onnx_graphsurgeon中,去安装onnx python sdk支持
cd ..
cd onnx_graphsurgeon
pip install onnx_graphsurgeon-0.3.12-py2.py3-none-any.whl
接着安装uff的whl文件,它在TensorRT-8.6.1.6/uff下面
然后将TensorRT的lib目录配置到系统的环境变量中
然后将启动解释器测试版本
>>> import tensorrt
>>> tensorrt.__version__
'8.6.1'
>>>
至此,TensorRT的python sdk安装完成。
关于TensorRT的快速入门,可以看这篇文章,建议和GitHub上的TensorRT配合起来看:
在IntroNotebooks下面,有jupyter notebook的教程。
我们先要导出推理文件:
python export.py --weights runs/train/exp2/weights/best.pt --include engine --device 0
这里的--device 0
是因为我这里只有一个GPU,如果有多张卡,可以换成其他数字,但不能省略--device
这个参数,否则会默认为cpu,因为TensorRT只能面向GPU,因此省略会报错。
这个导出时间稍微长了一点,导出后,终端显示如下:
官方推理程序也可以使用刚刚导出的engine文件进行推理:
python detect.py --source uav_bird_training/data/images/train/20220318_01.jpg --weights runs/train/exp2/weights/best.engine --data uav_bird_training/dataset.yaml --device 0
结果:
使用TensorRT,推理时间大幅下降,预处理+前向传播+后处理只需要10ms。需要注意的事,这里生成的engine文件是和硬件相关的,不同型号的显卡不能通用这个engine。
当然,官方推理程序是很难部署的,有很多依赖的类别和库,我们要自己写一段部署程序。
inference_tensorrt.py的文件,里面新建一个名为Inference_TensorRT的类,该类的结构与Inference_Opencv基本一样,改变的只有__init__和pred_img两个类内函数,改动的代码也只是针对TensorRT框架的设置和推理的相关过程。这两个函数的代码如下:
import os import cv2 import time import yaml import torch import numpy as np import tensorrt as trt from collections import OrderedDict, namedtuple class Inference_TensorRT(): # 全局设置(也可以在__init__中将它们设置成实例属性) INPUT_WIDTH = 640 INPUT_HEIGHT = 640 def __init__(self, engine_path, device, yaml_path, score_threshold=0.25, nms_threshold=0.45, out_dir='out'): """ 初始化方法 Args: engine_path: TensorRT引擎文件 device: 推理设备 yaml_path: 数据集配置文件路径,这里主要是通过它来获取数据集有哪些类别 score_threshold: 置信度得分阈值 nms_threshold: NMS时的IOU阈值 out_dir: 检测结果保存目录,暂时只能保存图像,摄像头/视频后续可以加 """ # 获取类列表 with open(yaml_path, "r", errors='ignore') as f: self.class_list = yaml.safe_load(f)['names'] # 推理引擎的相关配置 Binding = namedtuple('Binding', ('name', 'dtype', 'shape', 'data', 'ptr')) logger = trt.Logger(trt.Logger.INFO) with open(engine_path, 'rb') as f, trt.Runtime(logger) as runtime: self.engine = runtime.deserialize_cuda_engine(f.read()) # 注意这里的engine必须做成属性, # 即必须是self.engine,而不能是engine,虽然在self.pred_img中并没有直接使用model,但间接使用了 # 如果这里不做成类的属性,那么在初始化方法结束后,engine将被释放,使得推理报错 self.bindings = OrderedDict() for index in range(self.engine.num_bindings): name = self.engine.get_binding_name(index) dtype = trt.nptype(self.engine.get_binding_dtype(index)) shape = self.engine.get_binding_shape(index) data = torch.from_numpy(np.empty(shape, dtype=np.dtype(dtype))).to(device) self.bindings[name] = Binding(name, dtype, shape, data, int(data.data_ptr())) self.binding_addrs = OrderedDict((n, d.ptr) for n, d in self.bindings.items()) self.context = self.engine.create_execution_context() # 绘制预测框、文字所用的颜色 self.colors = [(255, 255, 0), (0, 255, 0), (0, 255, 255), (255, 0, 0)] # 预测框过滤相关的阈值设置 self.score_threshold = score_threshold self.nms_threshold = nms_threshold # 检测结果保存目录 self.out_dir = out_dir if not os.path.exists(self.out_dir): os.makedirs(self.out_dir) def pred_img(self, img_path): """ 预测图像 Args: img_path: 图像路径 """ start = time.time() # 读取图像 image = cv2.imread(img_path) time1 = time.time() print('read:', time1 - start) # 预处理 inputImage, factor, (dh, dw) = self.preprocess(image, (Inference_TensorRT.INPUT_HEIGHT, Inference_TensorRT.INPUT_WIDTH)) time2 = time.time() print('preprocess:', time2 - time1) # TensorRT推理 x_input = torch.from_numpy(inputImage).to(device) self.binding_addrs['images'] = int(x_input.data_ptr()) self.context.execute_v2(list(self.binding_addrs.values())) outs = self.bindings['output'].data.cpu().numpy() time3 = time.time() print('refer:', time3 - time2) # 解析推理结果(后处理) class_ids, scores, boxes = self.wrap_detection2(outs[0]) # self.wrap_detection2内部使用numpy广播机制 time4 = time.time() print('wrap_detection:', time4 - time3) # 绘图 image = self.draw_boxes(image, factor, (dh, dw), class_ids, scores, boxes) time5 = time.time() print('draw boxes:', time5 - time4) # 计算fps end = time.time() inf_end = end - start fps = 1 / inf_end fps_label = "FPS: %.2f" % fps cv2.putText(image, fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) time6 = time.time() print('compute fps:', time6 - time5) # 保存 basename = os.path.basename(img_path) cv2.imwrite(os.path.join(self.out_dir, basename), image) time7 = time.time() print('save:', time7 - time6)
其他关于前处理(preprocess)、后处理(wrap_detection2)、画框(draw_boxes)的函数,与Inference_Opencv类完全一致。
测试程序如下:
if __name__ == '__main__': engine_path = "runs/train/exp2/weights/best.engine" yaml_path = "uav_bird_training/dataset.yaml" device = 'cuda:0' inference_Model = Inference_TensorRT(engine_path, device, yaml_path) # 对保存在磁盘上的图片进行推理 start = time.time() inference_Model.pred_img('uav_bird_training/data/images/train/20220318_01.jpg') print(time.time() - start) print('---------------------------------') # 第二次推理 start = time.time() inference_Model.pred_img('uav_bird_training/data/images/train/20220318_01.jpg') print(time.time() - start)
输出:
[12/30/2023-02:29:32] [TRT] [I] Loaded engine size: 36 MiB [12/30/2023-02:29:32] [TRT] [I] [MemUsageChange] TensorRT-managed allocation in engine deserialization: CPU +0, GPU +33, now: CPU 0, GPU 33 (MiB) [12/30/2023-02:29:33] [TRT] [I] [MemUsageChange] TensorRT-managed allocation in IExecutionContext creation: CPU +0, GPU +33, now: CPU 0, GPU 66 (MiB) [12/30/2023-02:29:33] [TRT] [W] CUDA lazy loading is not enabled. Enabling it can significantly reduce device memory usage and speed up TensorRT initialization. See "Lazy Loading" section of CUDA documentation https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#lazy-loading read: 0.002992391586303711 preprocess: 0.0109710693359375 refer: 0.00698089599609375 wrap_detection: 0.0 draw boxes: 0.2449805736541748 compute fps: 0.0 save: 0.003989219665527344 0.2699141502380371 --------------------------------- read: 0.001994609832763672 preprocess: 0.004986763000488281 refer: 0.005983591079711914 wrap_detection: 0.0009980201721191406 draw boxes: 0.0 compute fps: 0.0 save: 0.003988981246948242 0.01795196533203125
我们看第二次推理的结果,预处理、模型前向传播、后处理三步合计耗时为不到11ms,其中模型前向传播耗时为5.98ms,相对于ONNXRUNTIME的8.98ms也是有所提升的。
大部分公司都要求图像算法工程师会C++,因此掌握C++编程,已经成为了算法工程师的一项基本能力。实际应用场景中,需要考虑模型的性能和效率,比如运行速度、内存占用、功耗等,此时Python很难满足要求,所以在深度学习模型部署时,一般是使用C++语言,对于OpenVINO、ONNXRUNTIME部署模型,也普遍是使用C++语言。不过,由于本课程的学员普遍缺乏C++基础,因此C++部署部分暂时不要求掌握,这里就不展开介绍。
VS2017配置TensorRT,可以看B站的这个视频。
至此,我们已经学习了如何通过OpenCV DNN、OpenVINO、ONNXRUNTIME和TensorRT部署yolov5模型,这几个框架的使用流程大同小异,也各有优缺点,具体如何选择,可以参考下面的几条经验:
(1)如果模型需要部署在CPU或者英特尔的产品上,则优先选择OpenVINO;
(2)如果模型需要部署在GPU或者英伟达的产品上,则优先选择TensorRT;
(3)如果模型比较新,并且其中使用了比较新的算子,那么先尝试ONNXRUNTIME,因为ONNXRUNTIME兼容性最好,OpenVINO和TensorRT对最新算子的支持,可能存在滞后性,随后再根据硬件平台选择OpenVINO或TensorRT。
本文介绍了如何使用ONNXRUNTIME和TensorRT,将模型部署到GPU上,本文的重点为:(1)通过ONNXRUNTIME进行深度学习模型GPU推理;(2)如何配置TensorRT,并导出TensorRT的推理文件(engine文件);(3)通过TensorRT进行深度学习模型GPU推理;(4)根据模型的部署硬件,合理选择推理框架。考虑到很多学员都没有C++基础,因此使用C++部署模型,暂时不要求掌握。
本系列文章——YOLOv5-6.1从训练到部署,至此全部结束,这四篇文章前前后后花了我一个多月的时间,在写教程的时候,自己也查了不少资料,也学了很多新的工具,在这过程中,我自身的水平也获得了相应的提高。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。