3. 通用yolov5模型部署

3.1. 引言

本文档介绍了如何将yolov5架构的模型部署在cv181x开发板的操作流程,主要的操作步骤包括:

  • yolov5模型pytorch版本转换为onnx模型

  • onnx模型转换为cvimodel格式

  • 最后编写调用接口获取推理结果

3.2. pt模型转换为onnx

1.首先可以下载yolov5官方仓库代码,地址如下: [ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite] (https://github.com/ultralytics/yolov5)

git clone https://github.com/ultralytics/yolov5.git

2.获取yolov5的.pt格式的模型,例如下载yolov5s模型的地址: [yolov5s](https://github.com/ultralytics/yolov5/releases/download/v7.0/yolov5s.pt)

3.下载TDL_SDK提供的yolov5 onnx导出脚本:

  • 官方导出方式的模型中解码部分不适合量化,因此需要使用TDL_SDK提供的导出方式

TDL_SDK 的导出方式可以直接使用yolo_export中的脚本,通过SFTP获取:下载站台:sftp://218.17.249.213 帐号:cvitek_mlir_2023 密码:7&2Wd%cu5k

通过SFTP找到下图对应的文件夹:

/home/公版深度学习SDK/yolo_export.zip

下载之后,解压压缩包并将 yolo_export/yolov5_export.py 复制到yolov5仓库目录下

4.然后使用以下命令导出TDK_SDK版本的onnx模型

#其中--weights表示权重文件的相对路径,--img-size表示输出尺寸为 640 x 640
python yolov5_export.py --weights ./weigthts/yolov5s.pt --img-size 640 640
#生成的onnx模型在当前目录

小技巧

如果输入为1080p的视频流,建议将模型输入尺寸改为384x640,可以减少冗余计算,提高推理速度,例如

python yolov5_export.py --weights ./weights/yolov5s.pt --img-size 384 640

3.3. 准备转模型环境

onnx转成cvimodel需要 TPU-MLIR 的发布包。TPU-MLIR 是算能TDL处理器的TPU编译器工程。

【TPU-MLIR工具包下载】 TPU-MLIR代码路径 https://github.com/sophgo/tpu-mlir,感兴趣的可称为开源开发者共同维护开源社区。

而我们仅需要对应的工具包即可,下载地位为算能官网的TPU-MLIR论坛,后面简称为工具链工具包: (https://developer.sophgo.com/thread/473.html)

TPU-MLIR工程提供了一套完整的工具链, 其可以将不同框架下预训练的神经网络, 转化为可以在算能 TPU 上高效运算的文件。

目前支持onnx和Caffe模型直接转换,其他框架的模型需要转换成onnx模型,再通过TPU-MLIR工具转换。

转换模型需要在指定的docker执行,主要的步骤可以分为两步:

  • 第一步是通过model_transform.py将原始模型转换为mlir文件

  • 第二步是通过model_deploy.py将mlir文件转换成cvimodel

> 如果需要转换为INT8模型,还需要在第二步之前调用run_calibration.py生成校准表,然后传给model_deploy.py

【Docker配置】

TPU-MLIR需要在Docker环境开发,可以直接下载docker镜像(速度比较慢),参考如下命令:

docker pull sophgo/tpuc_dev:latest

或者可以从【TPU工具链工具包】中下载的docker镜像(速度比较快),然后进行加载docker。

docker load -i  docker_tpuc_dev_v2.2.tar.gz

如果是首次使用Docker,可以执行下述命令进行安装和配置(仅首次执行):

sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

【进入docker环境】 确保安装包在当前目录,然后在当前目录创建容器如下:

docker run --privileged --name myname -v $PWD:/workspace -it sophgo/tpuc_dev:v2.2

后续的步骤假定用户当前处在docker里面的/workspace目录

#### 加载tpu-mlir工具包&准备工作目录

以下操作需要在Docker容器执行

【解压tpu_mlir工具包】 以下文件夹创建主要是为了方便后续管理,也可按照自己喜欢的管理方式进行文件分类

新建一个文件夹tpu_mlir,将新工具链解压到tpu_mlir/目录下,并设置环境变量:

##其中tpu-mlir_xxx.tar.gz的xxx是版本号,根据对应的文件名而决定
mkdir tpu_mlir && cd tpu_mlir
cp tpu-mlir_xxx.tar.gz ./
tar zxf tpu-mlir_xxx.tar.gz
source tpu_mli_xxx/envsetup.sh

【拷贝onnx模型】 创建一个文件夹,以yolov5s举例,创建一个文件夹yolov5s,并将onnx模型放在yolov5s/onnx/路径下

mkdir yolov5s && cd yolov5s
##上一节转出来的yolov5 onnx模型拷贝到yolov5s目录下
cp yolov5s.onnx ./
## 拷贝官网的dog.jpg过来做校验。
cp dog.jpg ./

上述准备工作完成之后,就可以开始转换模型

3.4. onnx转MLIR

如果模型是图片输入, 在转模型之前我们需要了解模型的预处理。

如果模型用预处理后的 npz 文件做输入, 则不需要考虑预处理。

本例子中yolov5的图片是rgb,mean和scale对应为:

  • mean: 0.0, 0.0, 0.0

  • scale: 0.0039216, 0.0039216, 0.0039216

模型转换的命令如下:

model_transform.py \
--model_name yolov5s \
--model_def yolov5s.onnx \
--input_shapes [[1,3,640,640]] \
--mean 0.0,0.0,0.0 \
--scale 0.0039216,0.0039216,0.0039216 \
--keep_aspect_ratio \
--pixel_format rgb \
--test_input ./dog.jpg \
--test_result yolov5s_top_outputs.npz \
--mlir yolov5s.mlir

其中model_transform.py参数详情, 请参考【tpu_mlir_xxxxx/doc/TPU-MLIR快速入门指南】

转换成mlir文件之后,会生成一个yolov5s_in_f32.npz文件,该文件是模型的输入文件

3.5. MLIR转INT8模型

【生成校准表】

转 INT8 模型前需要跑 calibration,得到校准表;输入数据的数量根据情况准备 100~1000 张 左右。

然后用校准表,生成cvimodel.生成校对表的图片尽可能和训练数据分布相似

## 这个数据集从COCO2017提取100来做校准,用其他图片也是可以的,这里不做强制要求。
run_calibration.py yolov5s.mlir \
--dataset COCO2017 \
--input_num 100 \
-o yolov5s_cali_table

运行完成之后会生成名为yolov5_cali_table的文件,该文件用于后续编译cvimode模型的输入文件

【生成cvimodel】

然后生成int8对称量化cvimodel模型,执行如下命令:

其中–quant_output参数表示将输出层也量化为int8,不添加该参数则保留输出层为float32。

从后续测试结果来说,将输出层量化为int8,可以减少部分ion,并提高推理速度, 并且模型检测精度基本没有下降,推荐添加–quant_output参数

model_deploy.py \
--mlir yolov5s.mlir \
--quant_input \
--quant_output \
--quantize INT8 \
--calibration_table yolov5s_cali_table \
--processor cv181x \
--test_input yolov5s_in_f32.npz \
--test_reference yolov5s_top_outputs.npz \
--tolerance 0.85,0.45 \
--model yolov5_cv181x_int8_sym.cvimodel

其中model_deploy.py的主要参数参考, 请参考【tpu_mlir_xxxxx/doc/TPU-MLIR快速入门指南】

编译完成后,会生成名为yolov5_cv181x_int8_sym.cvimodel的文件

在上述步骤运行成功之后,编译cvimodel的步骤就完成了,之后就可以使用TDL_SDK调用导出的cvimodel进行yolov5目标检测推理了。

小心

注意运行的对应平台要一一对应!

3.6. TDL_SDK接口说明

集成的yolov5接口开放了预处理的设置,yolov5模型算法的anchor,conf置信度以及nms置信度设置

预处理设置的结构体为Yolov5PreParam

/** @struct YoloPreParam
 *  @ingroup core_cvitdlcore
 *  @brief Config the yolov5 detection preprocess.
 *  @var YoloPreParam::factor
 *  Preprocess factor, one dimension matrix, r g b channel
 *  @var YoloPreParam::mean
 *  Preprocess mean, one dimension matrix, r g b channel
 *  @var YoloPreParam::rescale_type
 *  Preprocess config, vpss rescale type config
 *  @var YoloPreParam::keep_aspect_ratio
 *  Preprocess config aspect scale
 *  @var YoloPreParam:: resize_method
 *  Preprocess resize method config
 *  @var YoloPreParam::format
 *  Preprocess pixcel format config
 */
typedef struct {
  float factor[3];
  float mean[3];
  meta_rescale_type_e rescale_type;
  bool keep_aspect_ratio;
  VPSS_SCALE_COEF_E resize_method;
  PIXEL_FORMAT_E format;
} YoloPreParam;

以下是一个简单的设置案例:

  • 通过CVI_TDL_Get_YOLO_Preparam以及CVI_TDL_Get_YOLO_Algparam分别获取:初始化预处理设置YoloPreParam以及yolov5模型设置YoloAlgParam

  • 在设置了预处理参数和模型参数之后,再使用CVI_TDL_Set_YOLO_Preparam和CVI_TDL_Set_YOLO_Algparam传入设置的参数

    • yolov5是 anchor-based 的检测算法,为了方便使用,开放了anchor自定义设置,在设置YoloAlgParam中,需要注意anchors和strides的顺序需要一一对应,否则会导致推理结果出现错误

    • 另外支持自定义分类数量修改,如果修改了模型的输出分类数量,需要设置YolovAlgParam.cls为修改后的分类数量

  • 再打开模型 CVI_TDL_OpenModel

  • 再打开模型之后可以设置对应的置信度和nsm阈值:

    • CVI_TDL_SetModelThreshold 设置置信度阈值,默认0.5

    • CVI_TDL_SetModelNmsThreshold 设置nsm阈值,默认0.5

// set preprocess and algorithm param for yolov5 detection
// if use official model, no need to change param
CVI_S32 init_param(const cvitdl_handle_t tdl_handle) {
  // setup preprocess
  YoloPreParam preprocess_cfg =
      CVI_TDL_Get_YOLO_Preparam(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5);

  for (int i = 0; i < 3; i++) {
    printf("asign val %d \n", i);
    preprocess_cfg.factor[i] = 0.003922;
    preprocess_cfg.mean[i] = 0.0;
  }
  preprocess_cfg.format = PIXEL_FORMAT_RGB_888_PLANAR;

  printf("setup yolov5 param \n");
  CVI_S32 ret =
      CVI_TDL_Set_YOLO_Preparam(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, preprocess_cfg);
  if (ret != CVI_SUCCESS) {
    printf("Can not set Yolov5 preprocess parameters %#x\n", ret);
    return ret;
  }

  // setup yolo algorithm preprocess
  YoloAlgParam yolov5_param = CVI_TDL_Get_YOLO_Algparam(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5);
  uint32_t *anchors = new uint32_t[18];
  uint32_t p_anchors[18] = {10, 13, 16,  30,  33, 23,  30,  61,  62,
                            45, 59, 119, 116, 90, 156, 198, 373, 326};
  memcpy(anchors, p_anchors, sizeof(p_anchors));
  yolov5_param.anchors = anchors;
  yolov5_param.anchor_len = 18;

  uint32_t *strides = new uint32_t[3];
  uint32_t p_strides[3] = {8, 16, 32};
  memcpy(strides, p_strides, sizeof(p_strides));
  yolov5_param.strides = strides;
  yolov5_param.stride_len = 3;
  yolov5_param.cls = 80;

  printf("setup yolov5 algorithm param \n");
  ret = CVI_TDL_Set_YOLO_Algparam(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, yolov5_param);
  if (ret != CVI_SUCCESS) {
    printf("Can not set Yolov5 algorithm parameters %#x\n", ret);
    return ret;
  }

  // set thershold
  CVI_TDL_SetModelThreshold(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, 0.5);
  CVI_TDL_SetModelNmsThreshold(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, 0.5);

  printf("yolov5 algorithm parameters setup success!\n");
  return ret;
}

推理以及结果获取

通过本地或者流获取图片,并通过CVI_TDL_ReadImage函数读取图片,然后调用Yolov5推理接口CVI_TDL_Yolov5。 推理的结果存放在obj_meta结构体中,遍历获取边界框bbox的左上角以及右下角坐标点以及object score(x1, y1, x2, y2, score),另外还有分类classes

ret = CVI_TDL_OpenModel(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, model_path.c_str());
if (ret != CVI_SUCCESS) {
  printf("open model failed %#x!\n", ret);
  return ret;
}

// set thershold
CVI_TDL_SetModelThreshold(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, 0.5);
CVI_TDL_SetModelNmsThreshold(tdl_handle, CVI_TDL_SUPPORTED_MODEL_YOLOV5, 0.5);

std::cout << "model opened:" << model_path << std::endl;

VIDEO_FRAME_INFO_S fdFrame;
ret = CVI_TDL_ReadImage(str_src_dir.c_str(), &fdFrame, PIXEL_FORMAT_RGB_888);
std::cout << "CVI_TDL_ReadImage done!\n";

if (ret != CVI_SUCCESS) {
  std::cout << "Convert out video frame failed with :" << ret << ".file:" << str_src_dir
            << std::endl;
  // continue;
}

cvtdl_object_t obj_meta = {0};

CVI_TDL_Yolov5(tdl_handle, &fdFrame, &obj_meta);

for (uint32_t i = 0; i < obj_meta.size; i++) {
  printf("detect res: %f %f %f %f %f %d\n", obj_meta.info[i].bbox.x1, obj_meta.info[i].bbox.y1,
         obj_meta.info[i].bbox.x2, obj_meta.info[i].bbox.y2, obj_meta.info[i].bbox.score,
         obj_meta.info[i].classes);
}

3.7. 编译说明

  1. 获取交叉编译工具

wget https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz
tar xvf host-tools.tar.gz
cd host-tools
export PATH=$PATH:$(pwd)/gcc/riscv64-linux-musl-x86_64/bin
  1. 下载 TDL SDK

tdlsdk工具包的下载站台:sftp://218.17.249.213 帐号:cvitek_mlir_2023 密码:7&2Wd%cu5k。 我们将cvitek_tdl_sdk_1227.tar.gz下载下来。

  1. 编译TDL SDK

我们进入到cvitek_tdl_sdk下的sample目录下。

chmod 777 compile_sample.sh
./compile_sample.sh
  1. 编译完成之后,可以连接开发板并执行程序:

    • 开发板连接网线,确保开发板和电脑在同一个网关

    • 电脑通过串口连接开发板,波特率设置为115200,电脑端在串口输入ifconfig获取开发板的ip地址

    • 电脑通过 ssh 远程工具连接对应ip地址的开发板,用户名默认为:root,密码默认为:cvitek_tpu_sdk

    • 连接开发板之后,可以通过 mount 挂在sd卡或者电脑的文件夹:
      • 改载sd卡的命令是

      mount /dev/mmcblk0 /mnt/sd
      # or
      mount /dev/mmcblk0p1 /mnt/sd
      
      • 挂载电脑的命令是:

      mount -t nfs 10.80.39.3:/sophgo/nfsuser ./admin1_data -o nolock
      

      主要修改ip地址为自己电脑的ip,路径同样修改为自己的路径

  2. export 动态依赖库

    主要需要的动态依赖库为:

    • ai_sdk目录下的lib

    • tpu_sdk目录下的lib

    • middlewave/v2/lib

    • middleware/v2/3rd

    • ai_sdk目录下的sample/3rd/lib

示例如下:

export LD_LIBRARY_PATH=/tmp/lfh/cvitek_tdl_sdk/lib:\
                        /tmp/lfh/cvitek_tdl_sdk/sample/3rd/opencv/lib:\
                        /tmp/lfh/cvitek_tdl_sdk/sample/3rd/tpu/lib:\
                        /tmp/lfh/cvitek_tdl_sdk/sample/3rd/ive/lib:\
                        /tmp/lfh/cvitek_tdl_sdk/sample/3rd/middleware/v2/lib:\
                        /tmp/lfh/cvitek_tdl_sdk/sample/3rd/lib:\
                        /tmp/lfh/cvitek_tdl_sdk/sample/3rd/middleware/v2/lib/3rd:

小心

注意将/tmp/lfh修改为开发版可以访问的路径,如果是用sd卡挂载,可以提前将所有需要的lib目录下的文件拷贝在同一个文件夹,然后export对应在sd卡的路径即可

  1. 运行sample程序

  • 切换到挂载的cvitek_tdl_sdk/bin目录下

  • 然后运行以下测试案例

./sample_yolov5 /path/to/yolov5s.cvimodel /path/to/test.jpg

上述运行命令注意选择自己的cvimodel以及测试图片的挂载路径

3.8. 测试结果

以下是官方yolov5模型转换后在coco2017数据集测试的结果,测试平台为 CV1811h_wevb_0007a_spinor

以下测试使用阈值为:

  • conf_thresh: 0.001

  • nms_thresh: 0.65

输入分辨率均为 640 x 640

yolov5s模型的官方导出导出方式性能:

测试平台

推理耗时 (ms)

带宽 (MB)

ION(MB)

MAP 0.5

MAP 0.5-0.95

pytorch

N/A

N/A

N/A

56.8

37.4

cv180x

ion分配失败

ion分配失败

32.61

量化失败

量化失败

cv181x

92.8

100.42

16.01

量化失败

量化失败

cv182x

69.89

102.74

16

量化失败

量化失败

cv183x

25.66

73.4

14.44

量化失败

量化失败

cv186x

10.50

132.89

23.11

量化失败

量化失败

yolov5s模型的TDL_SDK导出方式性能:

测试平台

推理耗时 (ms)

带宽 (MB)

ION(MB)

MAP 0.5

MAP 0.5-0.95

onnx

N/A

N/A

N/A

55.4241

36.6361

cv180x

ion分配失败

ion分配失败

22.02

ion分配失败

ion分配失败

cv181x

87.76

85.74

15.8

54.204

34.3985

cv182x

65.33

87.99

15.77

54.204

34.3985

cv183x

22.86

58.38

14.22

54.204

34.3985

cv186x

5.72

69.48

15.13

52.44

33.37

yolov5m模型的官方导出导出方式性能:

测试平台

推理耗时 (ms)

带宽 (MB)

ION(MB)

MAP 0.5

MAP 0.5-0.95

pytorch

N/A

N/A

N/A

64.1

45.4

cv180x

ion分配失败

ion分配失败

80.07

量化失败

量化失败

cv181x

ion分配失败

ion分配失败

35.96

量化失败

量化失败

cv182x

180.85

258.41

35.97

量化失败

量化失败

cv183x

59.36

137.86

30.49

量化失败

量化失败

cv186x

24.44

241.48

39.26

量化失败

量化失败

yolov5m模型的TDL_SDK导出方式性能:

测试平台

推理耗时 (ms)

带宽 (MB)

ION(MB)

MAP 0.5

MAP 0.5-0.95

onnx

N/A

N/A

N/A

62.770

44.4973

cv180x

ion分配失败

ion分配失败

79.08

ion分配失败

ion分配失败

cv181x

ion分配失败

ion分配失败

35.73

ion分配失败

ion分配失败

cv182x

176.04

243.62

35.74

61.5907

42.0852

cv183x

56.53

122.9

30.27

61.5907

42.0852

cv186x

23.28

218.11

36.81

61.54

42.00