当前位置:   article > 正文

Yolov7的tensorRT部署与使用_yolo tensorrt

yolo tensorrt

一. 环境配置

1.tensorRT安装

        TensorRT是英伟达公司出品的高性能的推断C++库,应用于边缘设备的推理,可以将训练好的模型分解再进行融合,融合后的模型具有高度的集合度,其直接利用cuda以在显卡上运行,所有的代码库仅仅包括C++和cuda,在利用此优化库运行代码时,运行速度和所占内存的大小都会大大缩减。

        在官网下载tensorRT,本文选取版本8.4.2.4;

        下载并解压后,将tensorRT/bin目录加至Path环境变量中;

D:\TensorRT-8.4.2.4\bin

        将TensorRT\lib下的lib文件拷贝至CUDA的lib\x64路径,如C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\lib\x64;将TensorRT\lib下的dll文件拷贝至C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\bin。

        Visual Studio配置

        打开任意一个项目,打开项目属性->C/C++->常规->附加包含目录,将TensorRT\include加入附加包含目录;打开项目属性->链接器->附加库目录,将TensorRT\lib加入附加库目录;打开项目属性->链接器->输入->附加依赖项,将TensorRT\lib下的所有lib文件加入附加依赖项;

  1. nvinfer.lib
  2. nvinfer_plugin.lib
  3. nvonnxparser.lib
  4. nvparsers.lib

2. LibTorch安装

        从Libtorch官网下载并解压,此后对VS进行配置。打开项目属性->C/C++->附加包含目录,将Libtorch\include和Libtorch\include\torch\csrc\api\include加入;

        打开项目属性->链接器->常规->附加库目录,将Libtorch\lib加入;打开项目属性->链接器->输入->附加依赖性,将Libtorch\lib下的lib文件名全部加入;打开项目属性->配置属性->调试->环境,将D:\libtorch\bin;%PATH%加入其中;

二.TensorRT的使用

1.TensorRT的流程

       TensorRT包含解析器parser、构建器builder、引擎engine、执行上下文context等;解析器负责加载模型框架与权重,构建器负责构建网络,引擎与执行上下文负责在网络中进行前向传播。

        一般而言,TensorRT的支持自定义构建TensorRT模型和从外界导入模型两种。

 网络自定义构建

        TensorRT支持从头创建网络,其一般流程如下。

        创建logger->创建builder->创建Network->创建网络->标记输入输出->设置运行参数config->创建engine->创建上下文并推理;

  1. class Logger : public ILogger
  2. {
  3. void log(Severity severity, const char* msg) override
  4. {
  5. // suppress info-level messages
  6. if (severity != Severity::kINFO)
  7. std::cout << msg << std::endl;
  8. }
  9. } gLogger;
  10. //builder创建
  11. IBuilder* builder = createInferBuilder(gLogger);
  12. //network创建
  13. INetworkDefinition* network = builder->createNetworkV2(1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH));

        上述代码创建了builder与network,可以使用network->add函数以在模型中增加模块。亦可以在增加模块的时候对齐命名,使用getOutput->setName即可命名。

  1. IBuilderConfig* config = builder->createBuilderConfig();
  2. config->setMaxBatchSize(maxBatchSize);//设置批量大小
  3. config->setMaxWorkspaceSize(1 << 30);//设置最大运行空间
  4. //创建engine
  5. ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);

       上述代码则实现了config的创建,以配置运行空间等参数,此后创建了引擎。此后可以使用引擎进行推理,也可以使用engine->serialize()函数以将模型序列化保存。

        当使用完毕后,使用如下函数进行析构。

  1. serializedModel->destroy();
  2. engine->destroy();
  3. network->destroy();
  4. config->destroy();
  5. builder->destroy();

