华为Atlas500 yolov5模型部署全流程


 

python3.7.5安装(装在usr/local 以后复制到home目录)

    检查系统是否安装python依赖以及gcc等软件。

     
    分别使用如下命令检查是否安装gcc,make以及python依赖软件等。

        gcc --version
        make --version
        cmake --version
        g++ --version
        dpkg -l zlib1g| grep zlib1g| grep ii
        dpkg -l zlib1g-dev| grep zlib1g-dev| grep ii
        dpkg -l libsqlite3-dev| grep libsqlite3-dev| grep ii
        dpkg -l openssl| grep openssl| grep ii
        dpkg -l libssl-dev| grep libssl-dev| grep ii
        dpkg -l libffi-dev| grep libffi-dev| grep ii
        dpkg -l unzip| grep unzip| grep ii
        dpkg -l pciutils| grep pciutils| grep ii
        dpkg -l net-tools| grep net-tools| grep ii

     

    若分别返回如下信息则说明已经安装,进入下一步。

        gcc (Ubuntu 7.3.0-3ubuntu1~18.04) 7.3.0
        g++ (Ubuntu 7.3.0-3ubuntu1~18.04) 7.3.0
        GNU Make 4.1
        cmake version 3.10.2
        zlib1g:amd64   1:1.2.11.dfsg-0ubuntu2 amd64        compression library - runtime
        zlib1g-dev:amd64 1:1.2.11.dfsg-0ubuntu2 amd64        compression library - development
        libsqlite3-dev:amd64 3.22.0-1ubuntu0.3 amd64        SQLite 3 development files
        openssl        1.1.1-1ubuntu2.1~18.04.5 amd64        Secure Sockets Layer toolkit - cryptographic utility
        libssl-dev:amd64 1.1.1-1ubuntu2.1~18.04.5 amd64        Secure Sockets Layer toolkit - development files
        libffi-dev:amd64 3.2.1-8      amd64        Foreign Function Interface library (development files)
        unzip          6.0-21ubuntu1 amd64        De-archiver for .zip files
        pciutils       1:3.5.2-1ubuntu1.1 amd64        Linux PCI Utilities
        net-tools      1.60+git20161116.90da8a0-1ubuntu1 amd64        NET-3 networking toolkit

     
    否则请执行如下安装命令
sudo apt-get install -y gcc g++ make cmake zlib1g zlib1g-dev libsqlite3-dev openssl libssl-dev libffi-dev unzip pciutils net-tools


一、ATC模型转换
1 yolov5
(1)pt文件转换为onnx文件
下载ultralytics-5.0(软件包名为yolov5-5.0.tar.gz)。
wget https://github.com/ultralytics/yolov5/archive/v5.0.tar.gz
tar -xzf yolov5-5.0.tar.gz
vi yolov5-5.0/models/export.py
:set number
i
# 修改第77行opset_version为11
:wq!

在yolov5-5.0目录运行如下命令:
python models/export.py --weights ./yolov5s.pt --img 640 --batch 1
运行结果:生成yolov5s.onnx文件。

(2)onnx文件简化及算子处理
首先对导出的onnx图使用onnx-simplifer工具进行简化。在yolov5-5.0目录运行如下命令:
python -m onnxsim --skip-optimization yolov5s.onnx yolov5s_sim.onnx
运行结果:生成yolov5s_sim.onnx文件。
(3)然后利用附件脚本yolov5_demo/models/yolov5/modify_yolov5s_slice.py修改模型Slice算子。将附件脚本上传yolov5-5.0目录,运行如下命令:(查看netron视图编号是否一致,不一致需要改算子编号)

import sys
import onnx

INT_MAX = sys.maxsize

model_path = sys.argv[1]
model = onnx.load(model_path)

def get_node_by_name(nodes, name: str):
    for n in nodes:
        if n.name == name:
            return n
    return -1

