当前位置:   article > 正文

YOLOV5python训练到C++的dnn部署_yolov5c++

yolov5c++

目录

源码地址以及环境搭建:

数据集标注:

制作自己的配置文件ymal:

修改参数:

模型转换pt转为onnx (这里我就替换成官网的权重)

Cmake编译C++ opencv4.5.5(此过程大约20-40分钟)

C++调用opencv的依赖

yoloV5 C++部署

瑞思拜

引言:

        有人说要骑着蜗牛闯世界

源码地址以及环境搭建:

yoloV5源码地址:GitHub地址

github下载不下来的可以去我的百度网盘:链接:https://pan.baidu.com/s/1YrWVWLn6NxT1xtOITU7wgQ 
提取码:2233 

准备python环境:python3.8  CUDA:11.5,cudnn需要对应cuda版本 (这里是我自己的环境)

分享一个cuda的安装教程:(转自 Greif_Hairline博主)cuda安装教程+cudnn安装教程_hw@c14h10的博客-CSDN博客_cuda安装

命令行下载需要安装的内容 :pip install -r requirements.txt

这里的torch是cpu的如果需要安装GPU版本 可以去torch的官网下载

我的torch版本为 1.11.0+cu5 与其对应的 torchvision版本 0.12.0+cu115 (注:cu代表gpu,win10x64为电脑版本以及多少核,改gpu只支持英伟达显卡,我的显卡是2080SUPER+8G独显)

后续缺少安装包需要根据报错自行安装 注意版本需要对应

数据集标注

采用labelimage进行标注,标注格式为VOC的数据格式

VOC2007

|--------------Annotations

|--------------images

我们的数据集进行标注

标注完后将XML文件转为TXT格式:

参考链接:https://www.csdn.net/tags/MtjaUgxsNjg4NTctYmxvZwO0O0OO0O0O.html

这里我将代码贴出来:

1.将数据集切分 split.py 通常按照训练集9,测试集1的比例

  1. import random
  2. import argparse
  3. parser = argparse.ArgumentParser()
  4. parser.add_argument('--xml_path', default=r'E:\yolov5-master\VOCData\Annotations', type=str,
  5. help='input xml label path')
  6. parser.add_argument('--txt_path', default=r'E:\yolov5-master\VOCData\labels', type=str,
  7. help='output txt label path')
  8. opt = parser.parse_args()
  9. trainval_percent = 1.0
  10. train_percent = 0.9
  11. xmlfilepath = opt.xml_path
  12. txtsavepath = opt.txt_path
  13. total_xml = os.listdir(xmlfilepath)
  14. if not os.path.exists(txtsavepath):
  15. os.makedirs(txtsavepath)
  16. num = len(total_xml)
  17. list_index = range(num)
  18. tv = int(num * trainval_percent)
  19. tr = int(tv * train_percent)
  20. trainval = random.sample(list_index, tv)
  21. train = random.sample(trainval, tr)
  22. file_trainval = open(txtsavepath + '/trainval.txt', 'w')
  23. file_test = open(txtsavepath + '/test.txt', 'w')
  24. file_train = open(txtsavepath + '/train.txt', 'w')
  25. file_val = open(txtsavepath + '/val.txt', 'w')
  26. for i in list_index:
  27. name = total_xml[i][:-4] + '\n'
  28. if i in trainval:
  29. file_trainval.write(name)
  30. if i in train:
  31. file_train.write(name)
  32. else:
  33. file_val.write(name)
  34. else:
  35. file_test.write(name)
  36. file_trainval.close()
  37. file_train.close()
  38. file_val.close()
  39. file_test.close()

 2.将xml转为txt格式     txt2yolo_label.py

  1. # txt2yolo_label.py
  2. # -*- coding: utf-8 -*-
  3. import xml.etree.ElementTree as ET
  4. from tqdm import tqdm
  5. import os
  6. from os import getcwd
  7. sets = ['train', 'val', 'test']
  8. classes = ['element']
  9. def convert(size, box):
  10. dw = 1. / (size[0])
  11. dh = 1. / (size[1])
  12. x = (box[0] + box[1]) / 2.0 - 1
  13. y = (box[2] + box[3]) / 2.0 - 1
  14. w = box[1] - box[0]
  15. h = box[3] - box[2]
  16. x = x * dw
  17. w = w * dw
  18. y = y * dh
  19. h = h * dh
  20. return x, y, w, h
  21. def convert_annotation(image_id):
  22. # try:
  23. in_file = open(r'E:\yolov5-master\VOCData\Annotations/%s.xml' % (image_id), encoding='utf-8')
  24. out_file = open(r'E:\yolov5-master\VOCData\labels/%s.txt' % (image_id), 'w', encoding='utf-8')
  25. tree = ET.parse(in_file)
  26. root = tree.getroot()
  27. size = root.find('size')
  28. w = int(size.find('width').text)
  29. h = int(size.find('height').text)
  30. for obj in root.iter('object'):
  31. # difficult = obj.find('difficult').text
  32. cls = obj.find('name').text
  33. # if cls not in classes or int(difficult) == 1:
  34. if cls not in classes:
  35. continue
  36. cls_id = classes.index(cls)
  37. xmlbox = obj.find('bndbox')
  38. b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
  39. float(xmlbox.find('ymax').text))
  40. b1, b2, b3, b4 = b
  41. # 标注越界修正
  42. if b2 > w:
  43. b2 = w
  44. if b4 > h:
  45. b4 = h
  46. b = (b1, b2, b3, b4)
  47. bb = convert((w, h), b)
  48. out_file.write(str(cls_id) + " " +
  49. " ".join([str(a) for a in bb]) + '\n')
  50. wd = getcwd()
  51. for image_set in sets:
  52. if not os.path.exists(r'E:\yolov5-master\VOCData\labels/'):
  53. os.makedirs(r'E:\yolov5-master\VOCData\labels/')
  54. image_ids = open(r'E:\yolov5-master\VOCData\labels/%s.txt' %
  55. (image_set)).read().strip().split()
  56. list_file = open(r'E:\yolov5-master\VOCData\labels/%s.txt' % (image_set), 'w')
  57. for image_id in tqdm(image_ids):
  58. list_file.write('E:\yolov5-master\VOCData\images/%s.jpg\n' % (image_id))
  59. convert_annotation(image_id)
  60. list_file.close()

