地平线X3开发板模型部署之模型转换

这是地平线芯片开发经验系列文章中的第二篇,主要写写模型转换的方法,怎么样把一个pytorch的模型转到地平线芯片的模型。libfacedetection是一个很好用的人脸检测工具,现在这一版带5点的landmark,使用pytorch训练的,作者是于仕琪老师。这个检测模型,本来是借助于老师写的C++的推理的程序运行在CPU上的,但是如果想把它运行在深度学习芯片上,就需要转模型、部署等一系列工作。个人感觉,转这个模型的时候,遇到一些问题,可能会在转其他模型的时候也经常遇到,所以这里就以libfacedetection为例,移植这个人脸检测模型到板子上。此文章使用的是,后续地平线官方,。

初步尝试

在第一篇中,已经完成了对开发环境的搭建。现在,就可以使用hb_mapper来进行模型转换了。最新的libfacedetection是pytorch的模型,hb_mapper需要caffe或者onnx的模型,所以首先把模型转成onnx的模型。在5月17日(9ac244c)的commit中,添加了转onnx的代码,也提供了转好的onnx模型。我们先用这个代码来尝试一下。clone了这个repo后,创建好pytorch环境,直接在task/task1下执行python exportonnx.py,然后就在onnx文件夹下得到了facedetection.onnx。这里我们用netron看下这个模型。

模型

我特意把图像缩小到可以看到整体结构的程度。可以看到,这个模型还是很常见的检测模型,先提取信息,然后在不同尺度做检测,最后处理那些featuremap得到人脸的bounding box。
参考地平线的文档,转模型第一步是用hb_mapper的checker做检测,检测模型是否可以运行在BPU上。这里直接用得到的facedetection.onnx。执行下面的命令,先做检查。

cd onnx
hb_mapper checker \
   --model-type=onnx \
   --model=./facedetectcnn.onnx  \
   --output=1.log

问题1

这里可以看到,我们遇到了第一个问题,onnx的opset不匹配的问题。直接打开Python,用onnx加载这个模型看下。
模型信息

这里可以看到,pytorch生成的onnx模型,opset版本是9,而hb_mapper需要10。参考pytorch文档,在转模型的时候添加参数opset_version=10,解决问题。网上的教程,一般这里没有讲需要修改opset_version,所以这里在转模型的时候需要注意一下。把代码做出如下修改,即可转出opset版本为10的模型。
修改1

第二个问题

完成以上工作再次,重新导出onnx模型。再次用hb_mapper来尝试模型能否成功转出。这时候又出现了问题。

问题2

一般这种转模型工具,是C++的,海思和地平线都是如此,但是地平线在上层使用Python做了一个包装,更方便环境的配置以及调用。底层C++报段错误,进程直接结束,也不会有Python的报错提示。这就不是很好定位问题。
通常,这样的检测模型,后面都需要有一系列的方法来从featuremap中得到bounding box,这个过程,我们把它叫做后处理。比如yolov3模型,最后有个yolo层,这就是yolov3的后处理。在yolo的caffe版本中,是没有这一层的,然后在用caffe跑yolo的时候,获取最后一层的featuremap,再手动实现这个后处理部分。在后处理部分,通常会有很多的比较、信息提取等工作,比如从featuremap的第一个channel中找大于某个阈值的,然后再在其他channel对应的位置获取宽高信息。这些工作,在onnx中是用gather、unsqueeze、reshape等操作来实现的,根据之前的经验,这些操作nnie等并不一定支持,那么就有一定的依据判断bpu也不支持这个,而转换工具就是在处理这些的时候报错的。那么,就把这些干掉再试试。这里看yufacedetectnet.py,在实现网络的那个forward函数里。
forward

在这里,可以看到,l(x)和c(x)经过一系列的操作得到最终输出。所以在这直接把l(x)和c(x)返回出来。
修改2

做出以上修改后,再次导出onnx然后使用hb_mapper来check模型。可以看到,模型没有这么多复杂的后处理了,hb_mapper也执行成功了。
修改后的模型

成功

获得最终模型

转模型第一步检测模型完成后,按照官方文档,就要使用hb_mapper的makertbin功能来获取最终的模型了。这里解释一个细节:RGBP和BGRP。p可以理解成planar,就是平面,对于图像就是把RGB三个通道,每个通道铺平然后再排到一起。通常opencv读取图片是BGR顺序的,然后BGR像素排列是按照BGRBGRBGRBGRBGR......来排的,然后BGRP就是BBBB...GGGG...RRRR...这样排的,RGB也是以此类推。
按照官方的转模型的说明,首先处理校准图像。对于人脸检测模型,这里准备几十张包含人脸的,在典型场景下的图片,作为量化校准图像。然后参考samples/04_detection/01_yolov2/mapper/02_preprocess.sh,写一个data_transformer.py,完成图像的预处理。完成后,得到rgbp文件。这里预处理的方法是缩放图像,把图像等比缩放,使长或宽其中有一边与模型输入相等,然后在另一边两侧填充灰色区域,最终把图像缩处理到模型输出。如下图,缩放后,上下两边填充灰色区域。

