利用OpenVino部署yolov5模型

训练好的yolov5模型想部署在NUC小电脑的Ubuntu环境上,这里我们选择OpenVINO工具进行部署。OpenVINO是英特尔推出的一款全面的工具套件,用于快速部署应用和解决方案。

1. OpenVINO的安装

首先进入OpenVINO官网下载安装包【下载地址】,按如下选择版本。你可能会问为什么不选择2022的最新版本呢?因为2022.1的安装方式和之前有所变化,现在网上对于新版的博客资源较少,这里我们选择2021.4.2是较为稳妥的方式,有问题一般都可以在前人的博客中找到答案(bushi,其实就是我太菜…)。

aaa

Step1 下载完成后,解压安装包,

1
2
#在终端中打开, 使用带安装界面的安装脚本
sudo ./install_GUI.sh

依次勾选接收协议,拒绝收集信息,继续安装。安装过程中可能会提示没找到显卡,发现另一个OpenCV等信息,不用管它。

Step2 安装依赖

1
2
cd /opt/intel/openvino_2021.4.752/install_dependencies
sudo -E ./install_openvino_dependencies.sh

Step3 环境变量设置

1
2
sudo gedit ~/.bashrc
加入source /opt/intel/openvino_2021.4.752/bin/setupvars.sh

Step4 测试是否安装成功

1
2
3
cd //opt/intel/openvino_2021.4.752/deployment_tools/demo
./demo_security_barrier_camera.sh (测试cpu环境)
./demo_security_barrier_camera.sh -d GPU(测试gpu环境)

如果出现下面这张图片说明安装成功。

aaa

2. yolov5模型的部署

部署的话需要提前导出模型为OpenVINO需要的格式 best.xml和best.bin,以下代码主要参考知乎文章,这篇文章作者给的代码不全,所以我这里给出完整的部署实现代码。

