当前位置:   article > 正文

TRT4-trt-integrate - 1 YOLOV5导出、编译、推理_转trt模型时对于对于这种多个大跨度动态维度是否有

转trt模型时对于对于这种多个大跨度动态维度是否有

 模型导出

 修改Image的Input动态维度

首先可以看到这个模型导出的时候Input有三个维度都是动态,而我们之前说过只需要一个batch维度是动态,所以要在export的export onnx 进行修改,将

  1. torch.onnx.export(model, im, f, verbose=False, opset_version=opset,
  2. training=torch.onnx.TrainingMode.TRAINING if train else torch.onnx.TrainingMode.EVAL,
  3. do_constant_folding=not train,
  4. input_names=['images'],
  5. output_names=['output'],
  6. dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}, # shape(1,3,640,640)
  7. 'output': {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
  8. } if dynamic else None)

改为:

  1. torch.onnx.export(model, im, f, verbose=False, opset_version=opset,
  2. training=torch.onnx.TrainingMode.TRAINING if train else torch.onnx.TrainingMode.EVAL,
  3. do_constant_folding=not train,
  4. input_names=['images'],
  5. output_names=['output'],
  6. dynamic_axes={'images': {0: 'batch'}, # shape(1,3,640,640)
  7. 'output': {0: 'batch'} # shape(1,25200,85)
  8. } if dynamic else None)

修改完的已经变成了只有batch是动态维度 。

修改output

而且也可以看到,这里的output输出有四个tensor,其中三个都是fpn结构,80*80 , 40*40 , 20*20,这些我们在这里去掉,仅保留拼接后的结果。

将yolov5-6.0/models/yolo.py中的Class detect修改:

return x if self.training else (torch.cat(z, 1), x)

-->

return x if self.training else torch.cat(z, 1)

修改完毕的output仅保留拼接后的结果。

剪去多余节点:

之后发现这个onnx还是很丑啊

发现真正导致变丑的原因在于这些节点 比如Gather。所以下一步就是要干掉他。

这一步就是将:

  1. for i in range(self.nl):
  2. x[i] = self.m[i](x[i]) # conv
  3. bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
  4. x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

改为:

  1. x[i] = self.m[i](x[i]) # conv
  2. bs, _, ny, nx = map(int,x[i].shape) # x(bs,255,20,20) to x(bs,3,20,20,85)
  3. x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

可以看到明显改善了不少。

调整reshape

 但是在reshape中可以看到中间维度是-1,而我们要的是batch是-1

 

 而bs是-1 , y.view还有个-1,这肯定是不行的,那么我们就要手动把这个计算出来,首先y的shape和x的shape一样,x的shape是bs*self.na*self.no*ny*nx,那么这里就是y.view(bs , self.na*nx*ny,self,no)

之后保存再次导出,可以看到已经变成batch的-1了

修改多余节点:

 但是发现还有比如expand这种节点,推断可能是由于数据跟踪引起的

  1. def _make_grid(self, nx=20, ny=20, i=0):
  2. d = self.anchors[i].device
  3. yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d)])
  4. grid = torch.stack((xv, yv), 2).expand((1, self.na, ny, nx, 2)).float()
  5. anchor_grid = (self.anchors[i].clone() * self.stride[i]) \
  6. .view((1, self.na, 1, 1, 2)).expand((1, self.na, ny, nx, 2)).float()

所以在这里就没必要每个都保存起来,直接给一个常量值就可以。

anchor_grid = (self.anchors[i].clone() * self.stride[i]).view(1,-1,1,1,2)

然后将所有用到self.anchor_grid的部分都替换为anchor_grid。

这样看起来就变成很平整的样子了

 

刚刚的那一大堆就变成了1*3*1*1*2这样子的常量值,这个kind是Initializer,就是常量的这个意思。

CPP推理过程:

TRT:

 YOLO:

 可以看到置信度稍微有一些区别。

输入input作warpaffine:

