经过前面几个步骤,模型部署的相关环境以及必要条件都已准备就绪,接下来我使用自己的模型做部署测试:

1.模型转换

我这里使用的模型为一个人脸检测模型,由于模型为公司内部模型未开源,所以这里就演示整个开发的过程,其他模型同理;
此模型为一输入两输出结构的网络,模型格式为.onnx,依次进行以下操作:
(1)导入模型:

pegasus import onnx --model face.onnx --output-model face.json --output-data face.data

(2)生成yml文件:
输入预处理yml文件

pegasus generate inputmeta --model face.json --input-meta-output face_inputmeta.yml

输出后处理yml文件

pegasus generate postprocess-file --model face.json --postprocess-file-output face_postprocess_file.yml

这两个文件要根据自己的模型做相应修改,一般是修改inputmeta.yml,也就是根据自己的模型的预处理的不同来修改;这里的我的模型预处理主要是做了减均值,然后输入格式为uint8格式,所以我做了下面两处修改:

b85bios添加nvme b85m-e nvme_人工智能


(3)量化:

在量化之前要准备好dataset.txt文件,这里我准备了一张图片放入到dataset.txt中(我尝试放入多张图片,并且对应设置–batch-size为图片数,但是运行下面指令就会报错,暂时还不知道是啥原因),格式为:

./val/1.jpg 0

同时在同级文件夹下val文件夹中放入1.jpg图片,然后设置–batch-size为1:

pegasus quantize --model face.json --model-data face.data --batch-size 1 --device CPU --with-input-meta face_inputmeta.yml --rebuild --model-quantize face.quantize --quantizer asymmetric_affine --qtype uint8

(4)预推理:
预推理的目的是按照当前所得的模型以及预处理、后处理yml文件,对dataset.txt中所列的图片做推理得到图片的输入数据以及推理后的输出数据,以此可以在软件仿真时,或者部署时提供一个参照:

pegasus inference --model face.json --model-data face.data --batch-size 1 --device CPU --with-input-meta face_inputmeta.yml --postprocess-file face_postprocessmeta.yml

这会生成一个输入文件和两个输出文件。
(5)导出部署模型:
此步骤即可一键导出network_binary.nb格式的部署模型以及部署的模板代码:

pegasus export ovxlib --model face.json --model-data face.data --dtype quantized --model-quantize face.quantize --batch-size 1 --save-fused-graph --target-ide-project 'linux64' --with-input-meta face_inputmeta.yml --output-path ovxilb/face-simprj --pack-nbg-unify --postprocess-file face_postprocessmeta.yml --optimize "VIP9000PICO_PID0XEE" --viv-sdk ${VIV_SDK}

接下来将按照提供的模板代码写个部署demo:

2.模型部署

首先确定全志NPU提供的lib为VIPLite,上个步骤导出的模板代码中也都是调用的VIPLite的接口;建个工程,结构大概如下所示:

└── 3rd
    ├──libjpeg
   		├── include
   		└── lib
    └── viplite-driver
   		├── include
   		└── lib
├── build
├── CMakeLists.txt
├── inc
│   ├── image_utils.h
│   ├── ssd_helper.h
│   ├── vnn_global.h
│   ├── vnn_logicprocess.h
│   ├── vnn_postprocess.h
│   ├── vnn_preprocess.h
│   └── vnn_runtime.h
└── src
    ├── image_utils.cpp
    ├── main.cpp
    ├── vnn_logicprocess.cpp
    ├── vnn_postprocess.cpp
    ├── vnn_preprocess.cpp
    └── vnn_runtime.cpp

3rd中主要是存放使用到的两个库的头文件库文件,可以直接从全志提供的TinaSDK中copy就行;
CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.16)
project(v853demo)

set(CMAKE_SYSTEM_NAME Linux)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE "Release")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -pthread -fopenmp")

