手把手教你部署FreeYOLO

作者:Kissrabbit
原文链接: https://zhuanlan.zhihu.com/p/578830729

本章将讲解如何将torch训练好的权重文件转换为ONNX文件,并如何部署回到OpenVINO、TensorRT等框架下。笔者将以自己的FreeYOLO项目为例,来完成本章的内容讲解,相关代码如下:
https://link.zhihu.com/?target=https%3A//github.com/yjh0410/FreeYOLO
在这里插入图片描述
什么是FreeYOLO?这是笔者的一个业余目标检测项目,结合了YOLOXSimOTAYOLOv7的网络结构的anchor-free版的YOLO检测器,是目前笔者唯一在维护的业余项目了,感兴趣的读者可以点开另一篇文章来了解一下这个FreeYOLO项目,我们后续的内容就是在这个项目的基础上去讲解相关的部署。所以,为了方便后续内容的学习,这里建议读者下载笔者的FreeYOLO项目,并按照项目中给出的requirements.txt文件来完成相关环境的配置。

在学习这部分的内容时,笔者充分借鉴了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模型的推理结果

并且,我们还能看到前向推理和后处理的两部分的耗时,其中,转换成ONNX的FreeYOLO-Tiny在笔者的i5-12500H的CPU上的推理耗时为93 ms,后处理耗时为3.2ms。(图片显示的结果有误,差了10倍)

在这里插入图片描述

ONNX模型的推理耗时,测试平台为i5-12500H

为了方便读者的使用,笔者将这部分代码单独整理了出来:https://link.zhihu.com/?target=https%3A//github.com/yjh0410/ONNX-FreeYOLO

2、OpenVINO部署

对于本节,我们来介绍一下在OpenVINO平台上部署我们的FreeYOLO的操作。这里,我们分别从pythonC++两点来介绍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上的推理耗时

在这里插入图片描述

基于python环境的openvino推理耗时

可以看到,对于输入的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”的压缩包,将其解压后,进入文件夹中,会看到以下一些文件。

在这里插入图片描述

l_openvino_toolkit_p_2021.4.752文件夹中的内容

其中,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

代码及预训练权重

链接:https://pan.baidu.com/s/1EAqXJql-0AGbAY6e6eUGAw?pwd=rzdw
提取码:rzdw

猜你喜欢

转载自blog.csdn.net/weixin_38346042/article/details/131061105