趁着临近毕业之前再贡献一波热度吧!
本篇文章主要内容为在使用OpenVino时的一些注意点,方便带大家快速入门。OpenVino相比TVM和libtorch在intelx86的CPU端还是有很大优势的,可以说在X86上推断速度没有什么框架可以媲美OpenVino。实际测试中OpenVino确实出乎了我的意料,值得尝试。另外,Intel也在大力发展OpenVino(从跟新频率可以看出来),也有一些相关的活动和比赛可以参与试试。
看到右下角的计算棒了么(感谢提供此图的师弟)?
什么是OPENVINO
OpenVino和TensorRT类似,是硬件厂商针对自家的硬件平台开发的一套深度学习工具库,包含推断库,模型优化等等一系列与深度学习模型部署相关的功能。通俗易懂点说想要在intel-cpu或者嵌入式上部署深度学习模型,之前你使用libtorch或者tvm,这个时候可以考虑考虑openvino,其针对intel的多代cpu以及其他硬件平台做了针对的优化。
官方地址:https://docs.openvinotoolkit.org/latest/index.html
看看工作流程,其实和TensorRT以及其他的部署工具都差不多,训练好模型,解析成openvino专用的.xml
和.bin
,随后传入Inference Engine中进行推理。唯一不同的是与TensorRT一样,是闭源的,给你.so文件你去调用就行。
安装
安装按照官方流程来就可以了,简单快捷,就是一个已经编译好的库和一些头文件以及文档。个人在Ubuntu和Mac上都进行了安装,下载安装包直接装就可以,还是比较轻松的。在Windows平台可能稍微麻烦下。
另外友情提醒,官方的讲解虽然不是很详细但是很多细节问题都给你提到了,建议细致阅读官方文档。
本教程的讲解前提是正确安装了OpenVino,正确设置了环境变量,并且官方的验证测试都没问题。
开发
首先记得激活环境变量,或者将环境变量设为全局(设为全局可能会与部分已安装的程序库发生冲突,例如OpenCV),Openvino的环境变量非常方便,只要你设置好了,openvino依赖的组件Cmake时就都可以检测到。
激活命令:
代码语言:javascript复制source /opt/intel/openvino/bin/setupvars.sh
对于我来说,我需要在Ubuntu下进行开发,但我又不想设置全局环境,那么我只需要
代码语言:javascript复制prototype@prototype-X299-UD4-Pro:~/Downloads/clion-2018.3/bin$ source /opt/intel/openvino/bin/setupvars.sh
[setupvars.sh] OpenVINO environment initialized
prototype@prototype-X299-UD4-Pro:~/Downloads/clion-2018.3/bin$ sh clion.sh
通过命令行的方式打来Clion来二次开发Openvino。也就是说如果想使用IDE来调试openvino的代码,这里推荐使用Clion,需要注意的是,因为openvino需要设置很多的环境变量,如果你不想将其环境变量设置为全局(可能会与你的其他变量冲突),这时可以通过上面的方式来实现。
OpenVino在安装的时候已经自带了很多常用的库,例如OpenCV,这里的OpenCV是专门为intel处理器编译的优化的,拥有更好的处理视频图像流的能力。
优点
因为之前已经使用过TVM、TensorRT、libtorch、TFlite等一系列关于桌面端神经网络部署相关的库(移动端的有TVM、NCNN、MNN、TNN等),可以发现神经网络推断库是真的难以统一的,各家都想做各家的,我们平民玩家只能看哪个好用上哪个了,真的是太多了,不过大部分框架的原理以及使用方法是相差不大的(除了TVM是神经网络编译器,机器自动搜索优化),当然也有一些大厂自行开发未开源的推断库。
OpenVino给我的感觉就是一个比较成熟而且仍在快速发展的推理库,提供的demo和sample都很充足,上手比较容易,可以用来快速部署开发,在Intel的硬件平台上性能超过了大部分开源库。
个人也尝试过TVM在CPU平台上的优化,个人的测试结果来看是OpenVino更快一些,当然是不完全统计,总体感觉对于自家CPU的调教OpenVino应该更熟悉一些吧。
神经网络计算棒
神经网络计算棒即Intel Neural Myriad X 2 VPU,类似于加速器,也是OpenVino支持的一个硬件平台。支持的操作算子比使用CPU略微少一些,但是大部分的模型是足够胜任的。通过官方的例程以及官方的benchmark可以直接测试计算棒的性能,不用修改任何代码,将其插到usb-3.0接口上,命令行加参数-d MYRIAD
即可。
简单测试了下性能,没想到跑官方demo时速度竟然与i5-7360u相仿,经过性能测试HRnet-w32-256-192可以达到18fps,而在我的MacBook Pro2017上只能跑14fps左右(两者皆使用OpenVino部署)。就算是跑官方的human-pose多人姿态检测也可以达到10fps,跑posenet-224×224有99fps。
使用的HRNet源码以及权重:https://github.com/stefanopini/simple-HRNet 、pose_hrnet_w32_256x192.pth
总的来说,这个棒棒的计算能力超出了我的预料。
一个实际例子
这里简单以一个例子作演示,这里采用人体姿态估计的HRNet作测试。采用的模型是pose_hrnet_w32_256x192.pth
,作为对比,这个模型经过TVM的opt-level=3
优化后,可以在Intel® Core™ i7-7800X CPU @ 3.50GHz × 12
下跑到62ms左右(用了2个核)。
导出ONNX模型
我们选取pose_hrnet_w32_256x192.pth
,将其导出为ONNX模型。
from SimpleHRNet import SimpleHRNet
import torch
model = SimpleHRNet(
32,
17,
'scripts/weights/pose_hrnet_w32_256x192.pth',
model_name='HRNet',
resolution=(256, 192),
multiperson=False,
return_bounding_boxes=False,
max_batch_size=1,
)
model = model.model
example = torch.rand(1, 3, 256, 192)
torch_out = torch.onnx.export(model,
example,
"scripts/weights/pose_hrnet_w32_256x192.onnx",
verbose=True,
export_params=True,
opset_version=11
)
需要注意的地方,因为HRNet涉及到了大量的上采样(UpSample)操作,而ONNX对UpSample操作在新版本才有比较完备的支持,因此在导出的时候需要显示设置opset_version=11
,否则无法正常导出。
将ONNX模型转换为IR
OpenVino通过将ONNX模型转化为IR格式(.xml和.bin)来读取模型,所以需要将.onnx模型继续转化(每个推断工具都会搭配一个前端去解析不同的模型)。
首先根据官方教程安装好转换模型需要的库。
然后转换模型的.py代码在/opt/intel/openvino/deployment_tools/model_optimizer/mo.py
,根据每个人实际的安装位置来,进入这个目录后,执行:
python3 mo.py --input_model <INPUT_MODEL>.onnx
就可以进行转换了。
但显然并没有这么简单。
如果我们直接进行转换,将上一步导出的pose_hrnet_w32_256x192.onnx
进行转化,会报错哦。因为OpenVino的ONNX转换器不支持op11的resize(UpSample)操作,无法顺利去推断该节点前后的shape(一般来说,前端解释器需要推导每个结点后的shape才能进行下一步操作解析操作)
相关的问题:https://software.intel.com/en-us/forums/intel-distribution-of-openvino-toolkit/topic/856753
那怎么办?官方目前还没有支持,只好自己写一个简单的临时解决方案。因为OpenVino的前端解释代码是开源的,我们可以直接修改。转换的代码在/opt/intel/openvino/deployment_tools/model_optimizer
中,通过了解其中的转换代码可以发现,OpenVino的模型转化流程是先解析.ONNX模型,将模型的参数都记录下来,然后再依次替换成OpenVino的模型格式。
因为主要问题是Resize
算子,转换代码对应着/opt/intel/openvino/deployment_tools/model_optimizer/extensions/ops/upsample.py
部分。可以发现推断维度的代码在upsample_infer
部分,也就是说OpenVino的模型解析器无法推断出这个.ONNX模型Resize算子前后的维度信息,也就是out_height
和out_width
,它既然无法通过代码推导出,那我们自己推导下就行可以了。
推导的方式有很多种,可以直接运行Pytorch代码中的模型预测过程,观察所有resize的维度,或者通过其他平台的前端解释器来得到相应的维度,这里我使用TVM的ONNX解释器得到了这些resize算子的前后维度信息,不得不夸一下,TVM的ONNX解释器竟然支持op11的upsample算子。
于是乎,upsample.py
中的upsample_infer
方法修改为:
@staticmethod
def upsample_infer(node: Node):
layout = node.graph.graph['layout']
assert len(layout) == 4
input_shape = node.in_node(0).shape
temp_name = node.soft_get('name')
if temp_name in ['Resize_331','Resize_526','Resize_721','Resize_916', 'Resize_1174',
'Resize_1206', 'Resize_1512', 'Resize_1544']:
out_height, out_width = 32, 24
elif temp_name in ['Resize_1247', 'Resize_1585']:
out_height, out_width = 16, 12
else:
out_height, out_width = 64, 48
node['height_scale'] = out_height/input_shape[2]
node['width_scale'] = out_width / input_shape[3]
if input_shape is None:
return
assert node.has('width_scale') is not None and node.has('height_scale') is not None
node.out_node().shape = shape_for_layout(layout,
batch=input_shape[get_batch_dim(layout, 4)],
features=input_shape[get_features_dim(layout, 4)],
height=out_height,
width=out_width)
其中Resize_331
等是模型中需要upsample的地方,直接通过if-else的方式将我们上一步得到的维度写进来….这个是一次性的,只适合这个模型。
另外还有一个需要注意的,在替换步骤中,OpenVino的前端需要将解析好的参数替换为自己的算子结构,这一步的代码在/opt/intel/openvino/deployment_tools/model_optimizer/extensions/middle/UpsampleToResample.py
中,替换的时候需要height_scale
和width_scale
,代码如下:
...
height_scale = scales[2]
width_scale = scales[3]
if len(scales) == 5:
depth_scale = scales[4]
else:
height_scale = upsample['height_scale']
width_scale = upsample['height_scale']
因此需要在上一步中将height_scale
和height_scale
计算出来并且赋给node对象中的属性:
node['height_scale'] = out_height/input_shape[2]
node['width_scale'] = out_width / input_shape[3]
这样的话,就把这个不能转换的算子解决了。
转换输出:
代码语言:javascript复制Model Optimizer arguments:
Common parameters:
- Path to the Input Model: /home/prototype/Desktop/Deep-Learning/Pytorch-Learn/tvm_code/weights/pose_hrnet_w32_256x192.onnx
- Path for generated IR: /opt/intel/openvino_2020.2.120/deployment_tools/model_optimizer/.
- IR output name: pose_hrnet_w32_256x192
- 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: Not specified, inherited from the model
- Mean values: [0.485,0.456,0.406]
- Scale values: [0.229,0.224,0.225]
- Scale factor: Not specified
- Precision of IR: FP32
- Enable fusing: True
- Enable grouped convolutions fusing: True
- Move mean values to preprocess section: False
- Reverse input channels: False
ONNX specific parameters:
Model Optimizer version: 2020.2.0-60-g0bc66e26ff
[ SUCCESS ] Generated IR version 10 model.
[ SUCCESS ] XML file: /opt/intel/openvino_2020.2.120/deployment_tools/model_optimizer/./pose_hrnet_w32_256x192.xml
[ SUCCESS ] BIN file: /opt/intel/openvino_2020.2.120/deployment_tools/model_optimizer/./pose_hrnet_w32_256x192.bin
[ SUCCESS ] Total execution time: 52.49 seconds.
[ SUCCESS ] Memory consumed: 1693 MB.
还有一点需要注意,为了在之后预测的时候要对输入图像进行规范化,这里直接在转换模型的时候可以传入相关的参数使模型参数被规范化:--scale_values [0.229,0.224,0.225] --mean_values [0.485,0.456,0.406]
,也就是mean和std。
这样,我们导出的模型数据范围是0-1,输入图像的通道顺序是RGB(因为转的ONNX模型输入通道顺序就是RGB),之后在输入图像过程中需要根据这个来调整图像。
推理
推理过程主要也就那几个常用的步骤,加载模型、设置输入输出啥的。下图是官方的流程图,差不多可以一目了然。这部分要说的推理过程和官方示例非常相似,只不过修改了一部分因为模型不同而改变的其他代码。
代码直接从官方的demo中进行修改即可,这里我以human_pose_estimation_demo
为例,建议各位先看一下官方的例程,之后的部署代码是基于这个demo的。HRNet是自顶向下的姿态检测,而官方的姿态检测例子是基于OpenPose自顶向下,因为都是姿态检测示例,所以修改起来相对容易。
初始化Core
首先第一步是初始化Core,在human_pose_estimation_demo
中官方有一个HumanPoseEstimator
类,其中与引擎相关的私有成员变量为:
InferenceEngine::Core ie;
std::string targetDeviceName;
InferenceEngine::CNNNetwork network;
InferenceEngine::ExecutableNetwork executableNetwork;
InferenceEngine::InferRequest::Ptr requestNext;
InferenceEngine::InferRequest::Ptr requestCurr;
在构造函数中,首先读取模型的.xml
信息和.bin
(只需要.xml的地址就可以得到.bin的地址),随后检查这个模型的输入输出维度正确(不正确则报错),设定模型输入输出的数据类型,最后通过executableNetwork = ie.LoadNetwork(network, targetDeviceName)
得到特定处理器下的可执行网络:
network = ie.ReadNetwork(modelPath);
const auto& inputInfo = network.getInputsInfo();
if (inputInfo.size() != 1) {
throw std::runtime_error(modelPath ": expected to have 1 input");
}
const auto& imageInputInfo = *inputInfo.begin();
const auto& imageInputDims = imageInputInfo.second->getTensorDesc().getDims();
if (imageInputDims.size() != 4 || imageInputDims[0] != 1 || imageInputDims[1] != 3) {
throw std::runtime_error(
modelPath ": expected "" imageInputInfo.first "" to have dimensions 1x3xHxW");
}
inputLayerSize = cv::Size(imageInputDims[3], imageInputDims[2]);
// need to be fp32
imageInputInfo.second->setPrecision(InferenceEngine::Precision::FP32);
imageInputInfo.second->setLayout(InferenceEngine::Layout::NCHW);
InferenceEngine::OutputsDataMap outputInfo = network.getOutputsInfo();
// there is only one output in HRNET
auto outputIt = outputInfo.begin();
const auto& resOutputInfo = *outputIt ;
resBlobName = resOutputInfo.first;
auto output_data = resOutputInfo.second;
output_data->setPrecision(InferenceEngine::Precision::FP32);
const auto& resOutputDims = resOutputInfo.second->getTensorDesc().getDims();
if (resOutputDims.size() != 4 || resOutputDims[0] != 1
|| resOutputDims[1] != keypointsNumber) {
throw std::runtime_error(
modelPath ": expected "" resBlobName "" to have dimensions "
"1x" std::to_string(keypointsNumber) "xHFMxWFM");
}
executableNetwork = ie.LoadNetwork(network, targetDeviceName);
requestNext = executableNetwork.CreateInferRequestPtr();
requestCurr = executableNetwork.CreateInferRequestPtr();
初始化模型之后要进行推断,首先需要读取图像,设置输入图像。这里的主要步骤是将通过OpenCV读取的视频帧转化为推理引擎可以加载的格式,与TVM、libtorch以及TensorRT类似,主要步骤分为以下几步:
代码语言:javascript复制CV_Assert(image.type() == CV_8UC3);
// 得到模型的输入数据的内存地址buffer,之后将输入数据移至此地址
InferenceEngine::Blob::Ptr input = requestNext->GetBlob(network.getInputsInfo().begin()->first);
auto buffer = input->buffer().as<InferenceEngine::PrecisionTrait<InferenceEngine::Precision::FP32>::value_type *>();
cv::Mat resizedImage;
// 我们模型的输入维度是RGB,因此需要转换维度,并且除以255以规范化数据维度
cv::resize(image, resizedImage, cv::Size(inputLayerSize.width, inputLayerSize.height), cv::INTER_CUBIC);
cv::cvtColor(resizedImage, resizedImage, cv::COLOR_BGR2RGB);
cv::Mat tensor;
resizedImage.convertTo(tensor, CV_32FC3, 1.0/255);
// 此步骤根据偏移地址 定义planes分别指向了buffer的数据地址,随后通过split将tensor中的数据根
// 据RGB三个维度分割给plane,也就将输入数据移到了buffer的数据地址中。
std::vector<cv::Mat> planes(3);
for (size_t pId = 0; pId < planes.size(); pId ) {
planes[pId] = cv::Mat(inputLayerSize, CV_32FC1, buffer pId * inputLayerSize.area());
}
cv::split(tensor, planes);
这样就把读取的数据传入了推理引擎的输入端地址(没没弄明白的再好好看下上面的代码),接下来就是推理了,推理有两种方式,一种是同步一种是异步的方式,这也是我认为OpenVino和其他框架推理过程略微有区别的地方。用户可以借助OpenVino自带的异步方式提升整体网络推理的FPS。
以下是部分推理代码,其中startCurr()
和startNext()
中对输入图像进行了推理过程,其中startCurr
和startNext()
是成员函数,内容就是调用requestCurr->StartAsync()
执行推理,这里为了同时兼顾同步和异步两种方式,使用startCurr()
和readyCurr()
搭配来模拟同步和异步操作,如果是同步,直接startCurr()
后利用readyCurr()
判断是否结果回来了,没有回来就继续执行while(true)
的循环,直到结果出来为止;如果是异步的,那么可以继续等上一个结果,然后用startNext()
继续执行下一帧的预测,然后依次等待每次的结果出来然后获取。
while(true){
...
if (isAsyncMode) {
if (isModeChanged) {
estimator.startCurr();
}
if (!isLastFrame) {
estimator.startNext();
}
} else if (!isModeChanged) {
estimator.startCurr();
}
if (estimator.readyCurr()) {
poses = estimator.postprocessCurr();
std::cout << "pose get! " << std::endl;
}
...
}
相关成员函数具体定义展示:
代码语言:javascript复制void HumanPoseEstimator::startCurr() {
requestCurr->StartAsync();
}
void HumanPoseEstimator::startNext() {
requestNext->StartAsync();
}
bool HumanPoseEstimator::readyCurr() {
if (InferenceEngine::OK == requestCurr->Wait(InferenceEngine::IInferRequest::WaitMode::RESULT_READY)) {
return true;
} else {
return false;
}
}
其中的内部调度是由OpenVino的TBB去操控,TBB是Intel开发的一个多线程调度工具,可以快速安全地多线程分配任务。具体可以表现为上述的异步操作。
详细的介绍文章在:https://www.edge-ai-vision.com/2020/03/maximize-cpu-inference-performance-with-improved-threads-and-memory-management-in-intel-distribution-of-openvino-toolkit/
异步与同步
OpenVino中有一个值的关注的点是自带的异步同步机制。也就是说推理的时候有两种不同的inference方式,分别是inferRequest->infer()
和inferRequest->startAsync()
,其中inferRequest->infer()
会开始进行推理过程但是会阻塞主线程,也就是说你得等模型执行完才会执行下一步;而inferRequest->startAsync()
则会直接返回状态,代码继续向下执行。因为通过这个函数将推理步骤放到另一个线程执行,不会阻塞主线程,整体的代码不会卡到这里。
据介绍官方之前的多线程处理方式是借助OpenMP,但是官方也提到了使用OpenMP的一些小问题,想详细了解的童鞋可以看这里:https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_with_customer_application_new_API.html
总结
在推理后,通过requestCurr->GetBlob
方法可以得到输出的结果,至于得到结果后怎么去处理,这里就不多说了,以下是官方demo中的示例代码,在上述执行完startCurr
后取出相应的结果。
std::vector<HumanPose> HumanPoseEstimator::postprocessCurr() {
InferenceEngine::Blob::Ptr pafsBlob = requestCurr->GetBlob(pafsBlobName);
InferenceEngine::Blob::Ptr heatMapsBlob = requestCurr->GetBlob(heatmapsBlobName);
InferenceEngine::SizeVector heatMapDims = heatMapsBlob->getTensorDesc().getDims();
std::vector<HumanPose> poses = postprocess(
heatMapsBlob->buffer(),
heatMapDims[2] * heatMapDims[3],
keypointsNumber,
pafsBlob->buffer(),
heatMapDims[2] * heatMapDims[3],
pafsBlob->getTensorDesc().getDims()[1],
heatMapDims[3], heatMapDims[2], imageSize);
return poses;
}
如果对整个部署的流程还不是很清楚,可以一下官方的部署教程:https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_with_customer_application_new_API.html
优化步骤
如何达到模型最佳的优化效果,请看官方文档:https://docs.openvinotoolkit.org/latest/_docs_optimization_guide_dldt_optimization_guide.html
- 关于OpenCV cap/render time,将结果通过OpenCV代码渲染展示过程中需要的时间。
- 关于Detection time,是模型实际的执行时间。
- 关于Wallclock time,是所有步骤一块执行所需的总体时间,也就是模型执行时间和渲染时间。
另一个例子(生成so)
这个简单说一下如何将OpenVino推理代码封装成一个动态链接库(.so),使用python调用并且返回结果。因为有些时候我们需要其他的前端代码去包装我们的后端推理代码,这也是大多数应用程序所使用的一种结构。
拿官方的human_pose_estimation_demo
这个例子来说,我们将其中几个函数标记为export函数:
#ifndef DFROBOT_2D_POSE_C_API_H
#define DFROBOT_2D_POSE_C_API_H
#define EXPORT_DLL __attribute__((visibility("default")))
struct Points{
float data_x[18];
float data_y[18];
float score;
};
extern "C" {
EXPORT_DLL int runInference();
EXPORT_DLL bool isReady();
EXPORT_DLL Points getResult();
}
修改cmake,将# add_executable({IE_SAMPLE_NAME} {IE_SAMPLE_SOURCES} {IE_SAMPLE_HEADERS}) 修改为 add_library({IE_SAMPLE_NAME} SHARED
代码语言:javascript复制...
using namespace InferenceEngine;
using namespace human_pose_estimation;
// 定义对象的时候,构造函数中会初始化core,因此会出现问题。
HumanPoseEstimator estimator("human-pose-estimation-0001.xml", "CPU", false);
std::vector<HumanPose> poses;
bool poseReady = false;
bool isReady()
{
return poseReady;
}
int runInference() {
try {
cv::VideoCapture cap;
cap.open("action_demo.mp4");
// read input (video) frame
cv::Mat curr_frame; cap >> curr_frame;
cv::Mat next_frame;
if (!cap.grab()) {
throw std::logic_error("Failed to get frame from cv::VideoCapture");
}
...
此时有两种解决方法:
- 将模型执行环境切换为GPU或者MYRIAD(计算棒);
- 将全局的对象变为函数中的静态对象,只有在执行函数的时候会初始化一次;
这样就可以顺利将openvino封装为.so
。
一些总结和问题
OpenVino作为一个针对于Intel-CPU部署工具,和其他大多数的推理框架是类似的,和TensorRT一样,有着丰富的例程和相应的文档(TVM等开源社会框架则文档少一些),入门相对比较容易。
优点很明显,在Intel-CPU上的模型优化能力非常棒,大部分模型通吃,转化ONNX模型很少有失败的情况。可以充分利用多核优势,并且可以灵活设置使用的线程数量。在CPU端部署第一建议选择OpenVino。
缺点就是转换其他框架模型的前端还不是很完善,不过这也正常,毕竟每一段时间就会出现一些新的算子,这需要intel的工程师慢慢完善,当然如果自己比较急的话,可以自行修改官方的转换源码(Python),还是比较容易的。
对各个模型算子的支持程度:https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_Supported_Frameworks_Layers.html。
以及各种算子对设备的支持程度:https://docs.openvinotoolkit.org/latest/_docs_IE_DG_supported_plugins_Supported_Devices.html
转换模型的坑
转换模型正如之前流程中所提到的,不可能兼容到所有的op,因此需要我们等待官方完善或者自行添加这些算子转换代码,转换代码是python写的,修改还是比较容易的。
Openvino模型的数值范围
一般Pytorch、TF预训练模型的数值范围为0-1,但是openvino官方示例中的范围是0-255,这是因为大部分是由caffe转换过来的。
输入通道BGR
OpenVino的默认输入顺序是BGR(与OpenCV类似),然而我们一般需要转化的模型(例如从Pytorch导出的onnx)是RGB,利用官方的转换器默认是不会给你转换的,需要使用--reverse_input_channels
参数进行转换。
总之就是要使输入图像的通道顺序和模型的通道顺序一致即可。
ReShape
转化成IR后可以动态修改输入维度,这个是openvino一个比较独特的功能,可以在转换模型后通过修改输入图像的尺度信息,但仅限于模型不是很复杂,不能包含resize的op,在测试中HRNet无法正常reshape。
https://docs.openvinotoolkit.org/latest/_docs_IE_DG_ShapeInference.html
后记
写这篇文章写在临近毕业的那段日子,研究生的最后一段的校园时光,完成最后一个小项目,是一件挺有意义的事情,因为毕业要处理的事情也不少,写的略微有些匆忙,效率也不是很高。但总归是将自己所能想到的都总结了下来,希望对大家有一些帮助。最后,也希望自己的工作能够顺利,能够在忙碌中抽出时间静下心来好好感悟,享受人生。