Ubuntu20.04环境下使用OpenVINO部署BiSeNetV2模型
01 概述
语义分割(Semantic Segmentatio)是计算机视觉对现实世界理解的基础,大到自动驾驶,小到个人应用只要细心观察都可以发现语义分割的应用场所无处不在。其实语义分割相当于是图像分割+ 对分割区域的理解。
图一可以看出图像分割就只负责分割出图像种不同的区域。
与图一的图像分割相比,图二的语义分割明显更进一步。不仅分割出了不同的“区域”,同时也理解不同“区域”所代表的事物,比如:马,草地,围栏,天空。
因此图像语义分割也称为图像语义标注,由于图像语义分割不仅要识别出对象还要标出每个对象的边界,所以相关的模型会具有像素级别(Pixel Level)的密集预测能力。
本篇文章的主要目的是展示如何把已有的百度飞桨Paddle模型一步一步的部署到Intel的Movidius Myriad X VPU上。同时本文也展示了一种思路:如何在浩瀚的Internet网络找到正确的信息,少走弯路。本文用到的包含有Intel VPU的设备是 OAK-D Camera。值得一提的是,相较于部署老模型,本文用到的模型是作者自己训练出来的相对较新的BiSeNetV2路面分割模型(地址:https://arxiv.org/pdf/2004.02147.pdf)。BiSeNetV2是一个在准确度和性能方面拥有较好平衡的模型。所有的代码都可以在第6部分找到链接。
1.1 部署模型落地的重要性
1.2 这篇文章的着重点和非着重点
再来讲一下这篇文章不讲什么.这篇文章不讲解怎样安装Python,,Anaconda,OpenVINO这样的基础需求框架。以上几个产品的官方网站教程都做的非常详细,而且会实时更新,笔者不想让自己的文章过了几个月后 out of date,我相信对于每个不同的技术,读相对应的官方文档可以省去很多麻烦,少走弯路。这篇文章更多的精力会用在讲解模型之间的转换,部署,以及排错。
1.3 Intel OpenVINO简介
Intel OpenVINO(以下简称OV)是Intel发布的一个综合性工具包,用于快速开发解决各种任务的应用程序和解决方案。它包括人类视觉,自动语音识别,自然语言处理,推荐系统等。 该工具包基于最新一代人工神经网络,包括卷积神经网络 (CNN)、Recurrent Network和基于注意力的网络,可跨英特尔® 硬件扩展计算机视觉和非视觉工作负载,从而最大限度地提高性能。
1.4 百度飞桨
百度飞桨(以下简称Paddle)是百度旗下一个致力于让AI深度学习技术的创新与应用更加简单的工具集。其中包括,PaddleCV,PaddleSeg,PaddleClas等工具帮助您快速的搭建起AI应用程序,最快落地AI模型项目。对于有过Tensorflow, PyTorch经验的朋友来说, Paddle的角色和前面二者是一样的,都是高度集成的AI框架。目前Paddle有很活跃的开发者社区,有利于快速找到自己需要的答案和帮助。
02 面向的读者和需要的软件
2.1 面向的读者
本文面向的读者是具有一定编程能力和经验的开发人员,AI模型开发员,熟悉Python语言,并使用Anaconda,已有训练好的模型,期待能部署到边缘设备上来投入实际生产中。对于0基础的读者也是一个很好的机会通过一篇文章一并了解以上的几个技术以及怎样综合使用这些技术,让它们成为您得心应手的工具来帮助您最快的实现AI部署。
2.2 需要的软件
Anaconda,Python(创建Anaconda虚拟环境的时候会自带),OpenVINO,Paddle,PaddleSeg,Paddle2Onnx,mamba。
03 安装PaddlePaddle & PaddleSeg
在介绍完以上内容或,现在可以正式动工啦。由于本文用到的BiSeNetV2路面分割模型是用PaddleSeg训练的,所以需要先安装PaddleSeg的基础库PaddlePaddle。然后再安装PaddelSeg。
在安装Paddle组件之前,请确保您已经安装好了Anaconda 。(地址:
https://docs.anaconda.com/anaconda/install/index.html)
第一步:创建一个conda 虚拟环境:
conda create -n "paddle" python=3.8.8 ipython
创建好环境后 别忘了激活环境:
conda activate paddle
第二步:安装GPU或者CPU版本的PaddlePaddle:
至于是选择GPU还是CPU版本的Paddle,主要是根据您的硬件配置。如果您有NVIDIA最近几年的显卡例如:RTX 1060,RTX 2070等,那么请选择安装GPU版本。
首先安装NVIDIA的cudnn
conda install cudnn
安装的时候也可以把conda 换成mamba(地址:https://github.com/mamba-org/mamba),从而得到更快的****。
这里快速介绍一下mamba。它是Conda的C++实现。相比于Conda,它提供多线程下载,这也就意味着它可以比Conda更好的利用网络资源,下载的更快。同时它也能更快的解析依赖项。估计用Conda多的朋友应该会遇到过,Conda有时候查找依赖项非常慢、很耽误时间。Mamba是您的好朋友,以下教程种再看到conda的命令,大家都可以自动替换成mamba来提高速度, 让您的效率飞起来~!
安装PaddlePaddle的时候,Paddle的官网(https://www.paddlepaddle.org.cn/)是您的好朋友,(任何时候安装任何东西,找到官网一般就能获取最新的指南), 我以Ubuntu 20.04的系统为例(如果您用其他的系统,那么请选择相应的选项)
具体命令如下:
conda install paddlepaddle-gpu==2.1.2 cudatoolkit=11.2 -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/Paddle/ -c conda-forge
安装完底层框架之后,是时候安装PaddleSeg(地址:https://github.com/PaddlePaddle/PaddleSeg)啦。同理, 安装PaddleSeg的时候您的好朋友还是它的官网或者它的Github仓库。
pip install paddleseg
git clone https://github.com/PaddlePaddle/PaddleSeg
安装完PaddleSeg之后 我们就可以开始激动人心的步骤:导出已经训练好的模型~!
04 模型转换
模型的转换分为4个步骤:
1. 导出已经训练好的模型
2. 转换到ONNX模型
3. 通过ONNX模型转换到OpenVINO的 IR模型
4. 最后编译IR模型成为.blob模型
其中3和4都是可以在Intel Movidius Myriad X VPU上部署测试的。
4.1 导出已经训练好的模型
这里我以我的路面分割模型为例(如果您有自己的模型,请替换掉下面例子中的模型,并且更新相应的配置即可)。命令的格式如下:
export CUDA_VISIBLE_DEVICES=0 # 设置1张可用的卡
$ python export.py --config /Github/PaddleSeg/config***isenet/bisenet_road_224.yml --model /models/paddle/road_seg_224_miou_9383_6000iter.pdparams
具体训练好的模型以及训练模型的代码已经发布到了百度的AI Studio上面: [AI创造营] 基于BiSeNetV2的路面分割模型。[2](地址:
https://aistudio.baidu.com/aistudio/projectdetail/2284078?channelType=0&channel=0)
如果想知道更多参数,您的好朋友还是PaddleSeg的官方Github Repository:模型导出 (地址:
https://github.com/PaddlePaddle/PaddleSeg/blob/release/2.2/docs/export/export/model_export.md)
来讲解一下这条命令。
--config 是用来指定模型配置参数的文件。在这个文件里它说明了您使用的模型叫什么名字, 比如在我的例子中,使用的模型叫做:BiSeNetV2, 您需要的分类有多少种,用了什么优化器,损失函数是什么,batch size是多少等等都在这个文件里面。
来看一下bisenetv2_road_224.yml
(地址:https://github.com/franva/Intel-OpenVINO-Paddle)文件的内容
_base_: '../_base_/cityscapes_1024x1024.yml'
train_dataset:
type: Cityscape***r>
transforms:
- type: RandomPaddingCrop
crop_size: [384, 384]
- type: RandomHorizontalFlip
prob: 0.3
- type: Resize
target_size: [224,224]
- type: Normalize
mode: train
val_dataset:
type: Cityscape***r>
transforms:
- type: Resize
target_size: [224,224]
- type: Normalize
mode: val
model:
type: BiSeNetV2
num_classes: 2
optimizer:
type: sgd
weight_decay: 0.0005
loss:
types:
- type: CrossEntropyLos***r>
- type: CrossEntropyLos***r>
- type: CrossEntropyLos***r>
- type: DiceLos***r>
- type: DiceLos***r>
coef: [1, 1, 1, 1, 1]
batch_size: 8
iters: 6000
lr_scheduler:
type: PolynomialDecay
learning_rate: 0.01
end_lr: 0.0001
decay_steps: 0.9
一个小窍门就是,参考PaddleSeg项目里已有的模板 (例如您刚克隆的PaddleSeg代码下面的 config***isenet/bisenet_cityscapes_1024x1024_160k.yml) 一级一级的追查回到最底层的模板,您就差不多可以知道在xml文件里有哪些参数可以指定的了。同时也参考自己在训练AI模型的时候代码里用到了哪些参数,基本上都是要在这个config文件里面反映出来的。
--model 指向的是您已经训练好的模型文件。
4.2 转模型到ONNX: Paddle --> ONNX
模型导出后第一道转换现在开始了.Paddle提供了转换工具 Paddle2onnx. (地址:
https://github.com/PaddlePaddle/Paddle2ONNX)我们先来安装它:
pip install paddle2onnx
是时候转化模型到ONNX啦
paddle2onnx \ --model_dir inference \ --model_filename model.pdmodel \ --params_filename model.pdiparams \ --save_file road_seg.onnx \ --opset_version 11
--enable_onnx_checker True
这里 model_dir, model_filename, 以及params_filename和Save_file替换成自己的文件路径就好.
--model_dir是转换后的模型要保存的目录
--enable_onnx_checker 把这个也启动,让转换程序帮我们检查模型
我当时遇到的问题:
Opset_version的默认值是9(地址:
https://github.com/PaddlePaddle/Paddle2ONNX#parameters),当我在转BiSeNetV2的时候一开始并没有指定这个,而且出错了,经过研究,发现是因为BiSeNetV2的框架比较新,需要把这个opset_version调高到11,更改到11后就好了。目前看到官网能稳定支持的是 11,但是也有看到过别人用12的,大家可以边试边用。
如果转换成功啦则会看到类似的如下信息:
2021-08-23 22:14:33 [INFO] ONNX model generated is valid.
2021-08-23 22:14:33 [INFO] ONNX model saved in /onnx_models/road_seg.onnx
4.3 转换ONNX模型到OpenVINO IR模型
铺垫了很久,终于来到了这一步。
先来快速介绍一下OpenVINO的IR模型。IR的全称叫做Intermediate Representation. IR格式的模型是由2个文件组成的,它们分别是 .xml 和 .bin.
来到这一步之前请确保您的Intel OpenVINO安装成功啦。 怎样安装Intel OpenVINO呢?您的好朋友又要出现了:Intel OpenVINO官网安装教程(地址:
https://docs.openvinotoolkit.org/cn/latest/openvino_docs_install_guides_installing_openvino_linux.html#install-openvino),这里是Intel OpenVINO官方下载地址(https://software.seek.intel.com/openvino-toolkit)
Intel OpenVINO的安装包里3种安装选项分别是:
1. 图像界面GUI安***r>2. 命令行安***r>3. 命令行安装安静模式
对于新手,推荐用GUI安装,清清楚楚、明明白白。
4.3.1 设置外部软件依赖
安装成功后,记得把Install External Software Dependencies(地址:
https://docs.openvinotoolkit.org/cn/latest/openvino_docs_install_guides_installing_openvino_linux.html#install-external-dependencies)这个部分的要求也跟完这一步是需要的。
4.3.2 激活Intel OpenVINO环境变量
小提示:接下来要使用OV就要设置好它的环境变量。官方教程(地址:
https://docs.openvinotoolkit.org/cn/latest/openvino_docs_install_guides_installing_openvino_linux.html#set-the-environment-variables)要求把环境变量加载到您的 .bashrc文件里,这样每次打开任何命令行接口都可以自动加载OV的环境变量。但是我在实际使用过程中发现了一个问题。安装完OV后,我的一部分程序开始报错,出现了一个和Gstreamer相关的错误信息。经过研究发现原来OV的环境变量会和Anaconda的虚拟环境冲突,导致GStreamer出现问题。
其实解决方法也很简单。我们一般只会在模型转换的时候用到OV,那么就不要把OV的环境变量设置到.bashrc文件里面,只需要在使用OV之前,在命令行里激活OV的环境变量就行。 激活环境变量的方法如下:
source /opt/intel/openvino_2021/bin/setupvars.sh
记住/opt/intel/openvino_2021/bin 是默认的OV安装路径,如果您改变了路径,请记得也随之改变这里的路径。
4.3.3 配置模型优化器
Model Optimizer(MO)
相信我同志们,我知道过程很长,但是曙光就在眼前啦~!这个就是开始转OpenVINO IR模型前要调整的最后一步,坚持住~!
MO是一个基于Python的命令行工具,可以用来从其他流行的人工智能框架例如Caffe,ONNX,TensorFlow等导入训练好的模型。没有用MO优化过的模型是不能用来在OV上做推理的。
在这一步可以只为您需要的环境比如ONNX,或者Tensorflow等做配置,但也可以一下配置好可以适用于各种人工智能框架的环境。我在这里选择了后者,毕竟路慢慢其修远 现在用ONNX 之后也有可能用到任何其他网络。
那么第一步先CD到MO设置的文件夹里面:
cd /opt/intel/openvino_2021/deployment_tool***odel_optimizer/install_prerequisites
然后运行以下命令来安装要求的依赖项:
sudo ./install_prerequisites.sh
4.3.4 转ONNX模型到IR模式
cd /opt/intel/openvino_2021/deployment_tool***odel_optimizer
python mo_onnx.py --input_model /inference/onnx_models/road_seg.onnx \
--output_dir /openvino/FP16 \
--input_shape [1,3,224,224] \
--data_type FP16 \
--scale_values [127.5,127.5,127.5] \
--mean_values [127.5,127.5,127.5]
由上面的命令行可以看出,在转换的时候您可以用
--data_type来指定模型的精度,
--input_shape来指定模型接受图像的形状。
最关键的两点我要拿出来讲,分别是--mean_value(MV)和--scale_values(SV)。
在转到IR模型的时候如果指定了这2个参数,那么在之后用模型做推理的时候就可以不用做Normalization。或许您可能觉得这个并没有什么用处,但是这2个选项省了我很多事情,它们会在模型内部直接生成2个操作来帮您做Normalization。
如果转换成功,您将会看到如下输出:
Model Optimizer arguments:
Common parameters:
- Path to the Input Model: /inference/onnx_models/road_seg.onnx
- Path for generated IR: /openvino/FP16
- IR output name: road_seg
- Log level: ERROR
- Batch: Not specified, inherited from the model
- Input layers: Not specified, inherited from the model
- Output layers: Not specified, inherited from the model
- Input shapes: [1,3,224,224]
- Mean values: [127.5,127.5,127.5]
- Scale values: [127.5,127.5,127.5]
- Scale factor: Not specified
- Precision of IR: FP16
- Enable fusing: True
- Enable grouped convolutions fusing: True
- Move mean values to preprocess section: None
- Reverse input channels: False
ONNX specific parameters:
- Inference Engine found in: /opt/intel/openvino_2021/python/python3.8/openvino Inference Engine version: 2021.4.0-3839-cd81789d294-releases/2021/4 Model Optimizer version: 2021.4.0-3839-cd81789d294-releases/2021/4
[ SUCCESS ] Generated IR version 10 model.
[ SUCCESS ] XML file: /openvino/FP16/road_seg.xml
[ SUCCESS ] BIN file: /openvino/FP16/road_seg.bin
[ SUCCESS ] Total execution time: 10.50 second****r>
[ SUCCESS ] Memory consumed: 142 MB.
至此,如果不追求更小体积模型的话, 那么恭喜您 这个IR模型已经可以直接运行在Intel的Movidius Myriad X VPU上啦. 在推理的性能上和编译后的模型没有区别,但是在读取模型的时候会比编译的后模型稍微慢一些,不过就只发生在程序初始化读取模型的时候.
4.4 验证转换后的IR模型
在继续下去之前我们应该先检验一下这个模型是否真的转换成功。
在运行如下代码之前,请换一个命令行窗口,并且启动之前创建的Anaconda 环境,这样做是为了确保OV的环境变量和Conda的不互相冲突,产生错误。
运行如下代码ir.py检测转换后的模型的正确性:
import numpy as np
from openvino.inference_engine import IENetwork, IECore
import cv2
import paddleseg.transforms as T
def get_net(model_xml, model_bin, device_name="MYRIAD"):
ie = IECore()
# Read IR
net = IENetwork(model=model_xml, weight***odel_bin)
input_blob = next(iter(net.input*****r>
exec_net = ie.load_network(network=net, device_name=device_name)
del net
return exec_net, input_blob
def save_img(img, img_fn):
cv2.imwrite(img_fn, img)
model_xml = r'/openvino/ FP16/road_seg_half.xml'
model_bin = r'/openvino/ FP16/road_seg_half.bin'
transform*******r>
T.Resize(target_size=(224,224))
]
# Run inference
img_fn = '/data/boxhill_079.jpeg'
img = cv2.imread(img_fn)
img, _ = T.Compose(transforms)(img)
# add an new axis in front
img_input = img[np.newaxis, :]
exec_net, input_blob = get_net(model_xml, model_bin)
result = exec_net.infer(inputs={input_blob: img_input})
img_segmentation = result['save_infer_model/scale_0.tmp_1']
img_segmentation = np.squeeze(img_segmentation)
class_colors = [[0,0,0], [0,255,0]]
class_colors = np.asarray(class_colors, dtype=np.uint8)
img_mask = class_colors[img_segmentation]
img_mask, _ = T.Compose(transforms)(img_mask)
img_overlayed = cv2.addWeighted(img, 0.8, img_mask, 0.2, 0.5)
img_overlayed = img_overlayed.transpose(1,2,0)
img_overlayed = cv2.cvtColor(img_overlayed, cv2.COLOR_RGB2BGR)
save_img(img_overlayed, 'demo.jpg')
在运行程序之前请记住先把您的带有Intel Movidius Myriad X VPU的设备链接上电脑,我这里用的是OAK-D Camera。
请替换掉代码中的图片和IR模型路径
如果成功 那么应该能够看到这个路面分割模型会自动识别出路面部分并且在原有图像的路面上加上一层半透明的绿色层,如下图:
代码里面的 device_name=”MYRIAD”就是指定模型的部署方向,这里除了MYRIAD还可以是CPU等。
4.5 编译IR模型为Blob格式
最后的最后,如果您精益求精,不仅要部署到VPU上面,还想要进一步减小模型体积,那么可以用Intel OpenVINO来对IR模型进行编译。具体命令如下:
(base) winstonfan@wf-x:/opt/intel/openvino_2021/deployment_tools/tools/compile_tool$ ./compile_tool -m /openvino/FP16/road_seg.xml \
-d MYRIAD \
-o /openvino/compiled_u8.blob \
-ip U8 \
-il NCHW
Inference Engine:
IE version ......... 2021.4.0
Build ........... 2021.4.0-3839-cd81789d294-releases/2021/4
Network inputs:
x : U8 / NCHW
Network outputs:
save_infer_model/scale_0.tmp_1 : I32 / CHW
[Warning][VPU][Config] Deprecated option was used : VPU_MYRIAD_PLATFORM
Done. LoadNetwork time elapsed: 20295 ms
小提示:如果不知道各个参数的含义,可以使用 -h 参数来获取参数说明。
4.6 检验Blob模型
其实验证编译后的Blob模型的思路和IR模型是一样的。唯一需要替换的代码就是在调入神经网络时候的代码。
代码如下 blob_infer_img.py (地址:
https://github.com/franva/Intel-OpenVINO-Paddle):
import time
import cv2
import numpy as np
from openvino.inference_engine import IECore
import paddleseg.transforms as T
def get_net(model_blob, device_name='MYRIAD'):
ie = IECore()
exec_net = ie.import_network(model_blob, device_name = device_name)
input_blob = next(iter(exec_net.input*****r>
return exec_net, input_blob
def save_img(img, img_fn):
cv2.imwrite(img_fn, img)
# Run inference
img_fn = '/data/boxhill_079.jpeg'
img = cv2.imread(img_fn)
transform*******r>
T.Resize(target_size=(224,224))
]
model_blob = r'/openvino/blob/ model_uint8.blob'
img, _ = T.Compose(transforms)(img)
# add an new axis in front
img_input = img[np.newaxis, :]
t1 = time.time()
exec_net, input_blob = get_net(model_blob)
result = exec_net.infer(inputs={input_blob: img_input})
print(' time used : {}'.format(time.time() - t1))
img_segmentation = result['save_infer_model/scale_0.tmp_1']
# img_segmentation is int32
img_segmentation = np.squeeze(img_segmentation)
class_colors = [[0,0,0], [0,255,0]]
class_colors = np.asarray(class_colors, dtype=np.uint8)
img_mask = class_colors[img_segmentation]
img_mask, _ = T.Compose(transforms)(img_mask)
img_overlayed = cv2.addWeighted(img, 0.8, img_mask, 0.2, 0.5)
img_overlayed = img_overlayed.transpose(1,2,0)
img_overlayed = cv2.cvtColor(img_overlayed, cv2.COLOR_RGB2BGR)
save_img(img_overlayed, "demo2.jpg")
至此,整个流程结束。恭喜大家成功的把模型落地,并且部署到了边缘设备上。期待看到你们各个精彩的应用啦!
最后贴上Github 的本文章的陪同代码库(地址:https://github.com/franva/Intel-OpenVINO-Paddle),欢迎大家提出宝贵的意见。
05 总结
本文一开始先介绍了图像分割和语义分割,阐述了部署模型到边缘设备的重要性。快速介绍了Intel的OpenVINO以及百度的 PaddlePaddle框架。然后以一个训练好的百度飞桨Paddle模型为例开始,一步一步带着大家把模型转换到了OpenVINO的格式,最后编译优化,直到部署到Intel的Movidius Myriad X VPU上面。对于不同的模型,只需要适量的改动,便可以快速独立的开发属于自己的AI应用程序。 (作者:西域大菠菜)本文转自:英特尔物联网