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找到下图对应的文件夹:

下载之后,解压压缩包并将 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. 编译说明¶
获取交叉编译工具
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
下载 TDL SDK
tdlsdk工具包的下载站台:sftp://218.17.249.213 帐号:cvitek_mlir_2023 密码:7&2Wd%cu5k。 我们将cvitek_tdl_sdk_1227.tar.gz下载下来。
编译TDL SDK
我们进入到cvitek_tdl_sdk下的sample目录下。
chmod 777 compile_sample.sh ./compile_sample.sh
编译完成之后,可以连接开发板并执行程序:
开发板连接网线,确保开发板和电脑在同一个网关
电脑通过串口连接开发板,波特率设置为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,路径同样修改为自己的路径
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卡的路径即可
运行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 |