外界模型文件的导入

        tensorRT可以直接解析caffe和TensorFlow的网络模型;而caffe2,pytorch,mxnet,chainer,CNTK等框架则是需要将模型转为 ONNX(开放式神经网络交换工具) ,再对ONNX模型做解析以转化为TensorRT模型。其支持的精度包括FP32/FP16/TF32等。

        在得到导出的ONNX模型以后,应先使用onnxruntime包,在python上进行调用,以验证此模型的正确性,在得到了验证后再转化为TensorRT文件以进行后续部署。

        得到onnx文件后,在/tensorRT/bin目录下,调用trtexec.exe文件即可将onnx转为tensorRT文件。其调用指令如下。待运行完毕后即可得到tensorRT文件。

  1. #640 批量固定为1
  2. ./trtexec.exe --onnx=yolov7.onnx --saveEngine=yolov7-tiny.trt --buildOnly
  3. # 640 动态维度 最大最小shape可更改
  4. ./trtexec.exe --onnx=yolov7.onnx --saveEngine=yolov7.trt --buildOnly --minShapes=images:1x3x640x640 --optShapes=images:2x3x640x640 --maxShapes=images:4x3x640x640
  5. # 1280 动态维度
  6. ./trtexec.exe --onnx=yolov7-w6.onnx --saveEngine=yolov7-w6.trt --buildOnly --minShapes=images:1x3x1280x1280 --optShapes=images:2x3x1280x1280 --maxShapes=images:4x3x1280x1280

        而对于调用TRT模型而言,其一般流程如下:

        读入trt文件->创建Iruntime->Iruntime反序列化trt文件以得到engine->engine创建context->为运行分配空间->将数据拷贝至分配的空间上->模型推理->后处理;

  1. class Logger : public ILogger {
  2. public:
  3. void log(Severity severity, const char* msg) noexcept override
  4. {
  5. // suppress info-level messages
  6. if (severity <= Severity::kWARNING)
  7. std::cout << msg << std::endl;
  8. }
  9. }gLogger;
  10. //假设模型已经被读入到vector<char> modeltrt中
  11. initLibNvInferPlugins(&gLogger, "");
  12. //模型导入
  13. Logger m_logger;
  14. IRuntime* runtime = createInferRuntime(gLogger);
  15. ICudaEngine* engine = runtime->deserializeCudaEngine(modeltrt.data(), modeltrt.size(), nullptr);
  16. IExecutionContext* context = engine->createExecutionContext();
  17. //分配空间
  18. void* buffers[2];
  19. buffers[0] = buffers[1] = NULL;
  20. cudaMallocManaged(&buffers[0], 3 * 640 * 640 * 4);
  21. cudaMallocManaged(&buffers[1], 1 * 25200 * 85 * 4);
  22. cudaStream_t stream;
  23. cudaStreamCreate(&stream);
  24. //假设输入数据已经复制到buffers上,运行
  25. context->enqueueV2(buffers, stream, nullptr);

        TRT亦可直接解析ONNX/CAFFE模型等,其流程类似,即:

        创建Ibuilder->创建InetworkDefiniton->创建IParser->parser调用parsefromFile函数解析文件->标记网络输出->创建IbuilderConfig以配置运行环境->创建engine->序列化或进行推理;

  1. class Logger : public ILogger {
  2. public:
  3. void log(Severity severity, const char* msg) noexcept override
  4. {
  5. // suppress info-level messages
  6. if (severity <= Severity::kWARNING)
  7. std::cout << msg << std::endl;
  8. }
  9. }gLogger;
  10. IBuilder* builder = createInferBuilder(gLogger);
  11. //创建网络
  12. const auto explicitBatch = 1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
  13. INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
  14. //创建解析器
  15. nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, gLogger);
  16. //onnx_filename为onnx路径
  17. parser->parseFromFile(onnx_filename, ILogger::Severity::kWARNING);
  18. //设置运行参数
  19. IBuilderConfig* config = builder->createBuilderConfig();
  20. config->setMaxBatchSize(maxBatchSize);//设置最大batchsize
  21. config->setMaxWorkspaceSize(1 << 30);//2^30 ,这里是1G
  22. //创建engine
  23. ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
  24. //序列化
  25. IHostMemory *serializedModel = engine->serialize();

2.yolo的tensorRT部署

        而对于本文的yolo,首先从YOLOv7官网将项目克隆,从Release下载yolov7.pt权重后,使用以下指令导出动态维度的yolov7;

        亦可去除dynamic参数以导出不具有动态维度的onnx模型,此时批量大小为1。下文使用不带有动态维度的onnx模型。