"""
before:
           input
         /      \
slice4 slice14 slice24 slice34
   |      |       |       |
slice9 slice19 slice29 slice39
     \    \    /     /
           concat
after:
           input
         /      \
    slice4       slice24
      |             |
      t             t
   /    \         /     \
slice9 slice19 slice29 slice39
  |       |      |        |
  t       t      t        t
      \    \    /    /
           concat
"""
model.graph.node.remove(get_node_by_name(model.graph.node, "Slice_24"))
model.graph.node.remove(get_node_by_name(model.graph.node, "Slice_34"))

prob_info1 = onnx.helper.make_tensor_value_info('to_slice9', onnx.TensorProto.FLOAT, [1, 3, 640, 320])
prob_info3 = onnx.helper.make_tensor_value_info('to_slice19', onnx.TensorProto.FLOAT, [1, 3, 640, 320])
prob_info5 = onnx.helper.make_tensor_value_info('from_slice9', onnx.TensorProto.FLOAT, [1, 3, 320, 320])
prob_info6 = onnx.helper.make_tensor_value_info('from_slice19', onnx.TensorProto.FLOAT, [1, 3, 320, 320])
prob_info7 = onnx.helper.make_tensor_value_info('from_slice29', onnx.TensorProto.FLOAT, [1, 3, 320, 320])
prob_info8 = onnx.helper.make_tensor_value_info('from_slice39', onnx.TensorProto.FLOAT, [1, 3, 320, 320])
# slice4 slice24后的Transpose
node1 = onnx.helper.make_node(
    'Transpose',
    inputs=['131'],
    outputs=['to_slice9'],
    perm=[0, 1, 3, 2]
)
node3 = onnx.helper.make_node(
    'Transpose',
    inputs=['141'],
    outputs=['to_slice19'],
    perm=[0, 1, 3, 2]
)
# slice9 slice19 slice29 slice39后的Transpose
node5 = onnx.helper.make_node(
    'Transpose',
    inputs=['from_slice9'],
    outputs=['136'],
    perm=[0, 1, 3, 2]
)
node6 = onnx.helper.make_node(
    'Transpose',
    inputs=['from_slice19'],
    outputs=['146'],
    perm=[0, 1, 3, 2]
)
node7 = onnx.helper.make_node(
    'Transpose',
    inputs=['from_slice29'],
    outputs=['156'],
    perm=[0, 1, 3, 2]
)
node8 = onnx.helper.make_node(
    'Transpose',
    inputs=['from_slice39'],
    outputs=['166'],
    perm=[0, 1, 3, 2]
)
model.graph.node.append(node1)
model.graph.node.append(node3)
model.graph.node.append(node5)
model.graph.node.append(node6)
model.graph.node.append(node7)
model.graph.node.append(node8)

# slice9 slice19 换轴
model.graph.initializer.append(onnx.helper.make_tensor('starts_9', onnx.TensorProto.INT64, [1], [0]))
model.graph.initializer.append(onnx.helper.make_tensor('ends_9', onnx.TensorProto.INT64, [1], [INT_MAX]))
model.graph.initializer.append(onnx.helper.make_tensor('axes_9', onnx.TensorProto.INT64, [1], [2]))
model.graph.initializer.append(onnx.helper.make_tensor('steps_9', onnx.TensorProto.INT64, [1], [2]))
newnode1 = onnx.helper.make_node(
    'Slice',
    name='Slice_9',
    inputs=['to_slice9', 'starts_9', 'ends_9', 'axes_9', 'steps_9'],
    outputs=['from_slice9'],
)
model.graph.node.remove(get_node_by_name(model.graph.node, "Slice_9"))
model.graph.node.insert(9, newnode1)
newnode2 = onnx.helper.make_node(
    'Slice',
    name='Slice_19',
    inputs=['to_slice19', 'starts_9', 'ends_9', 'axes_9', 'steps_9'],
    outputs=['from_slice19'],
)
model.graph.node.remove(get_node_by_name(model.graph.node, "Slice_19"))
model.graph.node.insert(19, newnode2)

