当前位置:   article > 正文

YOLO v5 前后处理GPU加速部署_yolov5 gpu 后处理 batch

yolov5 gpu 后处理 batch

        深度学习模型部署中复杂的前后处理往往部署cpu中,这一方面极大地增加模型处理耗时,另一方面挤兑了cpu计算资源,导致整个软件平台卡死,本博客针对C#编写界面软件调用C++模型接口情形,分别从C#与C++间图片传输优化、前处理GPU加速、后处理GPU加速三个角度进行优化。

测试服务器:window10、Intel(R) Xeon(R) Gold 5115 CPU @ 2.40GHz、RTX2080 Ti

项目工程可以从github或者gitee上克隆下载:

  1. git clone https://github.com/fyf2022/yolov5_inference.git
  2. git clone https://gitee.com/fyf2022/yolov5_inference.git

1、C#与C++间图片传输优化

方式1:该项目利用数组将C#读取的图片传递给C++,避免编解码方式优化传递速度。

  1. Mat image = new Mat(img_path);
  2. int image_Cols = image.Cols;
  3. int image_Rows = image.Rows;
  4. var image_data = new byte[image.Total() * 3];//这里必须乘以通道数,不然数组越界,也可以用w*h*c
  5. Marshal.Copy(image.Data, image_data1, 0, image_data.Length);

方式2:通常的编解码传输数据到C++接口中的方式耗时:

  1. // 配置图片数据
  2. Mat image = new Mat(image_path);
  3. int max_image_length = image.Cols > image.Rows ? image.Cols : image.Rows;
  4. Mat max_image = Mat.Zeros(new Size(max_image_length, max_image_length), MatType.CV_8UC3);
  5. Rect roi = new Rect(0, 0, image.Cols, image.Rows);
  6. image.CopyTo(new Mat(max_image, roi));
  7. byte[] image_data = new byte[2048 * 2048 * 3];
  8. //存储byte的长度
  9. ulong image_size = new ulong();
  10. image_data = max_image.ImEncode(".bmp");
  11. image_size = Convert.ToUInt64(image_data.Length);

以5472*3648*3的高维图片进行测试,方式1耗时仅为64.4ms,而方式2需要440ms。

2、前处理GPU加速

本项目分别实现cpu、gpu版本前处理,C++核心代码如下:

  1. if (BN_means == 0)
  2. {
  3. std::vector<float> input_data(node_data_length);
  4. // 将图像归一化,并放缩到指定大小
  5. input_image = cv::dnn::blobFromImage(input_image, 1 / 255.0, node_shape, cv::Scalar(0, 0, 0), true, false);
  6. // 将图片数据copy到输入流中
  7. memcpy(input_data.data(), input_image.ptr<float>(), node_data_length * sizeof(float));
  8. // 创建cuda流
  9. cudaStreamCreate(&p->stream);
  10. // 将输入数据由内存到GPU显存
  11. cudaMemcpyAsync(p->data_buffer[node_index], input_data.data(), node_data_length * sizeof(float), cudaMemcpyHostToDevice, p->stream);
  12. std::vector<float>().swap(input_data);
  13. input_image.release();
  14. }
  15. else if (BN_means == 1)
  16. {
  17. std::vector<float> input_data(node_data_length);
  18. cv::cvtColor(input_image, input_image, cv::COLOR_BGR2RGB); // 将图片通道由 BGR 转为 RGB
  19. // 对输入图片按照tensor输入要求进行缩放
  20. cv::resize(input_image, input_image, node_shape, 0, 0, cv::INTER_CUBIC);
  21. // 图像数据归一化
  22. input_image.convertTo(input_image, CV_32FC3, 1.0f / 255.0);
  23. std::vector<cv::Mat> input_channels(3);
  24. cv::split(input_image, input_channels);
  25. //std::vector<float> input_data(node_dim.d[2] * node_dim.d[3] * 3);
  26. auto data = input_data.data();
  27. int channelLength = INPUT_W * INPUT_H;
  28. for (int i = 0; i < 3; ++i)
  29. {
  30. memcpy(data, input_channels[i].data, channelLength * sizeof(float));
  31. data += channelLength;
  32. }
  33. // 创建cuda流
  34. cudaStreamCreate(&p->stream);
  35. // 将输入数据由内存到GPU显存
  36. cudaMemcpyAsync(p->data_buffer[node_index], input_data.data(), node_data_length * sizeof(float), cudaMemcpyHostToDevice, p->stream);
  37. std::vector<float>().swap(input_data);
  38. input_image.release();
  39. }
  40. else
  41. {
  42. Npp8u *gpu_img_resize_buf;
  43. Npp32f *gpu_data_buf;
  44. Npp32f *gpu_data_planes;
  45. NppiSize dstSize = { INPUT_W, INPUT_H };;
  46. NppiRect dstROI = { 0, 0, INPUT_W, INPUT_H };;
  47. Npp32f m_scale[3] = { 0.00392157, 0.00392157, 0.00392157 };
  48. int aDstOrder[3] = { 2, 1, 0 };
  49. CHECK(cudaMalloc(&gpu_img_resize_buf, INPUT_W * INPUT_H * INPUT_C * sizeof(uchar)));
  50. CHECK(cudaMalloc(&gpu_data_buf, INPUT_W * INPUT_H * INPUT_C * sizeof(float)));
  51. CHECK(cudaMalloc(&gpu_data_planes, INPUT_W * INPUT_H * INPUT_C * sizeof(float)));
  52. NppiSize srcSize = { img_w, img_h };
  53. NppiRect srcROI = { 0, 0, img_w, img_h };
  54. Npp8u *gpu_img_buf;
  55. CHECK(cudaMalloc(&gpu_img_buf, img_w * img_h * img_c * sizeof(uchar)));
  56. Npp32f* dst_planes[3] = { gpu_data_planes, gpu_data_planes + INPUT_W * INPUT_H, gpu_data_planes + INPUT_W * INPUT_H * 2 };
  57. cudaMemcpy(gpu_img_buf, img_data, img_w*img_h * img_c, cudaMemcpyHostToDevice);
  58. nppiResize_8u_C3R(gpu_img_buf, img_w * img_c, srcSize, srcROI, gpu_img_resize_buf, INPUT_W * INPUT_C, dstSize, dstROI, NPPI_INTER_LINEAR);
  59. nppiSwapChannels_8u_C3IR(gpu_img_resize_buf, INPUT_W * INPUT_C, dstSize, aDstOrder);
  60. nppiConvert_8u32f_C3R(gpu_img_resize_buf, INPUT_W * INPUT_C, gpu_data_buf, INPUT_W * INPUT_C * sizeof(float), dstSize);
  61. nppiMulC_32f_C3IR(m_scale, gpu_data_buf, INPUT_W * INPUT_C * sizeof(float), dstSize);
  62. nppiCopy_32f_C3P3R(gpu_data_buf, INPUT_W * INPUT_C * sizeof(float), dst_planes, INPUT_W * sizeof(float), dstSize);
  63. cudaStreamCreate(&p->stream);
  64. // 将输入数据由内存到GPU显存
  65. cudaMemcpyAsync(p->data_buffer[node_index], gpu_data_planes, INPUT_C * INPUT_W * INPUT_H * sizeof(float), cudaMemcpyDeviceToDevice, p->stream);
  66. CHECK(cudaFree(gpu_img_buf));
  67. CHECK(cudaFree(gpu_img_resize_buf));
  68. CHECK(cudaFree(gpu_data_buf));
  69. CHECK(cudaFree(gpu_data_planes));
  70. }