缩放示例

然后,编辑好配置文件,就可以使用hb_mapper来转模型了。编写配置文件,保存为config.yaml,然后执行hb_mapper makertbin --config ./config.yaml --model-type onnx,就可以生成最终使用的模型。如下图,模型转换成功,并且生成了bin文件。
转换成功

以下提供一个配置文件示例,其实也是摘自地平线官方的文档和样例。这里输入为rgbp的格式,使用NCHW排列,对于图像的减均值等操作,请按照实际来填写。

# 模型转化相关的参数
model_parameters:
  onnx_model: 'facedetectcnn.onnx'
  # 指定模型转换过程中是否输出各层的中间结果,如果为True,则输出所有层的中间输出结果,
  layer_out_dump: False
  # 用于设置上板模型输出的layout, 支持NHWC和NCHW, 输入None则使用模型默认格式
  output_layout: NCHW
  # 日志文件的输出控制参数,
  # debug输出模型转换的详细信息
  # info只输出关键信息 
  # warn输出警告和错误级别以上的信息
  log_level: 'debug'
  # 模型转换输出的结果的存放目录
  working_dir: 'model_output'
  # 模型转换输出的用于上板执行的模型文件的名称前缀
  output_model_file_prefix: 'face'

# 模型输入相关参数, 若输入多个节点, 则应使用';'进行分隔, 使用默认缺省设置则写None
input_parameters:
  # 网络实际执行时,输入给网络的数据格式,包括 nv12/rgbp/bgrp/yuv444_128/gray/featuremap,
  # 如果输入的数据为yuv444_128, 模型训练用的是rgbp,则hb_mapper将自动插入YUV到RGBP(NCHW)转化操作
  input_type_rt: 'rgbp'
  # 网络训练时输入的数据格式,可选的值为rgbp/bgrp/gray/featuremap/yuv444_128
  input_type_train: 'rgbp'
  # 网络输入的预处理方法,主要有以下几种:
  # no_preprocess 不做任何操作
  # mean_file 减去从通道均值文件(mean_file)得到的均值
  # data_scale 对图像像素乘以data_scale系数
  # mean_file_and_scale 减去通道均值后再乘以scale系数
  norm_type: 'data_scale'
  # (可不填) 模型网络的输入大小, 以'x'分隔, 不填则会使用模型文件中的网络输入大小
  input_shape: '1x3x320x240'
  # 图像减去的均值存放文件, 文件内存放的如果是通道均值,均值之间必须用空格分隔
  mean_file: ''
  # 图像预处理缩放比例,该数值应为浮点数
  scale_value: 

calibration_parameters:
  # 模型量化的参考图像的存放目录,图片格式支持Jpeg、Bmp等格式,输入的图片
  # 应该是使用的典型场景,一般是从测试集中选择20~50张图片,另外输入
  # 的图片要覆盖典型场景,不要是偏僻场景,如过曝光、饱和、模糊、纯黑、纯白等图片
  # 若有多个输入节点, 则应使用';'进行分隔
  cal_data_dir: './quant_data'
  # 如果输入的图片文件尺寸和模型训练的尺寸不一致时,并且preprocess_on为true,
  # 则将采用默认预处理方法(opencv resize),
  # 将输入图片缩放或者裁减到指定尺寸,否则,需要用户提前把图片处理为训练时的尺寸
  preprocess_on: False
  # 模型量化的算法类型,支持kl、max,通常采用KL即可满足要求
  calibration_type: 'kl'
  # 模型的量化校准方法设置为promoter,mapper会根据calibration的数据对模型进行微调从而提高精度,
  # promoter_level的级别,可选的参数为-1到2,建议按照0到2的顺序实验,满足精度即可停止实验
  # -1: 不进行promoter
  # 0:表示对模型进行轻微调节,精度提高比较小
  # 1:表示相对0对模型调节幅度稍大,精度提高也比较多
  # 2:表示调节比较激进,可能造成精度的大幅提高也可能造成精度下降
  promoter_level: -1


# 编译器相关参数
compiler_parameters:
  # 编译策略,支持bandwidth和latency两种优化模式;
  # bandwidth以优化ddr的访问带宽为目标;
  # latency以优化推理时间为目标
  compile_mode: 'latency'
  # 设置debug为True将打开编译器的debug模式,能够输出性能仿真的相关信息,如帧率、DDR带宽占用等
  debug: True
  # 编译模型指定核数,不指定默认编译单核模型, 若编译双核模型,将下边注释打开即可
  # core_num: 2
  # 优化等级可选范围为O0~O3
  # O0不做任何优化, 编译速度最快,优化程度最低,
  # O1-O3随着优化等级提高,预期编译后的模型的执行速度会更快,但是所需编译时间也会变长。
  # 推荐用O2做最快验证
  optimize_level: 'O2'

你可能感兴趣的:(地平线X3开发板模型部署之模型转换)