python export.py --weights yolov7.pt  --dynamic  --grid

       通过onnxruntime调用验证其正确性后,调用上文的trtexec文件生成yolov7.engine文件。首先将trt文件读入至vector中;

  1. string TRTpath = "your trtpath";
  2. ifstream trtinput(TRTpath, ios_base::binary);
  3. int trtsize = 0;
  4. if (trtinput.is_open()) {
  5. trtinput.seekg(0, ios::end);
  6. trtsize = trtinput.tellg();
  7. trtinput.seekg(0, ios::beg);
  8. }
  9. vector<char> modeltrt(trtsize);
  10. trtinput.read(modeltrt.data(), trtsize);
  11. trtinput.close();

        此后创建Iruntime、IcudaEngine、IExecutionContext,ICudaEngine使用deserializeCudaEngine函数以反序列化模型;

  1. Logger m_logger;
  2. IRuntime* runtime = createInferRuntime(gLogger);
  3. ICudaEngine* engine = runtime->deserializeCudaEngine(modeltrt.data(), modeltrt.size(), nullptr);
  4. IExecutionContext* context = engine->createExecutionContext();

        此时已经创建好了对应的engine与context,接下来所需要的是分配对应的空间,由于在GPU上运行,需要使用cudaMallocManaged函数以进行分配。yolov7的输出为1*25200*85,使用float类型,故分配1*25200*85*4的空间,输入为三通道的640*640图像,故输入为3*640*640*4。

  1. void* buffers[2];
  2. buffers[0] = buffers[1] = NULL;
  3. cudaMallocManaged(&buffers[0], 3 * 640 * 640 * 4);
  4. cudaMallocManaged(&buffers[1], 1 * 25200 * 85 * 4);
  5. cudaStream_t stream;
  6. cudaStreamCreate(&stream);

        此后将数据复制到buffer数组上并推理。需要注意的是,yolo的输入为3*640*640,图像为640*640*3,需要调用permute函数交换维度的同时,还需要调用reshape函数。这是因为permute函数只是改变了张量解释的方法,并没有改变张量在内存中的存储,因而直接调用cudamemcpy函数以后仍然是没有调整维度的原张量,而reshape函数会重新分配一块内存,此时维度才能在内存上得到交换。最后将输出结果进行后处理即可。

  1. tensorimage1 = tensorimage1.permute({ 2,0,1 }).toType(torch::kFloat).unsqueeze(0).to(at::kCUDA);
  2. tensorimage1 = tensorimage1 / 255.0;
  3. tensorimage1 = tensorimage1.reshape(-1);
  4. cudaMemcpy(buffers[0], tensorimage1.data_ptr(), 4 * 3 * 640 * 640, cudaMemcpyHostToDevice);
  5. context->enqueueV2(buffers, stream, nullptr);
  6. torch::Tensor output = torch::ones({ 1 ,25200 ,85 }, at::kFloat);
  7. cudaMemcpy(output.data_ptr<float>(), buffers[1], 25200 * 85 * 4, cudaMemcpyDeviceToHost);

        后处理包括对每一个检测框进行IOU计算与NMS抑制。yolov7的输出为1*25200*85,本质上是由3*20*20*85+3*40*40*85+3*80*80*85,其含义为将图片划分为20*20或40*40或80*80的网格,每一个网格中检测3个检测框;而最后一维的85由四个坐标+置信度+80种类别的可能性组成,其含义为检测框的左上角和右下角的坐标+置信度+检测框的物体类别,80基于COCO80数据集。

       通过设置置信度阈值,选出大于置信度的所有检测框,再对每一个检测框进行NMS抑制,即可得到最终的检测框。

        在本文中,NMS和IOU计算函数如下。

  1. class Bbox
  2. {
  3. public:
  4. int x;
  5. int y;
  6. int h;
  7. int w;
  8. float score;
  9. int type_number;//类型编号
  10. };
  11. //iou计算
  12. float calculateIOU(Bbox& box1, Bbox& box2) {
  13. int min_x = max(box1.x - box1.h / 2, box2.x - box2.h / 2); // 找出左上角坐标哪个大
  14. int max_x = min(box1.x + box1.h / 2, box2.x + box2.h / 2); // 找出右上角坐标哪个小
  15. int min_y = max(box1.y - box1.w / 2, box2.y - box2.w / 2);
  16. int max_y = min(box1.y + box1.w / 2, box2.y + box2.w / 2);
  17. if (min_x >= max_x || min_y >= max_y) // 如果没有重叠
  18. return 0;
  19. float over_area = (max_x - min_x) * (max_y - min_y); // 计算重叠面积
  20. float area_a = box1.h * box1.w;
  21. float area_b = box2.h * box2.w;
  22. float iou = over_area / (area_a + area_b - over_area);
  23. return iou;
  24. }
  25. vector<Bbox> NMS(vector<Bbox>& boxes, float threshold) {
  26. sort(boxes.begin(), boxes.end(), cmp);
  27. vector<Bbox> result;
  28. while (boxes.size() > 0) {
  29. result.push_back(boxes[0]);
  30. int index = 1;
  31. while (index < boxes.size())
  32. {
  33. float iou_value = calculateIOU(boxes[0], boxes[index]);
  34. if (iou_value > threshold)
  35. boxes.erase(boxes.begin() + index);
  36. else
  37. index++;
  38. }
  39. boxes.erase(boxes.begin());
  40. }
  41. return result;
  42. }

3.部署效果

        下图为yolov7的tensorRT部署效果。

        在已有的项目中,使用TensorRT部署的yolov7,较部署前平均快了3-4倍,可见TensorRT的效率。     

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

闽ICP备14008679号