当前位置:   article > 正文

YoloV8部署:使用opencv的dnn模块部署在Linux平台上_cv2.dnn yolov8

cv2.dnn yolov8

       前段时间做了Yolov5的部署,就想训练yolov8并且部署在Linux平台上,中间也踩了很多坑,花了两天时间终于部署完成了,在这里浅浅记录一下。

一、yolov8训练

        yolov8是yolov5的作者写的,源码:https://github.com/ultralytics/ultralytics,但是新版的改动较yolov5较大,因为集成化程度较高,所以无法通过yolov5那样直接修改参数实现,一般都通过终端执行yolo命令来部署训练。至于环境配置,如果你之前搭建过yolov5的环境,那么yolov8环境搭建起来就很简单啦,只需要卸载torch,然后更新到支持GPU推理的版本即可,如果你的cuda版本太低的话,需要卸载原有的cuda版本,对于环境安装,这里不做过多讲述。

        这里着重需要讲的是,对于opencv的dnn模块来说,目前还不支持动态的batch_size,所以在训练好了自己的网络模型后,在转onnx的时候需要注意以下几点:

1)关闭参数dynamic

        这个参数代表了需不需要或者输出图像的batch可变,模型的输入输出有时是可变,因此可通过该参数设置。但是opencv目前是不支持动态推理的,所以dynamic需要设置为false。如果不设置为false,在读取模型的时候会报错。

2)关闭参数do_constant folding

        因为你要训练yolov8,那么就需要高版本的torch,如果torch版本高于2.0以上的话,不关闭此参数,也存在opencv读取不通过的情况,具体设置为:在yolov8的ultralytics文件夹下,找到engine文件夹,点击exporter.py,然后Ctrl+F搜索变量do_constant folding,然后将其值改为False。如下:

3)设置opset =12

          将onnx的算子集版本设置为12,默认支持11,最好设置为12

讲完这几点之后,那么只需要在pytorch环境下的终端中输入如下代码:

yolo export model=path/to/model.pt format=onnx dynamic=False  opset=12

其中model就是你训练好的pth文件的路径,然后在终端执行即可,至此onnx的转换就完成啦。转换完成就可以在netron中查看一下自己的输出对不对,如下所示:

如果输入是1x3x640x640,输出是[1,84,8400](针对coco数据集)那就是对的, 因为yolov8的输出变了,将yolov5之前的置信度参数整合到了其他地方,因此这里无类别概率一项。至此转换为onnx就完成了,下一步就是基于opencv的推理了。

二、推理ONNX模型

(1)部署opencv

        又到了老生常谈的问题,是的,opencv4.7.0以下版本不支持yolov8推理,重要的事情说三遍:

opencv4.7.0以下版本不支持yolov8推理

opencv4.7.0以下版本不支持yolov8推理

opencv4.7.0以下版本不支持yolov8推理

但是,我的ubuntu上4.7.0版本也读取模型失败,因此我换了4.8.1之后,问题解决,为了大家省事,直接上4.8.1版本:安装和之前是一样的,如果还未安装,请移至本人博客: yolov5的推理部署中有详细介绍opencv的安装,也可以在网盘里自行下载提取码:wert

  (2)yolov8.h文件
  1. #ifndef YOLOV8_H
  2. #define YOLOV8_H
  3. #pragma once
  4. #include<iostream>
  5. #include<opencv2/opencv.hpp>
  6. #include "yolov8_utils.h"
  7. using namespace cv;
  8. using namespace cv::dnn;
  9. class Yolov8 {
  10. public:
  11. Yolov8() {
  12. }
  13. ~Yolov8() {}
  14. bool ReadModel(cv::dnn::Net& net, std::string& netPath, bool isCuda);
  15. bool Detect(cv::Mat& srcImg, cv::dnn::Net& net, std::vector<OutputParams>& output);
  16. void DrawPred(cv::Mat& img,
  17. std::vector<OutputParams> result,
  18. std::vector<std::string> classNames,
  19. std::vector<cv::Scalar> color,
  20. bool isVideo = false
  21. );
  22. void LetterBox(const cv::Mat& image, cv::Mat& outImage,
  23. cv::Vec4d& params, //[ratio_x,ratio_y,dw,dh]
  24. const cv::Size& newShape = cv::Size(640, 640),
  25. bool autoShape = false,
  26. bool scaleFill = false,
  27. bool scaleUp = true,
  28. int stride = 32,
  29. const cv::Scalar& color = cv::Scalar(114, 114, 114));
  30. int _netWidth = 640; //ONNX图片输入宽度
  31. int _netHeight = 640; //ONNX图片输入高度
  32. //类别名,自己的模型需要修改此项
  33. std::vector<std::string> _className = { "OK","NG"};
  34. private:
  35. float _classThreshold = 0.25;
  36. float _nmsThreshold = 0.45;
  37. };
  38. #endif // YOLOV8_H

代码中的类别名根据自己的网络来修改,其他均不变,类似于yolov5

