当前位置:   article > 正文

实践教程|基于OpenVINO 2022.1 C++ API部署YOLOv7预训练模型

c++如何进行yolo7

点击下方卡片,关注“自动驾驶之心”公众号

ADAS巨卷干货,即可获取

点击进入→自动驾驶之心技术交流群

导读

 

本文将分享如何基于OpenVINO™ 2022.1工具套件部署YOLOv7官方提供的预训练模型。附C++/Python源码及使用方法。

任务背景

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

代码仓库:https://github.com/OpenVINO-dev-contest/YOLOv7_OpenVINO_cpp-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) x 3 = 255再乘上 feature map 的大小就是最终的输出了。整个开发流程可以分为数据处理模块定义、前处理任务、推理任务、后处理任务四部分组成。

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

1.数据处理模块

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

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

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

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

  1. cv::Mat letterbox(cv::Mat &src, int h, int w, std::vector<float> &padd)
  2. {
  3.     // Resize and pad image while meeting stride-multiple constraints
  4.     int in_w = src.cols;
  5.     int in_h = src.rows;
  6.     int tar_w = w;
  7.     int tar_h = h;
  8.     float r = min(float(tar_h) / in_h, float(tar_w) / in_w);
  9.     int inside_w = round(in_w * r);
  10.     int inside_h = round(in_h * r);
  11.     int padd_w = tar_w - inside_w;
  12.     int padd_h = tar_h - inside_h;
  13.     cv::Mat resize_img;
  14.     // resize
  15.     resize(src, resize_img, cv::Size(inside_w, inside_h));
  16.     // divide padding into 2 sides
  17.     padd_w = padd_w / 2;
  18.     padd_h = padd_h / 2;
  19.     padd.push_back(padd_w);
  20.     padd.push_back(padd_h);
  21.     // store the ratio
  22.     padd.push_back(r);
  23.     int top = int(round(padd_h - 0.1));
  24.     int bottom = int(round(padd_h + 0.1));
  25.     int left = int(round(padd_w - 0.1));
  26.     int right = int(round(padd_w + 0.1));
  27.     // add border
  28.     copyMakeBorder(resize_img, resize_img, top, bottom, left, right, 0, cv::Scalar(114114114));
  29.     return resize_img;
  30. }
  31. cv::Rect scale_box(cv::Rect box, std::vector<float> &padd)
  32. {   
  33.     // remove the padding area
  34.     cv::Rect scaled_box;
  35.     scaled_box.x = box.x - padd[0];
  36.     scaled_box.y = box.y - padd[1];
  37.     scaled_box.width = box.width;
  38.     scaled_box.height = box.height;
  39.     return scaled_box;
  40. }

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

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

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

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

  1. static void generate_proposals(int stride, const float *feat, float prob_threshold, std::vector<Object> &objects)
  2. {   
  3.     // get the results from proposals
  4.     float anchors[18] = {1216193640283675765572146142110192243459401};
  5.     int anchor_num = 3;
  6.     int feat_w = 640 / stride;
  7.     int feat_h = 640 / stride;
  8.     int cls_num = 80;
  9.     int anchor_group = 0;
  10.     if (stride == 8)
  11.         anchor_group = 0;
  12.     if (stride == 16)
  13.         anchor_group = 1;
  14.     if (stride == 32)
  15.         anchor_group = 2;
  16.     // 3 x h x w x (80 + 5)
  17.     for (int anchor = 0; anchor <= anchor_num - 1; anchor++)
  18.     {
  19.         for (int i = 0; i <= feat_h - 1; i++)
  20.         {
  21.             for (int j = 0; j <= feat_w - 1; j++)
  22.             {
  23.                 float box_prob = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 4];
  24.                 box_prob = sigmoid(box_prob);
  25.                 // filter the bounding box with low confidence
  26.                 if (box_prob < prob_threshold)
  27.                     continue;
  28.                 float x = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 0];
  29.                 float y = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 1];
  30.                 float w = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 2];
  31.                 float h = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + 3];
  32.                 double max_prob = 0;
  33.                 int idx = 0;
  34.                 // get the class id with maximum confidence
  35.                 for (int t = 5; t < 85; ++t)
  36.                 {
  37.                     double tp = feat[anchor * feat_h * feat_w * (cls_num + 5) + i * feat_w * (cls_num + 5) + j * (cls_num + 5) + t];
  38.                     tp = sigmoid(tp);
  39.                     if (tp > max_prob)
  40.                     {
  41.                         max_prob = tp;
  42.                         idx = t;
  43.                     }
  44.                 }
  45.                 // filter the class with low confidence
  46.                 float cof = box_prob * max_prob;
  47.                 if (cof < prob_threshold)
  48.                     continue;
  49.                 
  50.                 // convert results to xywh
  51.                 x = (sigmoid(x) * 2 - 0.5 + j) * stride;
  52.                 y = (sigmoid(y) * 2 - 0.5 + i) * stride;
  53.                 w = pow(sigmoid(w) * 22) * anchors[anchor_group * 6 + anchor * 2];
  54.                 h = pow(sigmoid(h) * 22) * anchors[anchor_group * 6 + anchor * 2 + 1];
  55.                 float r_x = x - w / 2;
  56.                 float r_y = y - h / 2;
  57.                 // store the results
  58.                 Object obj;
  59.                 obj.rect.x = r_x;
  60.                 obj.rect.y = r_y;
  61.                 obj.rect.width = w;
  62.                 obj.rect.height = h;
  63.                 obj.label = idx - 5;
  64.                 obj.prob = cof;
  65.                 objects.push_back(obj);
  66.             }
  67.         }
  68.     }
  69. }

