此文为毕设内容的一部分,分为两个大块:(1)YOLO模块 (2) openvino模块
下面分别介绍两个模块的安装流程。
1、环境配置
此次任务我采用的是yolov5算法,代码地址如下:https://github.com/ultralytics/yolov5
首先要把环境配置好,在本机终端输入pip install -r requirements.txt
若在colab则 添加代码块为:
git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt # install
执行即可,至此环境配置完成。
2、数据集构建
(1)在 yolov5目录下 新建文件夹 VOCData(可以自定义命名)
、92
(2)在VOCData下新建两个文件夹 Annotations 以及 images
images:用于存放要标注的图片(jpg格式)
Annotations :用于存放标注图片后产生的内容(这里采用XML格式)
然后使用labellmg进行数据集标注,标注完后会生成XML文件。
(3)在VOCData目录下创建程序 split_train_val.py 并运行
# coding:utf-8
import os
import random
import argparse
parser = argparse.ArgumentParser()
#xml文件的地址,根据自己的数据进行修改 xml一般存放在Annotations下
parser.add_argument('--xml_path', default='Annotations', type=str, help='input xml label path')
#数据集的划分,地址选择自己数据下的ImageSets/Main
parser.add_argument('--txt_path', default='ImageSets/Main', type=str, help='output txt label path')
opt = parser.parse_args()
trainval_percent = 1.0 # 训练集和验证集所占比例。 这里没有划分测试集
train_percent = 0.8 # 训练集所占比例,可自己进行调整
xmlfilepath = opt.xml_path
txtsavepath = opt.txt_path
total_xml = os.listdir(xmlfilepath)
if not os.path.exists(txtsavepath):
os.makedirs(txtsavepath)
num = len(total_xml)
list_index = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)
file_trainval = open(txtsavepath + '/trainval.txt', 'w')
file_test = open(txtsavepath + '/test.txt', 'w')
file_train = open(txtsavepath + '/train.txt', 'w')
file_val = open(txtsavepath + '/val.txt', 'w')
for i in list_index:
name = total_xml[i][:-4] + '\n'
if i in trainval:
file_trainval.write(name)
if i in train:
file_train.write(name)
else:
file_val.write(name)
else:
file_test.write(name)
file_trainval.close()
file_train.close()
file_val.close()
file_test.close()
运行完毕后 会生成 ImagesSets\Main 文件夹,且在其下生成 测试集、训练集、验证集,存放图片的名字(无后缀.jpg)由于没有分配测试集,所以测试集为空。若要分配,更改第 14、15 行代码,更改所在比例即可。
(4)在VOCData目录下创建程序 text_to_yolo.py 并运行
需要将第 7 行改成自己所标注的类别 以及修改各绝对路径
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
from os import getcwd
sets = ['train', 'val', 'test']
classes = ["light", "post"] # 改成自己的类别
abs_path = os.getcwd()
print(abs_path)
def convert(size, box):
dw = 1. / (size[0])
dh = 1. / (size[1])
x = (box[0] + box[1]) / 2.0 - 1
y = (box[2] + box[3]) / 2.0 - 1
w = box[1] - box[0]
h = box[3] - box[2]
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return x, y, w, h
def convert_annotation(image_id):
in_file = open('D:/Yolov5/yolov5/VOCData/Annotations/%s.xml' % (image_id), encoding='UTF-8')
out_file = open('D:/Yolov5/yolov5/VOCData/labels/%s.txt' % (image_id), 'w')
tree = ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
difficult = obj.find('difficult').text
#difficult = obj.find('Difficult').text
cls = obj.find('name').text
if cls not in classes or int(difficult) == 1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
float(xmlbox.find('ymax').text))
b1, b2, b3, b4 = b
# 标注越界修正
if b2 > w:
b2 = w
if b4 > h:
b4 = h
b = (b1, b2, b3, b4)
bb = convert((w, h), b)
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
wd = getcwd()
for image_set in sets:
if not os.path.exists('D:/Yolov5/yolov5/VOCData/labels/'):
os.makedirs('D:/Yolov5/yolov5/VOCData/labels/')
image_ids = open('D:/Yolov5/yolov5/VOCData/ImageSets/Main/%s.txt' % (image_set)).read().strip().split()
if not os.path.exists('D:/Yolov5/yolov5/VOCData/dataSet_path/'):
os.makedirs('D:/Yolov5/yolov5/VOCData/dataSet_path/')
list_file = open('dataSet_path/%s.txt' % (image_set), 'w')
# 这行路径不需更改,这是相对路径
for image_id in image_ids:
list_file.write('D:/Yolov5/yolov5/VOCData/images/%s.jpg\n' % (image_id))
convert_annotation(image_id)
list_file.close()
运行后会生成如下 labels 文件夹和 dataSet_path 文件夹。
其中 labels 中为不同图像的标注文件。每个图像对应一个txt文件,文件每一行为一个目标的信息,包括class, x_center, y_center, width, height格式,这种即为 yolo_txt格式
dataSet_path文件夹包含三个数据集的txt文件,train.txt等txt文件为划分后图像所在位置的路径,如train.txt就含有所有训练集图像的路径。
3、配置yaml文件
在 yolov5 目录下的 data 文件夹下 新建一个 myvoc.yaml文件(可以自定义命名),用记事本打开。
内容是:
训练集以及验证集(train.txt和val.txt)的路径(可以改为相对路径)
以及 目标的类别数目和类别名称。
还需要在model文件夹中,选择一个自己需要的模型,我这里选择的是yolov5s,把nc改成自己所标注的类别 nc = 1
4、训练
然后运行train.py即可。注意train.py中的参数。
weights:权重文件路径
cfg:存储模型结构的配置文件.
data:存储训练、测试数据的文件
epochs:指的就是训练过程中整个数据集将被迭代(训练)了多少次
batch-size:训练完多少张图片才进行权重更新,显卡不行就调小点。
img-size:输入图片宽高,显卡不行就调小点。
device:cuda device, i.e. 0 or 0,1,2,3 or cpu。选择使用GPU还是CPU
workers:线程数。默认是8。
修改自己参数和文件路径后在运行即可。
训练后得到best.pt模型,采用以下命令运行
python detect.py --weights runs/train/exp/weights/best.pt --source …/data/0001.png
source后接需要检测的图片、文件夹、视频等。我们在此还可以先把pt模型转换成onnx模型,直接运行export.py文件,需要改路径如下
选择所需要的模型onnx 后面openvino会用到
转换完成后会有best.onnx模型文件。至此yolo可以告一段落。
主要步骤有:
https://visualstudio.microsoft.com/zh-hans/downloads/
2.2下载并解压 OpenVINO Runtime
OpenVINO™ Runtime 2022.3 以压缩包(OpenVINO Archives)的形式提供,里面包含了开发 OpenVINO C++ 推理程序所必需的文件首先,打开下面的链接,下载 OpenVINO™ Runtime 2022.3 压缩包:
https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/download.html
然后,手动新建 C:\Program Files (x86)\Intel\openvino_2022.3.0 文件夹,并把压缩包解压后的文件拷贝到 openvino_2022.3.0 文件夹中,完成OpenVINO Runtime 压缩包的下载和解压工作,如图
2.3下载并解压 OpenCV
OpenCV 也是以压缩包的形式提供,从下方地址下载 Windows 平台上的压缩包:
https://opencv.org/releases/
首先,手动新建 C:\opencv 文件夹;然后,把 OpenCV 解压后的 build 文件夹全部拷贝到 C:\opencv 文件夹,如图 所示,完成 OpenCV 的下载并解压2.4在 Visual Studio 中配置项目属性
在 Visual Studio 中配置项目属性,主要是告诉 C++ 编译器和链接器,OpenVINO™ 和 OpenCV 的头文件以及库文件在哪里。
第一步,打开 Visual Studio,在“文件(F)”菜单中选择“新建(N)→项目§”,新建一个 C++ 空项目。
第二步,在“文件(F)菜单”中选择“新建(N)→文件(F)”,新建一个 main.cpp 文件,并添加到项目管理器的源文件文件夹中。
第三步,在“解决方案资源管理器”中,右键单击项目名称,在右键菜单中选择“属性®”,启动“属性页”对话框。
在“配置”栏选择“所有配置”,在“平台§”栏选择“所有平台”;
在“输出目录”输入
“$(SolutionDir)bin$(Platform)$(Configuration)\”;
在“中间目录”输入
“$(SolutionDir)Intermediate$(Platform)$(Configuration)\”
第四步,在“属性页”对话框中选中“C/C++→常规”;在“平台§”栏选择“x64”;在“附加包含目录”中输入:
C:\Program Files (x86)\Intel\openvino_2022.3.0\runtime\include
C:\opencv\build\include
第五步,在“属性页”对话框中选中“链接器→常规”;在“平台§”栏选择“x64”;在“附加库目录”中输入:
C:\Program Files (x86)\Intel\openvino_2022.3.0\runtime\lib\intel64$(Configuration)
C:\opencv\build\x64\vc16\lib
此处路径要具体看自己的解压位置和bin位置,并不唯一,文中写的只是我的路径。
第六步,在“属性页”对话框中选中“链接器→输入”,在“配置©”栏选择“Debug”,在“平台§”栏选择“x64”,然后在“附加依赖项”中添加:
openvinod.lib;opencv_world470d.lib;
将“配置”栏改为“Release”,然后在“附加依赖项”中添加:
openvino.lib;opencv_world470.lib;
此处也要看自己的C:\opencv\build\x64\vc16\lib(也有可能是x64\vc15\lib)路径下的文件名,不可一股脑复制 比如别人此路径下就为opencv_world3415 所以附加项依赖要改成3415.lib。
到此,完成在 Visual Studio 中配置 OpenVINO C++ 推理计算项目属性。
2.51.5 运行 OpenVINO C++ 范例程序
注意此代码中有图片路径需修改
#include
#include
#include //openvino header file
#include //opencv header file
int main(int argc, char* argv[]) {
// -------- Get OpenVINO runtime version --------
std::cout << ov::get_openvino_version().description << ':' << ov::get_openvino_version().buildNumber << std::endl;
// -------- Step 1. Initialize OpenVINO Runtime Core --------
ov::Core core;
// -------- Step 2. Get list of available devices --------
std::vector<std::string> availableDevices = core.get_available_devices();
// -------- Step 3. Query and print supported metrics and config keys --------
std::cout << "Available devices: " << std::endl;
for (auto&& device : availableDevices) {
std::cout << device << std::endl;
}
// -------- Step 4. Read a picture file and show by OpenCV --------
cv::Mat img = cv::imread("zidane.jpg"); //Load a picture into memory
cv::imshow("Test OpenVINO & OpenCV IDE", img);
std::cout << "Image width: " << img.cols << " height: " << img.rows << std::endl;
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
在这里直接给出代码
#include
#include
using namespace std;
using namespace ov;
using namespace cv;
// 数据集的标签
vector<string> class_names = { "bad" };
//模型文件路径
string model_file = "D:/yolov5_master/best.onnx";
//测试图片路径
string image_file = "D:/yolov5_master/00036.png";
int main(int argc, char** argv) {
//1.创建OpenVINO Runtime Core对象
Core core;
//2.载入并编译模型
CompiledModel compiled_model = core.compile_model(model_file, "GPU");
//3.创建推理请求
InferRequest infer_request = compiled_model.create_infer_request();
//4.设置模型输入
//4.1 获取模型输入节点形状
Tensor input_node = infer_request.get_input_tensor();
Shape tensor_shape = input_node.get_shape();
//4.2读取图片并按照模型输入要求进行预处理
Mat frame = imread(image_file, IMREAD_COLOR);
//Lettterbox resize is the default resize method in YOLOv5.
int w = frame.cols;
int h = frame.rows;
int _max = max(h, w);
Mat image = Mat::zeros(Size(_max, _max), CV_8UC3);
Rect roi(0, 0, w, h);
frame.copyTo(image(roi));
//交换RB通道
cvtColor(image, image, COLOR_BGR2RGB);
//计算缩放因子
size_t num_channels = tensor_shape[1];
size_t height = tensor_shape[2];
size_t width = tensor_shape[3];
float x_factor = image.cols / width;
float y_factor = image.rows / height;
int64 start = cv::getTickCount();
//缩放图片并归一化
Mat blob_image;
resize(image, blob_image, cv::Size(width, height));
blob_image.convertTo(blob_image, CV_32F);
blob_image = blob_image / 255.0;
// 4.3 将图像数据填入input tensor
Tensor input_tensor = infer_request.get_input_tensor();
// 获取指向模型输入节点数据块的指针
float* input_tensor_data = input_tensor.data<float>();
// 将图片数据填充到模型输入节点中
// 原有图片数据为 HWC格式,模型输入节点要求的为 CHW 格式
for (size_t c = 0; c < num_channels; c++) {
for (size_t h = 0; h < height; h++) {
for (size_t w = 0; w < width; w++) {
input_tensor_data[c * width * height + h * width + w] = blob_image.at<Vec<float, 3>>(h, w)[c];
}
}
}
// 5.执行推理计算
infer_request.infer();
// 6.处理推理计算结果
// 6.1 获得推理结果
const ov::Tensor& output = infer_request.get_tensor("output0");
const float* output_buffer = output.data<const float>();
// 6.2 解析推理结果,YOLOv5 output format: cx,cy,w,h,score
int out_rows = output.get_shape()[1]; //获得"output"节点的rows
int out_cols = output.get_shape()[2]; //获得"output"节点的cols
Mat det_output(out_rows, out_cols, CV_32F, (float*)output_buffer);
vector<cv::Rect> boxes;
vector<int> classIds;
vector<float> confidences;
for (int i = 0; i < det_output.rows; i++) {
float confidence = det_output.at<float>(i, 4);
if (confidence < 0.5) {
continue;
}
Mat classes_scores = det_output.row(i).colRange(0, 2);
Point classIdPoint;
double score;
minMaxLoc(classes_scores, 0, &score, 0, &classIdPoint);
// 置信度 0~1之间
if (score > 0.5)
{
float cx = det_output.at<float>(i, 0);
float cy = det_output.at<float>(i, 1);
float ow = det_output.at<float>(i, 2);
float oh = det_output.at<float>(i, 3);
int x = static_cast<int>((cx - 0.5 * ow) * x_factor);
int y = static_cast<int>((cy - 0.5 * oh) * y_factor);
int width = static_cast<int>(ow * x_factor);
int height = static_cast<int>(oh * y_factor);
Rect box;
box.x = x;
box.y = y;
box.width = width;
box.height = height;
boxes.push_back(box);
classIds.push_back(classIdPoint.x);
confidences.push_back(score);
}
}
// NMS
vector<int> indexes;
dnn::NMSBoxes(boxes, confidences, 0.25, 0.45, indexes);
for (size_t i = 0; i < indexes.size(); i++) {
int index = indexes[i];
int idx = classIds[index];
rectangle(frame, boxes[index], Scalar(0, 0, 255), 2, 8);
rectangle(frame, Point(boxes[index].tl().x, boxes[index].tl().y - 20),
Point(boxes[index].br().x, boxes[index].tl().y), Scalar(0, 255, 255), -1);
putText(frame, class_names[idx], Point(boxes[index].tl().x, boxes[index].tl().y - 10), FONT_HERSHEY_SIMPLEX, .5, Scalar(0, 0, 0));
}
// 计算FPS
float t = (getTickCount() - start) / static_cast<float>(getTickFrequency());
cout << "Infer time(ms): " << t * 1000 << "ms; Detections: " << indexes.size() << endl;
putText(frame, format("FPS: %.2f", 1.0 / t), Point(20, 40), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 0), 2, 8);
imshow("YOLOv5-6.1 + OpenVINO 2022.1 C++ Demo", frame);
waitKey(0);
destroyAllWindows();
return 0;
}
onnx模型经过剪枝后可得到IR模型,因笔者能力有限,IR模型暂未调试成功,希望后续会取得进步。