• yolov7模型部署——代码实现(python版和c#版)


    忙了两个星期,终于把c#版onnx调用整合到项目中,并和UI功能结合起来了~~~也终于腾出时间来总结一下,都快忘记踩过什么坑了T_T。

    一,python版

            python版比较容易,毕竟有官方的detect.py指导,据说之前官方放了个使用onnx推理的ipynb文件,但很快就删了~~~参考这篇博客。通过模型结构图查看模型输入要求尺寸为[1 3 640 640]。

    onnx调用的基本思想步骤就是:

    1. 根据EP建立session:
      1. providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider']
      2. session = ort.InferenceSession(w, providers=providers)
    2. 调用session.run:
      outputs = session.run(outname, inp)[0]
    3. 解析并可视化结果

    具体实现步骤为:

    • Resize原始图片,处理成onnx模型需要的大小,这个过程是通过letterbox()函数实现的。公主的模型输入尺寸为[640, 640]。根据官方提供的思想resize的时候需要保持原始图片的长宽比。因此需要首先计算与所需尺寸相差的最小ratio(即根据原始长度长,宽较长一边计算出的ratio),将图片resize之后,剩下的空隙用灰色来填充。公主输入图片原始尺寸为[512, 555, 3],转换之后为[640, 640, 3],如下图所示:

    • 将图像转为RGB格式,并增加一维以符合输入尺寸,通过以下命令实现
      image = image.transpose((2, 0, 1))
      image = np.expand_dims(image, 0)
      
    • 调用onnx模型
    • 可视化输出结果,输出是个二维n*7数组,n代表检测结果数,每一条结果的7个值分别代表:[batch_id, bbox左上角横坐标,bbox左上角纵坐标,bbox右下角横坐标,bbox右下角纵坐标,classIndex,置信度]。这里可视化bbox时注意,得出的bbox坐标为resize之后的图像中坐标,需要根据resize时用到的ratio和padding数来还原出原始坐标,才可以在原始图像中显示出来。

    python代码:

    1. import cv2
    2. import time
    3. import requests
    4. import random
    5. import numpy as np
    6. import onnxruntime as ort
    7. import onnx
    8. import torch
    9. # import torchvision
    10. # from PIL import Image
    11. # from pathlib import Path
    12. # from collections import OrderedDict, namedtuple
    13. cuda = torch.cuda.is_available()
    14. w = "bestE2E.onnx" # 模型名称
    15. # 判断模型是否有效,这步可以省略
    16. onnx_model = onnx.load(w)
    17. try:
    18. onnx.checker.check_model(onnx_model)
    19. except Exception:
    20. print("incorrect model")
    21. exit()
    22. else:
    23. print("correct model")
    24. # 创建session
    25. providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider']
    26. session = ort.InferenceSession(w, providers=providers)
    27. # Resize原始图像,预处理
    28. def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleup=True, stride=32):
    29. # Resize and pad image while meeting stride-multiple constraints
    30. shape = im.shape[:2] # current shape [height, width]
    31. if isinstance(new_shape, int):
    32. new_shape = (new_shape, new_shape)
    33. # Scale ratio (new / old)
    34. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    35. if not scaleup: # only scale down, do not scale up (for better val mAP)
    36. r = min(r, 1.0)
    37. # Compute padding
    38. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    39. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
    40. if auto: # minimum rectangle
    41. dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
    42. dw /= 2 # divide padding into 2 sides
    43. dh /= 2
    44. print(shape[::-1])
    45. if shape[::-1] != new_unpad: # resize
    46. im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    47. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    48. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    49. im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
    50. return im, r, (dw, dh)
    51. # 类别名称,根据需要自己修改
    52. names = ['bubbles']
    53. colors = {name: [random.randint(0, 255) for _ in range(3)] for i, name in enumerate(names)}
    54. # 读入待检测图像
    55. img_path = r"D:/YOLOV7/img/test.bmp"
    56. img = cv2.imread(img_path)
    57. # BGR to RGB,并增加一维
    58. image = img.copy()
    59. image, ratio, dwdh = letterbox(image, auto=False)
    60. image = image.transpose((2, 0, 1))
    61. image = np.expand_dims(image, 0)
    62. image = np.ascontiguousarray(image)
    63. # 归一化
    64. im = image.astype(np.float32)
    65. im /= 255
    66. outname = [i.name for i in session.get_outputs()]
    67. inname = [i.name for i in session.get_inputs()]
    68. # 调用模型得出结果,并记录时间
    69. inp = {inname[0]: im}
    70. t1 = time.time()
    71. outputs = session.run(outname, inp)[0]
    72. print('inference time :%.4f' % (time.time() - t1))
    73. ori_images = [img.copy()]
    74. # 可视化结果
    75. for i, (batch_id, x0, y0, x1, y1, cls_id, score) in enumerate(outputs):
    76. image = ori_images[int(batch_id)]
    77. box = np.array([x0, y0, x1, y1])
    78. box -= np.array(dwdh * 2)
    79. box /= ratio
    80. box = box.round().astype(np.int32).tolist()
    81. cls_id = int(cls_id)
    82. score = round(float(score), 3)
    83. name = names[cls_id]
    84. color = colors[name]
    85. name += ' ' + str(score)
    86. cv2.rectangle(image, box[:2], box[2:], color, 2)
    87. cv2.putText(image, name, (box[0], box[1] - 2), cv2.FONT_HERSHEY_SIMPLEX, 0.75, [225, 255, 255], thickness=2)
    88. cv2.imshow('dddd', ori_images[0])
    89. cv2.waitKey(0)
    90. cv2.destroyAllWindows()

    结果显示:

    二,C# 版

     公主做的demo界面如下:

     步骤与python版一模一样,公主一句一句翻译过来的。中间遇到过很多坑,但事情太多忘记一条一条记录了,有问题随时欢迎评论区讨论,完整代码如下:

    1. private void btn_analyze_Click(object sender, EventArgs e){
    2. // 原始图像切割为3份,分成1024*1024
    3. Mat img_img = this.img_mat;
    4. Mat[] subImg = new Mat[3];
    5. int w = Width / 3;
    6. subImg[0] = img_img[0, 1024, startX, startX + w];
    7. subImg[1] = img_img[0, 1024, startX + w, startX + w * 2];
    8. subImg[2] = img_img[0, 1024, startX + w * 2, startX + rect.Width - edge * 2];
    9. // pictureBox对象“们”存为数组
    10. PictureBox[] picbox = new PictureBox[3] { this.pictureBox2, this.pictureBox3, this.pictureBox4 };
    11. for(int i = 0; i < 3; i++)
    12. {
    13. int top = 0, left = 0;
    14. double ratio = 0.0;
    15. // after resize and padding
    16. Mat img_copy = new Mat();
    17. subImg[i].CopyTo(img_copy);
    18. // letterbox实现Resize图像
    19. Mat imageRgb = letterbox(img_copy, imgSize, out top, out left, out ratio);
    20. // 将Mat格式图像转化为onnx模型需要的tensor张量
    21. Bitmap imageBmp = BitmapConverter.ToBitmap(imageRgb);
    22. Tensor<float> input = FastToOnnxTensor(imageBmp);
    23. var inputs = new List
    24. {
    25. NamedOnnxValue.CreateFromTensor("images", input)
    26. };
    27. // 调用onnx模型,保存结果
    28. IDisposableReadOnlyCollection results = session.Run(inputs);
    29. // 图像后处理,并可视化
    30. var resultsArray = results.ToArray()[0];
    31. Tensor<float> tensors = resultsArray.AsTensor<float>();
    32. var array = tensors.ToArray();
    33. Mat newMat = new Mat();
    34. Cv2.CvtColor(subImg[i], newMat, ColorConversionCodes.GRAY2RGB);
    35. if (array.Length > 0)
    36. {
    37. for (int j = 0; j < tensors.Dimensions[0]; j++)
    38. {
    39. int startIndex = j * tensors.Dimensions[1];
    40. Scalar color = new Scalar(127, 255, 0); // 灰度颜色
    41. int lefttop_x = (int)((array[startIndex + 1] - left) / ratio);
    42. int lefttop_y = (int)((array[startIndex + 2] - top) / ratio);
    43. int rightbottom_x = (int)((array[startIndex + 3] - left) / ratio);
    44. int rightbottom_y = (int)((array[startIndex + 4] - top) / ratio);
    45. Cv2.Rectangle(newMat, new OpenCvSharp.Point(lefttop_x, lefttop_y),
    46. new OpenCvSharp.Point(rightbottom_x, rightbottom_y),
    47. color, 2);
    48. Cv2.PutText(newMat, LabelMap.Labels[(int)array[startIndex + 5]],
    49. new OpenCvSharp.Point(lefttop_x, lefttop_y - 5),
    50. HersheyFonts.HersheySimplex, 0.75, color, 2);
    51. }
    52. }
    53. picbox[i].Image = BitmapConverter.ToBitmap(newMat);
    54. }
    55. }
    56. // Load表格时创建session
    57. private void OnnxDemo_Load(object sender, EventArgs e){
    58. options = SessionOptions.MakeSessionOptionWithCudaProvider(0);
    59. session = new InferenceSession(path, options);
    60. }
    61. // letterbox预处理图像
    62. private Mat letterbox(Mat subImg, int newSize, out int top, out int left, out double ratio)
    63. {
    64. int width = subImg.Cols;
    65. int height = subImg.Rows;
    66. ratio = Math.Min(((double)newSize / (double)width), ((double)newSize / (double)height));
    67. int[] new_unpad = { (int)(Math.Round(height * ratio)), (int)(Math.Round(width * ratio)) };
    68. int[] dwh = { (newSize - new_unpad[0]) / 2, (newSize - new_unpad[1]) / 2 };
    69. if (dwh != new_unpad)
    70. {
    71. Cv2.Resize(subImg, subImg, new OpenCvSharp.Size(new_unpad[1], new_unpad[0]));
    72. }
    73. top = (int)(Math.Round(dwh[0] - 0.1));
    74. int bottom = newSize - top - new_unpad[0];
    75. left = (int)(Math.Round(dwh[1] - 0.1));
    76. int right = newSize - left - new_unpad[1];
    77. Cv2.CopyMakeBorder(subImg, subImg, top, bottom, left, right, BorderTypes.Constant, Scalar.Gray);
    78. Cv2.CvtColor(subImg, subImg, ColorConversionCodes.GRAY2RGB); //Gray to RGB
    79. return subImg;
    80. }
    81. // Bitmap格式转化为Tensor张量
    82. public Tensor<float> FastToOnnxTensor(Bitmap source)
    83. {
    84. var floatArray = new float[source.Width * source.Height * 3];
    85. var bitmap_data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
    86. var bitmap_bytes = new byte[Math.Abs(bitmap_data.Stride) * source.Height];
    87. Marshal.Copy(bitmap_data.Scan0, bitmap_bytes, 0, bitmap_bytes.Length);
    88. int total_pixels_count = source.Width * source.Height;
    89. Parallel.For(0, total_pixels_count, (p_idx, state) =>
    90. {
    91. var g_idx = p_idx + total_pixels_count;
    92. var b_idx = p_idx + total_pixels_count * 2;
    93. floatArray[p_idx] = bitmap_bytes[p_idx * 3] / 255f;//R
    94. floatArray[g_idx] = bitmap_bytes[p_idx * 3 + 1] / 255f;//G
    95. floatArray[b_idx] = bitmap_bytes[p_idx * 3 + 2] / 255f;//B
    96. });
    97. source.UnlockBits(bitmap_data);
    98. return new DenseTensor<float>(new Memory<float>(floatArray), new int[] { 1, 3, source.Height, source.Width });
    99. }

    静态变量:

    1. // 静态变量,用来保存类别名称
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Text;
    6. using System.Threading.Tasks;
    7. namespace OnnxDemo
    8. {
    9. public static class LabelMap
    10. {
    11. static LabelMap()
    12. {
    13. Labels = new string[]
    14. {
    15. "bubble"
    16. };
    17. }
    18. public static string[] Labels { set; get; }
    19. }
    20. }

    运行结果:

     Nice~Nice~Nice~~~公主对结果还是灰常满意哒!!!

  • 相关阅读:
    【Pygame实战】怀旧经典—这款给娃的棋类游戏,你还记得叫什么吧?(一定要收藏)
    【数学】leetcode 119. 杨辉三角 II
    Kotlin的标准函数和静态方法
    springboot + activiti实现activiti微服务化
    采用QTest进行数据集测试-性能测试-GUI测试
    计算机毕业设计选什么题目好?springboot 研究生管理系统
    Springboot打包部署到linux服务器的方法
    GIS与人工智能应用结合举例
    网工老大难经典提问之“IE要到期如何续证?”关于思科CCIE【重认证】常用方式,这次请务必保留好!
    transformer原理及各领域应用分析
  • 原文地址:https://blog.csdn.net/qing2019/article/details/127864904