制作自己的配置文件ymal:


        在data下制作自己的yaml配置文件,仿照COCO数据集的配置文件 有多少类别nc就为几 coco为80类

  1. train: E:\yolov5-master\VOCData\labels\train.txt
  2. val: E:\yolov5-master\VOCData\labels\val.txt
  3. test: E:\yolov5-master\VOCData\labels\test.txt
  4. # Classes
  5. nc: 1 # number of classes
  6. names: ['element'] #注,这里的顺序和txt2yolo_label.py文件中的classes保持一致

修改参数:

  1. train.py下 parse_opt函数中需要修改的信息

      --weights  

      --cfg

     --data

     --epochs

     --batch-siza  (显存不足建议往小调整)

     --imgsz  (640*640)  显存不足调整为460

     2.yolov5  detect.py预测部分:(这里根据自己数据集的训练结果来更改)

      --weights    使用自己的训练路径

      --source    使用收集好的测试集

      --data       使用训练时的配置文件

       --imgsz      640*640  显存不够460*460

      --conf-thres   0.7

      --iou-thres     0.5 

训练完后更改完以上配置 来进行一波预测:

python detect.py --weights 自己训练完的权重 --source 图片路径 --data 自己配置的ymal文件路径

查看效果:

 可以看到效果还是不错的  这个零件拿在手上得需要放大镜才能看到

这里我用的是大华的相机和镜头  分辨率为3072*2048

模型转换pt转为onnx (这里我就替换成官网的权重)

python E:\yolov5-master\export.py --weights ./models/yolov5s.pt --img 640 --batch 1  --opset 12

(官网提示opset设置为12,原来默认值是13,在opencv下会报错,原因未知)

Cmake编译C++ opencv4.5.5(此过程大约20-40分钟)

Opencv官网下载

/Opencv网址
opencv_contrib官网下载

https://github.com/opencv/opencv_contrib/releases/tag

cmake官网下载

Download | CMake

