0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何基于OpenVINO 2022.1工具套件部署YOLOv7预训练模型

英特尔物联网 来源:英特尔物联网 作者:英特尔物联网 2022-08-26 17:02 次阅读

作为视觉应用中最常见的任务之一,目标检测一直是各类新模型刷榜的必争之地,其中就以 YOLO 系列的网络结构最为突出。YOLO 的全称是 you only look once,指只通过 one-stage 的方式需要“浏览一次”就可以识别出图中的物体的类别和位置。近期YOLO官方团队又放出新版本——YOLOv7,速度、精度都超越其他变体。本文将分享如何基于 OpenVINO 2022.1工具套件部署 YOLOv7 官方提供的预训练模型。附 C++/Python 源码及使用方法。

OpenVINO 简介

用于高性能深度学习英特尔发行版 OpenVINO工具套件基于 oneAPI 而开发,以期在从边缘到云的各种英特尔平台上,帮助用户更快地将更准确的真实世界结果部署到生产系统中。通过简化的开发工作流程,OpenVINO可赋能开发者在现实世界中部署高性能应用程序和算法

在推理后端,得益于 OpenVINO 工具套件提供的“一次编写,随处部署”特性,转换后的模型能够在不同的英特尔硬件平台上运行,无需重新构建,有效简化了构建与迁移过程。此外,为了支持更多的异构加速单元,OpenVINO的 runtime api 底层采用了插件式的开发架构,基于oneAPI 中的 MKL-DNN、oneDNN 等函数计算加速库,针对 AVX-512 等通用指令集进行优化,为不同的硬件执行单元分别实现了一套完整的高性能算子库,提升模型在推理运行时的整体性能表现。

YOLOv7 简介

官方版的 YOLOv7 相同体量下比 YOLOv5 精度更高,速度快120%(FPS),比 YOLOX 快180%(FPS),比 Dual-Swin-T 快1200%(FPS),比 ConvNext 快550%(FPS),比 SWIN-L快500%(FPS)。在5FPS 到160FPS 的范围内,无论是速度或是精度,YOLOv7 都超过了目前已知的检测器,并且在GPU V100 上进行测试, 精度为56.8% AP的模型可达到30 FPS(batch=1)以上的检测速率,与此同时,这是目前唯一一款在如此高精度下仍能超过 30FPS 的检测器。

任务开发流程

我们先整体来看下 YOLOv7 的输入输出结构,首先对输入的图片 resize 为 640x640 大小,输入到 backbone 网络中,然后经 head 层网络输出三层不同 size 大小的 feature map,并输出预测结果,这里以 coco 数据集为例子,输出为 80 个类别,然后每个输出(x ,y, w, h, o) 即坐标位置和是否存在物体的置信度,3 是指的 anchor 数量,因此每一层的输出为 (80+5)x3 = 255再乘上 feature map 的大小就是最终的输出了。整个开发流程可以分为数据处理模块定义、前处理任务、推理任务、后处理任务四部分组成。

95734dfa-2455-11ed-ba43-dac502259ad0.png

图:YOLOv7官方预训练模型的输入输出结构

1. 数据处理模块

定义 Object 结构体用来存放模型的输出数据,包含 boundingbox 信息,类别标签,以及是否存在物体和类别的累计置信度。

定义 class_names 向量,用于存放 coco 数据集的所有标签。

struct Object{  cv::Rect_ rect;  int label;  float prob;};
const std::vector class_names = {  "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",  "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",  "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",  "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",  "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",  "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",  "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",  "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",  "hair drier", "toothbrush"};

定义 letterbox 与 scale_box 模块,分别用于在图像前处理任务中为输入数据添加 letterbox,以及在后处理任务还原 letterbox 带来坐标位置变换。这里特别值得注意的是我们增加了一个 padd 向量,用于存放在添加 letterbox 过程中 letterbox 的 size 信息以及相较原始图片的缩放比例信息,该组数据会用于在后处理任务中还原删除 letterbox 以后的结果。

