【MMDeploy&MMPose】手把手教你在Windows上使用MMDeploy1.x进行ONNX、TensorRT和ncnn的部署(SDK C++篇)

7 篇文章 1 订阅
5 篇文章 2 订阅

上一次我们在windows上进行了onnx和tensorrt的python部署,并且开了一个使用C++ SDK推理的坑,这次我们来填一下这个坑。

手上正好有一个arm开发板,思考了一下干脆来波大的,写一篇在ARM开发板上部署MMDeploy的文章。在arm上开发很难一蹴而就,所以在此之前,我们要先在windows上做好准备。

上一篇文章在python部署的时候没有编译mmdeploy的源码,本文又拟用ncnn部署,所以这次我们先在windows上编译推理需要的各个模块。

本篇文章使用的GitHub工程已上传至(未包含第三方子模块):https://github.com/kanminou/custom_mmdeploy

需要用到的工具:

VS 2022

CMake 3.26.4(建议不要高于这个版本,不然可能有兼容性问题,踩了无数坑!!)

1.编译OpenCV

这个地方不多说了,网上有很多教程,这里我编译的是最新版本4.7.0

2.编译protobuf-3.11.2

ncnn编译需要先编译protobuf,给出3.11.2的下载链接,这个版本是ncnn文档里要求的,我试了最新版的protobuf,在ncnn编译时会报错,所以最好用3.11.2版本的。下载解压后,打开命令行,进入目录:

mkdir build
cd build
cmake -A x64 -DCMAKE_INSTALL_PREFIX="../install" -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF ../cmake
cmake --build . --config Release -j 2
cmake --build . --config Release --target install

执行完成后,protobuf目录的install中就是编译完成的文件。如果在cmake时报错找不到第三方源码的,请到github下载相应源码,放入third_party文件夹中相应位置即可,一定要保证在cmake时没有error。

3.编译ncnn

在编译ncnn之前,先考虑清楚是否要用GPU加速,要使用GPU的话需要先下载Vulkan,下面给出链接,下载最新版本即可:

https://vulkan.lunarg.com/sdk/home

这里我们下载安装了Vulkan 1.3.250.0版本,下面开始在Windows上编译ncnn。首先从GitHub上下载源码:

git clone https://github.com/Tencent/ncnn.git
git submodule update --init

第二行主要是下载ncnn仓库中的子模块,子模块在github会用特殊颜色标出:

使用clone是下拉不了子模块的,不过如果使用第二行的命令下载失败,自己去github上下载源码,解压在相同路径下即可。编译ncnn要用到的子模块有两个:glslang和pybind11,pybind在python目录下。

上述操作完成后,进入ncnn目录,输入命令:

mkdir protobuf_build
cd protobuf_build
cmake .. -A x64 -DCMAKE_INSTALL_PREFIX="../install" `
-DProtobuf_INCLUDE_DIR="E:/protobuf-3.11.2/install/include" `
-DProtobuf_LIBRARIES="E:/protobuf-3.11.2/install/lib/libprotobuf.lib" `
-DProtobuf_PROTOC_EXECUTABLE="E:/protobuf-3.11.2/install/bin/protoc.exe" `
-DNCNN_VULKAN=ON `
-DNCNN_PYTHON=ON `
-DOpenCV_DIR="E:/opencv-4.7.0/build/install" 

上述cmake命令会生成makefile文件,其中:

  • -DProtobuf_INCLUDE_DIR                   后跟protobuf编译输出的include目录;
  • -DProtobuf_LIBRARIES                        后跟protobuf编译输出的lib/libprotobuf.lib库文件;
  • -DProtobuf_PROTOC_EXECUTABLE  后跟protobuf编译输出的bin/protoc.exe的可执行文件;
  • -DOpenCV_DIR                                     后跟OpenCV的编译输出文件夹;
  • -DNCNN_VULKAN=ON                         使用VULKAN加速;
  • -DNCNN_PYTHON=ON                        编译python包

注意,每次使用cmake创建makefile文件时请先清空build目录。

然后开始编译,输入命令:

cmake --build . --config Release -j 4

根据你的cpu核数确定-j后数字的大小,数字越大编译越快。编译时的警告信息可以忽略,但是如果出现error,请仔细检查前面的步骤是否都正确。编译完成后就可以安装了:

cmake --build . --config Release --target install

执行完命令后,ncnn目录中的install文件夹会存放编译好的内容。然后进入ncnn/python,输入命令:

pip install -e .

然后pip就会安装编译好的ncnn python包。