2.前处理任务

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

  1. 使用OpenCV读取图片文件;

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

  3. 将色彩通道从BGR转化为RGB;

  4. 将输入数据进行layout转置(NHWC=>NCHW),与归一化操作 (见模型推理部分代码);

  1. cv::Mat src_img = cv::imread(image_path);
  2.     cv::Mat img;
  3.     std::vector<float> padd;
  4.     cv::Mat boxed = letterbox(src_img, img_h, img_w, padd);
  5.     cv::cvtColor(boxed, img, cv::COLOR_BGR2RGB);

3.推理任务

2b733d44fc717fa294d4799b63dcaab4.jpeg
图:OpenVINOTM工具套件runtime开发流程

模型推理部分主要调用OpenVINO™ 的C++ API进行实现,OpenVINO™ 推理接口的调用流程如上图所示(可参考官方说明:https://docs.openvino.ai/latest/openvino_docs_OV_UG_Integrate_OV_with_your_application.html),在这里第2步 Complie Model亦可分为模型读取与模型编译两个步骤执行,同时可以发现相较2021.4之前的版本,OpenVINO™ 2022.1在输入以及输出数据的接口调用方式上有了极大的简化,可以通过Tensor相关的构造函数,轻松实现模型数据的载入和读取。在整个过程中开发者需要将输入数据进行layout转置(NHWC=>NCHW),并填充到input tensor所对应的数据指针地址中。在结果数据提取部分,由于该模型有3个不同尺度feature map output,因此我们需要逐一获取他们结果数据指针。

  1. // -------- Step 1. Initialize OpenVINO Runtime Core --------
  2.     ov::Core core;
  3.     // -------- Step 2. Read a model --------
  4.     std::shared_ptr<ov::Model> model = core.read_model(model_path);
  5.     // -------- Step 3. Loading a model to the device --------
  6.     ov::CompiledModel compiled_model = core.compile_model(model, device_name);
  7.     // Get input port for model with one input
  8.     auto input_port = compiled_model.input();
  9.     // Create tensor from external memory
  10.     // ov::Tensor input_tensor(input_port.get_element_type(), input_port.get_shape(), input_data.data());
  11.     // -------- Step 4. Create an infer request --------
  12.     ov::InferRequest infer_request = compiled_model.create_infer_request();
  13.     // -------- Step 5. Prepare input --------
  14.     ov::Tensor input_tensor1 = infer_request.get_input_tensor(0);
  15.     // NHWC => NCHW
  16.     auto data1 = input_tensor1.data<float>();
  17.     for (int h = 0; h < img_h; h++)
  18.     {
  19.         for (int w = 0; w < img_w; w++)
  20.         {
  21.             for (int c = 0; c < 3; c++)
  22.             {
  23.                 // int in_index = h * img_w * 3 + w * 3 + c;
  24.                 int out_index = c * img_h * img_w + h * img_w + w;
  25.                 data1[out_index] = float(img.at<cv::Vec3b>(h, w)[c]) / 255.0f;
  26.             }
  27.         }
  28.     }
  29.     // -------- Step 6. Start inference --------
  30.     infer_request.infer();
  31.     // -------- Step 7. Process output --------
  32.     auto output_tensor_p8 = infer_request.get_output_tensor(0);
  33.     const float *result_p8 = output_tensor_p8.data<const float>();
  34.     auto output_tensor_p16 = infer_request.get_output_tensor(1);
  35.     const float *result_p16 = output_tensor_p16.data<const float>();
  36.     auto output_tensor_p32 = infer_request.get_output_tensor(2);
  37.     const float *result_p32 = output_tensor_p32.data<const float>();

4.后处理任务

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

  1. generate_proposals(8, result_p8, prob_threshold, objects8);
  2.     proposals.insert(proposals.end(), objects8.begin(), objects8.end());
  3.     generate_proposals(16, result_p16, prob_threshold, objects16);
  4.     proposals.insert(proposals.end(), objects16.begin(), objects16.end());
  5.     generate_proposals(32, result_p32, prob_threshold, objects32);
  6.     proposals.insert(proposals.end(), objects32.begin(), objects32.end());
  7.     std::vector<int> classIds;
  8.     std::vector<float> confidences;
  9.     std::vector<cv::Rect> boxes;
  10.     for (size_t i = 0; i < proposals.size(); i++)
  11.     {
  12.         classIds.push_back(proposals[i].label);
  13.         confidences.push_back(proposals[i].prob);
  14.         boxes.push_back(proposals[i].rect);
  15.     }
  16.     std::vector<int> picked; 
  17.     // do non maximum suppression for each bounding boxx
  18.     cv::dnn::NMSBoxes(boxes, confidences, prob_threshold, nms_threshold, picked);

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

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

参考示例使用方法

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

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

1.依赖安装

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的编译后,执行make install命令。

Python环境依赖

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

$ pip install -r python/requirements

2.预训练模型下载

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

f799d68070521fc47f60976f3d59866d.jpeg

3.模型转换

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

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

4.测试运行

C++示例

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

  1. $ cd cpp
  2.   $ mkdir build && cd build
  3.   $ source '~/intel/openvino_2022.1.0.643/bin/setupvars.sh'
  4.   $ cmake ..

执行推理任务:

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

Python示例

执行推理任务:

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

5.测试结果

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

0ce7cec97d6164235033c54161e0c0c7.jpeg
图:推理运行结果

Benchmark App 介绍

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

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

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

结论

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

参考文献

  • YOLOv7官方仓库:https://github.com/WongKinYiu/yolov7

  • OpenVINO™ 开发文档:https://docs.openvino.ai/latest/openvino_docs_OV_UG_Integrate_OV_with_your_application.html

自动驾驶之心】全栈技术交流群

自动驾驶之心是首个自动驾驶开发者社区,聚焦目标检测、语义分割、全景分割、实例分割、关键点检测、车道线、目标跟踪、3D目标检测、多传感器融合、SLAM、光流估计、轨迹预测、高精地图、规划控制、AI模型部署落地等方向;

加入我们:自动驾驶之心技术交流群汇总!

自动驾驶之心【知识星球】

想要了解更多自动驾驶感知(分类、检测、分割、关键点、车道线、3D目标检测、多传感器融合、目标跟踪、光流估计、轨迹预测)、自动驾驶定位建图(SLAM、高精地图)、自动驾驶规划控制、领域技术方案、AI模型部署落地实战、行业动态、岗位发布,欢迎扫描下方二维码,加入自动驾驶之心知识星球(三天内无条件退款),日常分享论文+代码,这里汇聚行业和学术界大佬,前沿技术方向尽在掌握中,期待交流!

92ac96add07f40bd14e57c0386437063.jpeg

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/275921?site
推荐阅读
相关标签
  

闽ICP备14008679号