cv::Mat letterbox(cv::Mat &src, int h, int w, std::vector &padd){  // Resize and pad image while meeting stride-multiple constraints  int in_w = src.cols;  int in_h = src.rows;  int tar_w = w;  int tar_h = h;  float r = min(float(tar_h) / in_h, float(tar_w) / in_w);  int inside_w = round(in_w * r);  int inside_h = round(in_h * r);  int padd_w = tar_w - inside_w;  int padd_h = tar_h - inside_h;  cv::Mat resize_img;
  // resize  resize(src, resize_img, cv::Size(inside_w, inside_h));
  // divide padding into 2 sides  padd_w = padd_w / 2;  padd_h = padd_h / 2;  padd.push_back(padd_w);  padd.push_back(padd_h);
  // store the ratio  padd.push_back(r);  int top = int(round(padd_h - 0.1));  int bottom = int(round(padd_h + 0.1));  int left = int(round(padd_w - 0.1));  int right = int(round(padd_w + 0.1));
  // add border  copyMakeBorder(resize_img, resize_img, top, bottom, left, right, 0, cv::Scalar(114, 114, 114));  return resize_img;}
cv::Rect scale_box(cv::Rect box, std::vector &padd){    // remove the padding area  cv::Rect scaled_box;  scaled_box.x = box.x - padd[0];  scaled_box.y = box.y - padd[1];  scaled_box.width = box.width;  scaled_box.height = box.height;  return scaled_box;}

定义 generate_proposal 模块,该模块具体有以下几个功能:

01

根据定义的 anchors,在输入图像中生成各类 feature map 的先验框;

02

根据输出结果调整先验框位置和大小,并将其作为 bounding box 还原到输入图像的坐标系中;

03

过滤置信度较低的分类结果,获取类别结果。

static void generate_proposals(int stride, const float *feat, float prob_threshold, std::vector &objects){    // get the results from proposals  float anchors[18] = {12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401};  int anchor_num = 3;  int feat_w = 640 / stride;  int feat_h = 640 / stride;  int cls_num = 80;  int anchor_group = 0;  if (stride == 8)    anchor_group = 0;  if (stride == 16)    anchor_group = 1;  if (stride == 32)    anchor_group = 2;
  // 3 x h x w x (80 + 5)  for (int anchor = 0; anchor <= anchor_num - 1; anchor++)    {        for (int i = 0; i <= feat_h - 1; i++)        {            for (int j = 0; j <= feat_w - 1; j++)            {                float box_prob = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 4];                box_prob = sigmoid(box_prob);
                // filter the bounding box with low confidence                if (box_prob < prob_threshold)                    continue;                float x = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 0];                float y = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 1];                float w = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 2];                float h = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 3];
                double max_prob = 0;                int idx = 0;
                // get the class id with maximum confidence                for (int t = 5; t < 85; ++t)                {                    double tp = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + t];                    tp = sigmoid(tp);                    if (tp > max_prob)          {            max_prob = tp;            idx = t;          }        }
        // filter the class with low confidence        float cof = box_prob * max_prob;        if (cof < prob_threshold)                    continue;                                // convert results to xywh                x = (sigmoid(x) * 2 - 0.5 + j) * stride;                y = (sigmoid(y) * 2 - 0.5 + i) * stride;                w = pow(sigmoid(w) * 2, 2) * anchors[anchor_group * 6 + anchor * 2];                h = pow(sigmoid(h) * 2, 2) * anchors[anchor_group * 6 + anchor * 2 + 1];
                float r_x = x - w / 2;                float r_y = y - h / 2;
                // store the results                Object obj;                obj.rect.x = r_x;                obj.rect.y = r_y;                obj.rect.width = w;                obj.rect.height = h;                obj.label = idx - 5;                obj.prob = cof;                objects.push_back(obj);            }        }    }}

2. 前处理任务

前处理主要包含以下几个步骤:

01

使用 OpenCV 读取图片文件

02

对于原始图片进行 resize 并添加 letterbox

03

将色彩通道从 BGR 转化为 RGB

04

将输入数据进行 layout 转置(NHWC

=>NCHW), 与归一化操作(见模型推理部分代码)

cv::Mat src_img = cv::imread(image_path);  cv::Mat img;
  std::vector padd;  cv::Mat boxed = letterbox(src_img, img_h, img_w, padd);  cv::cvtColor(boxed, img, cv::COLOR_BGR2RGB);

3. 推理任务

958ed3a4-2455-11ed-ba43-dac502259ad0.png

图:OpenVINO工具套件 runtime 开发流程

模型推理部分主要调用 OpenVINO的 C++ API 进行实现,OpenVINO推理接口的调用流程如上图所示

可参考官方说明:

https://docs.openvino.ai/latest/openvino_docs_OV_UG_Integrate_OV_with_your_application.html

在这里第2步 ComplieModel 亦可分为模型读取与模型编译两个步骤执行,同时可以发现相较2021.4之前的版本,OpenVINO 2022.1在输入以及输出数据的接口调用方式上有了极大的简化,可以通过 Tensor 相关的构造函数,轻松实现模型数据的载入和读取。在整个过程中开发者需要将输入数据进行layout转置(NHWC=>NCHW),并填充到 inputtensor 所对应的数据指针地址中。在结果数据提取部分,由于该模型有3个不同尺度 featuremapoutput,因此我们需要逐一获取他们结果数据指针。

// -------- Step 1. Initialize OpenVINO Runtime Core --------  ov::Core core;
  // -------- Step 2. Read a model --------  std::shared_ptr model = core.read_model(model_path);
  // -------- Step 3. Loading a model to the device --------  ov::CompiledModel compiled_model = core.compile_model(model, device_name);
  // Get input port for model with one input  auto input_port = compiled_model.input();  // Create tensor from external memory  // ov::Tensor input_tensor(input_port.get_element_type(), input_port.get_shape(), input_data.data());  // -------- Step 4. Create an infer request --------  ov::InferRequest infer_request = compiled_model.create_infer_request();
  // -------- Step 5. Prepare input --------  ov::Tensor input_tensor1 = infer_request.get_input_tensor(0);  // NHWC => NCHW  auto data1 = input_tensor1.data();  for (int h = 0; h < img_h; h++)    {        for (int w = 0; w < img_w; w++)        {            for (int c = 0; c < 3; c++)            {                // int in_index = h * img_w * 3 + w * 3 + c;                int out_index = c * img_h * img_w + h * img_w + w;                data1[out_index] = float(img.at(h, w)[c]) / 255.0f;      }    }  }
  // -------- Step 6. Start inference --------  infer_request.infer();
  // -------- Step 7. Process output --------  auto output_tensor_p8 = infer_request.get_output_tensor(0);  const float *result_p8 = output_tensor_p8.data();  auto output_tensor_p16 = infer_request.get_output_tensor(1);  const float *result_p16 = output_tensor_p16.data();  auto output_tensor_p32 = infer_request.get_output_tensor(2);  const float *result_p32 = output_tensor_p32.data();

4. 后处理任务

后处理部分需要调用我们之前定义的 generate_proposals 用于还原每一个 featuremap 的结果数据,并进行堆叠,最后使用 OpenCVDNN 模块中自带的 NMS 方法,完成对结果 boundingbox 的非极大抑制过滤,获取我们在原始 inputimage 中的目标位置与类别信息。

 generate_proposals(8, result_p8, prob_threshold, objects8);  proposals.insert(proposals.end(), objects8.begin(), objects8.end());  generate_proposals(16, result_p16, prob_threshold, objects16);  proposals.insert(proposals.end(), objects16.begin(), objects16.end());  generate_proposals(32, result_p32, prob_threshold, objects32);  proposals.insert(proposals.end(), objects32.begin(), objects32.end());
  std::vector classIds;  std::vector confidences;  std::vector boxes;
  for (size_t i = 0; i < proposals.size(); i++)    {        classIds.push_back(proposals[i].label);        confidences.push_back(proposals[i].prob);        boxes.push_back(proposals[i].rect);    }
    std::vector picked; 
  // do non maximum suppression for each bounding boxx  cv::NMSBoxes(boxes, confidences, prob_threshold, nms_threshold, picked);

此外,我们还需要进一步调整模型 inputdata 对应的结果数据,将其还原到原始尺寸的图片上进行展示。

 int idx = picked[i];    cv::Rect box = boxes[idx];    cv::Rect scaled_box = scale_box(box, padd);    drawPred(classIds[idx], confidences[idx], scaled_box, padd[2], raw_h, raw_w, src_img, class_names);

参考示例使用方法

该示例分别提供了 C++和 Python 的参考实现,具体使用方法如下:

1. 依赖安装

# 下载示例仓库$ git clone https://github.com/OpenVINO-dev-contest/YOLOv7_OpenVINO_cpp-python.git

C++ 环境依赖

由于本示例的 C++ 版本只依赖OpenVINO和OpenCV 的运行库,所以需要开发者提前完成对这两个工具组件的安装:

OpenVINO C++ runtime 安装地址:

https://docs.openvino.ai/latest/openvino_docs_install_guides_installing_openvino_linux.html#install-openvino

OpenCV 环境安装地址:

https://docs.opencv.org/4.x/d7/d9f/tutorial_linux_install.html

注:由于该示例中提供的 CMakeList 使用 OpenCV 的默认路径,因此需要在完成 OpenCV 的编译后,执行 makeinstall 命令。

Python 环境依赖

Python 环境的安装相对简单,只需通过 pip 命令行工具进行依赖安装:

$ pip install -r python/requirements

2. 预训练模型下载

可以从官方 github 仓库提供的链接中下载基于 COCO 数据集的 YOLOv7 预训练模型权重:

https://github.com/WongKinYiu/yolov7

95af2f5a-2455-11ed-ba43-dac502259ad0.png

3. 模型转换

目前 OpenVINO runtime 可以直接支持 onnx 格式的模型部署,所以我们在得到.pt权重文件后,只需使用官方自带 export.py 脚本,就可将其导出为 onnx 格式模型,具体过程如下:

# 下载YOLOv7官方仓库:$ git clone git@github.com:WongKinYiu/yolov7.git$ cd yolov7/models$ python export.py --weights yolov7.pt

4. 测试运行

C++ 示例

编译 C++ 示例源码,编译完成后会在 build 目录下生成 YOLOv7 可执行文件:

 $ cd cpp $ mkdir build && cd build $ source '~/intel/openvino_2022.1.0.643/bin/setupvars.sh' $ cmake .. $ make

执行推理任务:

 $ yolov7 yolov7.onnx data/horses.jpg 'CPU' 

Python 示例

执行推理任务:

 $ python python/main.py -m yolov7.onnx -i data/horse.jpg

5. 测试结果

运行推理示例后,会在本地目录下生成代 boundingbox 以及 label 的图片,这里我们用到官方仓库中附带的马匹数据进行测试,具体结果如下:

图:推理运行结果

Benchmark App 介绍

OpenVINO提供了性能测试工具Benchmark App ,方便开发者快速测试OpenVINO模型在不同的硬件平台上的性能。我们以下面的例子,简单介绍benchmarkapp 的使用方法和相关参数,更多内容请参考BenchmarkApp 官方文档。

$ benchmark_app -m yolov7.onnx -hint throughput

-m: 指定模型路径。由于目前OpenVINO runtime是支持直接读取 onnx 格式文件的,所以这里我们设置为导出以后 onnx 模型。

-hint: 指定性能测试的优先策略,以自动选择底层性能优化相关参数。这里我们选择 throughput 模式来提升系统整体吞吐量。如果应用对延迟比较敏感,推荐使用 latency 模式来减少推理延迟。

结论

YOLOv7 由于其出色的精度和性能表现,在推出第一时就受到了极大的关注,目前 github 上的 star 已超过5K。本示例通过OpenVINO2022.1新版本的 C++ API 接口,实现对 YOLOv7 官方预训练模型的部署。最后使用 OpenVINO自带的 benchmark_app 工具进一步对模型的性能进行验证测试。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 英特尔
    +关注

    关注

    60

    文章

    9412

    浏览量

    168769
  • 源码
    +关注

    关注

    8

    文章

    572

    浏览量

    28583
  • 训练模型
    +关注

    关注

    1

    文章

    35

    浏览量

    3755

原文标题:基于 OpenVINO™ 2022.1 C++ API 部署 YOLOv7 预训练模型 | 开发者实战

文章出处:【微信号:英特尔物联网,微信公众号:英特尔物联网】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    怎样使用PyTorch Hub去加载YOLOv5模型

    PyTorch Hub 加载训练YOLOv5s 模型,model并传递图像进行推理。'yolov5s'是最轻最快的
    发表于 07-22 16:02

    yolov7 onnx模型在NPU上太慢了怎么解决?

    我将 yolov7tiny.pt(yolov7-tiny 模型)转换为具有 uint8 权重的 yolov7tiny.onnx,然后在 i.MX 8M Plus NPU 上运行
    发表于 04-04 06:13

    为什么无法通过Heroku部署OpenVINO工具套件

    无法通过 Heroku 部署OpenVINO工具套件: Importeror:libpython3.9.so.1.0:无法打开共享对象文件:无此类文件或目录
    发表于 08-14 08:58

    无法使用MYRIAD在OpenVINO trade中运行YOLOv7自定义模型怎么解决?

    无法确定如何将 YOLOv7 模型的重量(.pt 文件)转换为OpenVINO™中间表示 (IR) 并推断有 MYRIAD 的 IR。 分辨率 转换使用此 GitHub* 存储库
    发表于 08-15 08:29

    使用OpenVINO部署PaddleSeg模型库中的DeepLabV3+模型

          01 概述     本文是OpenVINO 工具套件与百度飞桨PaddlePaddle模型转换/部署系列的第二部。这篇文章专注于
    的头像 发表于 11-22 14:58 9043次阅读
    使用<b class='flag-5'>OpenVINO</b>™ <b class='flag-5'>部署</b>PaddleSeg<b class='flag-5'>模型</b>库中的DeepLabV3+<b class='flag-5'>模型</b>

    基于OpenVINO™ 的飞桨版 PGNet 实现案例

    OpenVINO 工具套件2022.1版于2022年3月22日正式发布,根据官宣OpenVINO 迎来迄今为止最重大更新,
    发表于 08-04 16:25 701次阅读

    在C++中使用OpenVINO工具部署YOLOv5模型

    下载并转换YOLOv5预训练模型的详细步骤,请参考:《基于OpenVINO™2022.2和蝰蛇峡谷优化并部署
    的头像 发表于 02-15 16:53 2162次阅读

    在AI爱克斯开发板上用OpenVINO™加速YOLOv8分类模型

    本系列文章将在 AI 爱克斯开发板上使用 OpenVINO 开发套件依次部署并测评 YOLOv8 的分类模型、目标检测
    的头像 发表于 05-05 11:47 593次阅读
    在AI爱克斯开发板上用<b class='flag-5'>OpenVINO</b>™加速<b class='flag-5'>YOLOv</b>8分类<b class='flag-5'>模型</b>

    在AI爱克斯开发板上用OpenVINO™加速YOLOv8目标检测模型

    《在 AI 爱克斯开发板上用 OpenVINO 加速 YOLOv8 分类模型》介绍了在 AI 爱克斯开发板上使用 OpenVINO 开发套件
    的头像 发表于 05-12 09:08 840次阅读
    在AI爱克斯开发板上用<b class='flag-5'>OpenVINO</b>™加速<b class='flag-5'>YOLOv</b>8目标检测<b class='flag-5'>模型</b>

    AI爱克斯开发板上使用OpenVINO加速YOLOv8目标检测模型

    《在AI爱克斯开发板上用OpenVINO加速YOLOv8分类模型》介绍了在AI爱克斯开发板上使用OpenVINO 开发套件
    的头像 发表于 05-26 11:03 716次阅读
    AI爱克斯开发板上使用<b class='flag-5'>OpenVINO</b>加速<b class='flag-5'>YOLOv</b>8目标检测<b class='flag-5'>模型</b>

    YOLOv7训练自己的数据集包括哪些

      YOLOv7训练自己的数据集整个过程主要包括:环境安装—制作数据集—模型训练模型测试—模型
    的头像 发表于 05-29 15:18 618次阅读
    <b class='flag-5'>YOLOv7</b><b class='flag-5'>训练</b>自己的数据集包括哪些

    在AI爱克斯开发板上用OpenVINO™加速YOLOv8-seg实例分割模型

    《在 AI 爱克斯开发板上用 OpenVINO 加速 YOLOv8 目标检测模型》介绍了在 AI 爱克斯开发板上使用 OpenVINO 开发套件
    的头像 发表于 06-05 11:52 598次阅读
    在AI爱克斯开发板上用<b class='flag-5'>OpenVINO</b>™加速<b class='flag-5'>YOLOv</b>8-seg实例分割<b class='flag-5'>模型</b>

    在AI爱克斯开发板上用OpenVINO™加速YOLOv8-seg实例分割模型

    《在 AI 爱克斯开发板上用 OpenVINO 加速 YOLOv8 目标检测模型》介绍了在 AI 爱克斯开发板上使用 OpenVINO 开发套件
    的头像 发表于 06-30 10:43 448次阅读
    在AI爱克斯开发板上用<b class='flag-5'>OpenVINO</b>™加速<b class='flag-5'>YOLOv</b>8-seg实例分割<b class='flag-5'>模型</b>

    三种主流模型部署框架YOLOv8推理演示

    深度学习模型部署OpenVINO、ONNXRUNTIME、TensorRT三个主流框架,均支持Python与C++的SDK使用。对YOLOv5~Y
    的头像 发表于 08-06 11:39 1827次阅读

    使用OpenVINO优化并部署训练好的YOLOv7模型

    在《英特尔锐炫 显卡+ oneAPI 和 OpenVINO 实现英特尔 视频 AI 计算盒训推一体-上篇》一文中,我们详细介绍基于英特尔 独立显卡搭建 YOLOv7 模型训练环境,并
    的头像 发表于 08-25 11:08 916次阅读
    使用<b class='flag-5'>OpenVINO</b>优化并<b class='flag-5'>部署</b><b class='flag-5'>训练</b>好的<b class='flag-5'>YOLOv7</b><b class='flag-5'>模型</b>