1 前言
本文主要介绍在地平线开发板上针对resizer模型的使用与部署,如果您是一个初学者,对于该内容可能会有以下几点疑问:
- 什么是resizer?什么是resizer模型?
- 为什么要用resizer模型?
- 如何得到resizer模型?
- resizer模型如何部署?
下面我将以具体的示例来对以上几个问题进行解释。-
答1:resizer是指芯片上用于处理图像缩放的一个功能模块,可以把图像(nv12) 里的一个指定 ROI,缩放成指定的大小,缩放的方法是双线性插值,通过BPU的functioncall驱动。在一些人工智能业务场景中需要使用到该模块的算法模型,称之为resizer模型。-
答2:为了方便大家理解,以车辆检测与识别场景为例,如下图所示。先通过一个上游网络检测得到图片中不同车辆的位置信息,把检测到的车辆图片信息送入小网络进行识别前,需要先进行尺寸缩放,为了适配类似业务场景,加快图片尺寸缩放速度,地平线芯片上提供针对resizer模型的专门加速硬件模块。
答3:在模型转换时,将编译参数 input_source 设置为 {‘input_name’: ‘resizer’}即可生成resizer模型,注意:该参数配置为resizer时,inpurt_type_rt参数仅支持配置为nv12和gray,具体参数配置细节可参考 地平线算法工具链用户手册4.1.1.6. 模型量化与编译 中的介绍。-
答4:在编译得到resizer模型后,具体使用和部署细节将在第3节详细介绍。
**注:**文中用户手册目录和open_explorer开发包的目录结构,均以XJ3为例进行讲解,J5用户请在类似目录下进行实践。
2 resizer模型约束
resizer模型推理时,输入从DDR读取,会先使用roi从原始图像中抠图后resize到模型输入大小,输出存储在一块特殊的片上内存中,只有BPU可以读取,且读取的过程会改变数据的排布,所以resizer的输出只能送入BPU用作模型推理,上述过程中,一些约束信息如下表所示,
XJ3芯片平台:-
J5芯片平台:-
H和W方向的缩放倍数均有限制,以下src_len表示roi.size.h或roi.size.w。dst_len表示输出的h或w。-
step = ((src_len - 1) * 65536 + (dst_len - 1) / 2) / (dst_len - 1)-
step需要在范围[0, 262143]内,即缩放倍数在[1/4, 65536)之内。如果相关接口中有keep ratio选项,并且设置该参数为true。则限制需要以keep ratio之后的输出尺寸来考虑step限制。-
h_ratio = roi.size.h * 65536 / dest_h-
w_ratio = roi.size.w * 65536 / dest_w-
如果h_ratio>w_ratio,则需要在上述公式中dest_w调整为-
roi.size.w * dest_h / roi.size.h-
如果w_ratio>h_ratio,则需要在上述公式中dest_h调整为-
roi.size.h * dest_w / roi.size.w
resizer模型目前支持多输入的nv12数据,resizer常用的输出尺寸(HxW)如下:
- 128x128
- 128x64
- 64x128
- 160x96
3 resizer模型使用流程
以horizon_xj3_open_explorer_v2.6.2b-py38_20230606
开发包(简称OE包)里的roi_infer示例,路径为ddk/samples/ai_toolchain/horizon_runtime_sample/code/01_api_tutorial/roi_infer
做一个简要说明,使用的resizer模型是mobilenetv1_128x128_resizer_nv12.bin
,路径为/open_explorer/ddk/samples/ai_toolchain/model_zoo/runtime/mobilenetv1
。
3.1 所需文件结构说明
resizer模型示例是在horizon_runtime_sample目录下,该目录下需要用到的目录结构如下所示:
├── code # 开发机中编译示例源码-
│ ├── 01_api_tutorial # dnn API使用示例代码-
│ │ └──roi_infer # resizer模型推理示例-
│ ├── build_xj3.sh # xj3 ARM端编译脚本-
│ ├── CMakeLists.txt-
│ ├── deps-
│ │ └──aarch64 # xj3 ARM端编译依赖库-
├── xj3 # 板端运行目录结构-
│ ├── data # 预置图片数据文件-
│ ├── model # resizer模型文件-
│ │ └──model_name.bin # mobilenetv1_128x128_resizer_nv12.bin-
│ ├── script # ARM端示例运行脚本-
│ ├── 01_api_tutorial # dnn API使用示例代码-
│ └──roi_infer.sh # resizer板端运行脚本-
└── README.md
大家初次使用时,重点关注code/build_xj3.sh
和xj3/01_api_tutorial/roi_infer.sh
两个脚本即可。
3.2 环境准备
想要完成整个resizer模型的使用和部署,需要用到开发机和开发板,因此环境准备工作包含 开发机部署 和 开发板部署 两个部分,在OE包中,地平线分别为大家提供了开发机中docker环境部署脚本run_docker.sh和开发板端环境部署脚本install.sh,具体使用方式欢迎参考地平线AI工具链用户手册2.1 环境部署。
3.3 板端部署的主要流程
resizer模型板端部署的主要流程如下图所示,首先通过预测库中提供的API,加载resizer混合异构模型(model_name.bin),获取模型输入输出信息,准备输入输出数据的内存,在获取到nv12输入数据后,使用hbDNNRoiInfer接口进行模型推理,然后将推理得到的结果进行后处理,当然,这些操作完成后,还要对相关资源进行释放。-
整个过程中,与常规模型部署不同的是使用了hbDNNRoiInfer这个API进行模型推理,下面对该API的参数和返回值进行详细解读。
hbDNNRoiInfer()
int32_t hbDNNRoiInfer(hbDNNTaskHandle_t *taskHandle,
hbDNNTensor **output,
const hbDNNTensor *input,
hbDNNRoi *rois,
int32_t roiCount,
hbDNNHandle_t dnnHandle,
hbDNNInferCtrlParam *inferCtrlParam);
参数介绍:
[out] taskHandle:任务句柄指针。-
[in/out] output:推理任务的输出。-
[in] input:推理任务的输入。-
[in] rois:Roi框信息。-
[in] roiCount:Roi框数量。-
[in] dnnHandle:dnn句柄指针。-
[in] inferCtrlParam:控制推理任务的参数。
返回值:
返回 0 则表示API成功执行,否则执行失败。
关于该API更多注解欢迎参考 算法工具链用户手册5.2.3.4.2 hbDNNRoiInfer()
其中需要注意的有四个参数,分别是output, input, rois, roiCount,为了方便大家理解,下面举两个例子进行介绍。
例1:下游网络是 单输入单输出单batch模型 (model_batch=1,即模型实际推理时,输入给模型的 batch_size=1),且输入源为resizer时(在转换yaml中配置input_source参数为resizer),也就是OE包中所提供示例的情况,如下图所示,传感器采集到1张图片,经过上游网络后,获取到两个roi区域(roi0, roi1),也就是模型需要推理的数据批数data_batch=2。通过resizer模块处理后,送入下游网络,下游网络是一个单输入网络,输入为:input0(128x64),下游网络有一个输出,命名为:output0。
此时,推理任务的输入input可以依次表示为:
[roi0_input0, roi1_input0]
推理任务的输出Output可以表示为:
[Output0]
其中Output0中内容包括:roi0_output0, roi1_output0,可以理解为data_batch=2,model_batch=1的模型推理。
注意:hbDNNRoiInfer函数中的的输入input数据始终是上游网络的输入原图。
例2:下游网络是多输入多输出单batch模型(model_batch=1),且每个输入源均是resizer时,如下图所示,传感器采集到1张图片,经过上游网络后,获取到两个roi区域(roi0, roi1),通过resizer模块处理后,送入下游网络,下游网络是一个三输入网络,三个输入分别为:input0(128x128)、input1(128x64)、input2(64x128),下游网络有两个输出,分别命名为:output0、output1。
此时,推理任务的输入input可以依次表示为:
[roi0_input0, roi1_input1, roi2_input2, roi3_input0, roi4_input1, roi5_input2]
其中,[roi0_input0, roi1_input1, roi2_input2]作为data_batch0的数据完成一次推理,[roi3_input0, roi4_input1, roi5_input2]作为data_batch1的数据完成一次推理。-
推理任务的输出Output可以依次表示为:
[output0, output1]
其中Output0中内容包括:data_batch0_output0, data_batch1_output0,Output1中内容包括:data_batch0_output1, data_batch1_output1,因此,在准备输出内存时需要结合data_batch数量和对应输出分支的shape来进行分配。
例3:地平线工具链从OE1.1.68版本开始支持model_batch_resizer的场景,针对多data_batch的场景有一定的加速效果,举一个例子来介绍一下。假设模型有 3 个输入分支(2个resizer输入源,1个ddr输入源)和 1 个输出分支,并以 model_batch=2 编译,模型共需处理 3 批数据共 6 个 roi(即data_batch=3,每批数据有2个roi)。
此时模型推理这 3 批数据需要准备独立地址的 input_tensor 数量为 3个输入分支 x 3批数据 = 9。
另假设模型输入/输出的静态信息如下:
- 模型输入(model_info):
- tensor_0_resizer: [2, 3, 128, 128]
- tensor_1_resizer: [2, 3, 256, 256]
- tensor_2_ddr: [2, 80, 1, 100]
- 模型输出(model_info):
- tensor_out:[2, 100, 1, 56]
那么模型在推理时的动态信息则为:
- 模型输入(input_tensors):
- [1x3x128x128, 1x3x256x256, 1x80x1x100, 1x3x128x128, 1x3x256x256, 1x80x1x100, 1x3x128x128, 1x3x256x256, 1x80x1x100]
- 模型输出(output_tensors):
- [4x100x1x56]
其中,因为 model_batch = 2,所以底层 BPU 单次执行可处理 2 批数据;又因为 data_batch = 3,所以 output_tensor 最高维的计算公式为 ceil[(data_batch) / model_batch] * model_batch
,可见其一定为 model_batch 的整数倍,这也是 BPU 硬件指令要求,缺少的输入会自动忽略计算。注意:这里模型输出[0~2x100x1x56]是有效数据,最后一组是无效数据。
3.4 hbDNNRoiInfer调用源码简析
在文件code/01_api_tutorial/roi_infer/src/roi_infer.cc
中有使用hbDNNRoiInfer
推理模型的全流程的详细代码,这里对该API调用部分代码进行简单呈现。
// load model
...
// Step1: get model handle
...
// Step2: set input data to nv12
// In the sample, since the input is a same image, can allocate a memory for
// reusing. image_mem is to save image data.
hbSysMem image_mem;
// image input size
int input_h = 0;
int input_w = 0;
{
// read a single picture, for multi_input model, you
// should set other input data according to model input properties.
read_image_2_nv12(FLAGS_image_file, &image_mem, &input_h, &input_w);
}
std::vector<hbDNNTensor> input_tensors;
std::vector<hbDNNTensor> output_tensors;
int input_count = 0;
int output_count = 0;
int data_batch = 2; // 该例子有两个roi
// Step3: prepare input and output tensor
{
// prepare input tensor
hbDNNGetInputCount(&input_count, dnn_handle);
input_tensors.resize(input_count * data_batch);
// 关于input_tensors、output_tensors,roi_num(data_batch数)关系详见本文3.3节
// prepare_input_tensor的具体内容详见下方函数定义
prepare_input_tensor(image_mem,
input_h,
input_w,
data_batch,
dnn_handle,
input_tensors.data());
// prepare output tensor
hbDNNGetOutputCount(&output_count, dnn_handle);
output_tensors.resize(output_count);
// 关于input_tensors、output_tensors,roi_num(data_batch数)关系详见本文3.3节
// prepare_output_tensor的具体内容详见下方函数定义
prepare_output_tensor(data_batch, dnn_handle, output_tensors.data());
// Step4: prepare roi info
/**
* For this model, there is only one input for the resizer input source;
* Suppose to infer 2 batches of data, the number of ROIs to be prepared is
* also 2.
*/
/**
* For a model with `resizer_count` resizer input sources, the number of ROIs
* that need to be prepared when inferring `batch` data is `resizer_count`*`data_batch`.
*/
std::vector<hbDNNRoi> rois;
// For XJ3: roi's left and top must be even, right and bottom must be odd.
// roi for data_batch 1
// left = 6, top = 12, right = 253, bottom = 253,注意是坐标信息
hbDNNRoi roi_1 = {6, 12, 253, 253};
// roi for data_batch 2
// left = 18, top = 24, right = 253, bottom = 251,注意是坐标信息
hbDNNRoi roi_2 = {18, 24, 253, 251};
rois.push_back(roi_1);
rois.push_back(roi_2);
int roi_num = rois.size();
// Step5: run inference
{
hbDNNRoiInfer(&task_handle,
&output,
input_tensors.data(),
rois.data(),
roi_num,
dnn_handle,
&infer_ctrl_param);
// wait task done
hbDNNWaitTaskDone(task_handle, 0);
}
// Step6: do postprocess with output data for every roi
...
// Step7: release resources
...
int prepare_input_tensor(hbSysMem image_mem,
int input_h,
int input_w,
int data_batch,
hbDNNHandle_t dnn_handle,
hbDNNTensor *input_tensor) {
int input_count = 0;
hbDNNGetInputCount(&input_count, dnn_handle);
hbDNNTensor *input = input_tensor;
/**
* Multi input resizer model inputs order is
* [batch_0_input0, batch_0_input1, ... ,batch_n_input0, batch_n_input1, ...]
* input num is data_batch * model_input_num
* */
for (int batch_id = 0; batch_id < data_batch; batch_id++) {
for (int i = 0; i < input_count; i++) {
int tensor_id = batch_id * input_count + i;
hbDNNGetInputTensorProperties(
&input[tensor_id].properties, dnn_handle, i);
/** Tips:
* In the sample, all batches use the same image, so allocate memory to
* save image. all input tensor can reuse the memory. if your model has
* different input, please allocate memory for all input.
* */
// 注意:hbSysMem是系统内存结构体,用于申请系统内存,
// 成员包括phyAddr、virAddr、memSize
input[tensor_id].sysMem[0] = image_mem;
/** Tips:
* resizer model should modify input validshape to input image shape.
* */
input[tensor_id].properties.validShape.dimensionSize[2] = input_h;
input[tensor_id].properties.validShape.dimensionSize[3] = input_w;
/** Tips:
* For input tensor, aligned shape should always be equal to the real
* shape of the user's data. If you are going to set your input data with
* padding, this step is not necessary.
* */
input[tensor_id].properties.alignedShape =
input[tensor_id].properties.validShape;
}
}
return 0;
}
int prepare_output_tensor(int data_batch,
hbDNNHandle_t dnn_handle,
hbDNNTensor *output_tensor) {
int output_count = 0;
hbDNNGetOutputCount(&output_count, dnn_handle);
hbDNNTensor *output = output_tensor;
/**
* Multi outputs resizer model, outputs num is model outputs num
* because every output tensor contains all data_batch outputs, assign data_batch num is
* 2, and take output[0] for example: outputs[0] contains: data_batch0_output0,
* data_batch1_output0
* */
for (int i = 0; i < output_count; i++) {
hbDNNGetOutputTensorProperties(&output[i].properties, dnn_handle, i);
int output_memSize = output[i].properties.alignedByteSize * data_batch;
hbSysAllocCachedMem(&output[i].sysMem[0], output_memSize);
}
return 0;
}
3.5 工程编译
工程编译的参考过程如下:-
1. 执行horizon_runtime_sample/code
目录下的build_xj3.sh
脚本即可一键编译生成xj3开发板端的可执行程序和对应依赖库,分别存放在xj3/script/aarch64
目录下的bin
和lib
子目录;
sh build_xj3.sh
2. 正确完成编译后,xj3/script/aarch64
的目录结构为:
aarch64-
├── bin-
│ └── roi_infer # 编译产生的可执行文件-
└── lib # 编译执行依赖相关库-
├── libdnn.so-
├── libhbrt_bernoulli_aarch64.so-
└── libopencv_world.so.3.4
3. 将xj3文件夹与roi_infer
相关的文件传输到x3开发板上,传输文件列表如下:
├── xj3 # 板端运行目录结构-
│ ├── data # 预置图片数据文件-
│ ├── model # resizer模型文件-
│ │ └──model_name.bin # 真实路径为model/runtime/mobilenetv1/xxx.bin-
│ ├── script # ARM端示例运行脚本-
│ ├── aarch64 # 板端可执行程序和对应依赖库,执行build_xj3.sh后自动生成-
│ ├── 01_api_tutorial # dnn API使用示例代码-
│ └──roi_infer.sh # resizer板端运行脚本
推荐您使用scp命令进行文件传输,执行命令可参考:
scp -r xj3/ root@board_ip:/userdata/
其中,board_ip
为xj3开发板的IP地址,请确保在开发机上可以访问该IP。
3.6 上板推理
正确完成编译后,进入开发板端/userdata/xj3/script/01_api_tutorial/
目录下,执行roi_infer.sh
脚本。
sh roi_infer.sh # 运行mobilenetv1_128x128_resizer_nv12.bin模型
运行结果如下:
I0216 01:20:52.592705 32259 roi_infer.cc:178] read image to nv12 success
I0216 01:20:52.592958 32259 roi_infer.cc:199] prepare input tensor success
I0216 01:20:52.593054 32259 roi_infer.cc:208] prepare output tensor success
I0216 01:20:52.597029 32259 roi_infer.cc:469] batch[0]: TOP 0 result id: 341
I0216 01:20:52.597089 32259 roi_infer.cc:469] batch[0]: TOP 1 result id: 283
I0216 01:20:52.597112 32259 roi_infer.cc:469] batch[0]: TOP 2 result id: 293
I0216 01:20:52.597136 32259 roi_infer.cc:469] batch[0]: TOP 3 result id: 397
I0216 01:20:52.597159 32259 roi_infer.cc:469] batch[0]: TOP 4 result id: 83
I0216 01:20:52.597311 32259 roi_infer.cc:469] batch[1]: TOP 0 result id: 341
I0216 01:20:52.597337 32259 roi_infer.cc:469] batch[1]: TOP 1 result id: 293
I0216 01:20:52.597360 32259 roi_infer.cc:469] batch[1]: TOP 2 result id: 283
I0216 01:20:52.597383 32259 roi_infer.cc:469] batch[1]: TOP 3 result id: 397
I0216 01:20:52.597406 32259 roi_infer.cc:469] batch[1]: TOP 4 result id: 324
至此,完成resizer模型推理和结果输出的全过程。
4 板端使用工具评测resizer模型
4.1 hrt_model_exec工具简介
hrt_model_exec
是一个模型执行工具,提供了模型推理 infer
、模型性能分析 perf
和查看模型信息 model_info
三类功能。 关于该工具的具体信息可参考地平线工具链手册 5.5.2. hrt_model_exec工具介绍。
hrt_model_exec
工具从1.13.1版本开始,新增roi_infer
和roi
两个参数用于支持resizer模型推理和性能评测,roi_infer输入为bool类型,用于使能resizer模型推理,roi输入为string类型,用于指定推理resizer模型时所需的roi区域。在板端,可通过hrt_model_exec -v
指令查看工具版本,通过hrt_model_exec -h
查看工具使用详情,下面将详细介绍如何使用该工具推理评测包含resizer输入源的模型。-
当模型包含resizer输入源时,infer
和perf
功能都需要设置 roi_infer
为true,并且配置与输入源一一对应的 input_file
和 roi
参数。例如:某模型有三个输入,输入源顺序分别为[ddr, resizer, resizer]
,则评测两组输入数据的命令如下:
# infer
hrt_model_exec infer --roi_infer=true --model_file=xxx.bin --input_file="xx0.bin,xx1.jpg,xx2.jpg,xx3.bin,xx4.jpg,xx5.jpg" --roi="2,4,123,125;6,8,111,113;27,46,143,195;16,28,131,183"
# perf
hrt_model_exec perf --roi_infer=true --model_file=xxx.bin --input_file="xx0.bin,xx1.jpg,xx2.jpg,xx3.bin,xx4.jpg,xx5.jpg" --roi="2,4,123,125;6,8,111,113;27,46,143,195;16,28,131,183"
注意:input_file的输入使用英文的逗号隔开,且不能有空格;每个 roi 输入之间用英文的分号隔开。
针对model_batch大于1的resizer模型场景,使用上需要注意model_file和input_file参数的配置。例如:某模型有一个输入,输入源为resizer,model_batch为2,则推理data_batch=1数据的命令如下:
hrt_model_exec infer --model_file xxx_batch2_resizer.bin --input_file="xxx.jpg" --core_id=0 --roi="6,12,253,253" --roi_infer=true
此时,输出的第一维是有效数据,第二维是无效数据
推理data_batch=2数据的命令如下:
hrt_model_exec infer --model_file mobilenetv1_224x224_nv12_batch2_resizer.bin --input_file="xxx.jpg,xxx.jpg" --core_id=0 --roi="6,12,253,253;27,46,143,195" --roi_infer=true
4.2 板端实测
以第3节中的mobilenetv1_128x128_resizer_nv12.bin
模型为例,使用hrt_model_exec
工具的模型推理 infer
、模型性能分析 perf
两类功能,展示hrt_model_exec
工具评测resizer模型的过程,需要的文件有:
├──j5 # 板端运行目录结构
│ ├── zebra_cls.jpg # 输入图片
│ ├── hrt_model_exec # 工具
│ └── mobilenetv1_128x128_resizer_nv12.bin # resizer模型文件
-
infer模型推理-
运行命令:./hrt_model_exec infer --model_file=mobilenetv1_128x128_resizer_nv12.bin --input_file=“zebra_cls.jpg” --core_id=0 --roi=“2,4,123,125” --roi_infer=true
输出结果:
I1116 14:49:44.500945 4860 main.cpp:1199] infer success
I1116 14:49:44.503501 4860 main.cpp:1205] task done
---------------------Frame 0 begin---------------------
Infer time: 4.103 ms
---------------------Frame 0 end---------------------
-
perf评测性能latency-
运行命令:./hrt_model_exec perf --model_file=mobilenetv1_128x128_resizer_nv12.bin --input_file=“zebra_cls.jpg” --core_id=1 --thread_num=1 --frame_count=1000 --roi=“2,4,123,125” --roi_infer=true
输出结果:
Running condition:
Thread number is: 1
Frame count is: 1000
Program run time: 1936.663000 ms
Perf result:
Frame totally latency is: 1839.814087 ms
Average latency is: 1.839814 ms
Frame rate is: 516.352096 FPS
-
perf评测性能FPS-
运行命令:./hrt_model_exec perf --model_file=mobilenetv1_128x128_resizer_nv12.bin --input_file=“zebra_cls.jpg” --core_id=0 --thread_num=5 --frame_count=1000 --roi=“2,4,123,125” --roi_infer=true
输出结果:
Running condition:
Thread number is: 5
Frame count is: 1000
Program run time: 526.053000 ms
Perf result:
Frame totally latency is: 2452.402344 ms
Average latency is: 2.452402 ms
Frame rate is: 1900.949144 FPS