RGB/INT8输入注意事项

背景

在大多数情况下,我们都推荐用户在编译处理图像任务的模型时,将input_type_rt参数配置nv12,这是考虑到视频通路传来的数据通常都是nv12类型,这样配置可以最大化地节约耗时,提高全流程的处理效率。 但在前期的算法验证阶段,往往用户更希望模型能直接读取rgb/bgr数据,以更加直观地检查量化效果。在这种使用场景下,往往需要对图像数据做一些专门的处理,否则会产生错误的输出结果。本教程会详细介绍如何在input_type_rt配置rgb/bgr时,正确地处理输入数据。

工具链环境

hbdk 3.49.15

horizon-nn 1.1.0

horizon_tc_ui 1.24.3

模型说明

这次作为范例使用的模型是mixvpr,这是一个视觉场景识别算法,输入为1x3x320x320的图像,输出是1x512的全局描述子。onnx模型的输入输出的具体信息如下图所示:

yaml配置情况如下,浮点模型由rgb训练,且希望验证rgb输入的效果,因此input_type_rt和input_type_train都配置为rgb,同时input_layout_rt和input_layout_train都配置为NCHW,也配置了mean和scale参数。

model_parameters:
  onnx_model: "mix.onnx"
  march: "bayes-e"
  working_dir: 'model_output'
  output_model_file_prefix: 'mix'
input_parameters:
  input_type_rt: 'rgb'
  input_layout_rt: 'NCHW'
  input_type_train: 'rgb'
  input_layout_train: 'NCHW'
  norm_type: 'data_mean_and_scale'
  mean_value: 123.675 116.280 103.530
  scale_value: 0.01712475 0.01750700 0.01742919
calibration_parameters:
  cal_data_dir: './calibration_data_rgb_f32'
  cal_data_type: 'float32'
compiler_parameters:
  optimize_level: 'O3'

模型的原始输入为一张320x320的彩色png图像,pytorch浮点模型的推理结果如下,可作为量化效果的参考:

模型编译完成后,可以运行hb_perf mix.bin,查看生成的png图,了解bin模型的子图结构:

推理quantized.onnx

首先我们需要确认quantized.onnx的输入情况,该模型的输入shape为1x3x320x320,数据类型为int8,并且色彩空间应当是rgb类型(对应input_type_rt)

但我们知道,opencv读图后,得到的数据是bgr,hwc,uint8,且3维的,因此需要做一些预处理,才可以得到正确的输入数据。从读图到预处理到推理quantized.onnx的完整代码,及详细注释如下:

from horizon_tc_ui import HB_ONNXRuntime
import numpy as np
import cv2

def read_img(input_name):
    # opencv读图,此时数据是bgr,hwc,uint8,且3维的
    data = cv2.imread("image.png")
    # 将bgr转换为rgb
    data = cv2.cvtColor(data, cv2.COLOR_BGR2RGB)
    # 将hwc转换为chw
    data = np.transpose(data,(2,0,1))
    # 将3x320x320转换为1x3x320x320,也就是将chw转换为nchw
    data = data[np.newaxis,:,:,:]
    print(data)
    # 将uint8的数据,在数值上-128并且转换为int8
    data = (data - 128).astype(np.int8)
    # 保存此时的数据,可以作为bin模型的输入
    data.tofile("rgb_128_int8.bin") 
    print(data)
    return data

def main(): 
    sess = HB_ONNXRuntime(model_file="./mix_quantized_model.onnx")
    input_names = [input.name for input in sess.get_inputs()]
    output_names = [output.name for output in sess.get_outputs()]
    feed_dict = dict()
    for input_name in input_names:
        feed_dict[input_name] = read_img(input_name)
    outputs = sess.run(output_names, feed_dict)  
    # 保存推理结果,用于对比正确性   
    print(outputs[0][0])
    np.savetxt('output_onnx.txt', outputs[0][0])
        