(推荐下载windows_x86_x64.zip,根据自身系统以及配置自行下载)

通过cmake/bin目录下的 cmake-gui.exe进行编译

Where is the source code:opencv的路径

Where to build the binaries:新建一个bulid文档

Condigure:安装(此过程需要一直重复并解决红色字段的报错,直至没有红色字段出现)

Generate: 开始配置(配置好的东西都在build/install中)

Open Project:打开此项目

红色字段报错的大致信息:

            缺少重要的exe,dill,cmake文件未下载 (需要去install/CMakeDownloadLog.txt中查看报错以及提示需要下载的链接,将链接输入GitHub Proxy 代理加速进行下载,最后根据txt中的提示改名并放入指定文件夹内)

VS打开 切换relase x64

 

’ 右击CMake Targets下的INSTALL点击重新生成

C++调用opencv的依赖

将刚刚编译好的opencv写入环境变量

Opencv/bulid/install/x64/vc16/bin

Opencv/bulid/bin

配置完成后重启电脑(如果不重启会发生文件丢失情况)

 打开VS,新建空项目,

       

       VS调至 relase  x64

       右击解决方案: 》 配置管理器 》配置relase 平台x64

 添加opencv包含目录和库目录:

右击项目->属性->VC++目录

 包含目录:opencv/build/install/include

                     opencv/build/install/include /opencv

                     opencv/build/install/include /opencv2

                        

 这里我的include中只有opencv2,所以我只添加一个

 库目录:opencv/build/install/x64/vc16/lib

        ​​​​​​​        

添加依赖项:

              链接器》输入》附加依赖心》进入附加依赖项,添加lib文件路径

Lib也在生成的install文件的子文件x64/vc16/lib/*.lib

Relase 版的话:opencv/build/install/x64/vc16/lib/*.lib

Debug 版的话: opencv/build/install/x64/vc16/lib/*d.lib

yoloV5 C++部署

新建立yolo.h

附上代码段:

  1. #pragma once
  2. #include<iostream>
  3. #include<opencv2/opencv.hpp>
  4. #define YOLO_P6 false //是否使用P6模型
  5. struct Output {
  6. int id; //结果类别id
  7. float confidence; //结果置信度
  8. cv::Rect box; //矩形框
  9. };
  10. class Yolo {
  11. public:
  12. Yolo() {
  13. }
  14. ~Yolo() {}
  15. bool readModel(cv::dnn::Net& net, std::string& netPath, bool isCuda);
  16. bool Detect(cv::Mat& SrcImg, cv::dnn::Net& net, std::vector<Output>& output);
  17. void drawPred(cv::Mat& img, std::vector<Output> result, std::vector<cv::Scalar> color);
  18. private:
  19. #if(defined YOLO_P6 && YOLO_P6==true)
  20. const float netAnchors[4][6] = { { 19,27, 44,40, 38,94 },{ 96,68, 86,152, 180,137 },{ 140,301, 303,264, 238,542 },{ 436,615, 739,380, 925,792 } };
  21. const int netWidth = 1280; //ONNX图片输入宽度
  22. const int netHeight = 1280; //ONNX图片输入高度
  23. const int strideSize = 4; //stride size
  24. #else
  25. const float netAnchors[3][6] = { { 10,13, 16,30, 33,23 },{ 30,61, 62,45, 59,119 },{ 116,90, 156,198, 373,326 } };
  26. const int netWidth = 640; //ONNX图片输入宽度
  27. const int netHeight = 640; //ONNX图片输入高度
  28. const int strideSize = 3; //stride size
  29. #endif // YOLO_P6
  30. const float netStride[4] = { 8, 16.0,32,64 };
  31. float boxThreshold = 0.25;
  32. float classThreshold = 0.25;
  33. float nmsThreshold = 0.45;
  34. float nmsScoreThreshold = boxThreshold * classThreshold;
  35. std::vector<std::string> className = { "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
  36. "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
  37. "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
  38. "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
  39. "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
  40. "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
  41. "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
  42. "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
  43. "hair drier", "toothbrush" }; //COCO数据集类别
  44. /*std::vector<std::string> className = { "element"};*/ //
  45. };

新建yolo.cpp文件

