前言
当今深度学习技术已经成为人工智能领域的核心技术之一,越来越多的企业和研究机构开始将其应用到实际业务中。但是,要将深度学习模型应用到实际业务中,需要先将其转换为可部署的模型并进行推理部署。这个过程需要考虑到多个方面,例如模型的性能、效率、精度等。ONNX Runtime是一种跨平台的深度学习推理引擎,可以帮助用户快速地将深度学习模型部署到各种不同的硬件设备上,并实现高效、高性能的推理。
在本文中我将为大家介绍如何使用onnxruntime替代opencv.dnn模块中的net.forward,并综合比对二者推理的差异。
注: 本实验代码以及源码是从使用python&C++对bubbliiiing的yolo系列进行opencv.dnn进行推理部署中变换而来,如大家需要更加详细的代码的话,可以点击连接或者在文末进行评论告知。
ORT推理
在使用onnxruntime推理部署(后续简称ORT推理部署)之前,应当了解其大体流程:
- 准备模型:使用支持ONNX格式的机器学习框架(如PyTorch、TensorFlow等)训练或导出一个ONNX格式的模型。
- 加载模型:使用ONNX Runtime提供的API将ONNX格式的模型加载到内存中。
- 创建会话:使用ONNX Runtime提供的API创建一个会话对象,用于执行推理任务。
- 配置会话:根据需要配置会话,包括硬件加速、运行优化等。
- 输入数据:将输入数据传递给会话。
- 执行推理:使用会话对象的API执行推理任务。
- 输出结果:从会话对象中获取推理结果。
我们通过上篇文章我们已经知道了第一步准备模型阶段以及根据网络的输出进行解码的部分,因此在本文中我们将重点放在加载模型、创建会话、配置会话、输出数据和执行推理。
读取网络
查看官网例子,我们容易得到,使用ort读取onnx的方式如下:
scss
复制代码
def readModel(netPath): net = ort.InferenceSession(netPath, providers=ort.get_available_providers()) return net
数据预处理阶段
在输入数据方面我们仍然是沿用了opencv.dnn模块的blobFromImage函数进行输入图像的处理,这样保证了可以在让网络正常输出图像数据。
ini
复制代码
blob = cv2.dnn.blobFromImage(netInputImg, scalefactor=1 / 255.0, size=(netWidth, netHeight), mean=[104, 117, 123], swapRB=True, crop=False)
网络推理阶段
在读取了onnx权重文件后我们需要进行ort推理了,我们需要知道在转onnx权重文件时的输入和输出名字(这两个可以通过代码获取,在这里我省去便于快速执行),将数据转为float32的格式
ini
复制代码
input_name = "images" output_name = "output" netOutputImg = net.run([output_name], {input_name: blob.astype(np.float32)})
在后续的解码部分和上篇文章中保持一致。这里就不过多的介绍了。
ORT推理输出结果&DNN推理输出结果
ORT
DNN
综合比对DNN与ORT推理
OpenCV和ONNX Runtime都是流行的深度学习推理框架,它们都支持跨平台的模型部署。下面是它们之间的一些比较:
模型支持:
OpenCV DNN支持许多深度学习框架的模型,例如Caffe、TensorFlow、Darknet、PyTorch等。但是,它不支持ONNX格式的模型。ONNX Runtime是专门为ONNX模型设计的,因此它支持ONNX格式的模型,但是它也可以加载和运行其他框架的模型。不过需要将其他框架的模型转换为ONNX格式。
模型转换
OpenCV DNN提供了一些工具和API,可以将各种深度学习框架的模型转换为OpenCV可用的格式,以便在OpenCV中进行推理。ONNX Runtime本身不提供模型转换的功能,但是有许多第三方工具可以将其他框架的模型转换为ONNX格式,例如ONNX官方提供的ONNX Converter。
性能
在相同硬件和模型条件下,ONNX Runtime通常比OpenCV DNN更快。这是因为ONNX Runtime使用了更先进的推理优化技术,例如TensorRT、OpenVINO等,可以在各种硬件平台上获得更好的性能。
API和易用性
OpenCV DNN的API相对简单,易于学习和使用。而ONNX Runtime的API更为复杂,需要一些深度学习和编程知识才能使用。
总体来说,OpenCV DNN和ONNX Runtime都是优秀的深度学习推理框架,可以满足不同的部署需求。如果您的模型已经是ONNX格式的,推荐使用ONNX Runtime进行部署。
结论与吐槽:
不管是在CPU端或是在GPU端,对于在深度学习有一定基础的同学们来说,在选择推理部署框架的时候,一律选择ORT推理部署框架作为首选!
代码:
ini
复制代码
import random import time from math import exp, pow import cv2 import numpy as np import onnxruntime as ort className = ["1", "2", "3", "4"] # 替换对应yolo的Anchors值 netAnchors = np.asarray([[10.0, 13.0, 16.0, 30.0, 33.0, 23.0], [30.0, 61.0, 62.0, 45.0, 59.0, 119.0], [116.0, 90.0, 156.0, 198.0, 373.0, 326.0]], dtype=np.float32) netStride = np.asarray([8.0, 16.0, 32.0], dtype=np.float32) netWidth = 512 netHeight = 512 nmsThreshold = 0.80 boxThreshold = 0.80 classThreshold = 0.80 def GetColors(color_num): ColorList = [] for num in range(color_num): R = random.randint(0, 255) G = random.randint(0, 255) B = random.randint(0, 255) BGR = (B, G, R) ColorList.append(BGR) return ColorList def Sigmoid(x): # x = float(x) # out = max(x, 0) out = (float(1.) / (float(1.) + exp(-x))) return out def readModel(netPath): net = ort.InferenceSession(netPath, providers=ort.get_available_providers()) return net def Detect(SrcImg, net, netWidth, netHeight): netInputImg = SrcImg blob = cv2.dnn.blobFromImage(netInputImg, scalefactor=1 / 255.0, size=(netWidth, netHeight), mean=[104, 117, 123], swapRB=True, crop=False) input_name = "images" output_name = "output" netOutputImg = net.run([output_name], {input_name: blob.astype(np.float32)}) ratio_h = float(netInputImg.shape[1] / netHeight) ratio_w = float(netInputImg.shape[0] / netWidth) pdata = netOutputImg[0] classIds = [] # 定义结果线性表 confidences = [] # 定义置信度线性表 boxes = [] # 定义坐标线性表 count = 0 for stride in range(3): # netStride = {8.0, 16.0, 32.0} = 3 grid_x = netWidth / netStride[stride] grid_y = netHeight / netStride[stride] grid_x, grid_y = int(grid_x), int(grid_y) # 系统默认是float32,这里是为了下面的循环转为int for anchor in range(3): # netAnchors 的层数 = 3 anchor_w = netAnchors[stride][anchor * 2] anchor_h = netAnchors[stride][anchor * 2 + 1] anchor_w, anchor_h = float(anchor_w), float(anchor_h) for i in range(grid_x): for j in range(grid_y): pdatabox = pdata[0][count][4] box_score = Sigmoid(pdatabox) # 获取每一行的box框中含有某个物体的概率 if box_score > boxThreshold: # box的阈值起作用了 scores = pdata[0][count][5:] # 这里的scores理应是一个多维矩阵 _, max_class_socre, _, classIdPoint = cv2.minMaxLoc(scores) # 求最大值以及最大值的位置&位置是元组 max_class_socre = np.asarray(max_class_socre, dtype=np.float64) max_class_socre = Sigmoid(max_class_socre) if max_class_socre > classThreshold: # 类别的置信度起作用 # rect[x,y,w,h] pdatax = pdata[0][count][0] x = (Sigmoid(pdatax) * float(2.) - float(0.5) + j) * netStride[stride] # x pdatay = np.asarray(pdata[0][count][1], dtype=np.float64) y = (Sigmoid(pdatay) * float(2.) - float(0.5) + i) * netStride[stride] # y pdataw = pdata[0][count][2] w = pow(Sigmoid(pdataw) * float(2.), float(2.0)) * anchor_w # w pdatah = pdata[0][count][3] h = pow(Sigmoid(pdatah) * float(2.), float(2.0)) * anchor_h # h left = (x - 0.5 * w) * ratio_w top = (y - 0.5 * h) * ratio_h left, top, W, H = int(left), int(top), int(w * ratio_w), int(h * ratio_h) # 对classIds & confidences & boxes classIds.append(classIdPoint[1]) # 获取最大值的位置 confidences.append(max_class_socre * box_score) boxes.append((left, top, W, H)) count += 1 # cv2.dnn.NMSBoxes的bboxes框应该是左上角坐标(x,y)和 w,h, 那么针对不同模型的,要具体情况转化一下,才能应用该函数。 nms_result = cv2.dnn.NMSBoxes(boxes, confidences, classThreshold, nmsThreshold) # 抑制处理 返回的是一个数组 if len(nms_result) == 0: return else: for idx in nms_result: classIdx = classIds[idx] confidenceIdx = confidences[idx] boxsIdx = boxes[idx] pt1 = (boxsIdx[0], boxsIdx[1]) pt2 = (boxsIdx[0] + boxsIdx[2], boxsIdx[1] + boxsIdx[3]) # 绘制图像目标位置 # x1, y1, x2, y2 = box[0], box[1], box[0] + box[2], box[1] + box[3] cv2.rectangle(SrcImg, pt1, pt2, classIdx, 2, 2) cv2.rectangle(SrcImg, (boxsIdx[0], boxsIdx[1] - 18), (boxsIdx[0] + boxsIdx[2], boxsIdx[1]), (255, 0, 255), -1) label = "%s:%s" % (className[classIdx], confidenceIdx) # 给目标进行添加类别名称以及置信度 FONT_FACE = cv2.FONT_HERSHEY_SIMPLEX cv2.putText(SrcImg, label, (boxsIdx[0] - 2, boxsIdx[1] - 5), FONT_FACE, 0.5, (0, 0, 0), 1)