# slice29 slice39 换轴
model.graph.initializer.append(onnx.helper.make_tensor('starts_29', onnx.TensorProto.INT64, [1], [1]))
model.graph.initializer.append(onnx.helper.make_tensor('ends_29', onnx.TensorProto.INT64, [1], [INT_MAX]))
model.graph.initializer.append(onnx.helper.make_tensor('axes_29', onnx.TensorProto.INT64, [1], [2]))
model.graph.initializer.append(onnx.helper.make_tensor('steps_29', onnx.TensorProto.INT64, [1], [2]))
newnode3 = onnx.helper.make_node(
    'Slice',
    name='Slice_29',
    inputs=['to_slice9', 'starts_29', 'ends_29', 'axes_29', 'steps_29'],
    outputs=['from_slice29'],
)
model.graph.node.remove(get_node_by_name(model.graph.node, "Slice_29"))
model.graph.node.insert(29, newnode3)
newnode4 = onnx.helper.make_node(
    'Slice',
    name='Slice_39',
    inputs=['to_slice19', 'starts_29', 'ends_29', 'axes_29', 'steps_29'],
    outputs=['from_slice39'],
)
model.graph.node.remove(get_node_by_name(model.graph.node, "Slice_39"))
model.graph.node.insert(39, newnode4)


# onnx.checker.check_model(model)
onnx.save(model, sys.argv[1].split('.')[0] + "_t.onnx")


python modify_yolov5s_slice.py yolov5s_sim.onnx
运行结果:生成yolov5s_sim_t.onnx文件。
注意:5.0的modify_yolov5s_slice.py和2.0 3.0的都不一样


2 Unet
把训练好的模型转onnx。# Copyright 2021 Huawei Technologies Co., Ltd


import torch
import torch.onnx
import sys
sys.path.append(r"./pytorch-ssd")
from modeling.unet import *


def pth2onx(model_path, out_path):

    model = Unet(n_channels=3, n_classes=6)
#参考代码改
    print("begin to load model")
    ckpt = torch.load(model_path, map_location='cpu')
    model.load_state_dict(ckpt['state_dict'])
    #model = model.cuda()
    input_names = ["image"]
    dummy_input = torch.randn(1, 3, 256, 256)
    print("begin to export")
    torch.onnx.export(model, dummy_input, out_path, input_names=input_names,
                      opset_version=11, verbose=True)
    print("end export")


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('Usage: python SSD_MobileNet_pth2onnx.py  ')
        sys.exit(0)

    model_path = sys.argv[1]
    out_path = sys.argv[2]
    pth2onx(model_path, out_path)


对导出的onnx图使用onnx-simplifer工具进行简化。
python -m onnxsim unet .onnx unet_sim.onnx


公司的unet模型没有多余的padding算子,不需要特殊操作。
如果使用官网的模型,需要处理掉多余的padding算子,用netron查看对应的算子编号逐个修改。否则转om会算子报错padv3不支持。
import onnx

def GetNodeIndex(graph, node_name):
    index = 0
    for i in range(len(graph.node)):
        if graph.node[i].name == node_name:
            index = i
            break
    return index
    
    
model = onnx.load("unet_sat_sim.onnx")
model.graph.node[GetNodeIndex(model.graph,'Concat_107')].input[1] = '275'
node_list = ["Pad_106"]
max_idx = len(model.graph.node)
rm_cnt = 0
for i in range(len(model.graph.node)):
    if i < max_idx:
        n = model.graph.node[i - rm_cnt]
        if n.name in node_list:
            print("remove {} total {}".format(n.name, len(model.graph.node)))
            model.graph.node.remove(n)
            max_idx -= 1
            rm_cnt += 1
            
model.graph.node[GetNodeIndex(model.graph,'Concat_85')].input[1] = '239'
node_list = ["Pad_84"]
max_idx = len(model.graph.node)
rm_cnt = 0
for i in range(len(model.graph.node)):
    if i < max_idx:
        n = model.graph.node[i - rm_cnt]
        if n.name in node_list:
            print("remove {} total {}".format(n.name, len(model.graph.node)))
            model.graph.node.remove(n)
            max_idx -= 1
            rm_cnt += 1            
