7. SigmaStar后处理模块
7.1.模块介绍¶
SigmaStar后处理模块位置在SGS_IPU_SDK/Scripts/postprocess
。 该模块主要以TFLitePostProcess类实现了一套生成TFLite Flatbuffer的API和一个检测网络后处理BBOX的通用生成方法。 使用该模块时,先根据后处理方法编写python后处理文件,生成独立的后处理模型文件,再使用网络连接程序将Backbone网络模型和后处理模型连接成一个网络模型文件。 编写的python文件可参考SGS_IPU_SDK/Scripts/postprocess/postprocess_method
文件夹下的示例。 编写完成后,生成方法:
-
编写文件保存在
SGS_IPU_SDK/Scripts/postprocess/postprocess_method
,在SGS_IPU_SDK/Scripts/postprocess/postprocess_method/__ini__.py
中添加刚刚保存的文件名称,以caffe_yolo_v2_postprocess.py
文件为例,然后在目录SGS_IPU_SDK/Scripts/postprocess/
处输入命令:python3 postprocess.py -n caffe_yolo_v2_postprocess
-
编写后处理网络python文件,运行postprocess.py时,
-n/--model_name
参数给定编写的后处理python文件路径。 连接网络程序在SGS_IPU_SDK/bin/concat_net
,后处理网络的输入名称在设置时需与Backbone网络输出的名称相符,否则在连接网络模型时会发生错误。
concat_net的参数说明如下:
--mode:
网络连接模式:concat或append。连接Backbone网络与后处理网络请使用append模式。
--input_config:
input_config.ini文件,需使用完整网络的配置文件。完整网络的配置文件与Backbone网络的配置文件仅在outputs的名称不同,其余设置应完全一致。
--model1:
Backbone网络模型sim路径。
--model2:
后处理网络模型sim路径。
--output:
合成网络模型的输出路径。
以下章节详细介绍封装好的后处理流程和自定义后处理流程的python文件编写,用以生成后处理网络模型文件。
7.2. bbox坐标解码模块使用¶
为方便使用,Sigmastar分析了SSD、YOLOv1、YOLOv2、YOLOv3等网络的后处理,针对bbox坐标的提取已经抽象出一套解码流程,不同网络在结构上一样,不同点在部分的算子使用和anchor参数的传入。因此可以通过配置config字典变量,即可生成bbox坐标的后处理网络模型。bbox坐标解码网络如下图所示:
生成bbox坐标解码网络模型,可修改config字典变量,该变量参数意义如下表所示:
参数名 | 参数类型 | 描述信息 |
---|---|---|
sh ape | [ int] | bbox tensor的形状,比如 [1,837] |
tx_func | (tflite.BuiltinOperator, str) | 1. tflite.BuiltinOperator为tflite内置算子类型; 2. str为字符串x_scale 或者None :当1中指定的算子为单口算子时str填None ,如果为双口算子,这里填x_scale 并在成员变量x_scale 中指定其值。 |
ty_func | (tflite.BuiltinOperator, str) | 1. tflite.BuiltinOperator为tflite内置算子类型; 2. str为字符串y_scale 或者None :当1中指定的算子为单口算子时str填None ,如果为双口算子,这里填y_scale 并在成员变量y_scale 中指定其值。 |
tw_func | (tflite.BuiltinOperator, str) | 1. tflite.BuiltinOperator为tflite内置算子类型; 2. str为字符串w_scale 或者None :当1中指定的算子为单口算子时str填None ,如果为双口算子,这里填w_scale并在成员变量w_scale中指定其值。 |
th_func | (tflite.BuiltinOperator, str) | 1. tflite.BuiltinOperator为tflite内置算子类型; 2. str为字符串h_scale 或者None :当1中指定的算子为单口算子时str填None ,如果为双口算子,这里填h_scale 并在成员变量h_scale 中指定其值。 |
x_scale | float | tx_func[1]为x_scale 时指定的值 |
y_scale | float | tx_func[1]为y_scale 时指定的值 |
w_scale | float | tx_func[1]为w_scale 时指定的值 |
h_scale | float | tx_func[1]为h_scale 时指定的值 |
anchor_selector | str | constant 或者为None 指定pw和ph是constant 还是有pw_func和ph_func生成 |
pw | [ float ] | 如果anchor_selector为constant 时pw指定为一个float列表 |
ph | [ float ] | 如果anchor_selector为constant 时ph指定为一个float列表 |
ppw | [ float ] | 如果anchor_selector为constant 时ppw指定为一个float列表 |
pph | [ float ] | 如果anchor_selector为constant 时pph指定为一个float列表 |
px | [ float ] | px指定为一个float列表 |
py | [ float ] | py指定为一个float列表 |
sx | [ float ] | sx指定为一个float列表 |
sy | [ float ] | sy指定为一个float列表 |
sw | [ float ] | sw指定为一个float列表 |
sh | [ float ] | sh指定为一个float列表 |
7.3. 创建节点TFlite Flatbuffer 节点API¶
7.3.1. buildBuffer¶
buildBuffer(buffer_name, buffer_data=None)
:param buffer_name:
一个字符串用来在coding时标识buffer,不会存入模型内部。
:param buffer_data:
默认如果创建的buffer是用于variable tensor使用,则使用默认None值即可。如果是常量tensor的buffer则传入data的字节流。
:return:
返回编码后的偏移量
7.3.2. buildTensor¶
buildTensor(shape, name, buffer=0,type=tflite.TensorType.TensorType().FLOAT32)
:param shape:
[int] int列表标识tensor的形状
:param name:
字符串标识创建tensor的名字
:param buffer:
int类型的索引值,标识在buffer array中的index :param type:tensor类型tflite.TensorType,默认为FLOAT32
:return:
返回创建的tensor在subgraph的tensor array中的index,如果是已经存在的tensor则直接返回index
7.3.3. buildOperatorCode¶
buildOperatorCode(opcode_name, builtin_code, custom_code=None)
:param opcode_name:
一个字符串,是用户用来标识记录区分算子的名字,实作会保障同一类型的OperatorCode只有一个存在于OperatorCode array中。
:param builtin_code:
tflite.BuiltinOperator类型,即内置算子类型
:param custom_code:
用户指定的字符串客户标记符
:return:
返回OperatorCode的index
7.3.4. buildOperator¶
buildOperator(op_code_name, input_names, output_names,builtin_options_type=None, builtin_options=None, custom_options=None, is_custom=False)
:param op_code_name:
buildOperatorCode中指定的OperatorCode标识符,使用名字来获取返回OperatorCode的index
:param input_names:
[str]输入tensor的名字列表
:param output_names:
[str]输出tensor的名字列表
:param builtin_options_type:
tflite.BuiltinOptions类型,如果需要指定必须的option参数,这里指定是哪一类算子的参数。
:param builtin_options:
int类型,对应builtin_options_type的算子参数内容的flatbuffer的offset,使用如createReshapeOptions等API创建的flatbuffer。目前TFLitePostProcess.py只实现了少数几个option的flatbuffer encoding。如果有其他没有实现的可以参照createReshapeOptions实作新的方法。
:param custom_options:
[byte] 用flexbuffer encoding后的bytearray。如果是custom算子,在这里指定参数,对应的算子解析自己的参数。
:param is_custom:
是否为客制化算子,默认为False
:return:
返回subgraph中operators的index。
7.3.5. buildSubGraph¶
buildSubGraph(input_tensor_names, output_tensor_names, subgraph_name)
:param input_tensor_names:
[str]类型,subgraph的输入tensor名字列表,必须依据使用buildTensor创建过的tensor的名字
:param output_tensor_names:
[str]类型,subgraph的输出tensor名字列表,必须依据使用buildTensor创建过的tensor的名字
:param subgraph_name:
str类型,指定一个名字标识subgraph
:return:
返回subgraph的flatbuffer offset
7.3.6. createModel¶
createModel(version, operator_codes, subgraphs, description, buffers, metadata_buffer=None)
:param version:
uint; tflite版本。传入3即可。
:param operator_codes:
[OperatorCode]; OperatorCode列表,使用buildOperatorCode创建,保存在TFLitePostProcess.operator_codes中。
:param subgraphs:
[SubGraph];SubGraph列表,保存在TFLitePostProcess.subgraphs中。
:param description:
string;用户指定一个描述字符串。
:param buffers:
[Buffer];buffer列表,使用buildBuffer创建,保存在TFLitePostProcess.buffers列表中。
:param metadata_buffer:
[int];目前没使用到,传None。
:return:
返回创建的完整tflite flatbuffer句柄
7.3.7. createFlexBuffer¶
createFlexBuffer(values)
:param values:
tuple类型的列表,tuple的类型为(str, int/float, str):
第一项为value名称,算子实作者使用这个名字来解析value
第二项为value,
第三项为value类型的字符串,标识第二项的类型,如果第二项为int 这里填'int';如果是float类型,这里填'float'。目前仅支持float和int类型。
:return:
返回编码后的bytearray。
使用sample:
cus_options = [(b"input_coordinate_x1",0,"int"),
(b"input_coordinate_y1",1,"int"),
(b"input_coordinate_x2",2,"int"),
(b"input_coordinate_y2",3,"int"),
(b"nms_score_threshold",0.4,"float"),
(b"nms_iou_threshold",0.45,"float")]
options = sgs_builder.createFlexBuffer(cus_options)
7.3.8. buildBoxDecoding¶
buildBoxDecoding(unpacked_box)
:param unpacked_box:
bbox的输入tensor名字列表,为四个tensor
:return:
返回x1,y1,x2,y2 四个decoded tensor的名字列表。
7.4. SigmaStar定制后处理算子¶
SigmaStar定制的后处理算子OperatorCode类型为tflite.BuiltinOperator.BuiltinOperator().CUSTOM
,因此需要使用createFlexBuffer的API来传递参数,参数传递必须使用三项的tuple类型且为(str, int/float, str)。
7.4.1. PostProcess_Unpack¶
PostProcess_Unpack算子目的是将Backbone网络的输出分离,支持最大分离出7个分支。 使用方法如下:
cus_options = [(b"x_offset",0,"int"),
(b"x_lengh",1,"int"),
(b"y_offset",1,"int"),
(b"y_lengh",1,"int"),
(b"w_offset",2,"int"),
(b"w_lengh",1,"int"),
(b"h_offset",3,"int"),
(b"h_lengh",1,"int"),
(b"confidence_offset",0,"int"),
(b"confidence_lengh",0,"int"),
(b"scores_offset",0,"int"),
(b"scores_lengh",0,"int"),
(b"max_score",0,"int")]
x_offset:
分出坐标x偏移量
x_lengh:
坐标x长度,一般为1
y_offset:
分出坐标y偏移量
y_lengh:
坐标y长度,一般为1
w_offset:
分出坐标w偏移量
w_lengh:
坐标w长度,一般为1
h_offset:
分出坐标h偏移量
h_lengh:
坐标h长度,一般为1
confidence_offset:
分出confidence偏移量
confidence_lengh:
confidence长度,一般为1
scores_offset:
分出scores偏移量
scores_lengh:
scores长度,为网络的分类数量。
max_score:
一般为1
通过参数设置不同,结合bbox坐标解码模块,PostProcess_Unpack有如下后处理网络示例:
1.分离bbox坐标
2.分离bbox坐标、confidence、scores、max_score
7.4.2. TFLite_Detection_NMS¶
TFLite_Detection_NMS算子将NMS操作组合成为一个算子,与PostProcess_Unpack算子配合,最大支持7个输入,输出为4个或5个。 使用方法如下:
cus_options = [(b"input_coordinate_x1",1,"int"),
(b"input_coordinate_y1",0,"int"),
(b"input_coordinate_x2",3,"int"),
(b"input_coordinate_y2",2,"int"),
(b"input_class_idx",5,"int"),
(b"input_score_idx",4,"int"),
(b"input_confidence_idx",-1,"int"),
(b"input_facecoordinate_idx",-1,"int"),
(b"output_detection_boxes_idx",0,"int"),
(b"output_detection_classes_idx",1,"int"),
(b"output_detection_scores_idx",2,"int"),
(b"output_num_detection_idx",3,"int"),
(b"output_detection_boxes_index_idx",-1,"int"),
(b"nms",0,"float"),
(b"clip",0,"float"),
(b"max_detections",10,"int"),
(b"max_classes_per_detection",1,"int"),
(b"detections_per_class",1,"int"),
(b"num_classes",90,"int"),
(b"bmax_score",0,"int"),
(b"num_classes_with_background",1,"int"),
(b"nms_score_threshold",9.99999994e-09,"float"),
(b"nms_iou_threshold",0.600000024,"float")]
input_coordinate_x1:
对应PostProcess_Unpack算子x_offset的序号。
input_coordinate_y1:
对应PostProcess_Unpack算子y_offset的序号。
input_coordinate_x2:
对应PostProcess_Unpack算子w_offset的序号。
input_coordinate_y2:
对应PostProcess_Unpack算子h_offset的序号。
input_class_idx:
对应类别的输入序号。
input_score_idx:
对应PostProcess_Unpack算子score的序号。
input_confidence_idx:
对应PostProcess_Unpack算子confidence的序号。
input_facecoordinate_idx:
默认为-1。
output_detection_boxes_idx:
输出检测bbox的坐标序号。
output_detection_classes_idx:
输出对应检测的类别序号。
output_detection_scores_idx:
输出对应检测的分值序号。
output_num_detection_idx:
输出共检测到的目标个数序号。
output_detection_boxes_index_idx:
输出检测到目标排序下标的序号。
nms:
0为Fast NMS,1为Normal NMS。
clip:
是否截断越界的bbox坐标值,1为截断,0为保留。
max_detections:
最大输出目标个数。
max_classes_per_detection:
默认为1。
detections_per_class:
默认为1。
num_classes:
网络模型类别数量(不包含背景,此选项仅为SSD后处理设置)。
bmax_score:
对应PostProcess_Unpack算子max_score时,为1,否则为0。
num_classes_with_background:
默认为1。
nms_score_threshold:
NMS分数阈值。
nms_iou_threshold:
NMS的IoU阈值。
Please Note:
- TFLite_Detection_NMS算子最大支持24576个bbox输入。
7.4.2.1. 选择是否需要NMS输出index信息¶
NMS可以选择4个或5个输出,其中必选4个输出为:检测bbox坐标、检测类别、检测分值、检测个数,可选1个输出为:检测bbox对应的下标。 如果需要增加检测bbox对应的下标输出,按照如下方法修改后处理python文件。 以下示例以ssd_mobilenet_v1模型的后处理为例,完整代码详见
SGS_IPU_SDK/Scripts/postprocess/postprocess_method/ssd_mobilenet_v1_index_postprocess.py
sgs_builder.buildTensor(model_config["out_shapes"][3],"numDetections")
nms_out_tensors.append("numDetections")
sgs_builder.buildTensor(model_config["out_shapes"][4],"detectionIndex")
nms_out_tensors.append("detectionIndex")
cus_code = 'TFLite_Detection_NMS'
sgs_builder.buildOperatorCode("SGS_nms",tflite.BuiltinOperator.BuiltinOperator().CUSTOM,cus_code)
cus_options = [(b"input_coordinate_x1",1,"int"),
(b"input_coordinate_y1",0,"int"),
(b"input_coordinate_x2",3,"int"),
(b"input_coordinate_y2",2,"int"),
(b"input_class_idx",5,"int"),
(b"input_score_idx",4,"int"),
(b"input_confidence_idx",-1,"int"),
(b"input_facecoordinate_idx",-1,"int"),
(b"output_detection_boxes_idx",0,"int"),
(b"output_detection_classes_idx",1,"int"),
(b"output_detection_scores_idx",2,"int"),
(b"output_num_detection_idx",3,"int"),
(b"output_detection_boxes_index_idx",4,"int"),
(b"nms",0,"float"),
(b"clip",0,"float"),
(b"max_detections",10,"int"),
(b"max_classes_per_detection",1,"int"),
(b"detections_per_class",1,"int"),
(b"num_classes",90,"int"),
(b"bmax_score",0,"int"),
(b"num_classes_with_background",1,"int"),
(b"nms_score_threshold",9.99999994e-09,"float"),
(b"nms_iou_threshold",0.600000024,"float")]
network_out_tensors = []
network_out_tensors.append("detectionBoxes")
network_out_tensors.append("detectionClasses")
network_out_tensors.append("detectionScores")
network_out_tensors.append("numDetections")
network_out_tensors.append("detectionIndex")
sgs_builder.subgraphs.append(sgs_builder.buildSubGraph(model_config["input"],network_out_tensors,model_config["name"]))
model_config = {"name":"ssdlite_mobilenet_v2",
"input" : ["Squeeze","convert_scores"],
"input_shape" : [[1,1917,4],[1,1917,91]],
"shape" : [1,1917],
"out_shapes" : [[1,10,4],[1,10],[1,10],[1],[1,10]]}
Please Note:
- 连接backbone网络时请先修改input_config.ini文件中outputs,因为多了一个输出Tensor,避免连接网络时发生错误。
7.5. 获取离线anchor数据¶
Caffe网络中如果PriorBox节点中数据是离线生成的,可以通过以下方法获取。 具体网络请参考SGS_Models/caffe/caffe_ssd_mobilenet_v1。 转换带PriorBox节点的backbone网络,使用Netron打开网络prototxt文件,如下图所示。
修改对应的input_config.ini文件和ConvertTool转换命令,有3个输出,生成backbone网络部分。 转换完成后,所有PriorBox节点生成为一个节点。使用Netron打开生成的backbone网络,先点击mbox_priorbox节点,再点击红框中的保存按钮,即可将anchor的数据保存为.npy文件。 在配置bbox坐标解码模块时,使用numpy.load读取.npy文件,配置好对应的变量。如果已有anchor数据,可以不用此方法,直接使用anchor数据。
后处理python文件在编写时,增加模型配置的输入为3个输入,增加mbox_priorbox的shape,具体代码详见
SGS_IPU_SDK/Scripts/postprocess/postprocess_method/caffe_ssd_mobilenet_v1_postprocess.py
model_config = {"name":"caffe_ssd_mobilenet_v1",
"input" : ["mbox_loc","mbox_conf_softmax","mbox_priorbox"],
"input_shape" : [[1,1917,4],[1,1917,21],[1917,4]],
"shape" : [1,1917],
"out_shapes" : [[1,10,4],[1,10],[1,10],[1]]}
生成后处理模型后,使用concat_net工具连接会自动清除mbox_priorbox节点。
7.6. 举例使用¶
以下示例以caffe_yolo_v2模型的后处理为例,具体代码详见
SGS_IPU_SDK/Scripts/postprocess/postprocess_method/caffe_yolo_v2_postprocess.py
7.6.1. 创建一个TFLitePostProcess实例¶
首先配置config字典变量,根据bbox坐标解码的实际计算方法配置config的各个参数。
配置参数:
box_num = 5
side_x = 13
side_y = 13
ppw = anchor.ones(845)
px = anchor.index_div_linear(1,1,0,box_num ,side_x,side_y)
pph = anchor.ones(845)
py = anchor.index_div_linear(1,1,0,side_x*box_num,side_y,1)
pw = anchor.ones(845)
ph = anchor.ones(845)
sx = anchor.ns(845,1.0/13)
sy = anchor.ns(845,1.0/13)
biases= [[1.3221,1.73145],[3.19275,4.00944],[5.05587,8.09892],[9.47112,4.84053],[11.2364,10.0071]]
sw = [x[0]/(2*13) for x in biases ]*(13*13)
sh = [x[1]/(2*13) for x in biases ]*(13*13)
配置config字典变量:
config = {"shape" : [1,845],
"tx_func" : (tflite.BuiltinOperator.BuiltinOperator().LOGISTIC,None),
"ty_func" : (tflite.BuiltinOperator.BuiltinOperator().LOGISTIC,None),
"tw_func" : (tflite.BuiltinOperator.BuiltinOperator().RESHAPE,None),
"th_func" : (tflite.BuiltinOperator.BuiltinOperator().RESHAPE,None),
"x_scale" : 0.1,
"y_scale" : 0.1,
"w_scale" : 1,
"h_scale" : 1,
"anchor_selector" : "constant",
"pw" : pw,
"ph" : ph,
"pw_func" : (None,None),
"ph_func" : (None,None),
"ppw" : ppw,
"px" : px,
"pph" : pph,
"py" : py,
"sx" : sx,
"sy" : sy,
"sw" : sw,
"sh" : sh
}
创建TFLitePostProcess实例:
yolov2 = TFLitePostProcess(config)
7.6.2. 创建常量Tensor¶
将float列表pack成bytearray
py_vector=[]
for value in self.py:
py_vector += bytearray(struct.pack("f", value))
self.buildBuffer("py_buffer",py_vector)
self.buildTensor([len(self.py)],"py_tensor",self.getBufferByName("py_buffer"))
7.6.3. 创建一个算子¶
创建一个双口Mul算子:
score1_out_tensors = []
score1_in_tensors = []
score1_in_tensors.append("confidence_tensor")
score1_in_tensors.append("score0_tensor")
sgs_builder.buildTensor([1,845], "SGS_score1")
score1_out_tensors.append("SGS_score1")
sgs_builder.buildOperatorCode("SGS_score_mul",tflite.BuiltinOperator.BuiltinOperator().MUL)
sgs_builder.buildOperator("SGS_score_mul",score1_in_tensors,score1_out_tensors)
创建一个Reshape算子,需要创建常量Tensor:
reshape_out_shape1 = [1,4695,4]
reshape_out_tensors1 = []
reshape_in_tensors1 = []
sgs_builder.buildBuffer('NULL')
sgs_builder.buildTensor([1,4695,1,4], '283_in')
reshape_in_tensors1.append('283_in')
reshape_vector1 = []
for value in reshape_out_shape1:
reshape_vector1 += bytearray(struct.pack("i", value))
sgs_builder.buildBuffer("reshape_vector1",reshape_vector1)
sgs_builder.buildTensor([len(reshape_out_shape1)],"reshape_shape1",sgs_builder.getBufferByName("reshape_vector1"),tflite.TensorType.TensorType().INT32)
reshape_in_tensors1.append("reshape_shape1")
sgs_builder.buildTensor(reshape_out_shape1,"reshape_tensor1")
reshape_out_tensors1.append("reshape_tensor1")
sgs_builder.buildOperatorCode("SGS_reshape1",tflite.BuiltinOperator.BuiltinOperator().RESHAPE)
reshape_newshape1 = sgs_builder.createReshapeOptions(reshape_out_shape1)
sgs_builder.buildOperator("SGS_reshape1",reshape_in_tensors1, reshape_out_tensors1,tflite.BuiltinOptions.BuiltinOptions().ReshapeOptions,reshape_newshape1)
7.6.4. 创建客制化算子¶
创建OperatorCode
sgs_builder.buildOperatorCode("SGS_nms",tflite.BuiltinOperator.BuiltinOperator().CUSTOM,cus_code)
cus_options = [(b"input_coordinate_x1",0,"int"),
(b"input_coordinate_y1",1,"int"),
(b"input_coordinate_x2",2,"int"),
(b"input_coordinate_y2",3,"int"),
(b"input_class_idx",6,"int"),
(b"input_score_idx",5,"int"),
(b"input_confidence_idx",4,"int"),
(b"input_facecoordinate_idx",-1,"int"),
(b"output_detection_boxes_idx",0,"int"),
(b"output_detection_classes_idx",1,"int"),
(b"output_detection_scores_idx",2,"int"),
(b"output_num_detection_idx",3,"int"),
(b"output_detection_boxes_index_idx ",-1,"int"),
(b"nms",0,"int"),
(b"clip",0,"int"),
(b"max_detections",100,"int"),
(b"max_classes_per_detection",1,"int"),
(b"detections_per_class",1,"int"),
(b"num_classes",20,"int"),
(b"bmax_score",1,"int"),
(b"num_classes_with_background",1,"int"),
(b"nms_score_threshold",0.4,"float"),
(b"nms_iou_threshold",0.45,"float")]
创建flexbuffer
options = sgs_builder.createFlexBuffer(cus_options)
使用flexbuffer创建Operator
sgs_builder.buildOperator("SGS_nms", nms_in_tensors, nms_out_tensors, None, None, options)
7.6.5. 创建model并保存为模型文件¶
创建subgraph
sgs_builder.subgraphs.append(sgs_builder.buildSubGraph(['conv23'],nms_out_tensors,'caffe_yolo_v2'))
创建model
sgs_builder.model = sgs_builder.createModel(3, sgs_builder.operator_codes,
sgs_builder.subgraphs, 'caffe_yolo_v2', sgs_builder.buffers)
file_identifier = b'TFL3'
sgs_builder.builder.Finish(sgs_builder.model, file_identifier)
输出模型
buf = sgs_builder.builder.Output()
保存模型到文件
outfilename = 'caffe_yolo_v2_postprocess.sim'
with open(outfilename, 'wb') as f:
f.write(buf)