因为我们的输入是一个确认了的输入:是640*640。所以要对图像做一个类似warpaffine。就是等比缩放剧中填充

 

  1. ///
  2. // letter box
  3. auto image = cv::imread("car.jpg");
  4. // 通过双线性插值对图像进行resize
  5. float scale_x = input_width / (float)image.cols;
  6. float scale_y = input_height / (float)image.rows;
  7. float scale = std::min(scale_x, scale_y);
  8. float i2d[6], d2i[6];
  9. // resize图像,源图像和目标图像几何中心的对齐
  10. i2d[0] = scale; i2d[1] = 0; i2d[2] = (-scale * image.cols + input_width + scale - 1) * 0.5;
  11. i2d[3] = 0; i2d[4] = scale; i2d[5] = (-scale * image.rows + input_height + scale - 1) * 0.5;
  12. cv::Mat m2x3_i2d(2, 3, CV_32F, i2d); // image to dst(network), 2x3 matrix
  13. cv::Mat m2x3_d2i(2, 3, CV_32F, d2i); // dst to image, 2x3 matrix
  14. cv::invertAffineTransform(m2x3_i2d, m2x3_d2i); // 计算一个反仿射变换
  15. //为什么要计算逆矩阵:因为正矩阵是图像变成warpaffine的过程,逆变换是把框变回到图像尺度的过程
  16. cv::Mat input_image(input_height, input_width, CV_8UC3);
  17. cv::warpAffine(image, input_image, m2x3_i2d, input_image.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar::all(114)); // 对图像做平移缩放旋转变换,可逆,填充全是常量值,114
  18. cv::imwrite("input-image.jpg", input_image);
  19. //存储一下warpaffine效果
  20. int image_area = input_image.cols * input_image.rows;
  21. unsigned char* pimage = input_image.data;
  22. float* phost_b = input_data_host + image_area * 0;
  23. float* phost_g = input_data_host + image_area * 1;
  24. float* phost_r = input_data_host + image_area * 2;
  25. for(int i = 0; i < image_area; ++i, pimage += 3){
  26. // 注意这里的顺序rgb调换了
  27. *phost_r++ = pimage[0] / 255.0f;
  28. *phost_g++ = pimage[1] / 255.0f;
  29. *phost_b++ = pimage[2] / 255.0f;
  30. }
  31. ///
  32. checkRuntime(cudaMemcpyAsync(input_data_device, input_data_host, input_numel * sizeof(float), cudaMemcpyHostToDevice, stream));

存储一下warpaffine效果

之后就是作推理:

  1. // 3x3输入,对应3x3输出
  2. auto output_dims = engine->getBindingDimensions(1);
  3. int output_numbox = output_dims.d[1];
  4. int output_numprob = output_dims.d[2];
  5. int num_classes = output_numprob - 5;//类别数
  6. int output_numel = input_batch * output_numbox * output_numprob;
  7. float* output_data_host = nullptr;
  8. float* output_data_device = nullptr;
  9. checkRuntime(cudaMallocHost(&output_data_host, sizeof(float) * output_numel));
  10. checkRuntime(cudaMalloc(&output_data_device, sizeof(float) * output_numel));
  11. // 明确当前推理时,使用的数据输入大小
  12. auto input_dims = engine->getBindingDimensions(0);
  13. input_dims.d[0] = input_batch;
  14. execution_context->setBindingDimensions(0, input_dims);
  15. float* bindings[] = {input_data_device, output_data_device};
  16. bool success = execution_context->enqueueV2((void**)bindings, stream, nullptr);
  17. checkRuntime(cudaMemcpyAsync(output_data_host, output_data_device, sizeof(float) * output_numel, cudaMemcpyDeviceToHost, stream));
  18. checkRuntime(cudaStreamSynchronize(stream));

