图片校准数据准备问题介绍与处理

1 问题介绍

使用PTQ模型转换方案时,在准备校准数据过程中不知从何下手,直接给一张jpg图片,遇到cannot reshape array of size xxx into shape (x, xxx, xxx)的问题该如何解决?

报错截图

2 解决方案

以ResNet18在imagenet数据集上的预处理为例,介绍最基本的校准数据准备过程。

2.1 pytorch数据预处理伪代码

import torchvision.transforms as transforms

def Data():
    image.convert(RGB)
    data_transform = transforms.Compose(
        image,
        [transforms.Resize(224),
         # ToTensor():数据归一化 + 图像从HWC变为CHW
         transforms.ToTensor(),     
         # Imagenet数据集上,图片 RGB 的均值、标准差,注意顺序
         transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])

2.2 文件准备

[root@7660ab0db525 resnet18]# tree -L 1
.
├── 03_build.sh              
├── origin_image                     # 放了10张jpg图片
├── preprocess.py                    # 校准数据准备脚本
├── resnet18_baseline_config.yaml    # 模型转换时的yaml配置文件
└── resnet18_baseline.onnx

2.3 准备校准数据

准备校准数据的代码放在了preprocess.py中,其具体内容如下:

import cv2
import os
import numpy as np

## ------------------------------------------------------------#
#   src_dir:原始jpg图片
#   dst_dir:处理后的图片存放的路径
#   pic_ext:处理后的图片后缀名(影响不大,只是为了说明它的通道顺序)
## ------------------------------------------------------------#
src_dir = './origin_image'
dst_dir = './image_converted_rgb_f32'   # yaml文件中cal_data_dir参数配置成这个路径即可
pic_ext = '.rgb'

if not os.path.exists(dst_dir):
    os.mkdir(dst_dir)

## ---------------------------------------#
#   一次只操作一张图片
## ---------------------------------------#
for src_name in sorted(os.listdir(src_dir)):
    ## -----------------------------#
    #   把图片路径拼出来
    ## -----------------------------#
    src_file = os.path.join(src_dir, src_name)  
 
    img = cv2.imread(src_file)
    # ---------------------------------------#
    #   常规操作是:先转成RGB,再减均值,除标准差
    #   到底要不要转成rgb,主要看,模型训练时用的是啥,
    #   毕竟在后面yaml配置中,这两个参数都行
    # ---------------------------------------#
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  
    
    ## -----------------------------#
    ##  opencv实现图片resize
    ## -----------------------------#
    img = cv2.resize(np.array(img), (224, 224), interpolation=cv2.INTER_CUBIC)
    
    ## ------------------------------------------------------------------#
    #   用的是Pytorch框架,totensor中会实现从HWC,变为CHW,故需要这一步。
    ## ------------------------------------------------------------------#
    img = img.transpose(2, 0, 1)    
    
    # ---------------------------------------------------------------------#
    #    这儿保存控制校准数据的类型,需要和yaml中的cal_data_type参数对应。
    #    若不对应,会造成报错:
    #    ERROR cannot reshape array of size xxx into shape (x,xxx,xxx)
    # ---------------------------------------------------------------------#
    img = img.astype(np.float32)
    # img = img.astype(np.uint8)
    
    ## -----------------------------------------------------#
    #   PC端网络训练时,数据需要归一化,为何在这儿不做?
    #   答:模型转换时,需要的图像输入type为uint,范围是0~255,归一化操作会集成
    #       在yaml文件中mean和scale中,故不要归一化。
    ## -----------------------------------------------------#
    # img /= 255.0        

    ## --------------------------------------------------------------#
    #   PC端网络训练时,数据需要减均值,除标准差,为何在这儿不做?
    #   答:为了和yaml中data_mean_and_scale下的mean_value与scale_value
    #       参数配合,在yaml文件中设置即可
    ## --------------------------------------------------------------#
    # img -= [0.485, 0.456, 0.406]
    # img /= [0.229, 0.224, 0.225]

    # -----------------------------------------------------#
    #   os.path.basename:返回最后的 文件名
    #   例如:os.path.basename("./src/1.jpg"),返回:1.jpg
    # -----------------------------------------------------#
    filename = os.path.basename(src_file)
    # print(src_file)

    # -----------------------------------------------------#
    #   os.path.splitext: 把图片名和图片扩展名分开,
    #   例如:1.jpg,short_name=1, ext=.jpg
    # -----------------------------------------------------#
    short_name, ext = os.path.splitext(filename)

    # ---------------------------------------#
    #   新的图片名
    # ---------------------------------------#
    pic_name = os.path.join(dst_dir, short_name + pic_ext)
    img.tofile(pic_name)
    print("write:%s" % pic_name)