(3)yolov8.cpp
  1. #include"yolov8.h"
  2. //using namespace std;
  3. //using namespace cv;
  4. //using namespace cv::dnn;
  5. bool Yolov8::ReadModel(cv::dnn::Net& net, std::string& netPath, bool isCuda = false) {
  6. try {
  7. net = cv::dnn::readNetFromONNX(netPath);
  8. #if CV_VERSION_MAJOR==4 &&CV_VERSION_MINOR==7&&CV_VERSION_REVISION==0
  9. net.enableWinograd(false); //bug of opencv4.7.x in AVX only platform ,https://github.com/opencv/opencv/pull/23112 and https://github.com/opencv/opencv/issues/23080
  10. //net.enableWinograd(true); //If your CPU supports AVX2, you can set it true to speed up
  11. #endif
  12. }
  13. catch (const std::exception&) {
  14. return false;
  15. }
  16. if (isCuda) {
  17. //cuda
  18. net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
  19. net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); //or DNN_TARGET_CUDA_FP16
  20. }
  21. else {
  22. //cpu
  23. std::cout << "Inference device: CPU" << std::endl;
  24. net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
  25. net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
  26. }
  27. return true;
  28. }
  29. void Yolov8::LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,
  30. bool autoShape, bool scaleFill, bool scaleUp, int stride, const cv::Scalar& color)
  31. {
  32. if (false) {
  33. int maxLen = MAX(image.rows, image.cols);
  34. outImage = cv::Mat::zeros(cv::Size(maxLen, maxLen), CV_8UC3);
  35. image.copyTo(outImage(cv::Rect(0, 0, image.cols, image.rows)));
  36. params[0] = 1;
  37. params[1] = 1;
  38. params[3] = 0;
  39. params[2] = 0;
  40. }
  41. cv::Size shape = image.size();
  42. float r = std::min((float)newShape.height / (float)shape.height,
  43. (float)newShape.width / (float)shape.width);
  44. if (!scaleUp)
  45. r = std::min(r, 1.0f);
  46. float ratio[2]{ r, r };
  47. int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };
  48. auto dw = (float)(newShape.width - new_un_pad[0]);
  49. auto dh = (float)(newShape.height - new_un_pad[1]);
  50. if (autoShape)
  51. {
  52. dw = (float)((int)dw % stride);
  53. dh = (float)((int)dh % stride);
  54. }
  55. else if (scaleFill)
  56. {
  57. dw = 0.0f;
  58. dh = 0.0f;
  59. new_un_pad[0] = newShape.width;
  60. new_un_pad[1] = newShape.height;
  61. ratio[0] = (float)newShape.width / (float)shape.width;
  62. ratio[1] = (float)newShape.height / (float)shape.height;
  63. }
  64. dw /= 2.0f;
  65. dh /= 2.0f;
  66. if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
  67. {
  68. cv::resize(image, outImage, cv::Size(new_un_pad[0], new_un_pad[1]));
  69. }
  70. else {
  71. outImage = image.clone();
  72. }
  73. int top = int(std::round(dh - 0.1f));
  74. int bottom = int(std::round(dh + 0.1f));
  75. int left = int(std::round(dw - 0.1f));
  76. int right = int(std::round(dw + 0.1f));
  77. params[0] = ratio[0];
  78. params[1] = ratio[1];
  79. params[2] = left;
  80. params[3] = top;
  81. cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color);
  82. }
  83. bool Yolov8::Detect(cv::Mat& srcImg, cv::dnn::Net& net, std::vector<OutputParams>& output) {
  84. cv::Mat blob;
  85. output.clear();
  86. int col = srcImg.cols;
  87. int row = srcImg.rows;
  88. cv::Mat netInputImg;
  89. cv::Vec4d params;
  90. LetterBox(srcImg, netInputImg, params, cv::Size(_netWidth, _netHeight));
  91. cv::dnn::blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(0, 0, 0), true, false);
  92. //**************************************************************************************************************************************************/
  93. //如果在其他设置没有问题的情况下但是结果偏差很大,可以尝试下用下面两句语句
  94. // If there is no problem with other settings, but results are a lot different from Python-onnx , you can try to use the following two sentences
  95. //
  96. //$ cv::dnn::blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(104, 117, 123), true, false);
  97. //$ cv::dnn::blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(114, 114,114), true, false);
  98. //****************************************************************************************************************************************************/
  99. net.setInput(blob);
  100. std::vector<cv::Mat> net_output_img;
  101. net.enableWinograd(false);
  102. net.forward(net_output_img, net.getUnconnectedOutLayersNames()); //get outputs
  103. //net.forward(net_output_img, "output0"); //获取output的输出
  104. std::vector<int> class_ids;// res-class_id
  105. std::vector<float> confidences;// res-conf
  106. std::vector<cv::Rect> boxes;// res-box
  107. cv::Mat output0=cv::Mat( cv::Size(net_output_img[0].size[2], net_output_img[0].size[1]), CV_32F, (float*)net_output_img[0].data).t(); //[bs,116,8400]=>[bs,8400,116]
  108. int net_width = output0.cols;
  109. int rows = output0.rows;
  110. int socre_array_length = net_width - 4;
  111. float* pdata = (float*)output0.data;
  112. for (int r = 0; r < rows; ++r) {
  113. cv::Mat scores(1, socre_array_length, CV_32FC1, pdata + 4);
  114. cv::Point classIdPoint;
  115. double max_class_socre;
  116. minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);
  117. max_class_socre = (float)max_class_socre;
  118. if (max_class_socre >= _classThreshold) {
  119. //rect [x,y,w,h]
  120. float x = (pdata[0] - params[2]) / params[0];
  121. float y = (pdata[1] - params[3]) / params[1];
  122. float w = pdata[2] / params[0];
  123. float h = pdata[3] / params[1];
  124. int left = MAX(int(x - 0.5 * w + 0.5), 0);
  125. int top = MAX(int(y - 0.5 * h + 0.5), 0);
  126. class_ids.push_back(classIdPoint.x);
  127. confidences.push_back(max_class_socre);
  128. boxes.push_back(cv::Rect(left, top, int(w + 0.5), int(h + 0.5)));
  129. }
  130. pdata += net_width;//next line
  131. }
  132. //NMS
  133. std::vector<int> nms_result;
  134. cv::dnn::NMSBoxes(boxes, confidences, _classThreshold, _nmsThreshold, nms_result);
  135. std::vector<std::vector<float>> temp_mask_proposals;
  136. cv::Rect holeImgRect(0, 0, srcImg.cols, srcImg.rows);
  137. for (int i = 0; i < nms_result.size(); ++i) {
  138. int idx = nms_result[i];
  139. OutputParams result;
  140. result.id = class_ids[idx];
  141. result.confidence = confidences[idx];
  142. result.box = boxes[idx] & holeImgRect;
  143. output.push_back(result);
  144. }
  145. if (output.size())
  146. return true;
  147. else
  148. return false;
  149. }
  150. void Yolov8::DrawPred(cv::Mat& img, std::vector<OutputParams> result, std::vector<std::string> classNames, std::vector<cv::Scalar> color, bool isVideo) {
  151. cv::Mat mask = img.clone();
  152. for (int i = 0; i < result.size(); i++) {
  153. int left=0, top=0;
  154. int color_num = i;
  155. if (result[i].box.area() > 0) {
  156. rectangle(img, result[i].box, color[result[i].id], 2, 8);
  157. left = result[i].box.x;
  158. top = result[i].box.y;
  159. }
  160. if (result[i].rotatedBox.size.width * result[i].rotatedBox.size.height > 0) {
  161. DrawRotatedBox(img, result[i].rotatedBox, color[result[i].id], 2);
  162. left = result[i].rotatedBox.center.x;
  163. top = result[i].rotatedBox.center.y;
  164. }
  165. if (result[i].boxMask.rows && result[i].boxMask.cols > 0)
  166. mask(result[i].box).setTo(color[result[i].id], result[i].boxMask);
  167. std::string label = classNames[result[i].id] + ":" + std::to_string(result[i].confidence);
  168. int baseLine;
  169. cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
  170. top = MAX(top, labelSize.height);
  171. //rectangle(frame, cv::Point(left, top - int(1.5 * labelSize.height)), cv::Point(left + int(1.5 * labelSize.width), top + baseLine), cv::Scalar(0, 255, 0), FILLED);
  172. putText(img, label, cv::Point(left, top), cv::FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 2);
  173. }
  174. cv::addWeighted(img, 0.5, mask, 0.5, 0, img); //add mask to src
  175. cv::imshow("1", img);
  176. if (!isVideo)
  177. cv::waitKey();
  178. //destroyAllWindows();
  179. }