model.graph.node[GetNodeIndex(model.graph,'Concat_63')].input[1] = '203'
node_list = ["Pad_62"]
max_idx = len(model.graph.node)
rm_cnt = 0
for i in range(len(model.graph.node)):
    if i < max_idx:
        n = model.graph.node[i - rm_cnt]
        if n.name in node_list:
            print("remove {} total {}".format(n.name, len(model.graph.node)))
            model.graph.node.remove(n)
            max_idx -= 1
            rm_cnt += 1              
model.graph.node[GetNodeIndex(model.graph,'Concat_41')].input[1] = '167'
node_list = ["Pad_40"]
max_idx = len(model.graph.node)
rm_cnt = 0
for i in range(len(model.graph.node)):
    if i < max_idx:
        n = model.graph.node[i - rm_cnt]
        if n.name in node_list:
            print("remove {} total {}".format(n.name, len(model.graph.node)))
            model.graph.node.remove(n)
            max_idx -= 1
            rm_cnt += 1  
onnx.checker.check_model(model)
onnx.save(model, "unet_sat_sim_final.onnx")

二、下载ATC工具。
1 安装 Python3开发环境
安装前请先使用pip3.7.5 list命令检查是否安装相关依赖,若已经安装,则请跳过该步骤;若未安装,则安装命令如下(如果只有部分软件未安装,则如下命令修改为只安装还未安装的软件即可)。其中:toolkit包中算子比对工具依赖:protobuf、scipy;profiling工具依赖:protobuf、grpcio、grpcio-tools、requests。

如果使用非root用户安装Python及其依赖,用户需要在本步骤中的每句命令结尾加上--user,保证安装的正常进行。命令示例为:pip3.7.5 install attrs --user

pip3.7.5 install attrs psutil decorator numpy protobuf==3.11.3 scipy sympy cffi grpcio grpcio-tools requests --user -i https://pypi.tuna.tsinghua.edu.cn/simple

    pip3.7.5 install attrs
    pip3.7.5 install psutil
    pip3.7.5 install decorator
    pip3.7.5 install numpy
    pip3.7.5 install protobuf==3.11.3
    pip3.7.5 install scipy
    pip3.7.5 install sympy
    pip3.7.5 install cffi
    pip3.7.5 install grpcio
    pip3.7.5 install grpcio-tools
    pip3.7.5 install requests
2 安装环境依赖

我们是在普通用户下安装的,首先确保当前环境中有一个普通用户和一个root用户,如果是新建的虚拟机需要先给root用户配置密码后才可以正常登录root用户(sudo passwd root)。以下安装普通用户以ascend举例。

    用户权限配置。
    普通用户安装开发套件,需要有sudo权限,所以首先需要给普通用户配置权限。

    切换为root用户。
    su root

    给sudoer文件配置写权限,并打开该文件。
    chmod u+w /etc/sudoers
    vi /etc/sudoers

    在该文件“ # User privilege specification”下面增加如下内容:
其中,ascend为开发环境种普通用户用户名,需要根据自己的环境修改。

配置源
源配置。
由于安装过程中涉及很多apt依赖和pip依赖的安装,所以配置一个国内源是一个加快进度的好办法。

    配置ubuntu18.04-x86的apt清华源。
    root用户下打开apt源文件。
    vi /etc/apt/sources.list

    将源文件内容替换为以下ubuntu18.04-x86的apt清华源。

        root@ubuntu:/home/ascend# cat /etc/apt/sources.list
        # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
        deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
        # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
        deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
        # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
        deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
        # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
        deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
        # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
        root@ubuntu:/home/ascend#