注意:校准数据处理过程中,除了归一化、减均值、除标准差三个操作之外,其余部分均应和训练时数据预处理保持一致。

归一化、减均值、除标准差三个操作可以通过在模型中插入预处理节点进行加速实现,如何配置实现这样的功能在2.4节模型转换中详细介绍。

运行preprocess.py脚本:

[root@7660ab0db525 resnet18]# python3 preprocess.py 
write:./image_converted_rgb_f32/ILSVRC2012_val_00000001.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000002.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000003.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000004.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000005.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000006.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000007.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000008.rgb
write:./image_converted_rgb_f32/ILSVRC2012_val_00000009.rgb

2.4 模型转换

归一化、减均值、除标准差三个操作整合到yaml参数配置中,通过norm_type、mean_value、scale_value三个参数实现,下面结合本节示例对yaml中这三个参数的用法进行详细的介绍。

mean和scale的换算逻辑如下:

data=(input/255−mean)×(1/std)=(input−255mean)×(1/255std)data=(input/255-mean)×(1/std)=(input-255mean)×(1/255std)data=(input/255−mean)×(1/std)=(input−255mean)×(1/255std)

255mean就是yaml中的mean_value,1/(255std)就是yaml中的scale_value,故上式可以写成:

data=(input−meanvalue)×scalevaluedata=(input-meanvalue)×scalevaluedata=(input−meanvalue)×scalevalue

# ------------------------------------------------#
  # 网络输入的预处理方法,主要有以下几种:
  # no_preprocess 不做任何操作
  # data_mean 减去通道均值mean_value
  # data_scale 对图像像素乘以data_scale系数
  # data_mean_and_scale 减去通道均值后再乘以scale系数
  # 注意:
  #    此处不只是减均值,除标准差,而是通过mean和scale
  #    两个参数实现归一化、减均值、除标准差三个功能
  # ------------------------------------------------#
  norm_type: 'data_mean_and_scale'

  # ---------------------------------------------------#
  # 图像减去的均值, 如果是通道均值,value之间必须用空格分隔
  # 注意:
  #    此处的均值需要根据输入数据的范围确定,
  #    例如ImageNet的R通道,应该是: 123.68 = 0.485x255
  # ---------------------------------------------------#
  mean_value: 123.68 116.28 103.53

  # -------------------------------------------------------#
  # 图像预处理缩放比例,如果是通道缩放比例,value之间必须用空格分隔
  # 注意:
  #    此处,scale是乘,以前的标准差是除,需要根据输入数据的范围确定
  #    例如ImageNet的R通道,应该是: 0.017 = 1 / (0.229x255)
  # -------------------------------------------------------#
  scale_value: 0.017 0.018 0.017

结合2.3节中生成的校准数据路径,配置yaml文件参数:cal_data_dir: ‘./image_converted_rgb_f32’,执行03_build.sh脚本即可完成模型转换。

[root@7660ab0db525 resnet18]# sh 03_build.sh 

cd $(dirname $0) || exit

config_file="./resnet18_baseline_config.yaml"
model_type="onnx"
# build model
hb_mapper makertbin --config ${config_file}  \
                    --model-type  ${model_type}
2022-11-16 10:30:05,057 INFO Start hb_mapper....
...
2022-11-16 10:30:38,935 INFO Convert to runtime bin file sucessfully!
2022-11-16 10:30:38,935 INFO End Model Convert

我的预处理文件(按照你上面来的):

