端侧部署YOLOv5模型

导出 ONNX模型

python export.py --weights runs/train/exp2/weights/

NPU不支持动态输入,使用onnxim工具进行转换为固定输入,先安装onnxsim工具。

pip install onnxsim -i https://pypi.doubanio.com/simple/

接着进行转换

python -m onnxsim runs/train/exp2/weights/best.onnx yolov5s-sim.onnx --input-shape 1,3,640,640

模型裁剪

在实际端侧中,NPU端量化的后处理运算不适合使用uint8量化,一般使用float的混合量化,但这样相对麻烦,本文示例将后处理放在CPU测进行,所以我们需要把下图中的后处理部分裁剪掉。

import onnx

onnx.utils.extract_model('./yolov5s-mask.onnx', './yolov5s-mask-rt.onnx', ['images'], ['/model.24/Reshape_output_0','/model.24/Reshape_8_output_0','/model.24/Reshape_16_output_0'])

使用上面的python可以进行裁剪,输入为[images],输出为3个节点,下面是最后一个节点的示例截图。实际要根据模型文件进行调整。

python extrat-mask.py

接着使用上面的命令,就输出了裁剪后的模型yolov5s-mask-rt.onnx。

如果原生的模型没有将后处理裁剪,输入输出如下:

输出的tensor[1,25200,85],其中25200=3x(20x20+40x40+80x80),即3个特征图一共25200个先验框。

原生模型使用裁剪后使用netron.app打开得到输入输出如下:

YOLOv5模型参数含义详细解释如下:

  • 模型名称: 表示模型或图结构的名称,此处为从主图结构中提取的名称。
  • 输入参数:float32[1,3,640,640],表示输入张量的数据类型和维度。1表示一次处理的样本,3为通道数,输入的高宽均为640,此前YOLOv3是416。
  • 输出参数:有3个输出张量。
    -- /model.24/Reshape_output_0:表示8倍降采样率,输出为float32[1,3,85,80,80],网格划分为80x80,每个网格有3给先验框,每个先验框预测包含85个元素(坐标4、置信度1、类别80)。
    -- /model.24/Reshape_8_output_0:float32[1,3,85,40,40],网格尺寸为40x40。
    -- /model.24/Reshape_16_output_0:float32[1,3,85,20,20],网格尺寸为20x20。
  • 模型参数数量:parameter参数为7225908,表示模型中参数的总数,这是模型复杂度和计算资源需求的重要指标。

再来看看此次针对口罩微调后裁剪后的模型输入输出,使用netron.app打开得到输入输出如下:

上图可以看到,网格划分、先验框数量是一样的,不一样的是预测的元素为8(坐标4,置信度1,类别3)。

创建端侧转换环境

sudo docker images
sudo docker run --ipc=host -itd -v /home/xxx/ai/docker_data:/workspace --name laumy_npu_v1.8.x ubuntu-npu:v1.8.11 /bin/bash
sudo docker ps -a
sudo docker exec -it 55f9cd9eb15e /bin/bash

在进行端侧部署前,需要准备量化环境,这里直接使用的是docker环境。

模型转换

创建目录

|-- data
|   |-- maksssksksss0.png
|   |-- maksssksksss1.png
|   |-- maksssksksss10.png
|   |-- maksssksksss11.png
|   |-- maksssksksss12.png
|   |-- maksssksksss13.png
|   |-- maksssksksss14.png
|   |-- maksssksksss15.png
|   |-- maksssksksss16.png
|   |-- maksssksksss17.png
|   |-- maksssksksss18.png
|   |-- maksssksksss19.png
|   |-- maksssksksss2.png
|   |-- maksssksksss3.png
|   |-- maksssksksss4.png
|   |-- maksssksksss5.png
|   |-- maksssksksss6.png
|   |-- maksssksksss7.png
|   |-- maksssksksss8.png
|   `-- maksssksksss9.png
|-- dataset.txt
`-- yolov5s-mask-rt.onnx

准备数据量化的数据data、数据配置dataset.txt(内容如下)、裁剪的模型yolov5s-mask-rt.onnx。

./data/maksssksksss0.png
./data/maksssksksss1.png
./data/maksssksksss2.png
./data/maksssksksss3.png
./data/maksssksksss4.png
./data/maksssksksss5.png
./data/maksssksksss6.png
./data/maksssksksss7.png
./data/maksssksksss8.png
./data/maksssksksss9.png
./data/maksssksksss10.png
./data/maksssksksss11.png
./data/maksssksksss12.png
./data/maksssksksss13.png
./data/maksssksksss14.png
./data/maksssksksss15.png
./data/maksssksksss16.png
./data/maksssksksss17.png
./data/maksssksksss18.png
./data/maksssksksss19.png

模型导入

pegasus import onnx --model yolov5s-mask-rt.onnx --output-data yolov5s-mask-rt.data --output-model yolov5s-mask-rt.json

模型导入将输出yolov5s-mask-rt.json和yolov5s-mask-rt.data文件。前者为后期量化需要的网络结构文件,后者为模型网络权重文件。

前后处理配置文件yml

生成前处理配置文件
pegasus generate inputmeta --model yolov5s-mask-rt.json --input-meta-output yolov5s-mask-rt_inputmeta.yml