该项目提供了C#调用C++不同版本前处理的接口,BNFlag.Normal来调用cv::dnn::blobFromImage的cpu前处理操作,BNFlag.fyf_cpu来调用自己实现的cpu版本C++前处理,MMSFlag.fyf_gpu来调用gpu版本前处理。

  1. nvinfer.load_image_data(input_node_name, image_data, image_Cols, image_Rows, BNFlag.Normal);
  2. nvinfer.load_image_data(input_node_name, image_data, image_Cols, image_Rows, BNFlag.fyf_cpu);
  3. nvinfer.load_image_data(input_node_name, image_data, image_Cols, image_Rows, BNFlag.fyf_gpu);

下表给出了前处理耗时对比,从测试结果看gpu前处理优势明显,且在模型输入越大优势越明显:

前处理耗时对比(ms)
BNFlag.NormalBNFlag.fyf_cpuBNFlag.fyf_gpu
输入 640*64010123
输入3072*307222022032

3、后处理GPU加速

本项目分别实现cpu、gpu版本后处理,C++核心代码如下:

  1. if (NMS_means==0)
  2. {
  3. // 读取输出数据
  4. // 创建输出数据
  5. float* result = new float[node_data_length];
  6. // 将输出数据由GPU显存到内存
  7. const clock_t t0 = clock();
  8. cudaMemcpyAsync(result, p->data_buffer[node_index], node_data_length * sizeof(float), cudaMemcpyDeviceToHost, p->stream);
  9. cv::Mat det_output = cv::Mat(feature_dim, class_num, CV_32F, result);
  10. post-process
  11. std::vector<cv::Rect> position_boxes;
  12. std::vector<int> classIds;
  13. std::vector<float> confidences;
  14. for (int i = 0; i < det_output.rows; i++) {
  15. float confidence = det_output.at<float>(i, 4);
  16. if (confidence < 0.2) {
  17. continue;
  18. }
  19. cv::Mat classes_scores = det_output.row(i).colRange(5, class_num);
  20. cv::Point classIdPoint;
  21. double score;
  22. // 获取一组数据中最大值及其位置
  23. minMaxLoc(classes_scores, 0, &score, 0, &classIdPoint);
  24. // 置信度 0~1之间
  25. if (score > 0.25)
  26. {
  27. float cx = det_output.at<float>(i, 0);
  28. float cy = det_output.at<float>(i, 1);
  29. float ow = det_output.at<float>(i, 2);
  30. float oh = det_output.at<float>(i, 3);
  31. int x = static_cast<int>(cx - 0.5 * ow);
  32. int y = static_cast<int>(cy - 0.5 * oh);
  33. int width = static_cast<int>(ow);
  34. int height = static_cast<int>(oh);
  35. cv::Rect box;
  36. box.x = x;
  37. box.y = y;
  38. box.width = width;
  39. box.height = height;
  40. position_boxes.push_back(box);
  41. classIds.push_back(classIdPoint.x);
  42. confidences.push_back(score);
  43. }
  44. }
  45. // NMS
  46. std::vector<int> indexes;
  47. cv::dnn::NMSBoxes(position_boxes, confidences, 0.25, 0.45, indexes);
  48. for (size_t i = 0; i < indexes.size(); i++) {
  49. int index = indexes[i];
  50. *output_result = classIds[index];
  51. output_result++;
  52. *output_result = confidences[index] * 1000;
  53. output_result++;
  54. *output_result = position_boxes[index].tl().x;
  55. output_result++;
  56. *output_result = position_boxes[index].tl().y;
  57. output_result++;
  58. *output_result = position_boxes[index].width;
  59. output_result++;
  60. *output_result = position_boxes[index].height;
  61. output_result++;
  62. }
  63. delete[] result;
  64. std::vector<cv::Rect>().swap(position_boxes);
  65. std::vector<int>().swap(classIds);
  66. std::vector<float>().swap(confidences);
  67. }
  68. else
  69. {
  70. float confidence_threshold = 0.25f;
  71. float nms_threshold = 0.45f;
  72. vector<Box> box_result;
  73. cudaStream_t stream = nullptr;
  74. checkRuntime(cudaStreamCreate(&stream));
  75. float* output_device = nullptr;
  76. float* output_host = nullptr;
  77. int max_objects = 1000;
  78. int NUM_BOX_ELEMENT = 7; // left, top, right, bottom, confidence, class, keepflag
  79. checkRuntime(cudaMalloc(&output_device, sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float)));
  80. //output_host = (float*)malloc(sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float));
  81. //memset(output_host, 0, sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float));
  82. checkRuntime(cudaMallocHost(&output_host, sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float)));
  83. //checkRuntime(cudaStreamSynchronize(stream));
  84. cudaDeviceSynchronize();
  85. decode_kernel_invoker(
  86. (float *)p->data_buffer[node_index], feature_dim, class_num - 5, confidence_threshold,
  87. nms_threshold, nullptr, output_device, max_objects, NUM_BOX_ELEMENT, stream
  88. );
  89. checkRuntime(cudaMemcpy(output_host, output_device,
  90. sizeof(int) + max_objects * NUM_BOX_ELEMENT * sizeof(float),
  91. cudaMemcpyDeviceToHost));
  92. int num_boxes = min((int)output_host[0], max_objects);
  93. for (int i = 0; i < num_boxes; ++i) {
  94. float* ptr = output_host + 1 + NUM_BOX_ELEMENT * i;
  95. int keep_flag = ptr[6];
  96. if (keep_flag) {
  97. *output_result = (int)ptr[5];
  98. output_result++;
  99. *output_result = (int)(ptr[4] * 1000);
  100. output_result++;
  101. *output_result = (int)ptr[0];
  102. output_result++;
  103. *output_result = (int)ptr[1];
  104. output_result++;
  105. *output_result = (int)(ptr[2] - ptr[0]);
  106. output_result++;
  107. *output_result = (int)(ptr[3] - ptr[1]);
  108. output_result++;
  109. // left, top, right, bottom, confidence, class, keepflag
  110. }
  111. }
  112. /*
  113. checkRuntime(cudaStreamDestroy(stream));
  114. //checkRuntime(cudaFree(output_device));
  115. //free(output_host);
  116. checkRuntime(cudaFreeHost(output_host));
  117. */
  118. }

 该项目提供了C#调用C++不同版本前处理的接口,MMSFlag.fyf_cpu来调用cpu版本C++后处理,MMSFlag.fyf_gpu来调用gpu版本后处理

  1. int[] result_array = nvinfer.read_infer_result(output_node_name, MMSFlag.fyf_cpu);
  2. int[] result_array = nvinfer.read_infer_result(output_node_name, MMSFlag.fyf_gpu);

下表给出了后处理耗时对比,从测试结果看gpu后处理优势明显,且在检测目标越多优势越明显:

后处理耗时对比(ms)
MMSFlag.fyf_cpuMMSFlag.fyf_gpu
输入 640*64050.8
输入3072*30725030

参考

[1] GitHub - guojin-yan/Inference: At OpenVINO ™、 TensorRT, ONNX runtime, and OpenCV Dnn deployment platforms are based on C # language deployment models.

[2] CPU/GPU(CUDA)版本的 YOLOv5后处理代码-CSDN博客

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

闽ICP备14008679号