# set arm cross-compiler toolchain
set(TOOLCHAIN_PATH /home/wangxu/wangxu_test/tina-v853/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/arm-openwrt-linux-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/arm-openwrt-linux-g++)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rd/viplite-driver/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rd/libjpeg/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)

aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)

add_executable(test ${SRC_LIST})

target_link_libraries(test ${CMAKE_CURRENT_SOURCE_DIR}/3rd/viplite-driver/lib/libVIPlite.so
                            ${CMAKE_CURRENT_SOURCE_DIR}/3rd/viplite-driver/lib/libVIPuser.so
                            ${CMAKE_CURRENT_SOURCE_DIR}/3rd/libjpeg/lib/libjpeg.so.9
                        )

这里需要注意的主要就是交叉编译工具链的设置路径,对于Tina4.0的SDK,可以直接在SDK源码路径下ctoolchain即可一键进入工具链的路径中;
主函数:

int main(int argc, char **argv)
{
    vip_status_e status = VIP_SUCCESS;
    vip_uint32_t version = 0;
    vip_network_items *network_items = VIP_NULL;
    if (argc < 3)
    {
        cout << usage << endl;
        printf("Arguments count %d is incorrect!\n", argc);
        return -1;
    }

    VNNPreProcess *VNNPreP = new VNNPreProcess();
    VNNPostProcess *VNNPostP = new VNNPostProcess();
    VNNRuntime *VNNRun = new VNNRuntime();

    version = vip_get_version();
    printf("init vip lite, driver version=0x%08x...\n", version);

    _CHECK_IF_ERROR(vip_init(1*3*448*448));
    
    _CHECK_IF_ERROR(VNNPreP->vnn_InitNetworkItem(&network_items, argc, argv));

    _CHECK_IF_ERROR(VNNPreP->vnn_CreateNeuralNetwork(network_items));

    _CHECK_IF_ERROR(VNNPreP->vnn_PreProcessNeuralNetwork(network_items));

    _CHECK_IF_ERROR(VNNRun->vnn_RunNeuralNetwork(network_items));

    _CHECK_IF_ERROR(VNNPostP->vnn_PostProcessNeuralNetwork(network_items));

    _CHECK_IF_ERROR(VNNPostP->vnn_ReleaseNeuralNetwork(network_items));
    
    delete VNNPreP;
    delete VNNPostP;
    delete VNNRun;

    return status;
}

这里基本按照提供的模板代码结构来写的;

然后中间的代码基本和提供的模板代码相同,需要更改的地方:

(a)因为我使用的是图片作为直接输入,而模板代码是文本数据作为输入,此处可以换成libjpeg解码的函数,这个函数可以在TinaSDK中的vpm_run部分找到,就是一个jpeg解压缩的操作;

b85bios添加nvme b85m-e nvme_深度学习_02


(b)主要就是加上人脸检测的后处理部分代码,可以在vnn_PostProcessNeuralNetwork函数中添加,可以根据需要选择以txt格式存储,还是按照SSD后处理的方式处理out_data,然后紧接着做NMS等操作即可;

b85bios添加nvme b85m-e nvme_人工智能_03


运行代码:

首先在x86平台对程序进行编译生成可执行文件,然后将可执行文件push到v853板子上,同时也将上面第一步骤生成的network_binary.nb模型文件push到板子上;还要准备一张resize好的人脸图片(这里吐槽一下全志没有提供相关的硬件解码的resize api,只能事先做resize,这对结果的精度以及工程化应用不太友好),准备完毕执行代码:

./test network_binary.nb obama_resized.jpg

打印log如下:

b85bios添加nvme b85m-e nvme_深度学习_04


可以看到这里创建网络对象耗时24ms左右,前处理耗时54ms左右,推理耗时3.43ms,最后经过后处理的检测框以及置信度输出为[x = 188.916550, y = 38.408157, w = 112.608871, h = 115.183685, conf = 0.999512],我们使用python脚本,调用opencv的rectengle函数将检测框在原图中画出:

b85bios添加nvme b85m-e nvme_人工智能_05