2.1 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
cmake_minimum_required(VERSION 3.5)
project(yolov5_openvino_cpp)
set(CMAKE_CXX_STANDARD 14)
SET(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
include_directories(
#OpenVINO推理引擎的头文件
/opt/intel/openvino/deployment_tools/inference_engine/include/
/opt/intel/openvino/deployment_tools/ngraph/include/
)

#查找必要的依赖包
find_package(OpenCV 4 REQUIRED)
set(InferenceEngine_DIR "/opt/intel/openvino/deployment_tools/inference_engine/share")
find_package(InferenceEngine)
# set(ngraph_DIR "/opt/intel/openvino/deployment_tools/ngraph")
# find_package(ngraph REQUIRED)
# set(ngraph_LIBRARIES "/opt/intel/openvino/deployment_tools/ngraph/lib/libngraph.so")
# set(ngraph_INCLUDE_DIRS "/opt/intel/openvino/deployment_tools/ngraph/include/")

add_subdirectory(./backward-cpp)

add_library (robot_detector_lib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/RobotDetector.cpp)
target_include_directories(robot_detector_lib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
PUBLIC ${OpenCV_INCLUDE_DIR}
PUBLIC ${InferenceEngine_INCLUDE_DIRS}
# PUBLIC ${ngraph_INCLUDE_DIRS}
)
target_link_libraries(robot_detector_lib
${OpenCV_LIBS}
${InferenceEngine_LIBRARIES}
${ngraph_LIBRARIES}
)

add_executable(robot_detector yolov5_openvino.cpp ${BACKWARD_ENABLE})
target_link_libraries(robot_detector
${OpenCV_LIBS}
robot_detector_lib
dw
)

2.2 yolov5_openvino.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include "RobotDetector.h"

int main(int argc, char const *argv[])
{
RobotDetector* detector = new RobotDetector;
std::string xml_path = "../model/best.xml";
detector->init(xml_path,0.5,0.5);//第一个0.5指预测的概率低于0.5的结果被舍弃
//第二个0.5是NMS的参数,与得分最高的框的IOU的大于0.5被滤除

//For Video
cv::VideoCapture capture;
std::string filename = "../video/第一视角-小组赛-HITCSC-第二局.mp4";
capture.open(filename);
cv::Mat src;
const std::vector<std::string> class_list = {"robot","red1","red2","blue1","blue2","grey"};
const std::vector<cv::Scalar> colors = {cv::Scalar(0, 255, 0), cv::Scalar(0, 0, 255), cv::Scalar(0, 0, 255),
cv::Scalar(255, 0, 0), cv::Scalar(255, 0, 0), cv::Scalar(128, 128, 128)};

auto start = std::chrono::high_resolution_clock::now();
int frame_count = 0;
float fps = -1;

while(1){
capture >> src;
if (src.empty())
{
std::cout << "End of stream\n";
break;
}
frame_count++;
//auto start = std::chrono::high_resolution_clock::now();

std::vector<RobotDetector::Object> detected_objects;
detector->process_frame(src,detected_objects);

// auto end = std::chrono::high_resolution_clock::now();
// std::chrono::duration<double> diff = end - start;
// std::cout<<"use "<<diff.count()<<" s" << std::endl;
if (frame_count >= 30)
{
auto end = std::chrono::high_resolution_clock::now();
fps = frame_count * 1000.0 / std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
frame_count = 0;
start = std::chrono::high_resolution_clock::now();
std::cout<< "FPS: " << fps <<std::endl;
}

if (fps > 0)
{
std::ostringstream fps_label;
fps_label << std::fixed << std::setprecision(2);
fps_label << "FPS: " << fps;
std::string fps_label_str = fps_label.str();
cv::putText(src, fps_label_str.c_str(), cv::Point(10, 25), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
}

for(int i = 0; i < detected_objects.size(); ++i){
int xmin = detected_objects[i].box.x;
int ymin = detected_objects[i].box.y;
int width = detected_objects[i].box.width;
int height = detected_objects[i].box.height;
cv::Rect rect(xmin, ymin, width, height);//左上坐标(x,y)和矩形的长(x)宽(y)

int classId = detected_objects[i].class_id;
cv::rectangle(src, rect, colors[classId], 1.5);
cv::rectangle(src, cv::Point(xmin, ymin - 20), cv::Point(xmin + width/3, ymin), colors[classId], cv::FILLED);
cv::putText(src, class_list[classId], cv::Point(xmin, ymin - 5), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255));
}
cv::imshow("cap",src);
cv::waitKey(1);
}

//For picture
// Mat src = imread("../res/125.jpg");
// Mat osrc = src.clone();
// resize(osrc,osrc,Size(640,640));
// vector<Detector::Object> detected_objects;
// auto start = chrono::high_resolution_clock::now();
// detector->process_frame(src,detected_objects);
// auto end = chrono::high_resolution_clock::now();
// std::chrono::duration<double> diff = end - start;
// cout<<"use "<<diff.count()<<" s" << endl;
// for(int i=0;i<detected_objects.size();++i){
// int xmin = detected_objects[i].rect.x;
// int ymin = detected_objects[i].rect.y;
// int width = detected_objects[i].rect.width;
// int height = detected_objects[i].rect.height;
// Rect rect(xmin, ymin, width, height);//左上坐标(x,y)和矩形的长(x)宽(y)
// cv::rectangle(osrc, rect, Scalar(0, 0, 255),1, LINE_8,0);
// }
// imshow("result",osrc);
// waitKey(0);
}

2.3 RobotDetector.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#ifndef ROBOT_DETECTOR_H
#define ROBOT_DETECTOR_H
#include <opencv2/opencv.hpp>
#include <inference_engine.hpp>
#include <iostream>
#include <chrono>
#include <opencv2/dnn/dnn.hpp>
#include <cmath>
#include <cstring>

using namespace InferenceEngine;

class RobotDetector
{
public:
typedef struct {
float confidence;
int class_id;
cv::Rect box;
} Object;

RobotDetector();
~RobotDetector();
//初始化
bool init(std::string xml_path,double cof_threshold,double nms_area_threshold);
//处理图像获取结果
bool process_frame(cv::Mat& inframe,std::vector<Object> &detected_objects);

private:
double sigmoid(double x);
std::vector<int> get_anchors(int net_grid);
bool parseYolov5(const Blob::Ptr &blob,
float cof_threshold,
std::vector<cv::Rect>& o_rect,
std::vector<float>& o_rect_cof,
std::vector<int>& classId);
cv::Mat letterBox(cv::Mat src);
cv::Rect detect2origin(const cv::Rect& dete_rect, float rate_to, int top, int left);
//存储初始化获得的可执行网络
ExecutableNetwork _network;
OutputsDataMap _outputinfo;
std::string _input_name;
//参数区
std::string _xml_path; //OpenVINO模型xml文件路径
double _cof_threshold; //置信度阈值,计算方法是框置信度乘以物品种类置信度
double _nms_area_threshold; //nms最小重叠面积阈值
float _ratio;
int _topPad;
int _btmPad;
int _leftPad;
int _rightPad;
// std::vector<std::string> _classes;

};



#endif

2.4 RobotDetector.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#include "RobotDetector.h"

#define IMAGE_LEN_F 640.0f
#define IMAGE_LEN 640

RobotDetector::RobotDetector(){}

RobotDetector::~RobotDetector(){}

//初始化
bool RobotDetector::init(std::string xml_file, double cof_threshold, double nms_area_threshold){
_xml_path = xml_file;
_cof_threshold = cof_threshold;
_nms_area_threshold = nms_area_threshold;
// std::string ids[]={"robot","red1","red2","blue1","blue2","grey"};
// _classes = std::vector<std::string>(ids, ids+6);
Core ie;
//查询支持硬件设备
std::vector<std::string> availableDev = ie.GetAvailableDevices();
for(int i=0;i<availableDev.size();i++){
std::cout << "Openvino Supported Device Name: " << availableDev[i].c_str() << std::endl;
}
//加载检测模型
auto cnnNetwork = ie.ReadNetwork(_xml_path);
//网络输入设置
InputsDataMap inputInfo(cnnNetwork.getInputsInfo());
InputInfo::Ptr& input = inputInfo.begin()->second;
_input_name = inputInfo.begin()->first;
input->setPrecision(Precision::FP32);
input->getInputData()->setLayout(Layout::NCHW);
ICNNNetwork::InputShapes inputShapes = cnnNetwork.getInputShapes();
SizeVector& inSizeVector = inputShapes.begin()->second;
cnnNetwork.reshape(inputShapes);
//网络输出设置
_outputinfo = OutputsDataMap(cnnNetwork.getOutputsInfo());
for (auto &output : _outputinfo) {
output.second->setPrecision(Precision::FP32);
}
//获取可执行网络
//_network = ie.LoadNetwork(cnnNetwork, "GPU");
_network = ie.LoadNetwork(cnnNetwork, "CPU");
std::cout << "Running on CPU\n";
return true;
}

//注意此处的阈值是框和物体prob乘积的阈值
bool RobotDetector::parseYolov5(const Blob::Ptr &blob,
float cof_threshold,
std::vector<cv::Rect>& o_rect,
std::vector<float>& o_rect_cof,
std::vector<int> &classId)
{
//如果输入图像大小为[1,3,640,640],那么三个输出的尺寸为3*80*80*(4+1)*80 | 40*40 | 20*20
//利用blob->getTensorDesc().getDims()获取输出头的参数,包括w,h,c,batch_size,class_num
const int net_grid_h = static_cast<int>(blob->getTensorDesc().getDims()[2]); // 80/40/20
const int net_grid_w = static_cast<int>(blob->getTensorDesc().getDims()[3]); // 80/40/20
//const int batch_size = static_cast<int>(blob->getTensorDesc().getDims()[0]); // batch_size
const int anchor_num = static_cast<int>(blob->getTensorDesc().getDims()[1]); // anchor_num,3
// const int item_size = static_cast<int>(blob->getTensorDesc().getDims()[4]); // (4+1+class_num)
const int item_size = 11;//item_size为 类别个数+5

//std::cout << "item size: " << item_size <<std::endl;
//std::cout << "anchor scale: " << net_grid_h <<std::endl;
//std::cout << "net_grid_h : " << net_grid_h <<" net_grid_w: "<<net_grid_w<<std::endl;

if (net_grid_h != net_grid_w)
{
//std::cout << "网格长宽不一致" <<std::endl;
return false;
}

//根据输出的尺度,利用get_anchors方法获得相应的锚框
std::vector<int> anchors = get_anchors(net_grid_h);
//输出分类内存分配
LockedMemory<const void> blobMapped = as<MemoryBlob>(blob)->rmap();
const float *output_blob = blobMapped.as<float *>();

int net_grid = net_grid_h;
std::size_t gi = net_grid*item_size;
std::size_t ggi = net_grid*gi;
std::size_t anchor_n = anchor_num;
//输出解析与预测框解码部分
for(int n = 0; n < anchor_num; ++n)
for(int i = 0; i < net_grid; ++i)
for(int j = 0; j < net_grid; ++j){
//获取输出置信度
double box_prob = sigmoid(output_blob[n*ggi + i*gi + j*item_size + 4]);

if(box_prob < cof_threshold)
continue;

// 获取输出目标框
//注意此处输出为中心点坐标,需要转化为角点坐标
double x = output_blob[n*ggi + i*gi + j*item_size + 0];
double y = output_blob[n*ggi + i*gi + j*item_size + 1];
double w = output_blob[n*ggi + i*gi + j*item_size + 2];
double h = output_blob[n*ggi + i*gi + j*item_size+ 3];

//获取输出类别索引
double max_prob = 0;
int idx=0;
for(int t = 5; t < item_size; ++t){
double tp= sigmoid(output_blob[n*ggi + i*gi + j*item_size + t]);
if(tp > max_prob){
max_prob = tp;
idx = t-5;
}
}

float cof = box_prob * max_prob;
//对于边框置信度小于阈值的边框,不关心其他数值,不进行计算减少计算量
if(cof < cof_threshold)
continue;

//预测框解码
x = (sigmoid(x)*2 - 0.5 + j)*IMAGE_LEN_F/net_grid;
y = (sigmoid(y)*2 - 0.5 + i)*IMAGE_LEN_F/net_grid;
w = pow(sigmoid(w)*2,2) * anchors[n*2];
h = pow(sigmoid(h)*2,2) * anchors[n*2 + 1];

double r_x = x - w/2;
double r_y = y - h/2;
cv::Rect rect = cv::Rect(round(r_x),round(r_y),round(w),round(h));
//将结果保存在vector中
o_rect.push_back(rect);
o_rect_cof.push_back(cof);
classId.push_back(idx);
}
return true;
// if(o_rect.size() == 0) //如果没有结果 返回false,目前不采用
// return false;
// else
// return true;
}

//处理图像获取结果
bool RobotDetector::process_frame(cv::Mat& inframe,
std::vector<Object>& detected_objects){
if(inframe.empty()){
std::cout << "无效图片输入" << std::endl;
return false;
}
cv::Mat resize_img = letterBox(inframe);
std::size_t img_size = 640 * 640;

//建立推理请求
InferRequest infer_request = _network.CreateInferRequest();
Blob::Ptr frameBlob = infer_request.GetBlob(_input_name);
LockedMemory<void> blobMapped = as<MemoryBlob>(frameBlob)->wmap();
float* blob_data = blobMapped.as<float*>();

//nchw 将输入图像关联到Blob
for(std::size_t row = 0;row <IMAGE_LEN;row++){
for(std::size_t col =0;col <IMAGE_LEN;col++){
for(std::size_t ch=0;ch<3;ch++){
//遍历输入图像的每个像素保存到blob_data,目前不理解blob_data的索引值
blob_data[img_size*ch + row*IMAGE_LEN + col] = float(resize_img.at<cv::Vec3b>(row,col)[ch]/255.0f);
}
}
}
//执行预测
infer_request.Infer();

//获取各层结果,保存到origin_rect,origin_rect_cof,classId
std::vector<cv::Rect> origin_rect;
std::vector<float> origin_rect_cof;
std::vector<int> classId;

//大规模计算之前先收集指针
std::vector<Blob::Ptr> blobs;
//blobs保存推理结果,用于输入到parseYolov5方法进行解析
for (auto &output : _outputinfo) {
auto output_name = output.first;
Blob::Ptr blob = infer_request.GetBlob(output_name);
blobs.push_back(blob);
}
//blob的大小为3
for(int i=0;i<blobs.size();i++){
float th = 0.5;
//TODO:根据网格大小使用不同阈值,可自己调配
if(i == 0) { th = 0.55; } //小目标严格要求
if(i == 1) { th = 0.45; } //大目标放宽要求
if(i == 2) { th = 0.40; }

//用于保存解析结果的临时vector
std::vector<cv::Rect> origin_rect_temp;
std::vector<float> origin_rect_cof_temp;
std::vector<int> classId_temp;
//解析blobs
parseYolov5(blobs[i], th, origin_rect_temp, origin_rect_cof_temp, classId);
origin_rect.insert(origin_rect.end(), origin_rect_temp.begin(), origin_rect_temp.end());
origin_rect_cof.insert(origin_rect_cof.end(), origin_rect_cof_temp.begin(), origin_rect_cof_temp.end());
classId.insert(classId.end(), classId_temp.begin(), classId_temp.end());
}

//后处理获得最终检测结果
std::vector<int> final_id;
cv::dnn::NMSBoxes(origin_rect, origin_rect_cof, _cof_threshold, _nms_area_threshold, final_id);
//根据final_id获取最终结果
for(int i = 0; i < final_id.size(); ++i){
cv::Rect resize_rect= origin_rect[final_id[i]];
//调用detect2origin方法将框映射到原图
cv::Rect rawrect = detect2origin(resize_rect, _ratio, _topPad, _leftPad);
detected_objects.push_back(Object{
origin_rect_cof[final_id[i]],
classId[final_id[i]],
rawrect
});
}
return true;
}



//以下为工具函数
double RobotDetector::sigmoid(double x){
return (1 / (1 + exp(-x)));
}

const int anchorBig = 640/8; //8倍下采样
const int anchorMid = 640/16; //16倍下采样
const int anchorSml = 640/32; //32倍下采样
/*yolov5s.yaml内容
anchors:
- [10,13,16,30,32,23] # P3/8
- [30,61,62,45,59,119] # P4/16
- [116,90,156,198,373,326] # P5/32
*/
const int aBig[6] = {10,13,16,30,32,23};
const int aMid[6] = {30,61,62,45,59,119};
const int aSml[6] = {116,90,156,198,373,326};
//获取锚框
std::vector<int> RobotDetector::get_anchors(int net_grid)
{
std::vector<int> anchors(6);
if(net_grid == anchorBig) { anchors.insert(anchors.begin(),aBig,aBig+6); }
else if(net_grid == anchorMid) { anchors.insert(anchors.begin(),aMid,aMid+6); }
else if(net_grid == anchorSml) { anchors.insert(anchors.begin(),aSml,aSml+6); }
return anchors;
}

//图像缩放与填充
cv::Mat RobotDetector::letterBox(cv::Mat src)
{
if(src.empty()) { std::cout <<"input image invalid" << std::endl; return cv::Mat();}
//以下为带边框图像生成
int in_w = src.cols;
int in_h = src.rows;
int tar_w = 640;
int tar_h = 640;
//哪个缩放比例小选哪个
_ratio = std::min(float(tar_h)/in_h,float(tar_w)/in_w);
int inside_w = std::round(in_w * _ratio);
int inside_h = std::round(in_h * _ratio);
int pad_w = tar_w - inside_w;
int pad_h = tar_h - inside_h;
//内层图像resize
cv::Mat resize_img;
cv::resize(src,resize_img,cv::Size(inside_w,inside_h)); //最小的Resize
cv::cvtColor(resize_img,resize_img,cv::COLOR_BGR2RGB);
pad_w = pad_w / 2;
pad_h = pad_h / 2;
//外层边框填充灰色
_topPad = int(std::round(pad_h - 0.1));
_btmPad = int(std::round(pad_h + 0.1));
_leftPad = int(std::round(pad_w - 0.1));
_rightPad = int(std::round(pad_w + 0.1));

cv::copyMakeBorder(resize_img,resize_img, _topPad, _btmPad, _leftPad, _rightPad,cv::BORDER_CONSTANT,cv::Scalar(114,114,114));
return resize_img;
}

//还原 从detect得到的xywh转换回到原图xywh
cv::Rect RobotDetector::detect2origin(const cv::Rect &det_rect, float rate_to, int top, int left)
{
//detect坐标转换到内部纯图坐标
int inside_x = det_rect.x - left;
int inside_y = det_rect.y - top;
int ox = std::round(float(inside_x)/rate_to);
int oy = std::round(float(inside_y)/rate_to);
int ow = std::round(float(det_rect.width)/rate_to);
int oh = std::round(float(det_rect.height)/rate_to);

cv::Rect origin_rect(ox,oy,ow,oh);
return origin_rect;
}


3. 运行结果

aaa