## 用opencv的dnn模块做yolov5目标检测

https://blog.csdn.net/qq_35135771/article/details/116592203
感谢大佬

导出U版YOLOv5的ONNX模型,openCV直接读取会失败,原因在于Pytorch2ONNX不支持对slice对象赋值,需要更改下列两个地方代码:

yolov5/models/common.py


  
  
  1. class Focus(nn.Module):
  2. # Focus wh information into c-space
  3. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
  4. super(Focus, self).__init__()
  5. self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
  6. # self.contract = Contract(gain=2)
  7. def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
  8. return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
  9. # return self.conv(self.contract(x))
  10. 更改为:
  11. class Focus(nn.Module):
  12. # Focus wh information into c-space
  13. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
  14. super(Focus, self).__init__()
  15. self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
  16. # self.contract = Contract(gain=2)
  17. def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
  18. # return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
  19. N, C, H, W = x.size() # assert (H / s == 0) and (W / s == 0), 'Indivisible gain'
  20. s = 2
  21. x = x.view(N, C, H // s, s, W // s, s) # x(1,64,40,2,40,2)
  22. x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40)
  23. y = x.view(N, C * s * s, H // s, W // s) # x(1,256,40,40)
  24. return self.conv(y)

具体原因可看博客2021.03.11更新 c++下使用opencv部署yolov5模型(一)_爱晚乏客游的博客-CSDN博客Pytorch转ONNX-实战篇2(实战踩坑总结) - 知乎,这里不做过多解释

yolov5/models/yolo.py


  
  
  1. class Detect(nn.Module):
  2. stride = None # strides computed during build
  3. export = False # onnx export
  4. def __init__( self, nc=80, anchors=(), ch=()): # detection layer
  5. super(Detect, self).__init__()
  6. self.nc = nc # number of classes
  7. self.no = nc + 5 # number of outputs per anchor
  8. self.nl = len(anchors) # number of detection layers
  9. self.na = len(anchors[ 0]) // 2 # number of anchors
  10. self.grid = [torch.zeros( 1)] * self.nl # init grid
  11. a = torch.tensor(anchors). float().view(self.nl, - 1, 2)
  12. self.register_buffer( 'anchors', a) # shape(nl,na,2)
  13. self.register_buffer( 'anchor_grid', a.clone().view(self.nl, 1, - 1, 1, 1, 2)) # shape(nl,1,na,1,1,2)
  14. self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv
  15. def forward( self, x):
  16. # x = x.copy() # for profiling
  17. z = [] # inference output
  18. self.training |= self.export
  19. for i in range(self.nl):
  20. x[i] = self.m[i](x[i]) # conv
  21. bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
  22. x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute( 0, 1, 3, 4, 2).contiguous()
  23. x[i] = x[i].view(bs * self.na * ny * nx, self.no).contiguous()
  24. return torch.cat(x)
  25. # if not self.training: # inference
  26. # if self.grid[i].shape[2:4] != x[i].shape[2:4]:
  27. # self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
  28. #
  29. # y = x[i].sigmoid()
  30. # y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i] # xy
  31. # y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
  32. # z.append(y.view(bs, -1, self.no))
  33. #
  34. # return x if self.training else (torch.cat(z, 1), x)

更改完成之后,使用netron查看导出的onnx模型,可发现上述操作把YOLO层的三个输出合并为一个输出,格式类似yolov4的格式。


  
  
  1. python
  2. import netron
  3. netron.start( "best.onnx")

接下来就是使用openCV调用导出的onnx,本文使用的openCV版本为4.5.1,使用4.4版本的可能会出现错误。代码参考了博客2021.09.02更新说明 c++下使用opencv部署yolov5模型 (三)_爱晚乏客游的博客-CSDN博客,我也是依葫芦画瓢

yolo5.h


  
  
  1. #pragma once
  2. #include <fstream>
  3. #include <sstream>
  4. #include <iostream>
  5. #include <opencv2/dnn.hpp>
  6. #include <opencv2/imgproc.hpp>
  7. #include <opencv2/highgui.hpp>
  8. #include<time.h>
  9. using namespace cv;
  10. using namespace dnn;
  11. using namespace std;
  12. struct Output {
  13. int id; //结果类别id
  14. float confidence; //结果置信度
  15. cv::Rect box; //矩形框
  16. };
  17. class YOLO
  18. {
  19. public:
  20. YOLO() {};
  21. bool readModel(Net &net, string &netPath, bool isCuda = false);
  22. bool Detect(cv::Mat &SrcImg, cv::dnn::Net &net, std::vector<Output> &output);
  23. void drawPred(cv::Mat &img, std::vector<Output> result, std::vector<cv::Scalar> color);
  24. private:
  25. //计算归一化函数
  26. float Sigmoid(float x) {
  27. return static_cast< float>( 1.f / ( 1.f + exp(-x)));
  28. }
  29. const float netAnchors[ 3][ 6] = { { 17.0, 22.0, 23.0, 32.0, 31.0, 44.0 },{ 40.0, 60.0, 51.0, 85.0, 68.0, 117.0 },{ 108.0, 93.0, 94.0, 168.0, 170.0, 331.0 } };
  30. const float netStride[ 3] = { 8.0, 16.0, 32.0 };
  31. const int netWidth = 608;
  32. const int netHeight = 608;
  33. float nmsThreshold = 0.2;
  34. float boxThreshold = 0.5;
  35. float classThreshold = 0.5;
  36. std::vector<std::string> className = { "head", "tail" };
  37. };

yolo5.cpp


  
  
  1. #include "yolo5.h"
  2. #include<iostream>
  3. #include<time.h>
  4. using namespace cv;
  5. using namespace std;
  6. bool YOLO::readModel(Net & net, string & netPath, bool isCuda)
  7. {
  8. try {
  9. net = readNetFromONNX(netPath);
  10. }
  11. catch ( const std::exception&) {
  12. return false;
  13. }
  14. //cuda
  15. if (isCuda) {
  16. net. setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
  17. net. setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
  18. }
  19. //cpu
  20. else {
  21. net. setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
  22. net. setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
  23. }
  24. return true;
  25. }
  26. bool YOLO::Detect(cv::Mat & SrcImg, cv::dnn::Net & net, std::vector<Output>& output)
  27. {
  28. Mat blob;
  29. int col = SrcImg.cols, row = SrcImg.rows;
  30. int maxLen = std:: max(col,row);
  31. Mat netInputImg = SrcImg. clone();
  32. if (maxLen > 1.2*col || maxLen > 1.2*row) { //纠正之前边框检测不准确的问题(2022-06-31)
  33. Mat resizeImg = Mat:: zeros(maxLen,maxLen,CV_8UC3);
  34. SrcImg. copyTo( resizeImg( Rect( 0, 0,col,row)));
  35. netInputImg = resizeImg. clone();
  36. }
  37. blobFromImage(SrcImg, blob, 1 / 255.0, cv:: Size(netWidth, netHeight), Scalar( 0, 0, 0), true, false);
  38. net. setInput(blob);
  39. vector<Mat> netOutputImg;
  40. net.forward(netOutputImg, net. getUnconnectedOutLayersNames());
  41. vector< int> classIds; //结果id数组
  42. vector< float> confidences; //结果每个id对应置信度数组
  43. vector<Rect> boxes; //每个id矩形框
  44. float ratio_h = ( float)netInputImg.rows / netHeight; //纠正
  45. float ratio_w = ( float)netInputImg.cols / netWidth; //纠正
  46. int net_width = className. size() + 5; //输出的网络宽度是类别数+5
  47. float* pdata = ( float*)netOutputImg[ 0].data;
  48. for ( int stride = 0; stride < 3; stride++) { //stride
  49. int grid_x = ( int)(netWidth / netStride[stride]);
  50. int grid_y = ( int)(netHeight / netStride[stride]);
  51. for ( int anchor = 0; anchor < 3; anchor++) { //anchors
  52. const float anchor_w = netAnchors[stride][anchor * 2];
  53. const float anchor_h = netAnchors[stride][anchor * 2 + 1];
  54. for ( int i = 0; i < grid_x; i++) {
  55. for ( int j = 0; j < grid_y; j++) {
  56. float box_score = Sigmoid(pdata[ 4]); //获取每一行的box框中含有某个物体的概率
  57. if (box_score > boxThreshold) {
  58. cv::Mat scores(1, className.size(), CV_32FC1, pdata + 5);
  59. Point classIdPoint;
  60. double max_class_socre;
  61. minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);
  62. max_class_socre = Sigmoid(( float)max_class_socre);
  63. if (max_class_socre > boxThreshold) {
  64. //rect [x,y,w,h]
  65. float x = ( Sigmoid(pdata[ 0]) * 2.f - 0.5f + j) * netStride[stride]; //x
  66. float y = ( Sigmoid(pdata[ 1]) * 2.f - 0.5f + i) * netStride[stride]; //y
  67. float w = powf( Sigmoid(pdata[ 2]) * 2.f, 2.f) * anchor_w; //w
  68. float h = powf( Sigmoid(pdata[ 3]) * 2.f, 2.f) * anchor_h; //h
  69. int left = (x - 0.5*w)*ratio_w;
  70. int top = (y - 0.5*h)*ratio_h;
  71. classIds. push_back(classIdPoint.x);
  72. confidences. push_back(max_class_socre);
  73. boxes. push_back( Rect(left, top, int(w*ratio_w), int(h*ratio_h)));
  74. }
  75. }
  76. pdata += net_width; //指针移到下一行
  77. }
  78. }
  79. }
  80. }
  81. vector< int> nms_result;
  82. NMSBoxes(boxes, confidences, classThreshold, nmsThreshold, nms_result);
  83. for ( int i = 0; i < nms_result. size(); i++) {
  84. int idx = nms_result[i];
  85. Output result;
  86. result.id = classIds[idx];
  87. result.confidence = confidences[idx];
  88. result.box = boxes[idx];
  89. output. push_back(result);
  90. }
  91. if (output. size())
  92. return true;
  93. else
  94. return false;
  95. }
  96. void YOLO::drawPred(Mat &img, vector<Output> result, vector<Scalar> color) {
  97. for ( int i = 0; i < result. size(); i++)
  98. {
  99. int left, top;
  100. left = result[i].box.x;
  101. top = result[i].box.y;
  102. int color_num = i;
  103. rectangle(img, result[i].box, color[result[i].id], 2, 8);
  104. string label = className[result[i].id] + ":" + to_string(result[i].confidence);
  105. int baseLine;
  106. Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
  107. top = max(top, labelSize.height);
  108. putText(img, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 1);
  109. }
  110. cv:: resize(img, img, cv:: Size( 1400, 800));
  111. imshow( "test", img);
  112. waitKey();
  113. }