附上代码段:

  1. #include"yolo.h"
  2. using namespace std;
  3. using namespace cv;
  4. using namespace cv::dnn;
  5. bool Yolo::readModel(Net& net, string& netPath, bool isCuda = false) {
  6. try {
  7. net = readNet(netPath);
  8. }
  9. catch (const std::exception&) {
  10. return false;
  11. }
  12. //cuda
  13. if (isCuda) {
  14. net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
  15. net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);
  16. }
  17. //cpu
  18. else {
  19. net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
  20. net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
  21. }
  22. return true;
  23. }
  24. bool Yolo::Detect(Mat& SrcImg, Net& net, vector<Output>& output) {
  25. Mat blob;
  26. int col = SrcImg.cols;
  27. int row = SrcImg.rows;
  28. int maxLen = MAX(col, row);
  29. Mat netInputImg = SrcImg.clone();
  30. if (maxLen > 1.2 * col || maxLen > 1.2 * row) {
  31. Mat resizeImg = Mat::zeros(maxLen, maxLen, CV_8UC3);
  32. SrcImg.copyTo(resizeImg(Rect(0, 0, col, row)));
  33. netInputImg = resizeImg;
  34. }
  35. blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(0, 0, 0), true, false);
  36. //如果在其他设置没有问题的情况下但是结果偏差很大,可以尝试下用下面两句语句
  37. //blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(104, 117, 123), true, false);
  38. //blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(114, 114,114), true, false);
  39. net.setInput(blob);
  40. std::vector<cv::Mat> netOutputImg;
  41. //vector<string> outputLayerName{"345","403", "461","output" };
  42. //net.forward(netOutputImg, outputLayerName[3]); //获取output的输出
  43. net.forward(netOutputImg, net.getUnconnectedOutLayersNames());
  44. std::vector<int> classIds;//结果id数组
  45. std::vector<float> confidences;//结果每个id对应置信度数组
  46. std::vector<cv::Rect> boxes;//每个id矩形框
  47. float ratio_h = (float)netInputImg.rows / netHeight;
  48. float ratio_w = (float)netInputImg.cols / netWidth;
  49. int net_width = className.size() + 5; //输出的网络宽度是类别数+5
  50. float* pdata = (float*)netOutputImg[0].data;
  51. for (int stride = 0; stride < strideSize; stride++) { //stride
  52. int grid_x = (int)(netWidth / netStride[stride]);
  53. int grid_y = (int)(netHeight / netStride[stride]);
  54. for (int anchor = 0; anchor < 3; anchor++) { //anchors
  55. const float anchor_w = netAnchors[stride][anchor * 2];
  56. const float anchor_h = netAnchors[stride][anchor * 2 + 1];
  57. for (int i = 0; i < grid_y; i++) {
  58. for (int j = 0; j < grid_x; j++) {
  59. float box_score = pdata[4]; ;//获取每一行的box框中含有某个物体的概率
  60. if (box_score >= boxThreshold) {
  61. cv::Mat scores(1, className.size(), CV_32FC1, pdata + 5);
  62. Point classIdPoint;
  63. double max_class_socre;
  64. minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);
  65. max_class_socre = (float)max_class_socre;
  66. if (max_class_socre >= classThreshold) {
  67. //rect [x,y,w,h]
  68. float x = pdata[0]; //x
  69. float y = pdata[1]; //y
  70. float w = pdata[2]; //w
  71. float h = pdata[3]; //h
  72. int left = (x - 0.5 * w) * ratio_w;
  73. int top = (y - 0.5 * h) * ratio_h;
  74. classIds.push_back(classIdPoint.x);
  75. confidences.push_back(max_class_socre * box_score);
  76. boxes.push_back(Rect(left, top, int(w * ratio_w), int(h * ratio_h)));
  77. }
  78. }
  79. pdata += net_width;//下一行
  80. }
  81. }
  82. }
  83. }
  84. //执行非最大抑制以消除具有较低置信度的冗余重叠框(NMS)
  85. vector<int> nms_result;
  86. NMSBoxes(boxes, confidences, nmsScoreThreshold, nmsThreshold, nms_result);
  87. for (int i = 0; i < nms_result.size(); i++) {
  88. int idx = nms_result[i];
  89. Output result;
  90. result.id = classIds[idx];
  91. result.confidence = confidences[idx];
  92. result.box = boxes[idx];
  93. output.push_back(result);
  94. }
  95. if (output.size())
  96. return true;
  97. else
  98. return false;
  99. }
  100. void Yolo::drawPred(Mat& img, vector<Output> result, vector<Scalar> color) {
  101. for (int i = 0; i < result.size(); i++) {
  102. int left, top;
  103. left = result[i].box.x;
  104. top = result[i].box.y;
  105. int color_num = i;
  106. rectangle(img, result[i].box, color[result[i].id], 2, 8);
  107. string label = className[result[i].id] + ":" + to_string(result[i].confidence);
  108. int baseLine;
  109. Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
  110. top = max(top, labelSize.height);
  111. //rectangle(frame, Point(left, top - int(1.5 * labelSize.height)), Point(left + int(1.5 * labelSize.width), top + baseLine), Scalar(0, 255, 0), FILLED);
  112. putText(img, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 2);
  113. }
  114. imshow("1", img);
  115. //imwrite("out.bmp", img);
  116. waitKey();
  117. //destroyAllWindows();
  118. }