4.编译mmdeploy自定义算子

注意,如果你要使用C++ SDK推理,可以跳到第五步,编译SDK时自定义算子会一起编译。

这里使用的是上一篇文章的mmdeploy1.0代码。但是github上已经更新了1.1版本,这里使用git push更新代码到了1.1版本。如果你没有mmdeploy,请参考我的上一篇文章

上一篇文章的mmdeploy使用的是预编译包,而官方的预编译包中没有ncnn自定义算子,所以这我们要自己编译mmdeploy的自定义算子。

首先编译onnx的自定义算子,要使用到上一篇文章下载的onnxruntime:

mkdir build -ErrorAction SilentlyContinue
cd build
cmake .. -A x64 -DMMDEPLOY_TARGET_BACKENDS="ort" -DONNXRUNTIME_DIR="E:/onnxruntime-win-x64-gpu-1.15.1/lib"
cmake --build . --config Release -- /m
cmake --install . --config Release

编译完成后,mmdeploy的mmdeploy\lib目录会多一个.lib文件和.dll文件。

然后编译tensorrt的自定义算子,要使用到上一篇文章下载的Tensorrt,注意先清空build目录里的文件:

mkdir build -ErrorAction SilentlyContinue
cd build
cmake .. -A x64 -DMMDEPLOY_TARGET_BACKENDS="trt" -DTENSORRT_DIR="E:/TensorRT-8.6.1.6" -DCUDNN_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.6"
cmake --build . --config Release -- /m
cmake --install . --config Release

需要注意的是这里需要填入cudnn的目录,编译完成后mmdeploy\lib目录会多一个.lib文件。

最后编译ncnn的自定义算子,要使用到上文编译的ncnn和protobuf,注意先清空build目录里的文件:

mkdir build -ErrorAction SilentlyContinue
cd build
cmake .. -A x64 -DMMDEPLOY_TARGET_BACKENDS="ncnn" -Dncnn_DIR="E:/ncnn/install" -DProtobuf_INCLUDE_DIR="E:/protobuf-3.11.2/build_a/install/include" -DProtobuf_LIBRARIES="E:/protobuf-3.11.2/build_a/install/lib/libprotobuf.lib" -DProtobuf_PROTOC_EXECUTABLE="E:/protobuf-3.11.2/build_a/install/bin/protoc.exe"
cmake --build . --config Release -- /m
cmake --install . --config Release

这里一定要指定protobuf的路径,不然cmake会识别到python中安装的protobuf,最后编译时会报错。编译完成后mmdeploy\lib目录会多一个.lib文件和.dll文件,在mmdeploy\backend\ncnn\目录下会多一个mmdeploy_onnx2ncnn.exe文件,这个文件很重要,后面转ncnn时要用到。下面给出我编译出的文件做一个参考:

5.编译mmdeploy推理SDK

因为我们要使用tensorrt,所以在编译SDK之前我们要先安装pplcv,cmake编译时会下载两个子模块,如果编译失败大概率是网络问题,可以多试几次:

git clone https://github.com/openppl-public/ppl.cv.git
cd ppl.cv
mkdir pplcv-build
cd pplcv-build
cmake .. -A x64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=install -DPPLCV_USE_CUDA=ON -DPPLCV_USE_MSVC_STATIC_RUNTIME=OFF
cmake --build . --config Release -- /m
cmake --install . --config Release

然后切换到mmdeploy根目录,进行编译。注意:编译SDK需要子模块spdlog,如果使用git submodule update --init命令下载不了,请去github下载解压到mmdeploy/third_party/spdlog即可。