安装相关apt依赖。
普通用户下安装,这些是开发环境中套件包所依赖的一些apt软件,都需要成功安装。
sudo apt-get install -y gcc make cmake unzip zlib1g zlib1g-dev libsqlite3-dev openssl libssl-dev libffi-dev pciutils net-tools
3 安装python环境(装在主目录)
执行以下命令,进入普通用户家目录。
cd $HOME

下载python3.7.5源码包并解压。
wget https://www.python.org/ftp/python/3.7.5/Python-3.7.5.tgz
tar -zxvf Python-3.7.5.tgz

进入解压后的文件夹,执行配置、编译和安装命令。
cd Python-3.7.5
./configure --prefix=/usr/local/python3.7.5 --enable-shared
make
sudo make install

执行以下命令将so拷贝到lib中,并设置软链接。
sudo cp /usr/local/python3.7.5/lib/libpython3.7m.so.1.0 /usr/lib
sudo ln -s /usr/local/python3.7.5/bin/python3 /usr/bin/python3.7
sudo ln -s /usr/local/python3.7.5/bin/pip3 /usr/bin/pip3.7
sudo ln -s /usr/local/python3.7.5/bin/python3 /usr/bin/python3.7.5
sudo ln -s /usr/local/python3.7.5/bin/pip3 /usr/bin/pip3.7.5

执行以下命令,安装环境所需的相关pip依赖。
pip3.7.5 install attrs psutil decorator numpy protobuf==3.11.3 scipy sympy cffi grpcio grpcio-tools requests --user
4 安装toolkit开发工具包 华为官网下载

https://support.huawei.com/enterprise/zh/ascend-computing/cann-pid-251168373/software

将包放置到开发环境普通用户的$HOME目录下。
并执行以下命令,切换到普通用户的$HOME目录下。
cd $HOME
ascend
执行以下命令,给run包增加可执行权限。
chmod 755 *.run

执行以下命令,安装toolkit包。
./Ascend-Toolkit-20.0.0.RC1-x86_64-linux_gcc7.3.0.run –install

三、使用ATC工具把onnx转化成om文件(避免版本问题,使用华为主机ATC,无需配置环境变量)
1 配置环境变量
export PATH=/usr/local/python3.7.5/bin:/home/wyc/Ascend/ascend-toolkit/latest/atc/ccec_compiler/bin:/home/wyc/Ascend/ascend-toolkit/latest/atc/bin:$PATH
export PYTHONPATH=/home/wyc/Ascend/ascend-toolkit/latest/atc/python/site-packages:/home/wyc/Ascend/ascend-toolkit/latest/atc/python/site-packages/auto_tune.egg/auto_tune:$/home/wyc/Ascend/ascend-toolkit/latest/atc/python/site-packages/schedule_search.egg
export export LD_LIBRARY_PATH=/home/wyc/Ascend/ascend-toolkit/latest/lib64:/home/wyc/Ascend/ascend-toolkit/latest/compiler/lib64/plugin/opskernel:/home/wyc/Ascend/ascend-toolkit/latest/compiler/lib64/plugin/nnengine:$LD_LIBRARY_PATH:${HOME}/Ascend/ascend-toolkit/latest/atc/lib64/stub
export SLOG_PRINT_TO_STDOUT=1
export ASCEND_AICPU_PATH=/home/wyc/Ascend/ascend-toolkit/latest
export ASCEND_OPP_PATH=/home/wyc/Ascend/ascend-toolkit/latest/opp
export TOOLCHAIN_HOME=/home/wyc/Ascend/ascend-toolkit/latest/toolkit
export ASCEND_AUTOML_PATH=/home/wyc/Ascend/ascend-toolkit/latest/tools

2 运行atc指令(如果以root身份运行,也要安装相关依赖)
atc --model=yolov5s_sim_t.onnx --framework=5 --output=yolov5s --input_format=NCHW --input_shape="images:1,3,640,640" --enable_small_channel=1 --insert_op_conf=aipp_yolov5.cfg --soc_version=Ascend310 –log=info
\atc --model=unet_sim_t.onnx --framework=5 –output=unet --input_format=NCHW --input_shape="images:1,3,480,480" --enable_small_channel=1  --soc_version=Ascend310 –log=info