源文件中 main.cpp调用

附上代码段:

  1. //#include "stdafx.h"
  2. #include "yolo.h"
  3. #include <iostream>
  4. #include<opencv2//opencv.hpp>
  5. #include<math.h>
  6. using namespace std;
  7. using namespace cv;
  8. using namespace dnn;
  9. int main()
  10. {
  11. //string img_path = "C:\\Users\\m\\source\\repos\\yolov5\\wifi_01.jpg";
  12. string img_path = "C:\\Users\\m\\source\\repos\\yolov5\\bus.jpg";
  13. //string model_path = "C:\\Users\\m\\source\\repos\\yolov5\\best.onnx";
  14. string model_path = "C:\\Users\\m\\source\\repos\\yolov5\\yolov5s.onnx";
  15. //int num_devices = cv::cuda::getCudaEnabledDeviceCount();
  16. //if (num_devices <= 0) {
  17. //cerr << "There is no cuda." << endl;
  18. //return -1;
  19. //}
  20. //else {
  21. //cout << num_devices << endl;
  22. //}
  23. Yolo test;
  24. Net net;
  25. if (test.readModel(net, model_path, false)) {
  26. cout << "read net ok!" << endl;
  27. }
  28. else {
  29. return -1;
  30. }
  31. //生成随机颜色
  32. vector<Scalar> color;
  33. srand(time(0));
  34. for (int i = 0; i < 80; i++) {
  35. int b = rand() % 256;
  36. int g = rand() % 256;
  37. int r = rand() % 256;
  38. color.push_back(Scalar(b, g, r));
  39. }
  40. vector<Output> result;
  41. Mat img = imread(img_path);
  42. if (test.Detect(img, net, result)) {
  43. test.drawPred(img, result, color);
  44. }
  45. else {
  46. cout << "Detect Failed!" << endl;
  47. }
  48. system("pause");
  49. return 0;
  50. }

加载完后重新生成

 去指定的文件中查找

双击yoloV5.exe

黑窗口提示read net ok 程序正常运行!!!

(这里用的是yoloV5S 轻量级的效果还是可以的,如果训练自己的数据集,特征不明显的话还是推荐比V5L或者更大参数量的V5X当然计算时间也会延长,最后根据自己模型的实际情况对置信度和非极大值抑制做出调整)

==============================2023.5.26更新=============================

增加视频流检测代码

main.cpp修改内容

  1. VideoCapture cap(0); //视频检测将0改成视频地址
  2. while (true) {
  3. Mat fram;
  4. cap >> fram;
  5. vector<Output> result;
  6. if (test.Detect(fram, net, result))
  7. test.drawPred(fram, result, color);
  8. imshow("detect output", fram);
  9. if (waitKey(2)==27) break; //esc退出
  10. }
  11. cap.release();
  12. destroyAllWindows();

 将drawPred()中的inshow和waitkey注释掉。

瑞思拜


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

闽ICP备14008679号