main.cpp


  
  
  1. #include <opencv2/opencv.hpp>
  2. #include <opencv2/dnn.hpp>
  3. #include <highgui/highgui_c.h>
  4. #include <iostream>
  5. #include "yolo5.h"
  6. using namespace cv;
  7. using namespace cv::dnn;
  8. using namespace std;
  9. int main()
  10. {
  11. string img_path = "./test.jpg";
  12. string model_path = "./best.onnx";
  13. YOLO test;
  14. Net net;
  15. if (test. readModel(net, model_path, false)) {
  16. cout << "read net ok!" << endl;
  17. }
  18. else {
  19. return -1;
  20. }
  21. //生成随机颜色
  22. vector<Scalar> color;
  23. srand( time( 0));
  24. for ( int i = 0; i < 80; i++) {
  25. int b = rand() % 256;
  26. int g = rand() % 256;
  27. int r = rand() % 256;
  28. color. push_back( Scalar(b, g, r));
  29. }
  30. vector<Output> result;
  31. Mat img = imread(img_path);
  32. if (test. Detect(img, net, result)) {
  33. test. drawPred(img, result, color);
  34. }
  35. else {
  36. cout << "Detect Failed!" << endl;
  37. }
  38. system( "pause");
  39. return 0;
  40. }

我做了一个夜间车辆检测,下面贴出运行结果。感觉检测结果边框有点变形,回头再看看哪出错了。模型文件可在我的博客资源里下载yolov5夜间车辆检测模型-深度学习文档类资源-CSDN文库

GitHub 加速计划 / opencv31 / opencv
77.34 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:1 个月前 )
e1fec156 features2d: fixed out of bounds access in SIFT 15 天前
63087396 - 15 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