通常,我们部署yolo系列的模型,都是以GPU或者其他专用的NPU硬件作为部署平台,进行模型加速。如果我们手头没有合适的硬件平台,但是,我们由于实时推理的需求的时候,那么,使用OpenVINO进行部署是我们可以选择的方案之一,也是首选。下面,将详细介绍,部署流程和细节。
部署相关的模型、代码资料均已上传,下载地址。
我的部署硬件环境和系统环境如下:
一、OpenVINO
OpenVINO(Open Visual Inference and Neural Network Optimization)是英特尔推出的一款开源工具套件,旨在优化和部署深度学习模型,支持从云端到边缘的高效推理。它通过兼容主流框架(如TensorFlow、PyTorch、ONNX等)的模型转换工具,提供模型优化、量化和压缩等功能,加速深度学习推理。OpenVINO支持多种英特尔硬件(如CPU、GPU、FPGA等),并提供丰富的工具和API,帮助开发者轻松实现跨平台部署,提升应用性能。
二、yoloV5
YOLOv5是一种先进的单阶段目标检测算法,由Ultralytics团队开发,是YOLO(You Only Look Once)系列第一个大规模工业落地的版本。YOLOv5在速度和准确性上均有显著提升,采用创新的网络架构和优化策略,支持快速实时检测,适用于智能交通、工业检测、农业监控等广泛领域。其轻量化设计和多版本(如YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x)满足不同场景需求,尤其适合对推理速度和计算资源有限制的应用。YOLOv5凭借高效简洁的设计,成为目标检测任务中的首选模型之一。
在这次部署实验中,我们采用yolov5s进行部署实验。
三、OpenVINO的C++环境配置
请参考我的另外一篇博文,VS2022+OpenVINO的开发环境配置。
四、模型部署
4.1 部署流程
部署流程主要包含模型转化、模型加载、前后处理等步骤,流程示意图如下:
4.2 转onnx
使用yolov5的export.py脚本,导出yolov5s的onnx模型即可。
4.3 模型加载
创建openVINO的运行对话对象,加载模型,并创建模型对应的前后处理对象。
ov::Core core;
//1、导入模型
std::shared_ptr<ov::Model> model = core.read_model("./model/yolov5s.onnx");
//2、创建模型的前后处理器
ov::preprocess::PrePostProcessor ppp = ov::preprocess::PrePostProcessor(model);
4.4 预处理
预处理主要是完成输入图像的尺寸缩放和补边,还有数值归一化。代码如下:
//3、设定模型输入的图像格式为NHWC的U8,颜色顺序为BGR
ppp.input().tensor().set_element_type(ov::element::u8).set_layout("NHWC").set_color_format(ov::preprocess::ColorFormat::BGR);
//4、设定预处理管道:先进行浮点化处理、再调换BGR为RGB、最归一化到0-1之间
ppp.input().preprocess().convert_element_type(ov::element::f32).convert_color(ov::preprocess::ColorFormat::RGB).scale({ 255., 255., 255. });
4.5 输出
设定输出的数据格式和数据类型。
//5、设定模型的输入格式为NCHW
ppp.input().model().set_layout("NCHW");
//6、设定模型的输出为浮点数
ppp.output().tensor().set_element_type(ov::element::f32);
4.6 模型推理
OpenVINO的模型推理就简单的两行代码。
//推理
ov::InferRequest infer_request = mCompiledModel.create_infer_request();
infer_request.set_input_tensor(input_tensor);
infer_request.infer();
4.6 后处理
后处理主要是从模型的输出解析出目标检测结果,以及,进行非极大值抑制处理(NMS)。NMS的主要作用是去除与当前边界框重叠度较高的边界框,保留最可能正确的边界框。通过抑制与当前边界框重叠度较高的边界框,可以有效减少冗余检测框,提高检测效率的同时也提升了检测的准确性。在这里,我们之间调用opencv的dnn模块提供的API进行实现。
const ov::Tensor& output_tensor = infer_request.get_output_tensor();
ov::Shape output_shape = output_tensor.get_shape();
float* detections = output_tensor.data<float>();
std::vector<cv::Rect> boxes;
std::vector<int> class_ids;
std::vector<float> confidences;
//系数倒数
float normal_scale = 1.0f / scale;
//边界
cv::Rect frm_box(0, 0, frame.cols, frame.rows);
for (int i = 0; i < output_shape[1]; i++)//遍历所有框
{
float* detection = &detections[i * output_shape[2]];//bbox(x y w h obj cls)
float confidence = detection[4];//当前bbox的obj
if (confidence >= 0.25) //判断是否为前景
{
float* classes_scores = &detection[5];
cv::Mat scores(1, output_shape[2] - 5, CV_32FC1, classes_scores);
cv::Point class_id;
double max_class_score;
cv::minMaxLoc(scores, 0, &max_class_score, 0, &class_id);//返回最大得分和最大类别
if (max_class_score * confidence > confi_thr)//满足得分
{
float x = detection[0] * normal_scale;
float y = detection[1] * normal_scale;
float w = detection[2] * normal_scale;
float h = detection[3] * normal_scale;
float xmin = x - (w / 2);
float ymin = y - (h / 2);
//越界目标
cv::Rect box = frm_box & cv::Rect(xmin, ymin, w, h);
//极小目标不可信
if (cv::min(box.width, box.height) > 5)
{
boxes.push_back(box);
confidences.push_back(confidence);
class_ids.push_back(class_id.x);
}
}
}
}
std::vector<int> nms_result;
cv::dnn::NMSBoxes(boxes, confidences, 0.25, nms_thr, nms_result);
std::vector<TargetInfo> output;
for (int i = 0; i < nms_result.size(); i++)
{
TargetInfo result;
int idx = nms_result[i];
result.cls = class_ids[idx];
result.score = confidences[idx];
result.box = boxes[idx];
output.push_back(result);
}
return output;
五、接口封装
为方便后续使用,将OpenVINO部署yolov5进行类封装,可以对任意尺寸的图像进行推理。接口设计如下:
#ifndef DETECTOR_H
#define DETECTOR_H
#include <string>
#include "opencv2/opencv.hpp"
#include "openvino/openvino.hpp"
#include "utils.h"
class Detector
{
public:
Detector();
virtual ~Detector();
void initialize(const std::string& model_path);
std::vector<TargetInfo> run(cv::Mat& frame, const float& confi_thr = 0.25f, const float& nms_thr = 0.4f);
private:
ov::CompiledModel mCompiledModel;
};
#endif // DETECTOR_H
六、测试
编写一个简单的测试代码,读取一幅图像,进行100次推理,打印耗时,以及绘制检测结果。
cv::Mat input_image = cv::imread("test.png", cv::IMREAD_COLOR);
Detector detector;
detector.initialize("./model/yolov5s.onnx");
std::vector<TargetInfo> result;
for (int j = 0; j < 100; j++)
{
int64 start_time = cv::getTickCount();
result = detector.run(input_image);
int64 end_time = cv::getTickCount();
printf("cost time:%f ms\n", (end_time - start_time) / (float)cv::getTickFrequency() * 1000.0f);
}
for (int i = 0; i < result.size(); i++)
{
cv::rectangle(input_image, result[i].box, cv::Scalar(0, 255, 0), 2);
}
cv::imshow("openvino", input_image);
cv::waitKey();
return 0;
7、小结
在我的电脑配置下,640*640的yolov5s模型使用OpenVINO进行部署后,无任何的量化、剪枝操作,推理耗时在60ms左右,基本达到了预期。后续,将测试OpenVINO的量化部署,进一步减少耗时,达到真正地实时运行推理。