if __name__ == '__main__':
    main()

推理完成后查看输出结果,发现和浮点输出较为接近,说明该推理结果可靠

[-0.05279211  0.01489008 -0.02978017 -0.03113381 -0.07580406  0.02707288
 -0.05143847 -0.06091398 -0.01489008 -0.0216583   0.06226762 -0.11506554
  0.01759737  0.         -0.01082915 -0.00406093  0.00406093  0.04331661
 -0.00270729  0.05820669 -0.0121828  -0.01489008  0.11777283 -0.12454105
 ......
 -0.00270729 -0.01489008  0.05414576 -0.01895102 -0.01353644 -0.06362127
 -0.02842652  0.02030466  0.08121864 -0.01759737 -0.08121864  0.00541458
 -0.05279211  0.00541458  0.0676822  -0.04196296  0.04737754 -0.05956033
 -0.05956033  0.0893405 ]

需要特别强调的是uint8转换int8的方法,代码中写的是data=(data-128).astype(np.int8),如果用户不写,那么HB_ONNXRuntime会自动做这一操作(相关处理如下图),得到的推理结果是完全一样的。

但如果用户误写成了data=data-128,或者data=data.astype(np.int8),那么将得到完全错误的推理结果,这是因为uint8转int8有两种方法。 方法1(正确):在数值上,所有数据全部-128,对应上文代码中的data=(data-128).astype(np.int8) 方法2(错误):在数值上,0到127的值不变,128到255的值-256,对应data=data.astype(np.int8) 如果用户只写了data=data-128,那么此时的数据类型还是uint8,HB_ONNXRuntime会继续做data=(data-128).astype(np.int8)操作,这两个操作就等效成了data=data.astype(np.int8)

方法2处理后,得到的推理结果也是完全错误的,和浮点输出对不上

[-0.03068066  0.01534323 -0.01227575 -0.03222021 -0.07364288  0.
  0.02301195 -0.05062512  0.00766871 -0.04755764 -0.01534323 -0.07364288
 -0.01534323 -0.01994446  0.04602389  0.00306749 -0.02301195  0.0214782
  0.          0.06290087  0.02761317 -0.02914692  0.10585728 -0.0675021
  ......
  0.01534323 -0.01841072 -0.02761317  0.02301195  0.02454569 -0.0214782
  0.02761317 -0.02914692  0.01380949 -0.02301195  0.01687698 -0.04755764
 -0.04755764  0.03835518  0.07671037 -0.03988893  0.01227575 -0.01994446
  0.0352877   0.01841072  0.03988893 -0.05983339  0.         -0.01534323
 -0.01534323  0.06290087]

因此请务必确保,在uint8转int8这一步,使用data=(data-128).astype(np.int8),或者不转,让HB_ONNXRuntime自动执行这一操作。

推理bin

可以借助hrt_model_exec的infer功能推理模型,hrt_model_exec的源码已经开放,用户可以直接参考/package/board/hrt_tools/src/hrt_model_exec目录。

hrt_model_exec infer --model-file mix.bin --input-file rgb_128_int8.bin --enable_dump true --dump_format txt

其中--input-file的rgb_128_int8.bin,就是刚才推理quantized.onnx时,保存的预处理后的输入文件。对于hrt_model_exec工具来说,推理RGB输入且为int8的模型,不能直接给png图像作为输入数据,而需要提前做好预处理,保存成二进制文件再提供给模型。 对于rgb_128_int8.bin输入,模型的推理结果如下,和quantized.onnx的输出结果完全一致。

-0.052792113 
0.014890083 
-0.029780166 
-0.031133810 
-0.075804062 
0.027072879 
-0.051438469 
-0.060913976 
-0.014890083 
-0.021658303 
......
-0.081218638 
0.005414576 
-0.052792113 
0.005414576 
0.067682199 
-0.041962963 
0.047377538 
-0.059560332 
-0.059560332 
0.089340501