参数说明:
--model:待转换的ONNX模型。
--framework:5代表ONNX模型。
--output:输出的om模型。
--input_format:输入数据的格式。
--input_shape:输入数据的shape。
--insert_op_conf=./aipp_yolov5.cfg:AIPP插入节点,通过config文件配置算子信息,功能包括图片色域转换、裁剪、归一化,主要用于处理原图输入数据,常与DVPP配合使用,详见下文数据预处理。

详细ATC命令转换学习请参考:
https://support.huaweicloud.com/atctool-cann502alpha5infer/atlasatc_16_0001.html

PS:unet 模型的预处理要和python 端的一致(归一化用aipp做)
示例  yolov5
aipp_op {
    aipp_mode : static
    related_input_rank : 0
    input_format : YUV420SP_U8     //输入格式,经过dvpp解码是yuv格式,这里转rgb

    src_image_size_w : 640
    src_image_size_h : 640 //尺寸一样
    crop : false
    csc_switch : true
    rbuv_swap_switch : false// 这里可以控制输出rgb或者bgr,官网手册有各种转化模板
    matrix_r0c0 : 256
    matrix_r0c1 : 0
    matrix_r0c2 : 359//旋转矩阵
    matrix_r1c0 : 256
    matrix_r1c1 : -88
    matrix_r1c2 : -183
    matrix_r2c0 : 256
    matrix_r2c1 : 454
    matrix_r2c2 : 0
    input_bias_0 : 0
    input_bias_1 : 128
    input_bias_2 : 128
    var_reci_chn_0 : 0.0039216
    var_reci_chn_1 : 0.0039216
    var_reci_chn_2 : 0.0039216 //归一化的方差倒数,这里只做了归一化没有做标准化,unet都要作
}

unet示例
aipp_op {
    aipp_mode : static
    related_input_rank : 0
    input_format : YUV420SP_U8

    src_image_size_w : 512
    src_image_size_h : 512
    crop : false
    csc_switch : true
    rbuv_swap_switch : false
    matrix_r0c0 : 256
    matrix_r0c1 : 0
    matrix_r0c2 : 359
    matrix_r1c0 : 256
    matrix_r1c1 : -88
    matrix_r1c2 : -183
    matrix_r2c0 : 256
    matrix_r2c1 : 454
    matrix_r2c2 : 0
    input_bias_0 : 0
    input_bias_1 : 128
    input_bias_2 : 128
    mean_chn_0: 0
    mean_chn_1: 0
    mean_chn_2: 0
    mean_chn_3: 0//每个通道的均值
    min_chn_0: 0.0
    min_chn_1: 0.0
    min_chn_2: 0.0
    min_chn_3: 0.0//每个通道减去的数值
    var_reci_chn_0 : 0.0039216
    var_reci_chn_1 : 0.0039216
    var_reci_chn_2 : 0.0039216
}

四、模型推理
1 修改
run.sh中MX_SDK_HOME为MindX SDK安装目录
export MX_SDK_HOME=/root/MindX_SDK/mxVision

2、执行run.sh
bash run.sh

五、YoLoV5 demo详解

1 技术流程图


视频解码:调用DVPP解码能力,转换为 YUV 格式图像数据。
图像缩放:调用DVPP,将图像缩放到一定尺寸大小。
目标检测:YoLoV5模型针对图像进行目标检测。
模型后处理:针对推理结果进行后处理文字转换。
数据序列化:将stream结果组装成json字符串输出。

2 pipeline详解

参数说明:
modelPath:模型路径,请根据模型实际路径修改。
postProcessConfigPath:模型配置文件路径,请根据模型配置文件的实际路径修改。
labelPath:标签文件路径,请根据标签文件的实际路径修改。

3 源码详解
main.cpp

你可能感兴趣的:(服务器,linux,python,华为云)