这个结果就是我们之前YOLOV5的predict(https://blog.csdn.net/zhuangtu1999/article/details/131499750?spm=1001.2014.3001.5501)

但在这里根之前不太一样了:

  1. vector<vector<float>> bboxes;
  2. float confidence_threshold = 0.25;
  3. float nms_threshold = 0.5;
  4. for(int i = 0; i < output_numbox; ++i){
  5. float* ptr = output_data_host + i * output_numprob;
  6. float objness = ptr[4];
  7. if(objness < confidence_threshold)
  8. continue;
  9. float* pclass = ptr + 5;
  10. int label = std::max_element(pclass, pclass + num_classes) - pclass;
  11. float prob = pclass[label];
  12. float confidence = prob * objness;
  13. if(confidence < confidence_threshold)
  14. continue;
  15. // 中心点、宽、高
  16. float cx = ptr[0];
  17. float cy = ptr[1];
  18. float width = ptr[2];
  19. float height = ptr[3];
  20. // 预测框
  21. float left = cx - width * 0.5;
  22. float top = cy - height * 0.5;
  23. float right = cx + width * 0.5;
  24. float bottom = cy + height * 0.5;
  25. // 对应图上的位置
  26. float image_base_left = d2i[0] * left + d2i[2];
  27. float image_base_right = d2i[0] * right + d2i[2];
  28. float image_base_top = d2i[0] * top + d2i[5];
  29. float image_base_bottom = d2i[0] * bottom + d2i[5];
  30. bboxes.push_back({image_base_left, image_base_top, image_base_right, image_base_bottom, (float)label, confidence});
  31. }
  32. printf("decoded bboxes.size = %d\n", bboxes.size());

这里的预测框,left,top等等对应的是warpaffine之后的图片,但我们要做的是把他在原来的图片上加入回来,所以还要做一个反变换的过程。

这里也是我们值前提到过,咱们只有缩放和平移的时候,有效的参数只有三个:scale , dx , dy,这里对应的就是d2i[0] , d2i[2] , d2i[5]。

在之后就是nms:

  1. // nms非极大抑制
  2. std::sort(bboxes.begin(), bboxes.end(), [](vector<float>& a, vector<float>& b){return a[5] > b[5];});
  3. std::vector<bool> remove_flags(bboxes.size());
  4. std::vector<vector<float>> box_result;
  5. box_result.reserve(bboxes.size());
  6. auto iou = [](const vector<float>& a, const vector<float>& b){
  7. float cross_left = std::max(a[0], b[0]);
  8. float cross_top = std::max(a[1], b[1]);
  9. float cross_right = std::min(a[2], b[2]);
  10. float cross_bottom = std::min(a[3], b[3]);
  11. float cross_area = std::max(0.0f, cross_right - cross_left) * std::max(0.0f, cross_bottom - cross_top);
  12. float union_area = std::max(0.0f, a[2] - a[0]) * std::max(0.0f, a[3] - a[1])
  13. + std::max(0.0f, b[2] - b[0]) * std::max(0.0f, b[3] - b[1]) - cross_area;
  14. if(cross_area == 0 || union_area == 0) return 0.0f;
  15. return cross_area / union_area;
  16. };
  17. for(int i = 0; i < bboxes.size(); ++i){
  18. if(remove_flags[i]) continue;
  19. auto& ibox = bboxes[i];
  20. box_result.emplace_back(ibox);
  21. for(int j = i + 1; j < bboxes.size(); ++j){
  22. if(remove_flags[j]) continue;
  23. auto& jbox = bboxes[j];
  24. if(ibox[4] == jbox[4]){
  25. // class matched
  26. if(iou(ibox, jbox) >= nms_threshold)
  27. remove_flags[j] = true;
  28. }
  29. }
  30. }
  31. printf("box_result.size = %d\n", box_result.size());

通过cv::rectangle画框:

  1. for(int i = 0; i < box_result.size(); ++i){
  2. auto& ibox = box_result[i];
  3. float left = ibox[0];
  4. float top = ibox[1];
  5. float right = ibox[2];
  6. float bottom = ibox[3];
  7. int class_label = ibox[4];
  8. float confidence = ibox[5];
  9. cv::Scalar color;
  10. tie(color[0], color[1], color[2]) = random_color(class_label);//通过标签随机选择颜色
  11. cv::rectangle(image, cv::Point(left, top), cv::Point(right, bottom), color, 3);
  12. auto name = cocolabels[class_label];
  13. auto caption = cv::format("%s %.2f", name, confidence);
  14. int text_width = cv::getTextSize(caption, 0, 1, 2, nullptr).width + 10;
  15. cv::rectangle(image, cv::Point(left-3, top-33), cv::Point(left + text_width, top), color, -1);
  16. cv::putText(image, caption, cv::Point(left, top-5), 0, 1, cv::Scalar::all(0), 2, 16);
  17. }
  18. cv::imwrite("image-draw.jpg", image);
  19. checkRuntime(cudaStreamDestroy(stream));
  20. checkRuntime(cudaFreeHost(input_data_host));
  21. checkRuntime(cudaFreeHost(output_data_host));
  22. checkRuntime(cudaFree(input_data_device));
  23. checkRuntime(cudaFree(output_data_device));
  24. }

总结:

在这次过程中,warpaffine(预处理)和后处理过程都可以使用我们之前的核函数去处理,这一部分打包到GPU上的话性能会变得更高。

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

闽ICP备14008679号