mkdir build -ErrorAction SilentlyContinue
cd build
cmake .. -A x64 `
    -DMMDEPLOY_BUILD_SDK=ON `
    -DMMDEPLOY_BUILD_EXAMPLES=ON `
    -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON `
    -DMMDEPLOY_TARGET_DEVICES="cpu;cuda" `
    -DMMDEPLOY_TARGET_BACKENDS="ort;trt;ncnn" `
    -DONNXRUNTIME_DIR="E:/onnxruntime-win-x64-gpu-1.15.1"  `
    -DTENSORRT_DIR="E:/TensorRT-8.6.1.6" `
    -DCUDNN_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.6" `
    -Dncnn_DIR="E:/ncnn/install" `
    -DProtobuf_INCLUDE_DIR="E:/protobuf-3.11.2/build_a/install/include" `
    -DProtobuf_LIBRARIES="E:/protobuf-3.11.2/build_a/install/lib/libprotobuf.lib" `
    -DProtobuf_PROTOC_EXECUTABLE="E:/protobuf-3.11.2/build_a/install/bin/protoc.exe" `
    -DOpenCV_DIR="E:/opencv-4.7.0/build/install" `
    -Dpplcv_DIR="E:/ppl.cv/pplcv-build/install/lib/cmake/ppl"
cmake --build . --config Release -- /m
cmake --install . --config Release

编译的参数信息可以查看mmdeploy的官方文档。编译可能会花上一些时间,上述指令会生成onnx、tensorrt和ncnn的SDK。假如你只想要其中的两个或者一个backend,你可以删除命令中相应的内容。build/install中就是编译好的内容。

6.安装自己编译过的mmdeploy

切换到mmdeploy根目录,输入:

pip install -e .

pip会自动卸载老版本的mmdeploy并安装我们编译好的mmdeploy。安装完成之后的mmdeploy就能进行onnx、tensorrt和ncnn的部署了。这里贴一张cheak_env.py的输出供参考:

7.进行模型转换

上一篇文章已经进行过onnx和tensorrt的模型转换了,下面我们进行进行ncnn模型转换,我们同样选择RTMPose做部署。像上一篇文章一样,要先修改mmdeploy的config文件,这里要注意的是,上一篇文章我们把onnx的两个输出取名为"output1","output2",在推理时可以正常跑。但是因为要用SDK推理,想要正常使用RTMPose,必须把onnx的输出取名为'simcc_x','simcc_y',如下图所示:

 然后转换模型:

python tools/deploy.py configs/mmpose/pose-detection_ncnn_static-256x192_RTMPose.py ../mmpose/configs/body_2d_keypoint/rtmpose/coco/rtmpose-t_8xb256-420e_coco-256x192.py ../mmpose/deploy_models/rtmpose-tiny_simcc-coco_pt-aic-coco_420e-256x192-e613ba3f_20230127.pth demo/resources/human-pose.jpg --work-dir mmdeploy_models/mmpose/rtmpose_t_ncnn --device cpu --dump-info

转换完成后查看可视化结果(左:pytorch,右:ncnn):

 同样写一个推理脚本:

from mmdeploy.apis.utils import build_task_processor
from mmdeploy.utils import get_input_shape, load_config
import torch
import time


deploy_cfg = 'configs/mmpose/pose-detection_ncnn_static-256x192_RTMPose.py'
model_cfg = '../mmpose/configs/body_2d_keypoint/rtmpose/coco/rtmpose-t_8xb256-420e_coco-256x192.py'
device = 'cpu'
backend_model = ['mmdeploy_models/mmpose/rtmpose_t_ncnn/end2end.param','mmdeploy_models/mmpose/rtmpose_t_ncnn/end2end.bin']
image = '000033016.jpg'

# read deploy_cfg and model_cfg
deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg)

# build task and backend model
task_processor = build_task_processor(model_cfg, deploy_cfg, device)
model = task_processor.build_backend_model(backend_model)

# process input image
input_shape = get_input_shape(deploy_cfg)
model_inputs, _ = task_processor.create_input(image, input_shape)

# do model inference
num_warmup = 5
pure_inf_time = 0

    # benchmark with total batch and take the average
for i in range(1000):

    start_time = time.perf_counter()
    with torch.no_grad():
        result = model.test_step(model_inputs)
    elapsed = time.perf_counter() - start_time

    if i >= num_warmup:
        pure_inf_time += elapsed
        if (i + 1) % 100 == 0:
            its = (i + 1 - num_warmup)/ pure_inf_time
            print(f'Done item [{i + 1:<3}],  {its:.2f} items / s')
print(f'Overall average: {its:.2f} items / s')
print(f'Total time: {pure_inf_time:.2f} s')

      

# visualize results
task_processor.visualize(
    image=image,
    model=model,
    result=result[0],
    window_name='visualize',
    output_file='output_pose.png')

 结果应该是要比onnx快的,不知道什么原因,我的vsc装了cmake和c++插件之后推理速度暴跌,使用onnx推理只有30+fps。

8.使用C++ SDK进行推理

同样用RTMPose做SDK推理演示的,模型转换已经在前面完成了,现在我们的目录下有这几个文件:

 那我们怎么用C++的SDK做推理呢?其实我们在编译时给了生成example的选项,在build/install/bin下可以看到编译生成的可执行文件:

现在我们在用命令行来执行一下,在mmdeploy根目录执行:

build\install\bin\pose_detector.exe cpu mmdeploy_models\mmpose\rtmpose_t demo\resources\human-pose.jpg

注意输入模型路径要给到文件夹,不能给到具体文件,不然会报错。然后mmdeploy路径下会生成output_pose.png

可以看到模型有正常输出,证明部署和推理过程是没有问题的。

9.编写代码进行SDK基准推理测试

自己编写一个基准测试文件:

#include <fstream>
#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <string>

#include "mmdeploy/pose_detector.h"
#include "mmdeploy/pipeline.h"
#include "mmdeploy/model.h"

int main(int argc,char *argv[]) {
  if (argc != 5) {
    fprintf(stderr, "usage:\n  pose_detection device_name model_path image_path test_batch\n");
    return 1;
  }
  auto device_name = argv[1];
  auto model_path = argv[2];
  auto image_path = argv[3];
  int test_batch ;
  sscanf_s(argv[4],"%d",&test_batch);
  cv::Mat img = cv::imread(image_path);
  if (!img.data) {
    fprintf(stderr, "failed to load image: %s\n", image_path);
    return 1;
  }

  mmdeploy_profiler_t profiler{};
  mmdeploy_profiler_create("profiler_data.txt", &profiler);

  mmdeploy_model_t model{};
  mmdeploy_model_create_by_path(model_path, &model);

  
  mmdeploy_context_t context{};
  mmdeploy_context_create_by_device(device_name, 0, &context);
  mmdeploy_context_add(context, MMDEPLOY_TYPE_PROFILER, nullptr, profiler);

  mmdeploy_pose_detector_t pose_detector{};
  int status{};
  status = mmdeploy_pose_detector_create_v2(model, context, &pose_detector);
  if (status != MMDEPLOY_SUCCESS) {
    fprintf(stderr, "failed to create pose_estimator, code: %d\n", (int)status);
    return 1;
  }

  mmdeploy_mat_t mat{
      img.data, img.rows, img.cols, 3, MMDEPLOY_PIXEL_FORMAT_BGR, MMDEPLOY_DATA_TYPE_UINT8};


  fprintf(stderr, "Start inference benchmark\n");


  for (int i = 0; i < test_batch; i++) {
    mmdeploy_pose_detection_t *res{};
    status = mmdeploy_pose_detector_apply(pose_detector, &mat, 1, &res);
    mmdeploy_pose_detector_release_result(res, 1);
    if ((i+1)%100==0){
      fprintf(stderr, "[%d/%d]\n",i+1,test_batch);
    }
  }

  mmdeploy_pose_detector_destroy(pose_detector);
  mmdeploy_model_destroy(model);
  mmdeploy_profiler_destroy(profiler);
  mmdeploy_context_destroy(context);

  return 0;
}

 放入mmdeploy目录,更改一下mmdeploy的cmake,重新编译一下,具体内容我会上传到GitHub。在build/install/bin里面会生成custom_pose_detector.exe,命令行输入:

build\install\bin\custom_pose_detector.exe cpu mmdeploy_models\mmpose\rtmpose_t demo\resources\human-pose.jpg 5000

后面的数字指的是推理轮数,运行完成后,mmdeploy目录会生成一个名为profiler_data.txt文件,我们可以使用

python tools/sdk_analyze.py profiler_data.txt

进行分析,输出如下:

其中 name 表示节点的名称,n_call表示调用的次数,t_mean 表示平均耗时,t_50% t_90% 表示耗时的百分位数。可以看到rtmpose-t在onnx cpu的平均推理时间为15.3ms。

再测试一下tensorrt,平均时间在3.5ms:

 ncnn,平均时间在14.6ms:

 ncnn_int8,平均时间21.4ms,

 使用int8反而变慢了,不过这跟网络有很大的关系,于是我试了一下自己的网络:

ncnn推理速度,45.6ms:

ncnn_int8推理速度,46.7ms:

 发现还是变慢了,虽然慢的不多,网上找了下资料,“模型量化实际的推理速度很大程度取决于推理引擎的优化以及硬件的支持。”跟硬件还有关系,妥协了,这个部分就留到arm在验证吧。

10.写在后面

这篇文章很长,要做的事情也很多,花费了作者大量的时间,如果能够按着流程跟下来,那么你肯定对mmdeploy的部署有了更深的认识。同时,这也是为后面的内容打下基础,下一篇文章,我们会在Linux系统上进行mmdeploy的部署。

如果觉得这篇文章对你有帮助,请点赞收藏,谢谢~~

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值