技术文章:Yolov3 CPU推理性能比较-Onnx、OpenCV、Darknet
磐创AI为实时目标检测应用程序选择正确的推理框架变得非常具有挑战性,尤其是当模型应该在低功耗设备上运行时。在本文中,你将了解如何根据你的需要选择最佳的推理检测器,并发现它可以给你带来巨大的性能提升。通常,当我们将模型部署到CPU或移动设备上时,往往只关注于轻量级的模型体系结构,而忽略了对快速推理机的研究。在研究CPU设备上的快速推理时,我测试了提供稳定python API的各种框架。今天将重点介绍Onnxruntime、opencvdnn和Darknet框架,并从性能(运行时间)和准确性方面对它们进行度量。
Onnxruntime
opencvdnn
Darknet
我们将使用两种常见的目标检测模型进行性能测量:Yolov3架构:image_size = 480*480
classes = 98
BFLOPS =87.892
Tiny-Yolov3_3layers 体系结构:image_size= 1024*1024
classes =98
BFLOPS= 46.448
这两个模型都是使用AlexeyAB的Darknet框架对自定义数据进行训练的。现在,让我们用我们要测试的探测器来运行推理。Darknet探测器Darknet是训练 YOLO 目标检测模型的官方框架。此外,它还提供了对*.weights文件格式的模型进行推理的能力,该文件格式与训练输出的格式相同。推理有两种方法:不同数量的图像:darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights -thresh 0.25
一个图像darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights dog.png
OpenCV DNN探测器Opencv-DNN是计算机视觉领域常用的Opencv库的扩展。Darknet声称OpenCV-DNN是“CPU设备上YOLV4/V3最快的推理实现”,因为它高效的C&C++实现。由于其方便的Python API,直接将darknet权重加载到opencv-dnn即可。这是E2E推理的代码片段:import cv2
import numpy as np
# 指定模型的网络大小
network_size = (480,480)
# Darknet cfg文件路径
cfg_path = 'yolov3.cfg'
# Darknet 权重路径
weights_path = 'yolov3.weights'
# 定义推理引擎
net = cv2.dnn.readNetFromDarknet(cfg_path, weights_path)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
_layer_names = net.getLayerNames()
_output_layers = [_layer_names[i[0] - 1] for i in net.getUnconnectedOutLayers()]
# 读取图像作为输入
img_path = 'dog.png'
img = cv2.imread(img_path)
image_blob = cv2.dnn.blobFromImage(img, 1 / 255.0, network_size, swapRB=True, crop=False)
net.setInput(image_blob, "data")
# 运行推理
layers_result = net.forward(_output_layers)
# 将layers_result转换为bbox,conf和类
def get_final_predictions(outputs, img, threshold, nms_threshold):
height, width = img.shape[0], img.shape[1]
boxes, confs, class_ids = [], [], []
for output in outputs:
for detect in output:
scores = detect[5:]
class_id = np.argmax(scores)
conf = scores[class_id]
if conf > threshold:
center_x = int(detect[0] * width)
center_y = int(detect[1] * height)
w = int(detect[2] * width)
h = int(detect[3] * height)
x = int(center_x - w/2)
y = int(center_y - h / 2)
boxes.append([x, y, w, h])
confs.append(float(conf))
class_ids.append(class_id)
merge_boxes_ids = cv2.dnn.NMSBoxes(boxes, confs, threshold, nms_threshold)
# 仅过滤nms之后剩余的框
boxes = [boxes[int(i)] for i in merge_boxes_ids]
confs = [confs[int(i)] for i in merge_boxes_ids]
class_ids = [class_ids[int(i)] for i in merge_boxes_ids]
return boxes, confs, class_ids
boxes, confs, class_ids = get_final_predictions(layers_result, img, 0.3, 0.3)
Onnxruntime检测器Onnxruntime是由微软维护的,由于其内置的优化和独特的ONNX权重格式文件,它声称可以显著加快推理速度。正如你在下一张图片中看到的,它支持各种风格和技术。在我们的比较中,我们将使用PythondCPU风格。
ONNX格式定义了一组通用的操作符(机器学习和深度学习模型的构建块)和一种通用的文件格式,使AI开发人员能够将模型与各种框架、工具、运行时和编译器一起使用。转换Darknet权重> Onnx权重为了使用Onnxruntime运行推理,我们必须将*.weights格式转换为*.onnx fomrat。我们将使用专门为将darknet*.weights格式转换为*.pt(PyTorch)和*.onnx(onnx格式)而创建的存储库。
克隆repo并安装需求。用cfg和weights和img_size参数运行converter.py。python converter.py yolov3.cfg yolov3.weights 1024 1024
将在yolov3.weights目录中创建一个yolov3.onnx文件。请记住,在使用ONNX格式进行推理时,由于转换过程的原因,精度会降低约0.1 mAP%。转换器模仿PyTorch中的Darknet功能,但并非完美无缺为了支持除yolov3之外的其他darknet架构的转换,可以随意创建issues/PR在我们成功地将模型转换为ONNX格式之后,我们可以使用Onnxruntime运行推理。下面是E2E推理的代码片段:import onnxruntime
import cv2
import numpy as np
# 转换后的onnx权重
onnx_weights_path = 'yolov3.onnx'
# 指定模型的网络大小
network_size = (480, 480)
# 声明onnxruntime会话
session = onnxruntime.InferenceSession(onnx_weights_path)
session.get_modelmeta()
input_name = session.get_inputs()[0].name
output_name_1 = session.get_outputs()[0].name
output_name_2 = session.get_outputs()[1].name
# 阅读图片
img_path = 'dog.png'
img = cv2.imread(img_path)
image_blob = cv2.dnn.blobFromImage(img, 1 / 255.0, network_size, swapRB=True, crop=False)
# 运行推理
layers_result = session.run([output_name_1, output_name_2],
{input_name: image_blob})
layers_result = np.concatenate([layers_result[1], layers_result[0]], axis=1)
# 将layers_result转换为bbox,conf和类
def get_final_predictions(outputs, img, threshold, nms_threshold):
height, width = img.shape[0], img.shape[1]
boxes, confs, class_ids = [], [], []
matches = outputs[np.where(np.max(outputs[:, 4:], axis=1) > threshold)]
for detect in matches:
scores = detect[4:]
class_id = np.argmax(scores)
conf = scores[class_id]
center_x = int(detect[0] * width)
center_y = int(detect[1] * height)
w = int(detect[2] * width)
h = int(detect[3] * height)
x = int(center_x - w/2)
y = int(center_y - h / 2)
boxes.append([x, y, w, h])
confs.append(float(conf))
class_ids.append(class_id)
merge_boxes_ids = cv2.dnn.NMSBoxes(boxes, confs, threshold, nms_threshold)
#将layers_result转换为bbox,conf和类
boxes = [boxes[int(i)] for i in merge_boxes_ids]
confs = [confs[int(i)] for i in merge_boxes_ids]
class_ids = [class_ids[int(i)] for i in merge_boxes_ids]
return boxes, confs, class_ids
boxes, confs, class_ids = get_final_predictions(layers_result, img, 0.3, 0.3)
性能比较祝贺你,我们已经完成了所有的技术细节,你现在应该有足够的知识来推理每一个探测器。现在让我们来讨论我们的主要目标——性能比较。在PC cpu(英特尔i7第9代)上,分别针对上述每个型号(Yolov3,Tiny-Yolov3)分别测量了性能**。**对于opencv和onnxruntime,我们只测量前向传播的执行时间,以便将其与前/后进程隔离开来。概要分析:opencvlayers_result = self.net.forward(_output_layers)
Onnxruntimelayers_result = session.run([output_name_1, output_name_2], {input_name: image_blob})
layers_result = np.concatenate([layers_result[1], layers_result[0]], axis=1)
Darknetdarknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights -thresh 0.25
判断Yolov3Yolov3在400张独特的图片上进行了测试。ONNX Detector是推断我们的Yolov3模型的最快方法。确切地说,它比opencv-dnn快43%,后者被认为是可用的最快的检测器之一。
每个图像的平均时间:
Tiny-Yolov3Tiny-Yolov3在600张独特的图像上进行了测试。在我们的Tiny-Yolov3模型上,ONNX探测器比opencv-dnn快33%。
每个图像的平均时间:
结论我们已经看到 onnxruntime 比 opencvdnn 运行的速度要快得多。即使Yolvo3更大,我们也可以用比Tiny-Yolov3更少的时间运行Yolov3。我们拥有必要的工具,可以将在darknet中训练的模型转换为*.onnx格式。