生成后处理配置文件,因为后处理是在cpu上处理并且我们已经裁剪掉了onn模型的后处理,可以不生成。
pegasus generate postprocess-file --model yolov5s-mask-rt.json --postprocess-file-output yolov5s-mask-rt_postprocess_file.yml

上面根据模型网络结构文件yolov5s-mask-rt.json转化生成后续量化需要的前处理和后处理描述文件,格式为yml格式。

input_meta:
  databases:
  - path: dataset.txt #表示模型量化需要的数据描述文件
    type: TEXT
    ports:
    - lid: images_205  #表示输入节点名称。
      category: image
      dtype: float32
      sparse: false
      tensor_name:
      layout: nchw #输入数据的排列格式,n表示batch,c表示channel,h表示高,w表示宽
      shape:  #模型输入的形状
      - 1     #输入数据的batch,如果后面量化的batch参数不为1,需要改这里。
      - 3
      - 640
      - 640
      fitting: scale
      preprocess:
        reverse_channel: true
        mean:
        - 0
        - 0
        - 0
        scale:  #3通道的缩放值,yolov5s需要改为0.00392157
        - 1.0
        - 1.0
        - 1.0
        preproc_node_params:
          add_preproc_node: false #是否添加预处理节点,用于格式转化和裁剪,这里要改为true
          preproc_type: IMAGE_RGB #预处理输入的格式
          preproc_image_size:
          - 640
          - 640
          preproc_crop:
            enable_preproc_crop: false
            crop_rect:
            - 0
            - 0
            - 640
            - 640
          preproc_perm:
          - 0
          - 1
          - 2
          - 3
      redirect_to_output: false

对于模型的输入yml,需要将scale修改为0.00392157,同时默认使用cpu预处理图像,所以add_preproc_node设置为true。

量化

pegasus quantize --model yolov5s-mask-rt.json --model-data yolov5s-mask-rt.data --device CPU --iterations=12 --with-input-meta yolov5s-mask-rt_inputmeta.yml --rebuild --model-quantize yolov5s-mask-rt.quantize --quantizer asymmetric_affine --qtype uint8

下面是量化模型的参数解析:

  • model: 模型的网络结构文件
  • model-data:模型需要的权重文件
  • with-input-meta:模型需要前处理配置文件
  • model-quantize: 输出的模型文件
  • iterations: 模型量化使用的数据量,设置1(默认)使用dataset第一条数据。若设置20,会先遍历dataset.txt中的20行数据。
  • qtype:量化数据类型,有int8,uint8,int16等。
  • batch_size: 量化多少轮,如果不为1,需要改输入的yml文件shape,先用默认。

量化后,会生成量化文件yolov5s-mask-rt.quantize。

推理

pegasus inference --model yolov5s-mask-rt.json --model-data yolov5s-mask-rt.data --dtype quantized --model-quantize yolov5s-mask-rt.quantize --device CPU --with-input-meta yolov5s-mask-rt_inputmeta.yml --postprocess-file yolov5s-mask-rt_postprocess_file.yml

模型推理是为了验证量化后的模型效果,这步可省略。

导出模型

pegasus export ovxlib --model yolov5s-mask-rt.json --model-data yolov5s-mask-rt.data --dtype quantized --model-quantize yolov5s-mask-rt.quantize --save-fused-graph --target-ide-project 'linux64' --with-input-meta yolov5s-mask-rt_inputmeta.yml --output-path ovxilb/yolov5s-mask-rt/yolov5s-simprj --pack-nbg-unify --postprocess-file yolov5s-mask-rt_postprocess_file.yml --optimize "VIP9000PICO_PID0XEE" --viv-sdk ${VIV_SDK}

模型导出,最终会生成ovxilb/yolov5s-mask-rt_nbg_unify/network_binary.nb

端侧部署

修改端侧后处理的分类名和数量配置文件。

改完之后,执行./build_linux.sh -t \编译生成端侧的应用,将可执行应用推到设备端。

./yolov5 -b network_binary.nb -i mask-test.jpeg 
model_file=network_binary.nb, input=mask-test.jpeg, loop_count=1, malloc_mbyte=10 
input  0 dim 3 640 640 1, data_format=2, quant_format=0, name=input[0], none-quant
output 0 dim 80 80 8 3, data_format=0, name=uid_20000_sub_uid_1_out_0, none-quant
output 1 dim 40 40 8 3, data_format=0, name=uid_20001_sub_uid_1_out_0, none-quant
output 2 dim 20 20 8 3, data_format=0, name=uid_20002_sub_uid_1_out_0, none-quant
nbg name=network_binary.nb, size: 7196096. 
create network 0: 35626 us.
prepare network: 38972 us.
buffer ptr: 0xb6996000, buffer size: 1228800 
feed input cost: 94526 us.
network: 0, loop count: 1
run time for this network 0: 119081 us.
detection num: 1
 1:  94
draw objects time : 154 ms
destory npu finished. 
~NpuUint.

参考:

  1. https://v853.docs.aw-ol.com/en/npu/dev_npu/
  2. https://blog.csdn.net/weixin_42904656/article/details/127768309