如果模型加载成功的话,那么运行代码是完全没有问题的,如果报错,那就是你的模型问题,没有按照第一节的设置来弄。

(4)main函数

main函数中就很简单了,只需要先实例化一个yolov8的对象,然后先调用detect函数,在调用drawPred函数,最后就可以看到预测结果了,当然作者是基于qt开发的,不局限于平台,都可以的。

  1. int main()
  2. {
  3. Yolov8 yolov8;
  4. Net net;
  5. std::string model_path = "./models/yolov8n.onnx"; //onnx的路径,填绝对路径
  6. if (yolov8.ReadModel(net, model_path, false)) {
  7. std::cout << "read net ok!" << std::endl;
  8. }
  9. else {
  10. return -1;
  11. }
  12. //生成随机颜色
  13. std::vector<cv::Scalar> color;
  14. srand(time(0));
  15. for (int i = 0; i < 80; i++) {
  16. int b = rand() % 256;
  17. int g = rand() % 256;
  18. int r = rand() % 256;
  19. color.push_back(cv::Scalar(b, g, r));
  20. }
  21. std::vector<OutputParams> result;
  22. if (yolov8.Detect(img, net, result)) {
  23. DrawPred(img, result, yolov8._className, color);
  24. }
  25. else {
  26. std::cout << "Detect Failed!" << std::endl;
  27. }
  28. system("pause");
  29. return 0;
  30. }

部署到这里就完成啦,其实大家如果之前部署过yolov5的话,对于yolov8变化不大,部署起来也很简单,希望能帮到大家,写的比较仓促,错误之处希望大家及时批评指正!!! 

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

闽ICP备14008679号