作者:Kissrabbit
原文链接: https://zhuanlan.zhihu.com/p/578830729
本章将讲解如何将torch训练好的权重文件转换为ONNX文件,并如何部署回到OpenVINO、TensorRT
等框架下。笔者将以自己的FreeYOLO项目为例,来完成本章的内容讲解,相关代码如下:
https://link.zhihu.com/?target=https%3A//github.com/yjh0410/FreeYOLO
什么是FreeYOLO
?这是笔者的一个业余目标检测项目,结合了YOLOX
的SimOTA
和YOLOv7
的网络结构的anchor-free
版的YOLO检测器,是目前笔者唯一在维护的业余项目了,感兴趣的读者可以点开另一篇文章来了解一下这个FreeYOLO项目,我们后续的内容就是在这个项目的基础上去讲解相关的部署。所以,为了方便后续内容的学习,这里建议读者下载笔者的FreeYOLO项目,并按照项目中给出的requirements.txt
文件来完成相关环境的配置。
- YOLO入门教程:FreeYOLO-(终)
在学习这部分的内容时,笔者充分借鉴了YOLOX项目的诸多相关操作和代码实现,所以,非常感谢旷视开源的YOLOX工作,非常感谢YOLOX团队开源的多平台部署的代码。
1. ONNX部署
1.1 PyTorch模型转ONNX
在开始这部分工作之前,我们先安装一些必要的库,以便完成后续的操作:
pip install onnxruntime
pip install onnxsim
对于,第二个onnxsim
库,是可选的,仅仅适用于简化onnxruntime转换得到的onnx文件。至少要确保已安装onnxruntime库,否则无法将pytorch的.pth
权重文件转换为ONNX
格式。
cd <FreeYOLO_dir>
cd tools/
首先,读者可以打开FreeYOLO项目的tools文件夹,可以看到一个名为export_onnx.py
的文件 ,其中,就写好了用于转换ONNX文件的代码。我们主要来看其中的main()函数。首先,我们构建FreeYOLO模型:
@logger.catch
def main():
args = make_parser().parse_args()
logger.info("args value: {}".format(args))
device = torch.device('cpu')
# config
cfg = build_config(args)
# build model
model = build_model(args=args,
cfg=cfg,
device=device,
num_classes=80,
trainable=False)
然后,载入已训练好的COCO权重,读者可以从项目中的README
文件中下载到相应的权重文件。假设我们已经下载好了FreeYOLO-Tiny
模型的权重文件yolo_free_tiny.pth
# load trained weight
model = load_weight(model=model, path_to_ckpt=args.weight)
model = model.to(device).eval()
# replace nn.SiLU with SiLU
model = replace_module(model, nn.SiLU, SiLU)
然后,我们就可以将其转换为ONNX格式:
logger.info("loading checkpoint done.")
dummy_input = torch.randn(args.batch_size, 3, cfg['test_size'], cfg['test_size'])
# save onnx file
save_path = os.path.join(args.save_dir, str(args.opset))
os.makedirs(save_path, exist_ok=True)
output_name = os.path.join(save_path, args.output_name)
torch.onnx._export(
model,
dummy_input,
output_name,
input_names=[args.input],
output_names=[args.output],
dynamic_axes={args.input: {0: 'batch'},
args.output: {0: 'batch'}} if args.dynamic else None,
opset_version=args.opset,
)
logger.info("generated onnx model named {}".format(output_name))
中,args.save_dir
为保存转换后的文件的路径,如weights/onnx/
;args.output_name
为输出的文件的名字,如yolo_free_large.onnx;args.opset是ONNX的opset版本号,如果后续不打算将ONNX格式再缓缓为OpenVINO
的XML格式,这里就使用默认的11
,否则的话,需要我们在运行的时候,手动传入–opset 10 来设定opset的版本号,以便后续再转为OpenVINO的XML
格式。
请注意,转换之前,我们需要设置一个名为dummy_input的变量,它的作用是存储测试图片的尺寸,后续在用ONNX格式的模型做推理时,输入图片的尺寸必须和这里设定的尺寸要保持一致。 比如,这里我们设定了dummy_input = torch.randn(args.batch_size,3, 416, 416)
,那么后续推理时,就必须给定416x416的图片,而不能是其他尺寸,否则会报错。
另外,如果我们安装了onnxsim库,且args.no_onnxsim为False的话,代码会进一步将转换好的 ONNX格式做一次简化。不过,目前这个简化后的ONNX格式有什么优势笔者暂时还不了解,这里就不做多的介绍了。
转换ONNX格式的代码很简单,最后读者通过下面这段命令即可完成转换:
python3 export_onnx.py --output-name yolo_free_tiny.onnx -n yolo_free_tiny --weight ../weight/coco/yolo_free_tiny/yolo_free_tiny.pth --no_decode
其中,--no_decode
表示我们在推理阶段不去做后处理,因为后处理会包含一些非pytorch
的操作,会报错,我们需要在外部单独来写后处理代码,这一点我们会在后续的内容讲到,这里,我们只需要让模型做不包含后处理操作的前向推理即可,读者可以在模型文件models/yolo_free/yolo_free.py
中看到对应的处理。
if self.no_decode:
# no post process
obj_preds = torch.cat(all_obj_preds, dim=0)
cls_preds = torch.cat(all_cls_preds, dim=0)
reg_preds = torch.cat(all_reg_preds, dim=0)
# [n_anchors_all, 4 + 1 + C]
outputs = torch.cat([reg_preds, obj_preds.sigmoid(), cls_preds.sigmoid()], dim=-1)
return outputs
运行上端命令时,顺利的情况下,我们会看到如图所示的输出信息:
至此,我们成功地将训练好的权重转成ONNX格式,读者可以在项目的weights/onnx/11/
文件夹下找到转换好后的文件。
1.2 基于Python的ONNX推理
在完成了上述的转换后,我们即可使用ONNX格式的模型文件来做推理。请读者打开项目的deployment/ONNXRuntime/文件夹
,可以看到一个onnx_inference.py
文件和一个README
文件。
cd <FreeYOLO_dir>/
cd deployment/ONNXRuntime/
我们先来看一下onnx_inference.py
文件的命令行参数,以便我们后续使用,如下:
def make_parser():
parser = argparse.ArgumentParser("onnxruntime inference sample")
parser.add_argument("--model", type=str, default="../../weights/onnx/11/yolo_free_tiny.onnx",
help="Input your onnx model.")
parser.add_argument("-i", "--image_path", type=str, default='../test_image.jpg',
help="Path to your input image.")
parser.add_argument("-o", "--output_dir", type=str, default='../../det_results/onnx/',
help="Path to your output directory.")
parser.add_argument("-s", "--score_thr", type=float, default=0.3,
help="Score threshould to filter the result.")
parser.add_argument("-size", "--img_size", type=int, default=640,
help="Specify an input shape for inference.")
return parser
其中,--model
就是我们转换好的ONNX文件的存放路径;--image_path
是测试图片的读取路径;–output_dir是保存测试结果的存放路径;--score_thr
是置信度的阈值,默认为0.3;–img_size为测试图片的尺寸,由于转换ONNX格式前,我们手动设定了一个尺寸,这里必须要和这一设定值保持一致。
接着,我们看核心代码。首先,为了后面的推理,我们准备一些有必要的变量:
if __name__ == '__main__':
args = make_parser().parse_args()
# class color for better visualization
np.random.seed(0)
class_colors = [(np.random.randint(255),
np.random.randint(255),
np.random.randint(255)) for _ in range(80)]
# preprocessor
prepocess = PreProcessor(img_size=args.img_size)
# postprocessor
postprocess = PostProcessor(
img_size=args.img_size, strides=[8, 16, 32],
num_classes=80, conf_thresh=args.score_thr, nms_thresh=0.5)
其中,class_color
是为了可视化好看,使得不同类别的框有着不同的颜色,便于区分;preprocess
用作预处理读取进来的图像,读者可以在utils/pre_process.py
文件中找到该类的定义:
# designed for demo
import numpy as np
import cv2
class PreProcessor(object):
def __init__(self, img_size):
self.img_size = img_size
self.input_size = [img_size, img_size]
def __call__(self, image, swap=(2, 0, 1)):
"""
Input:
image: (ndarray) [H, W, 3] or [H, W]
formar: color format
"""
if len(image.shape) == 3:
padded_img = np.ones((self.input_size[0], self.input_size[1], 3), np.float32) * 114.
else:
padded_img = np.ones(self.input_size, np.float32) * 114.
# resize
orig_h, orig_w = image.shape[:2]
r = min(self.input_size[0] / orig_h, self.input_size[1] / orig_w)
resize_size = (int(orig_w * r), int(orig_h * r))
if r != 1:
resized_img = cv2.resize(image, resize_size, interpolation=cv2.INTER_LINEAR)
else:
resized_img = image
# pad
padded_img[:resize_size[1], :resize_size[0]] = resized_img
# [H, W, C] -> [C, H, W]
padded_img = padded_img.transpose(swap)
padded_img = np.ascontiguousarray(padded_img, dtype=np.float32)
return padded_img, r
注意
,这里我们不再像以往那样,还要对opencv读进来的图像做归一化、BGR转RGB格式等操作。笔者的FreeYOLO项目中的预处理代码也取消了这些操作
。从最近的YOLOv5、YOLOX和YOLOv7中,我们都已经看不到这些操作了,直接将opencv读进来的图像拿去训练,在YOLO这种大epoch、多种数据增强的
训练方式下,图像归一化就显得不必要了
。并且,在C++部署的时候,也很省去相应的代码,也很方便。
另外,除了预处理,我们还要做后处理
操作,读者可以在utils/post_process.py
文件中postprocess的定义,该类用于做后处理,包括解算边界框、阈值筛选和NMS处理
:
# designed for demo
import numpy as np
from .nms import multiclass_nms
class PostProcessor(object):
def __init__(self, img_size, strides, num_classes, conf_thresh=0.15, nms_thresh=0.5):
self.img_size = img_size
self.num_classes = num_classes
self.conf_thresh = conf_thresh
self.nms_thresh = nms_thresh
self.strides = strides
# generate anchors
self.anchors, self.expand_strides = self.generate_anchors()
def generate_anchors(self):
"""
fmp_size: (List) [H, W]
"""
all_anchors = []
all_expand_strides = []
for stride in self.strides:
# generate grid cells
fmp_h, fmp_w = self.img_size // stride, self.img_size // stride
anchor_x, anchor_y = np.meshgrid(np.arange(fmp_w), np.arange(fmp_h))
# [H, W, 2]
anchor_xy = np.stack([anchor_x, anchor_y], axis=-1)
shape = anchor_xy.shape[:2]
# [H, W, 2] -> [HW, 2]
anchor_xy = (anchor_xy.reshape(-1, 2) + 0.5) * stride
all_anchors.append(anchor_xy)
# expanded stride
strides = np.full((*shape, 1), stride)
all_expand_strides.append(strides.reshape(-1, 1))
anchors = np.concatenate(all_anchors, axis=0)
expand_strides = np.concatenate(all_expand_strides, axis=0)
return anchors, expand_strides
def decode_boxes(self, anchors, pred_regs):
"""
anchors: (List[Tensor]) [1, M, 2] or [M, 2]
pred_reg: (List[Tensor]) [B, M, 4] or [B, M, 4]
"""
# center of bbox
pred_ctr_xy = anchors[..., :2] + pred_regs[..., :2] * self.expand_strides
# size of bbox
pred_box_wh = np.exp(pred_regs[..., 2:]) * self.expand_strides
pred_x1y1 = pred_ctr_xy - 0.5 * pred_box_wh
pred_x2y2 = pred_ctr_xy + 0.5 * pred_box_wh
pred_box = np.concatenate([pred_x1y1, pred_x2y2], axis=-1)
return pred_box
def __call__(self, predictions):
"""
Input:
predictions: (ndarray) [n_anchors_all, 4+1+C]
"""
reg_preds = predictions[..., :4]
obj_preds = predictions[..., 4:5]
cls_preds = predictions[..., 5:]
scores = np.sqrt(obj_preds * cls_preds)
# scores & labels
labels = np.argmax(scores, axis=1) # [M,]
scores = scores[(np.arange(scores.shape[0]), labels)] # [M,]
# bboxes
bboxes = self.decode_boxes(self.anchors, reg_preds) # [M, 4]
# thresh
keep = np.where(scores > self.conf_thresh)
scores = scores[keep]
labels = labels[keep]
bboxes = bboxes[keep]
# nms
scores, labels, bboxes = multiclass_nms(
scores, labels, bboxes, self.nms_thresh, self.num_classes, True)
return bboxes, scores, labels
有了这些工具后,我们就可以使用ONNX模型做推理了。首先,我们使用OpenCV
库读取测试用的图片test_image.jpg
,然后做预处理:
# read an image
input_shape = tuple([args.img_size, args.img_size])
origin_img = cv2.imread(args.image_path)
# preprocess
x, ratio = prepocess(origin_img)
再交给ONNX模型去做推理,得到输出变量output:
t0 = time.time()
# inference
session = onnxruntime.InferenceSession(args.model)
ort_inputs = {session.get_inputs()[0].name: x[None, :, :, :]}
output = session.run(None, ort_inputs)
print("inference time: {:.1f} ms".format((time.time() - t0)*100))
其中,output[0]
是FreeYOLO的输出,我们对其做后处理,得到边界框、得分和类别三个处理结果:
t0 = time.time()
# post process
bboxes, scores, labels = postprocess(output[0])
bboxes /= ratio
print("post-process time: {:.1f} ms".format((time.time() - t0)*100))
最后,我们可视化推理的结果:
# visualize detection
origin_img = visualize(
img=origin_img,
bboxes=bboxes,
scores=scores,
labels=labels,
vis_thresh=args.score_thr,
class_colors=class_colors
)
# show
cv2.imshow('onnx detection', origin_img)
cv2.waitKey(0)
# save results
os.makedirs(args.output_dir, exist_ok=True)
output_path = os.path.join(args.output_dir, os.path.basename(args.image_path))
cv2.imwrite(output_path, origin_img)
读者可以运行下面的命令来查看推理结果:
python3 onnx_inference.py --weight path/to/onnx -i ../test_image.jpg -s 0.5 --img_size 416
并且,我们还能看到前向推理和后处理的两部分的耗时,其中,转换成ONNX的FreeYOLO-Tiny
在笔者的i5-12500H的CPU上的推理耗时为93 ms
,后处理耗时为3.2ms
。(图片显示的结果有误,差了10倍)
为了方便读者的使用,笔者将这部分代码单独整理了出来:https://link.zhihu.com/?target=https%3A//github.com/yjh0410/ONNX-FreeYOLO
2、OpenVINO部署
对于本节,我们来介绍一下在OpenVINO平台上部署我们的FreeYOLO的操作。这里,我们分别从python
和C++
两点来介绍OpenVINO的使用操作。
2.1 基于python的OpenVINO推理-ONNX篇
首先,我们介绍在python环境下如何使用openvino来做推理,这部分的内容是最为简单的,读者可以打开项目的deployment/OpenVINO/python/onnx_inference.py
文件。
cd <FreeYOLO_DIR>
cd deployment/OpenVINO/python
我们来简单看一下代码的命令行内容:
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser("openvino inference sample")
parser.add_argument("-m", "--model", type=str, default="../../../weights/onnx/10/yolo_free_tiny.xml",
help="Input your XML model.")
parser.add_argument("-i", "--image_path", type=str, default='../../test_image.jpg',
help="Path to your input image.")
parser.add_argument("-o", "--output_dir", type=str, default='../../../det_results/openvino/',
help="Path to your output directory.")
parser.add_argument("-s", "--score_thr", type=float, default=0.3,
help="Score threshould to filter the result.")
parser.add_argument('-d', '--device', default='CPU', type=str,
help='Optional. Specify the target device to infer on; CPU, GPU, \
MYRIAD, HDDL or HETERO: is acceptable. The sample will look \
for a suitable plugin for device specified. Default value \
is CPU.')
parser.add_argument("-size", "--img_size", type=int, default=640,
help="Specify an input shape for inference.")
return parser.parse_args()
其中,--model
是使用OpenVINO库转好的XML模型的读取路径,但现在的OpenVINO已经支持直接读取ONNX模型,所以,我们也可以传入ONNX模型的读取路径。其他参数就不做介绍了,很好理解。
为了能够运行这段代码,我们需要安装openvino库:
pip install openvino
假设,我们在先前已经转好了一个ONNX模型,如yolo_free_tiny.onnx
。接着,我们看一下运行部分的代码。首先是要初始化OpenVINO的引擎:
log.basicConfig(format='[ %(levelname)s ] %(message)s', level=log.INFO, stream=sys.stdout)
args = parse_args()
# ---------------------------Step 1. Initialize inference engine core--------------------------------------------------
log.info('Creating Inference Engine')
ie = IECore()
接着,我们读取模型,本小节,我们使用ONNX模型,而对于OpenVINO的XML模型,我们在后续讲解如何将ONNX转换为XML时再用到。
# ---------------------------Step 2. Read a model in OpenVINO Intermediate Representation or ONNX format---------------
log.info(f'Reading the network: {args.model}')
# (.xml and .bin files) or (.onnx file)
net = ie.read_network(model=args.model)
if len(net.input_info) != 1:
log.error('Sample supports only single input topologies')
return -1
if len(net.outputs) != 1:
log.error('Sample supports only single output topologies')
return -1
为了后续的有效运行,我们要对输入和输出做一些必要的配置:
# ---------------------------Step 3. Configure input & output----------------------------------------------------------
log.info('Configuring input and output blobs')
# Get names of input and output blobs
input_blob = next(iter(net.input_info))
out_blob = next(iter(net.outputs))
# Set input and output precision manually
net.input_info[input_blob].precision = 'FP32'
net.outputs[out_blob].precision = 'FP16'
# Get a number of classes recognized by a model
num_of_classes = max(net.outputs[out_blob].shape)
将模型放到指定的设备上,这里默认使用CPU:
# ---------------------------Step 4. Loading model to the device-------------------------------------------------------
log.info('Loading the model to the plugin')
exec_net = ie.load_network(network=net, device_name=args.device)
接下来,我们就可以去读取测试图片并做推理了,大体流程和之前的ONNX推理是差不多的,只有一些细节上的小差异,读者稍微留意一下即可。
# ---------------------------Step 5. Create infer request--------------------------------------------------------------
# class color for better visualization
np.random.seed(0)
class_colors = [(np.random.randint(255),
np.random.randint(255),
np.random.randint(255)) for _ in range(80)]
# preprocessor
prepocess = PreProcessor(img_size=args.img_size)
# postprocessor
postprocess = PostProcessor(
img_size=args.img_size, strides=[8, 16, 32],
num_classes=80, conf_thresh=args.score_thr, nms_thresh=0.5)
# ---------------------------Step 6. Prepare input---------------------------------------------------------------------
input_shape = tuple([args.img_size, args.img_size])
origin_img = cv2.imread(args.image_path)
x, ratio = prepocess(origin_img)
# ---------------------------Step 7. Do inference----------------------------------------------------------------------
log.info('Starting inference in synchronous mode')
t0 = time.time()
output = exec_net.infer(inputs={input_blob: x})
print("inference time: {:.1f} ms".format((time.time() - t0)*100))
# ---------------------------Step 8. Process output--------------------------------------------------------------------
output = output[out_blob]
t0 = time.time()
# post process
bboxes, scores, labels = postprocess(output)
bboxes /= ratio
print("post-process time: {:.1f} ms".format((time.time() - t0)*100))
# ---------------------------Step 9. Visualization--------------------------------------------------------------------
# visualize detection
origin_img = visualize(
img=origin_img,
bboxes=bboxes,
scores=scores,
labels=labels,
vis_thresh=args.score_thr,
class_colors=class_colors
)
# show
cv2.imshow('onnx detection', origin_img)
cv2.waitKey(0)
# save results
os.makedirs(args.output_dir, exist_ok=True)
output_path = os.path.join(args.output_dir, os.path.basename(args.image_path))
cv2.imwrite(output_path, origin_img)
读者使用下面的命令即可来运行这段在python环境下的openvino推理代码:
python openvino_inference.py --model path/to/onnx
其他的命令行参数使用默认参数。如果读者想测试其他的图片,需要手动给–image_path 传入测试图片的读取路径。我们会看到如下的输出:
同时,这段代码还会推理结果的可视化图片如下所示。
我们看一下在笔者的i5-12500H型号的CPU上的推理耗时
可以看到,对于输入的416x416
的图片,我们的FreeYOLO-Tiny在OpenVINO库的加持下以及FP16的精度推理下,在CPU上的推理只需要22ms(图片显示的结果有误,差了10倍),满足了在CPU上的实时推理需求。
以上,是我们在python环境下,通过读取ONNX格式的模型文件来完成OpenVINO的推理。接下来,我们要介绍如何将ONNX模型转成OpenVINO的XML格式的模型文件,并分别使用python环境和C++环境去完成推理。
为了方柏读者使用这段代码,笔者将其单独整理了出来,放在了github上,相应的代码链接如下,后续,我们也会给出基于C++的OpenVINO推理的代码。
https://github.com/yjh0410/OpenVINO-Python-FreeYOLO
2.2 Linux下安装OpenVINO
在上一小节,我们讲解了在python环境下的基于OpenVINO库的推理,其中,我们使用的是ONNX模型,而OpenVINO库是有自己的“特色”格式的,即XML格式的模型文件。相较于ONNX格式的模型文件,XML的体积会更小,不过,由于笔者刚接触这方面的知识不久,暂时还不清楚在OpenVINO框架下,ONNX文件和XML文件相比较的优劣势。
在本小节,我们的目标是使用OpenVINO自带的XML格式的模型文件来完成python环境下和C++环境下的两种推理。为此,我们需要先将ONNX格式的模型文件转换成XML格式,为实现这一点,就需要我们先安装好OpenVINO库。由于笔者的大部分操作都是在Linux系统上完成的,因此,我们这里只介绍在Linux系统中的OpenVINO安装过程。
(1) 创建python虚拟环境并激活:
python3 -m venv openvino_env
source openvino_env/bin/activate
(2). 将pip升级到最高版本,并安装相应的依赖
python -m pip install --upgrade pip
pip install openvino-dev[ONNX]==2021.4.2
(3). 输入下面的命令,如果输出正常,就表示环境已配置成功。
mo -h
在这个虚拟环境下,我们继续OpenVINO的后续安装步骤。首先,在OpenVINO官网(Download Intel® Distribution of OpenVINO™ Toolkit)下载安装包,下载界面如下图所示,我们选好Linux操作系统下的2021.4.2 LTS版本的安装包即可。对于最新的2022版,笔者暂未尝试,所以,其中的一些坑尚不了解,感兴趣的读者可以自行尝试
下载完毕后,我们会得到一个名为“l_openvino_toolkit_p_2021.4.752.tgz”
的压缩包,将其解压后,进入文件夹中,会看到以下一些文件。
其中,install.sh
文件和install_GUI.sh
文件是安装文件,后者是在GUI界面
下去进行安装,这里推荐都这么使用GUI界面来安装,安装命令如下:
sudo ./install_GUI.sh
基本都是用默认勾选即可。在安装完成后,我们再安装python3的环境,这里推荐安装anaconda
库,有关于再Linux系统中安装anaconda的教程,读者还请查阅相关文章,比如笔者的另一篇文章:深度学习服务器配置教程
在安装好了python3的环境后,我们再使用下方的命令来激活OpenVINO的环境:
source /opt/intel/openvino/bin/setupvars.sh
众所周知,这种激活方法是一次性的,也就是说,关机重启之后,需要我们再次手动激活才行,因此,如果读者想省去这个麻烦,可以把这行命令写进~/.bashrc文件中,这样每次开机他都会自己激活。
在完成了激活后, 我们还需要安装一些依赖,以便我们把模型文件转换成openvino的XML文件。安装命令如下:
cd /opt/intel/openvino_2021/deployment_tools/model_optimizer/install_prerequisites/
sudo ./install_prerequisites_onnx.sh
完成后,我们退回到上一级文件夹model_optimizer,即可准备将ONNX模型转换成XML模型。
2.3 ONNX转XML
完成了上面的安装工作后,我们进入到model_optimizer
文件夹中,准备将ONNX模型转换成XML模型,假设,我们的ONNX文件名为yolo_free_tiny_opset_10.onnx
,注意,这里的ONNX必须是在opset=10的设置下转换而来的。读者可以参考下方的命令来完成模型的转换:
python mo.py --input_model yolo_free_tiny_opset_10.onnx --input_shape [1,3,416,416] --data_type FP16 --output_dir path/to/save/files
其中,--input_model
是ONNX文件的读取路径,--input_shape
是图片尺寸,这里必须要和我们转ONNX模型时设定好的图片尺寸保持一致,--data_type
就是推理精度,我们采用半精度来做推理,--output_dir
是保存我们转换后的文件的存放路径。 在存放路径中,我们可以看到以下三个文件:
其中的yolo_free_tiny.xml
文件就是我们转换好的XML模型文件。
2.4 基于Python的OpenVINO推理-XML篇
现在,我们有了XML文件后,就可以用它去做推理了,我们只需要把之前的运行命令中的模型路径改成XML模型的路径即可,如下:
python openvino_inference.py --model path/to/xml
我们会看到和上一次同样的输出,推理速度也没有明显的差异。
2.5 基于C++的OpenVINO推理
这部分的代码已经整合到了单独的项目中,链接如下,其中c++的代码文件是copy了YOLOX项目提供的源码,再次感谢YOLOX工作。
https://github.com/yjh0410/OpenVINO-CPP-FreeYOLO
读者可以先按照README
给出的操作运行即可,转好的XML模型文件都已经提供了。后期有时间我会把这一小节的内容更新上。
在Linux系统下的操作如下:
首先,激活OpenVINO的环境:
source /opt/intel/openvino_2021/bin/setupvars.sh
然后使用cmake来编译:
mkdir build
cd build
cmake ..
make
接下来,就可以运行了:
./yolo_free_openvino <XML_MODEL_PATH> <IMAGE_PATH> <DEVICE>
比如,我们运行FreeYOLO-Nano模型:
./yolo_free_openvino path/to/yolo_free_nano.xml path/to/test_image.jpg CPU