import cv2import osimport numpy as np## ------------------------------------------------------------##   src_dir:原始jpg图片#   dst_dir:处理后的图片存放的路径#   pic_ext:处理后的图片后缀名(影响不大,只是为了说明它的通道顺序)## ------------------------------------------------------------#src_dir = './image_origin'dst_dir = './image_converted_rgb_f32'   # yaml文件中cal_data_dir参数配置成这个路径即可pic_ext = '.rgb'if not os.path.exists(dst_dir):    os.mkdir(dst_dir)## ---------------------------------------##   一次只操作一张图片## ---------------------------------------#for src_name in sorted(os.listdir(src_dir)):    ## -----------------------------#    #   把图片路径拼出来    ## -----------------------------#    src_file = os.path.join(src_dir, src_name)       img = cv2.imread(src_file)    # ---------------------------------------#    #   常规操作是:先转成RGB,再减均值,除标准差    #   到底要不要转成rgb,主要看,模型训练时用的是啥,    #   毕竟在后面yaml配置中,这两个参数都行    # ---------------------------------------#    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)          ## -----------------------------#    ##  opencv实现图片resize    ## -----------------------------#    img = cv2.resize(np.array(img), (224, 224), interpolation=cv2.INTER_CUBIC)        ## ------------------------------------------------------------------#    #   用的是Pytorch框架,totensor中会实现从HWC,变为CHW,故需要这一步。    ## ------------------------------------------------------------------#    img = img.transpose(2, 0, 1)            # ---------------------------------------------------------------------#    #    这儿保存控制校准数据的类型,需要和yaml中的cal_data_type参数对应。    #    若不对应,会造成报错:    #    ERROR cannot reshape array of size xxx into shape (x,xxx,xxx)    # ---------------------------------------------------------------------#    img = img.astype(np.float32)    # img = img.astype(np.uint8)        ## -----------------------------------------------------#    #   PC端网络训练时,数据需要归一化,为何在这儿不做?    #   答:模型转换时,需要的图像输入type为int,范围是0~255,归一化操作会集成    #       在yaml文件中mean和scale中,故不要归一化。    ## -----------------------------------------------------#    # img /= 255.0            ## --------------------------------------------------------------#    #   PC端网络训练时,数据需要减均值,除标准差,为何在这儿不做?    #   答:为了和yaml中data_mean_and_scale下的mean_value与scale_value    #       参数配合,在yaml文件中设置即可    ## --------------------------------------------------------------#    # img -= [0.485, 0.456, 0.406]    # img /= [0.229, 0.224, 0.225]    # -----------------------------------------------------#    #   os.path.basename:返回最后的 文件名    #   例如:os.path.basename("./src/1.jpg"),返回:1.jpg    # -----------------------------------------------------#    filename = os.path.basename(src_file)    # print(src_file)    # -----------------------------------------------------#    #   os.path.splitext: 把图片名和图片扩展名分开,    #   例如:1.jpg,short_name=1, ext=.jpg    # -----------------------------------------------------#    short_name, ext = os.path.splitext(filename)    # ---------------------------------------#    #   新的图片名    # ---------------------------------------#    pic_name = os.path.join(dst_dir, short_name + pic_ext)    img.tofile(pic_name)    print("write:%s" % pic_name)

我的config.yaml文件:

# 模型转化相关的参数model_parameters:  # ONNX浮点网络数据模型文件  onnx_model: './yolov5s-best.onnx'  # 适用BPU架构  march: "bernoulli2"  # 指定模型转换过程中是否输出各层的中间结果,如果为True,则输出所有层的中间输出结果,  layer_out_dump: False  # 日志文件的输出控制参数,  # debug输出模型转换的详细信息  # info只输出关键信息  # warn输出警告和错误级别以上的信息  log_level: 'debug'  # 模型转换输出的结果的存放目录  working_dir: 'model_output'  # 模型转换输出的用于上板执行的模型文件的名称前缀  output_model_file_prefix: 'yolov5s_224x224_rgb'  # 模型输入相关参数, 若输入多个节点, 则应使用';'进行分隔, 使用默认缺省设置则写Noneinput_parameters:  # (选填) 模型输入的节点名称, 此名称应与模型文件中的名称一致, 否则会报错, 不填则会使用模型文件中的节点名称  input_name: ""  # 网络实际执行时,输入给网络的数据格式,包括 nv12/rgb/bgr/yuv444/gray/featuremap,  # pytorch模型一般是rgb  input_type_rt: 'rgb'  # 网络实际执行时输入的数据排布, 可选值为 NHWC/NCHW  # 若input_type_rt配置为nv12,则此处参数不需要配置  # !!!此处必须是NHWC!!!  input_layout_rt: 'NHWC'  # 网络训练时输入的数据格式,可选的值为rgb/bgr/gray/featuremap/yuv444  # pytorch模型一般是rgb  input_type_train: 'rgb'  # 网络训练时输入的数据排布, 可选值为 NHWC/NCHW  input_layout_train: 'NCHW'  # (选填) 模型网络的输入大小, 以'x'分隔, 不填则会使用模型文件中的网络输入大小,否则会覆盖模型文件中输入大小  input_shape: ''  # 网络实际执行时,输入给网络的batch_size, 默认值为1  #input_batch: 1    # 网络输入的预处理方法,主要有以下几种:  # no_preprocess 不做任何操作  # data_mean 减去通道均值mean_value  # data_scale 对图像像素乘以data_scale系数  # data_mean_and_scale 减去通道均值后再乘以scale系数  # 注意:此处不是减去均值,除以方差!  norm_type: 'data_mean_and_scale'  #norm_type: 'no_preprocess'  # 图像减去的均值, 如果是通道均值,value之间必须用空格分隔  # 注意:此处的均值是没有归一化的均值,例如ImageNet的R通道,应该是: 124.16 = 0.485x255  mean_value: 124.16 116.28 103.53  # 图像预处理缩放比例,如果是通道缩放比例,value之间必须用空格分隔  # 注意:此处的scale是乘以,以前的方差是除以。且是没有归一化数据  #	例如ImageNet的R通道,应该是: 0.0171248 = 1 / (0.229x255)  scale_value: 0.0171248 0.0175070 0.0174292# 模型量化相关参数calibration_parameters:  # 模型量化的参考图像的存放目录,图片格式支持Jpeg、Bmp等格式,输入的图片  # 应该是使用的典型场景,一般是从测试集中选择20~100张图片,另外输入  # 的图片要覆盖典型场景,不要是偏僻场景,如过曝光、饱和、模糊、纯黑、纯白等图片  # 若有多个输入节点, 则应使用';'进行分隔  # 预处理后的图片所在路径  cal_data_dir: './image_converted_rgb_f32'  #cal_data_dir: './image_origin'  # 如果输入的图片文件尺寸和模型训练的尺寸不一致时,并且preprocess_on为true,  # 则将采用默认预处理方法(skimage resize),  # 将输入图片缩放或者裁减到指定尺寸,否则,需要用户提前把图片处理为训练时的尺寸  #preprocess_on: False  # 模型量化的算法类型,支持kl、max、default、load,通常采用default即可满足要求, 若为QAT导出的模型, 则应选择load  calibration_type: 'default'# 编译器相关参数compiler_parameters:  # 编译策略,支持bandwidth和latency两种优化模式;  # bandwidth以优化ddr的访问带宽为目标;  # latency以优化推理时间为目标  compile_mode: 'latency'  # 设置debug为True将打开编译器的debug模式,能够输出性能仿真的相关信息,如帧率、DDR带宽占用等  debug: False  # 编译模型指定核数,不指定默认编译单核模型, 若编译双核模型,将下边注释打开即可  # core_num: 2  # 优化等级可选范围为O0~O3  # O0不做任何优化, 编译速度最快,优化程度最低,  # O1-O3随着优化等级提高,预期编译后的模型的执行速度会更快,但是所需编译时间也会变长。  # 推荐用O2做最快验证  optimize_level: 'O3'

目前在执行makerbin的时候仍然报错,内容如下:

ERROR load cal data for input ‘images’ error

ERROR cannot reshape array of size 150528 into shape (3,640,640)

麻烦帮忙看看是什么原因

你好,从代码看,校准数据准备的是224x224的,但你的模型输入尺寸是640x640的,两个尺寸是需要保持一致的哈~

按照这个用户的写法,在推理的时候输入图像的格式应该是 224*224*3 然后被网络变成1*3*224*224然后在送入下一步么?