FBAGSTM's picture
STM32 AI Experimentation Hub
747451d
# /*---------------------------------------------------------------------------------------------
# * Copyright (c) 2023 STMicroelectronics.
# * All rights reserved.
# *
# * This software is licensed under terms that can be found in the LICENSE file in
# * the root directory of this software component.
# * If no LICENSE file comes with this software, it is provided AS-IS.
# *--------------------------------------------------------------------------------------------*/
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import os
import shutil
import numpy as np
import tensorflow as tf
from hydra.core.hydra_config import HydraConfig
from omegaconf import DictConfig
from common.utils import aspect_ratio_dict, color_mode_n6_dict
from object_detection.tf.src.postprocessing.tflite_ssd_postprocessing_removal.ssd_model_cut_function import ssd_post_processing_removal
def gen_h_user_file_h7(config: DictConfig = None, quantized_model_path: str = None) -> None:
"""
Generates a C header file containing user configuration for the AI model.
Args:
config: A configuration object containing user configuration for the AI model.
quantized_model_path: The path to the quantized model file.
"""
class Flags:
def __init__(self, **entries):
self.__dict__.update(entries)
params = Flags(**config)
if quantized_model_path.lower().endswith('.tflite'):
interpreter_quant = tf.lite.Interpreter(model_path=quantized_model_path)
input_details = interpreter_quant.get_input_details()[0]
output_details = interpreter_quant.get_output_details()[0]
input_shape = input_details['shape']
elif quantized_model_path.lower().endswith('.onnx'):
import onnxruntime
params = config
model = onnxruntime.InferenceSession(quantized_model_path)
inputs = model.get_inputs()
outputs = model.get_outputs()
input_shape_raw = inputs[0].shape
input_shape = [1,input_shape_raw[2],input_shape_raw[3],input_shape_raw[1]]
else:
raise TypeError("Please provide a TFLITE or ONNX model for N6 deployment")
class_names = params.dataset.class_names
path = os.path.join(HydraConfig.get().runtime.output_dir, "C_header/")
try:
os.mkdir(path)
except OSError as error:
print(error)
TFLite_Detection_PostProcess_id = False
XY, WH = None, None
if params.model.model_type == "st_ssd_mobilenet_v1":
classes = '{\\\n"background",'
elif params.model.model_type =="ssd_mobilenet_v2_fpnlite":
name_of_post_process_layer='TFLite_Detection_PostProcess'
for op in interpreter_quant._get_ops_details():
if op['op_name'] == name_of_post_process_layer:
TFLite_Detection_PostProcess_id = op['index']
if TFLite_Detection_PostProcess_id:
print('[INFO] : This TFLITE model contains a post-processing layer')
anchors_path = os.path.join(path,'anchors.h')
path_cut_model, XY, WH = ssd_post_processing_removal(quantized_model_path, TFLite_Detection_PostProcess_id, anchors_path)
quantized_model_path = path_cut_model
else:
print('[INFO] : This TFLITE model doesnt contain a post-processing layer')
classes = '{\\\n"background",'
else:
classes = '{\\\n'
for i, x in enumerate(params.dataset.class_names):
if i == (len(class_names) - 1):
classes = classes + ' "' + str(x) + '"' + '}\\'
else:
classes = classes + ' "' + str(x) + '"' + ' ,' + ('\\\n' if (i % 5 == 0 and i != 0) else '')
with open(os.path.join(path, "ai_model_config.h"), "wt") as f:
f.write("/**\n")
f.write(" ******************************************************************************\n")
f.write(" * @file ai_model_config.h\n")
f.write(" * @author Artificial Intelligence Solutions group (AIS)\n")
f.write(" * @brief User header file for Preprocessing configuration\n")
f.write(" ******************************************************************************\n")
f.write(" * @attention\n")
f.write(" *\n")
f.write(" * Copyright (c) 2024 STMicroelectronics.\n")
f.write(" * All rights reserved.\n")
f.write(" *\n")
f.write(" * This software is licensed under terms that can be found in the LICENSE file in\n")
f.write(" * the root directory of this software component.\n")
f.write(" * If no LICENSE file comes with this software, it is provided AS-IS.\n")
f.write(" *\n")
f.write(" ******************************************************************************\n")
f.write(" */\n\n")
f.write("/* --------------- Generated code ----------------- */\n")
f.write("#ifndef __AI_MODEL_CONFIG_H__\n")
f.write("#define __AI_MODEL_CONFIG_H__\n\n\n")
f.write("/* I/O configuration */\n")
if params.model.model_type == "st_ssd_mobilenet_v1" or params.model.model_type == "ssd_mobilenet_v2_fpnlite":
f.write("#define NB_CLASSES ({})\n".format(len(class_names)+1))
else:
f.write("#define NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define INPUT_HEIGHT ({})\n".format(int(input_shape[1])))
f.write("#define INPUT_WIDTH ({})\n".format(int(input_shape[2])))
f.write("#define INPUT_CHANNELS ({})\n".format(int(input_shape[3])))
f.write("\n")
f.write("/* Classes */\n")
f.write("#define CLASSES_TABLE const char* classes_table[NB_CLASSES] = {}\n".format(classes))
f.write("\n\n")
f.write("/***** Preprocessing configuration *****/\n\n")
f.write("/* Cropping configuration */\n")
yaml_opt = [False, "crop", "padding"]
opt = ["ASPECT_RATIO_FIT", "ASPECT_RATIO_CROP", "ASPECT_RATIO_PADDING"]
f.write("#define ASPECT_RATIO_FIT (1)\n")
f.write("#define ASPECT_RATIO_CROP (2)\n")
f.write("#define ASPECT_RATIO_PADDING (3)\n")
f.write("\n")
f.write("#define ASPECT_RATIO_MODE {}\n".format(aspect_ratio_dict[params.preprocessing.resizing.aspect_ratio]))
f.write("\n")
f.write("/***** Postprocessing configuration *****/\n\n")
f.write("/* Postprocessing type configuration */\n")
f.write("#define POSTPROCESS_CENTER_NET (1)\n")
f.write("#define POSTPROCESS_YOLO_V2 (2)\n")
f.write("#define POSTPROCESS_ST_SSD (3)\n")
f.write("#define POSTPROCESS_SSD (4)\n")
f.write("#define POSTPROCESS_ST_YOLO_X (5)\n\n")
if (params.model.model_type == "st_ssd_mobilenet_v1" or params.model.model_type == "ssd_mobilenet_v2_fpnlite") and not TFLite_Detection_PostProcess_id:
f.write("#define POSTPROCESS_TYPE POSTPROCESS_ST_SSD\n\n")
elif TFLite_Detection_PostProcess_id:
f.write("#define POSTPROCESS_TYPE POSTPROCESS_SSD\n\n")
elif params.model.model_type == "CENTER_NET":
f.write("#define POSTPROCESS_TYPE POSTPROCESS_CENTER_NET\n\n")
elif (params.model.model_type == "yolov2t" or params.model.model_type =="st_yololcv1"):
f.write("#define POSTPROCESS_TYPE POSTPROCESS_YOLO_V2\n\n")
elif params.model.model_type == "st_yoloxn":
f.write("#define POSTPROCESS_TYPE POSTPROCESS_ST_YOLO_X\n\n")
else:
raise TypeError("please select one of this supported post processing options [CENTER_NET,st_yoloxn, st_yololcv1, yolov2t, st_ssd_mobilenet_v1, ssd_mobilenet_v2_fpnlite ]")
if (params.model.model_type == "st_ssd_mobilenet_v1" or params.model.model_type == "ssd_mobilenet_v2_fpnlite") and not TFLite_Detection_PostProcess_id:
f.write("/* Postprocessing ST_SSD configuration */\n")
f.write("#define AI_OBJDETECT_SSD_ST_PP_NB_CLASSES ({})\n".format(len(class_names)+1))
f.write("#define AI_OBJDETECT_SSD_ST_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OBJDETECT_SSD_ST_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OBJDETECT_SSD_ST_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OBJDETECT_SSD_ST_PP_TOTAL_DETECTIONS ({})\n".format(int(output_details['shape'][1])))
elif TFLite_Detection_PostProcess_id:
f.write("\n/* Postprocessing SSD configuration */\n")
f.write("#define AI_OBJDETECT_SSD_PP_XY_SCALE ({})\n".format(XY))
f.write("#define AI_OBJDETECT_SSD_PP_WH_SCALE ({})\n".format(WH))
f.write("#define AI_OBJDETECT_SSD_PP_NB_CLASSES ({})\n".format(len(class_names)+1))
f.write("#define AI_OBJDETECT_SSD_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OBJDETECT_SSD_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OBJDETECT_SSD_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OBJDETECT_SSD_PP_TOTAL_DETECTIONS ({})\n".format(int(output_details['shape'][1])))
elif (params.model.model_type == "yolov2t" or params.model.model_type == "st_yololcv1"):
f.write("\n/* Postprocessing TINY_YOLO_V2 configuration */\n")
yolo_anchors = np.concatenate(params.postprocessing.yolo_anchors).flatten()
f.write("#define AI_OBJDETECT_YOLOV2_PP_NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define AI_OBJDETECT_YOLOV2_PP_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride)))
f.write("#define AI_OBJDETECT_YOLOV2_PP_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride)))
f.write("#define AI_OBJDETECT_YOLOV2_PP_NB_INPUT_BOXES (AI_OBJDETECT_YOLOV2_PP_GRID_WIDTH * AI_OBJDETECT_YOLOV2_PP_GRID_HEIGHT)\n")
f.write("#define AI_OBJDETECT_YOLOV2_PP_NB_ANCHORS ({})\n".format(int(len(yolo_anchors)/2)))
anchors_string = "{" + ", ".join([f"{(x):.6f}" for x in yolo_anchors]) + "}"
f.write("static const float32_t AI_OBJDETECT_YOLOV2_PP_ANCHORS[2*AI_OBJDETECT_YOLOV2_PP_NB_ANCHORS] ={};\n".format(anchors_string))
f.write("#define AI_OBJDETECT_YOLOV2_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OBJDETECT_YOLOV2_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OBJDETECT_YOLOV2_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
elif (params.model.model_type == "st_yoloxn"):
f.write("\n/* Postprocessing ST_YOLO_X configuration */\n")
yolo_anchors = np.concatenate(params.postprocessing.yolo_anchors).flatten()
f.write("#define AI_OBJDETECT_YOLOVX_PP_NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define AI_OBJDETECT_YOLOVX_PP_L_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride[0])))
f.write("#define AI_OBJDETECT_YOLOVX_PP_L_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride[0])))
f.write("#define AI_OBJDETECT_YOLOVX_PP_L_NB_INPUT_BOXES (AI_OBJDETECT_YOLOVX_PP_L_GRID_WIDTH * AI_OBJDETECT_YOLOVX_PP_L_GRID_HEIGHT)\n")
f.write("#define AI_OBJDETECT_YOLOVX_PP_M_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride[1])))
f.write("#define AI_OBJDETECT_YOLOVX_PP_M_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride[1])))
f.write("#define AI_OBJDETECT_YOLOVX_PP_M_NB_INPUT_BOXES (AI_OBJDETECT_YOLOVX_PP_M_GRID_WIDTH * AI_OBJDETECT_YOLOVX_PP_M_GRID_HEIGHT)\n")
f.write("#define AI_OBJDETECT_YOLOVX_PP_S_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride[2])))
f.write("#define AI_OBJDETECT_YOLOVX_PP_S_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride[2])))
f.write("#define AI_OBJDETECT_YOLOVX_PP_S_NB_INPUT_BOXES (AI_OBJDETECT_YOLOVX_PP_S_GRID_WIDTH * AI_OBJDETECT_YOLOVX_PP_S_GRID_HEIGHT)\n")
f.write("#define AI_OBJDETECT_YOLOVX_PP_NB_ANCHORS ({})\n".format(int(len(yolo_anchors)/2)))
anchors_string = "{" + ", ".join([f"{(x*int(input_shape[2]//params.postprocessing.network_stride[0])):.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OBJDETECT_YOLOVX_PP_L_ANCHORS[2*AI_OBJDETECT_YOLOVX_PP_NB_ANCHORS] ={};\n".format(anchors_string))
anchors_string = "{" + ", ".join([f"{(x*int(input_shape[2]//params.postprocessing.network_stride[1])):.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OBJDETECT_YOLOVX_PP_M_ANCHORS[2*AI_OBJDETECT_YOLOVX_PP_NB_ANCHORS] ={};\n".format(anchors_string))
anchors_string = "{" + ", ".join([f"{(x*int(input_shape[2]//params.postprocessing.network_stride[2])):.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OBJDETECT_YOLOVX_PP_S_ANCHORS[2*AI_OBJDETECT_YOLOVX_PP_NB_ANCHORS] ={};\n".format(anchors_string))
f.write("#define AI_OBJDETECT_YOLOVX_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OBJDETECT_YOLOVX_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OBJDETECT_YOLOVX_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("\n")
f.write("/* Input color format configuration */\n")
yaml_opt = ["rgb", "bgr", "grayscale"]
opt = ["RGB_FORMAT", "BGR_FORMAT", "GRAYSCALE_FORMAT"]
f.write("#define RGB_FORMAT (1)\n")
f.write("#define BGR_FORMAT (2)\n")
f.write("#define GRAYSCALE_FORMAT (3)\n")
f.write("\n")
f.write("#define PP_COLOR_MODE {}\n".format(opt[yaml_opt.index(params.preprocessing.color_mode)]))
f.write("\n")
f.write("/* Input/Output quantization configuration */\n")
opt = ["UINT8_FORMAT", "INT8_FORMAT", "FLOAT32_FORMAT"]
f.write("#define UINT8_FORMAT (1)\n")
f.write("#define INT8_FORMAT (2)\n")
f.write("#define FLOAT32_FORMAT (3)\n")
f.write("\n")
f.write("#define QUANT_INPUT_TYPE {}\n".format(
opt[[np.uint8, np.int8, np.float32].index(input_details['dtype'])]))
f.write("#define QUANT_OUTPUT_TYPE {}\n".format(
opt[[np.uint8, np.int8, np.float32].index(output_details['dtype'])]))
f.write("\n")
f.write("#endif /* __AI_MODEL_CONFIG_H__ */\n")
return TFLite_Detection_PostProcess_id, quantized_model_path
def gen_h_user_file_n6(config: DictConfig = None, quantized_model_path: str = None) -> None:
"""
Generates a C header file containing user configuration for the AI model.
Args:
config: A configuration object containing user configuration for the AI model.
quantized_model_path: The path to the quantized model file.
"""
class Flags:
def __init__(self, **entries):
self.__dict__.update(entries)
params = Flags(**config)
if quantized_model_path.lower().endswith('.tflite'):
interpreter_quant = tf.lite.Interpreter(model_path=quantized_model_path)
input_details = interpreter_quant.get_input_details()[0]
output_details = interpreter_quant.get_output_details()[0]
input_shape = input_details['shape']
elif quantized_model_path.lower().endswith('.onnx'):
import onnxruntime
params = config
model = onnxruntime.InferenceSession(quantized_model_path)
inputs = model.get_inputs()
outputs = model.get_outputs()
input_shape_raw = inputs[0].shape
input_shape = [1,input_shape_raw[2],input_shape_raw[3],input_shape_raw[1]]
else:
raise TypeError("Please provide a TFLITE or ONNX model for N6 deployment")
class_names = params.dataset.class_names
path = os.path.join(HydraConfig.get().runtime.output_dir, "C_header/")
try:
os.mkdir(path)
except OSError as error:
print(error)
TFLite_Detection_PostProcess_id = False
XY, WH = None, None
if params.model.model_type == "st_ssd_mobilenet_v1":
classes = '{\\\n"background",'
elif params.model.model_type =="ssd_mobilenet_v2_fpnlite":
name_of_post_process_layer='TFLite_Detection_PostProcess'
for op in interpreter_quant._get_ops_details():
if op['op_name'] == name_of_post_process_layer:
TFLite_Detection_PostProcess_id = op['index']
if TFLite_Detection_PostProcess_id:
print('[INFO] : This TFLITE model contains a post-processing layer')
anchors_path = os.path.join(path,'anchors.h')
path_cut_model, XY, WH = ssd_post_processing_removal(quantized_model_path, TFLite_Detection_PostProcess_id, anchors_path)
quantized_model_path = path_cut_model
else:
print('[INFO] : This TFLITE model doesnt contain a post-processing layer')
classes = '{\\\n"background",'
else:
classes = '{\\\n'
for i, x in enumerate(params.dataset.class_names):
if i == (len(class_names) - 1):
classes = classes + ' "' + str(x) + '"' + '}\\'
else:
classes = classes + ' "' + str(x) + '"' + ' ,' + ('\\\n' if (i % 5 == 0 and i != 0) else '')
if params.model.model_type == "face_detect_front":
outs_info = interpreter_quant.get_output_details()
#print(outs_info)
output_shapes =[]
for buffer in outs_info:
output_shapes.append(buffer["shape"])
sorted_shapes = sorted(output_shapes, key=lambda arr: (arr[1], arr[2]), reverse=True)
SSD_OPTIONS_FRONT = {
'num_layers': 4,
'input_size_height': 128,
'input_size_width': 128,
'anchor_offset_x': 0.5,
'anchor_offset_y': 0.5,
'strides': [8, 16, 16, 16],
'interpolated_scale_aspect_ratio': 1.0}
from object_detection.tf.src.postprocessing import ssd_generate_anchors
anchors=ssd_generate_anchors(SSD_OPTIONS_FRONT)
anch_0_rows = int(sorted_shapes[0][1])
anch_1_rows = int(sorted_shapes[2][1])
anch_0 = anchors[:anch_0_rows, :]
anch_1 = anchors[anch_0_rows:, :]
anch_0_flat = anch_0.reshape(int(anch_0.shape[0] * anch_0.shape[1]))
anch_1_flat = anch_1.reshape(int(anch_1.shape[0] * anch_1.shape[1]))
# Format the array elements as strings with 'f' suffix for floats in C
formatted_anch_0_flat = ", ".join(f"{x:.6f}" for x in anch_0_flat)
c_anch_0_str = f"const float32_t g_Anchors_0[{int(anch_0.shape[0] * anch_0.shape[1])}] = {{ {formatted_anch_0_flat} }};"
formatted_anch_1_flat = ", ".join(f"{x:.6f}" for x in anch_1_flat)
c_anch_1_str = f"const float32_t g_Anchors_1[{int(anch_1.shape[0] * anch_1.shape[1])}] = {{ {formatted_anch_1_flat} }};"
with open(os.path.join(path, "fd_blazeface_anchors_0.h"), "wt") as f:
f.write("#ifndef __ANCHORS_0_H__\n")
f.write("#define __ANCHORS_0_H__\n\n")
f.write(c_anch_0_str)
f.write("\n")
f.write("#endif /* __ANCHORS_0_H__ */\n")
with open(os.path.join(path, "fd_blazeface_anchors_1.h"), "wt") as f:
f.write("#ifndef __ANCHORS_1_H__\n")
f.write("#define __ANCHORS_1_H__\n\n")
f.write(c_anch_1_str)
f.write("\n")
f.write("#endif /* __ANCHORS_1_H__ */\n")
# Copy the anchors to the C project
anchors_0_path_C = os.path.join(params.deployment.c_project_path, 'Application', params.deployment.hardware_setup.board, 'Inc', 'fd_blazeface_anchors_0.h')
anchors_1_path_C = os.path.join(params.deployment.c_project_path, 'Application', params.deployment.hardware_setup.board, 'Inc', 'fd_blazeface_anchors_1.h')
if os.path.exists(anchors_0_path_C):
os.remove(anchors_0_path_C)
if os.path.exists(anchors_1_path_C):
os.remove(anchors_1_path_C)
shutil.copy(os.path.join(path, "fd_blazeface_anchors_0.h"), anchors_0_path_C)
shutil.copy(os.path.join(path, "fd_blazeface_anchors_1.h"), anchors_1_path_C)
with open(os.path.join(path, "app_config.h"), "wt") as f:
f.write("/**\n")
f.write("******************************************************************************\n")
f.write("* @file app_config.h\n")
f.write("* @author GPM Application Team\n")
f.write("*\n")
f.write("******************************************************************************\n")
f.write("* @attention\n")
f.write("*\n")
f.write("* Copyright (c) 2023 STMicroelectronics.\n")
f.write("* All rights reserved.\n")
f.write("*\n")
f.write("* This software is licensed under terms that can be found in the LICENSE file\n")
f.write("* in the root directory of this software component.\n")
f.write("* If no LICENSE file comes with this software, it is provided AS-IS.\n")
f.write("*\n")
f.write("******************************************************************************\n")
f.write("*/\n\n")
f.write("/* --------------- Generated code ----------------- */\n")
f.write("#ifndef APP_CONFIG\n")
f.write("#define APP_CONFIG\n\n")
f.write('#include "arm_math.h"\n\n')
f.write("#define USE_DCACHE\n\n")
f.write("/*Defines: CMW_MIRRORFLIP_NONE; CMW_MIRRORFLIP_FLIP; CMW_MIRRORFLIP_MIRROR; CMW_MIRRORFLIP_FLIP_MIRROR;*/\n")
f.write("#define CAMERA_FLIP CMW_MIRRORFLIP_NONE\n\n")
f.write("")
f.write("#define ASPECT_RATIO_CROP (1) /* Crop both pipes to nn input aspect ratio; Original aspect ratio kept */\n")
f.write("#define ASPECT_RATIO_FIT (2) /* Resize both pipe to NN input aspect ratio; Original aspect ratio not kept */\n")
f.write("#define ASPECT_RATIO_FULLSCREEN (3) /* Resize camera image to NN input size and display a fullscreen image */\n")
f.write("#define ASPECT_RATIO_MODE {}\n".format(aspect_ratio_dict[params.preprocessing.resizing.aspect_ratio]))
f.write("\n")
f.write("/* Postprocessing type configuration */\n")
if (params.model.model_type == "st_ssd_mobilenet_v1" or params.model.model_type == "ssd_mobilenet_v2_fpnlite") and not TFLite_Detection_PostProcess_id:
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_ST_SSD_UF\n")
elif TFLite_Detection_PostProcess_id:
raise TypeError("Not supported yet on N6")
elif params.model.model_type == "CENTER_NET":
raise TypeError("Not supported yet on N6")
elif (params.model.model_type == "yolov2t" or params.model.model_type == "st_yololcv1"):
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_YOLO_V2_UI\n\n")
elif params.model.model_type in ("yolov8n", "yolov11n", "yolov5u"):
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_YOLO_V8_UI\n")
elif params.model.model_type == "st_yoloxn":
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_ST_YOLOX_UI\n")
elif params.model.model_type == "face_detect_front":
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_BLAZEFACE_UI\n")
else:
raise TypeError("Please select one of the supported model_type")
f.write("\n")
f.write("#define COLOR_BGR (0)\n")
f.write("#define COLOR_RGB (1)\n")
f.write("#define COLOR_MODE {}\n".format(color_mode_n6_dict[params.preprocessing.color_mode]))
f.write("/* Classes */\n")
if params.model.model_type == "st_ssd_mobilenet_v1" or params.model.model_type == "ssd_mobilenet_v2_fpnlite":
f.write("#define NB_CLASSES ({})\n".format(len(class_names)+1))
else:
f.write("#define NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define CLASSES_TABLE const char* classes_table[NB_CLASSES] = {}\n".format(classes))
if (params.model.model_type == "st_ssd_mobilenet_v1" or params.model.model_type == "ssd_mobilenet_v2_fpnlite") and not TFLite_Detection_PostProcess_id:
f.write("/* Postprocessing ST_SSD configuration */\n")
f.write("#define AI_OD_SSD_ST_PP_NB_CLASSES ({})\n".format(len(class_names)+1))
f.write("#define AI_OD_SSD_ST_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OD_SSD_ST_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_SSD_ST_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OD_SSD_ST_PP_TOTAL_DETECTIONS ({})\n".format(int(output_details['shape'][1])))
elif TFLite_Detection_PostProcess_id:
f.write("\n/* Postprocessing SSD configuration */\n")
f.write("#define AI_OD_SSD_PP_XY_SCALE ({})\n".format(XY))
f.write("#define AI_OD_SSD_PP_WH_SCALE ({})\n".format(WH))
f.write("#define AI_OD_SSD_PP_NB_CLASSES ({})\n".format(len(class_names)+1))
f.write("#define AI_OD_SSD_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OD_SSD_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_SSD_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OD_SSD_PP_TOTAL_DETECTIONS ({})\n".format(int(output_details['shape'][1])))
elif (params.model.model_type in ("yolov2t", "st_yololcv1")):
f.write("\n/* Postprocessing TINY_YOLO_V2 configuration */\n")
f.write("#define AI_OD_YOLOV2_PP_NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define AI_OD_YOLOV2_PP_NB_ANCHORS ({})\n".format(int(len(params.postprocessing.yolo_anchors))))
f.write("#define AI_OD_YOLOV2_PP_GRID_WIDTH ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride)))
f.write("#define AI_OD_YOLOV2_PP_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride)))
f.write("#define AI_OD_YOLOV2_PP_NB_INPUT_BOXES (AI_OD_YOLOV2_PP_GRID_WIDTH * AI_OD_YOLOV2_PP_GRID_HEIGHT)\n")
anchors_string = "{" + ", ".join([f"{x:.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OD_YOLOV2_PP_ANCHORS[2*AI_OD_YOLOV2_PP_NB_ANCHORS] ={};\n".format(anchors_string))
f.write("#define AI_OD_YOLOV2_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_YOLOV2_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OD_YOLOV2_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
elif (params.model.model_type == "st_yoloxn"):
f.write("\n/* Postprocessing ST_YOLO_X configuration */\n")
yolo_anchors = np.concatenate(params.postprocessing.yolo_anchors).flatten()
f.write("#define AI_OD_ST_YOLOX_PP_NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define AI_OD_ST_YOLOX_PP_L_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride[0])))
f.write("#define AI_OD_ST_YOLOX_PP_L_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride[0])))
f.write("#define AI_OD_ST_YOLOX_PP_L_NB_INPUT_BOXES (AI_OD_ST_YOLOX_PP_L_GRID_WIDTH * AI_OD_ST_YOLOX_PP_L_GRID_HEIGHT)\n")
f.write("#define AI_OD_ST_YOLOX_PP_M_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride[1])))
f.write("#define AI_OD_ST_YOLOX_PP_M_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride[1])))
f.write("#define AI_OD_ST_YOLOX_PP_M_NB_INPUT_BOXES (AI_OD_ST_YOLOX_PP_M_GRID_WIDTH * AI_OD_ST_YOLOX_PP_M_GRID_HEIGHT)\n")
f.write("#define AI_OD_ST_YOLOX_PP_S_GRID_WIDTH ({})\n".format(int(input_shape[2]//params.postprocessing.network_stride[2])))
f.write("#define AI_OD_ST_YOLOX_PP_S_GRID_HEIGHT ({})\n".format(int(input_shape[1]//params.postprocessing.network_stride[2])))
f.write("#define AI_OD_ST_YOLOX_PP_S_NB_INPUT_BOXES (AI_OD_ST_YOLOX_PP_S_GRID_WIDTH * AI_OD_ST_YOLOX_PP_S_GRID_HEIGHT)\n")
f.write("#define AI_OD_ST_YOLOX_PP_NB_ANCHORS ({})\n".format(int(len(yolo_anchors)/2)))
anchors_string = "{" + ", ".join([f"{(x*int(input_shape[2]//params.postprocessing.network_stride[0])):.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OD_ST_YOLOX_PP_L_ANCHORS[2*AI_OD_ST_YOLOX_PP_NB_ANCHORS] ={};\n".format(anchors_string))
anchors_string = "{" + ", ".join([f"{(x*int(input_shape[2]//params.postprocessing.network_stride[1])):.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OD_ST_YOLOX_PP_M_ANCHORS[2*AI_OD_ST_YOLOX_PP_NB_ANCHORS] ={};\n".format(anchors_string))
anchors_string = "{" + ", ".join([f"{(x*int(input_shape[2]//params.postprocessing.network_stride[2])):.6f}" for row in params.postprocessing.yolo_anchors for x in row]) + "}"
f.write("static const float32_t AI_OD_ST_YOLOX_PP_S_ANCHORS[2*AI_OD_ST_YOLOX_PP_NB_ANCHORS] ={};\n".format(anchors_string))
f.write("#define AI_OD_ST_YOLOX_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write("#define AI_OD_ST_YOLOX_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_ST_YOLOX_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
elif (params.model.model_type in ("yolov8n", "yolov11n", "yolov5u")):
f.write("\n/* Postprocessing YOLO_V8 configuration */\n")
if quantized_model_path.lower().endswith('.tflite'):
out_shape = output_details["shape"]
elif quantized_model_path.lower().endswith('.onnx'):
out_shape = outputs[0].shape
else:
raise TypeError("Please provide a TFLITE or ONNX model for N6 deployment")
f.write("#define AI_OD_YOLOV8_PP_NB_CLASSES ({})\n".format(int(out_shape[1]-4)))
f.write("#define AI_OD_YOLOV8_PP_TOTAL_BOXES ({})\n".format(int(out_shape[2])))
f.write("#define AI_OD_YOLOV8_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OD_YOLOV8_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_YOLOV8_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
elif (params.model.model_type in ("face_detect_front")):
f.write("\n/* Postprocessing OD_BLAZEFACE configuration */\n")
outs_info = interpreter_quant.get_output_details()
#print(outs_info)
output_shapes =[]
for buffer in outs_info:
output_shapes.append(buffer["shape"])
sorted_shapes = sorted(output_shapes, key=lambda arr: (arr[1], arr[2]), reverse=True)
f.write("#define AI_OD_BLAZEFACE_PP_NB_KEYPOINTS ({})\n".format(int((sorted_shapes[0][2]-4)/2)))
f.write("#define AI_OD_BLAZEFACE_PP_NB_CLASSES ({})\n".format(int(sorted_shapes[-1][-1])))
f.write("#define AI_OD_BLAZEFACE_PP_IMG_SIZE ({})\n".format(int(input_shape[1])))
f.write("#define AI_OD_BLAZEFACE_PP_OUT_0_NB_BOXES ({})\n".format(int(sorted_shapes[0][1])))
f.write("#define AI_OD_BLAZEFACE_PP_OUT_1_NB_BOXES ({})\n".format(int(sorted_shapes[-1][1])))
f.write("#define AI_OD_BLAZEFACE_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OD_BLAZEFACE_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_BLAZEFACE_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write('#define WELCOME_MSG_1 "{}"\n'.format(os.path.basename(params.model.model_path)))
# @Todo retieve info from stedgeai output
if config.deployment.hardware_setup.board == 'NUCLEO-N657X0-Q':
f.write('#define WELCOME_MSG_2 ((char *[2]) {"Model Running in STM32 MCU", "internal memory"})')
else:
f.write('#define WELCOME_MSG_2 "{}"\n'.format("Model Running in STM32 MCU internal memory"))
f.write("\n")
f.write("#endif /* APP_CONFIG */\n")
return TFLite_Detection_PostProcess_id, quantized_model_path
def gen_h_user_file_n6_onnx_ssd(config, quantized_model_path: str = None) -> None:
"""
Generates a C header file containing user configuration for the AI model.
Args:
config: A configuration object containing user configuration for the AI model.
quantized_model_path: The path to the quantized model file.
"""
import onnxruntime
import sys
params = config
model = onnxruntime.InferenceSession(quantized_model_path)
inputs = model.get_inputs()
outputs = model.get_outputs()
input_shape_raw = inputs[0].shape
class_names = params.dataset.class_names
path = os.path.join(HydraConfig.get().runtime.output_dir, "C_header/")
try:
os.mkdir(path)
except OSError as error:
print(error)
classes = '{\\\n"background",'
for i, x in enumerate(params.dataset.class_names):
if i == (len(class_names) - 1):
classes = classes + ' "' + str(x) + '"' + '}\\'
else:
classes = classes + ' "' + str(x) + '"' + ' ,' + ('\\\n' if (i % 5 == 0 and i != 0) else '')
if params.model.model_type == "ssd":
from object_detection.tf.src.postprocessing import generate_ssd_priors
center_variance = 0.1
size_variance = 0.2
image_size = (input_shape_raw[2], input_shape_raw[3], input_shape_raw[1])
model_anchor_centers = generate_ssd_priors(image_size[0])
anch_arr = model_anchor_centers.numpy()
anch_flat = anch_arr.reshape(int(anch_arr.shape[0] * anch_arr.shape[1]))
# Format the array elements as strings with 'f' suffix for floats in C
formatted_anch_flat = ", ".join(f"{float(x)}" for x in anch_flat)
c_anch_str = f"const float32_t g_Anchors[{int(anch_arr.shape[0] * anch_arr.shape[1])}] = {{ {formatted_anch_flat} }};"
with open(os.path.join(path, "ssd_anchors.h"), "wt") as f:
f.write("#ifndef __ANCHORS_H__\n")
f.write("#define __ANCHORS_H__\n\n")
f.write(c_anch_str)
f.write("\n")
f.write("#endif /* __ANCHORS_H__ */\n")
# Copy the anchors to the C project
anchors_path_C = os.path.join(params.deployment.c_project_path, 'Application', params.deployment.hardware_setup.board, 'Inc', 'ssd_anchors.h')
if os.path.exists(anchors_path_C):
os.remove(anchors_path_C)
shutil.copy(os.path.join(path, "ssd_anchors.h"), anchors_path_C)
with open(os.path.join(path, "app_config.h"), "wt") as f:
f.write("/**\n")
f.write("******************************************************************************\n")
f.write("* @file app_config.h\n")
f.write("* @author GPM Application Team\n")
f.write("*\n")
f.write("******************************************************************************\n")
f.write("* @attention\n")
f.write("*\n")
f.write("* Copyright (c) 2023 STMicroelectronics.\n")
f.write("* All rights reserved.\n")
f.write("*\n")
f.write("* This software is licensed under terms that can be found in the LICENSE file\n")
f.write("* in the root directory of this software component.\n")
f.write("* If no LICENSE file comes with this software, it is provided AS-IS.\n")
f.write("*\n")
f.write("******************************************************************************\n")
f.write("*/\n\n")
f.write("/* --------------- Generated code ----------------- */\n")
f.write("#ifndef APP_CONFIG\n")
f.write("#define APP_CONFIG\n\n")
f.write('#include "arm_math.h"\n\n')
f.write("#define USE_DCACHE\n\n")
f.write("/*Defines: CMW_MIRRORFLIP_NONE; CMW_MIRRORFLIP_FLIP; CMW_MIRRORFLIP_MIRROR; CMW_MIRRORFLIP_FLIP_MIRROR;*/\n")
f.write("#define CAMERA_FLIP CMW_MIRRORFLIP_NONE\n\n")
f.write("")
f.write("#define ASPECT_RATIO_CROP (1) /* Crop both pipes to nn input aspect ratio; Original aspect ratio kept */\n")
f.write("#define ASPECT_RATIO_FIT (2) /* Resize both pipe to NN input aspect ratio; Original aspect ratio not kept */\n")
f.write("#define ASPECT_RATIO_FULLSCREEN (3) /* Resize camera image to NN input size and display a fullscreen image */\n")
f.write("#define ASPECT_RATIO_MODE {}\n".format(aspect_ratio_dict[params.preprocessing.resizing.aspect_ratio]))
f.write("\n")
f.write("/* Postprocessing type configuration */\n")
if params.model.model_type == "ssd":
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_SSD_UI\n")
else:
raise TypeError("Please select one of the supported model_type")
f.write("\n")
f.write("#define COLOR_BGR (0)\n")
f.write("#define COLOR_RGB (1)\n")
f.write("#define COLOR_MODE {}\n".format(color_mode_n6_dict[params.preprocessing.color_mode]))
f.write("/* Classes */\n")
f.write("#define NB_CLASSES ({})\n".format(len(class_names)+1))
f.write("#define CLASSES_TABLE const char* classes_table[NB_CLASSES] = {}\n".format(classes))
if params.model.model_type == "ssd":
f.write("\n/* Postprocessing SSD configuration */\n")
f.write("#define AI_OD_SSD_PP_NB_CLASSES ({})\n".format(len(class_names)+1))
f.write("#define AI_OD_SSD_PP_TOTAL_DETECTIONS ({})\n".format(int(anch_arr.shape[0])))
f.write("#define AI_OD_SSD_PP_XY_VARIANCE ({})\n".format(float(center_variance)))
f.write("#define AI_OD_SSD_PP_WH_VARIANCE ({})\n".format(float(size_variance)))
f.write("#define AI_OD_SSD_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OD_SSD_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_SSD_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write('#define WELCOME_MSG_1 "{}"\n'.format(os.path.basename(params.model.model_path)))
# @Todo retieve info from stedgeai output
if config.deployment.hardware_setup.board == 'NUCLEO-N657X0-Q':
f.write('#define WELCOME_MSG_2 ((char *[2]) {"Model Running in STM32 MCU", "internal memory"})')
else:
f.write('#define WELCOME_MSG_2 "{}"\n'.format("Model Running in STM32 MCU internal memory"))
f.write("\n")
f.write("#endif /* APP_CONFIG */\n")
return None, quantized_model_path
def gen_h_user_file_n6_onnx_yolod(config, quantized_model_path: str = None) -> None:
"""
Generates a C header file containing user configuration for the AI model.
Args:
config: A configuration object containing user configuration for the AI model.
quantized_model_path: The path to the quantized model file.
"""
import onnxruntime
import sys
params = config
model = onnxruntime.InferenceSession(quantized_model_path)
inputs = model.get_inputs()
outputs = model.get_outputs()
input_shape_raw = inputs[0].shape
class_names = params.dataset.class_names
path = os.path.join(HydraConfig.get().runtime.output_dir, "C_header/")
try:
os.mkdir(path)
except OSError as error:
print(error)
classes = '{\\\n'
for i, x in enumerate(params.dataset.class_names):
if i == (len(class_names) - 1):
classes = classes + ' "' + str(x) + '"' + '}\\'
else:
classes = classes + ' "' + str(x) + '"' + ' ,' + ('\\\n' if (i % 5 == 0 and i != 0) else '')
if params.model.model_type == "st_yolod":
image_size = (input_shape_raw[2], input_shape_raw[3], input_shape_raw[1])
with open(os.path.join(path, "app_config.h"), "wt") as f:
f.write("/**\n")
f.write("******************************************************************************\n")
f.write("* @file app_config.h\n")
f.write("* @author GPM Application Team\n")
f.write("*\n")
f.write("******************************************************************************\n")
f.write("* @attention\n")
f.write("*\n")
f.write("* Copyright (c) 2023 STMicroelectronics.\n")
f.write("* All rights reserved.\n")
f.write("*\n")
f.write("* This software is licensed under terms that can be found in the LICENSE file\n")
f.write("* in the root directory of this software component.\n")
f.write("* If no LICENSE file comes with this software, it is provided AS-IS.\n")
f.write("*\n")
f.write("******************************************************************************\n")
f.write("*/\n\n")
f.write("/* --------------- Generated code ----------------- */\n")
f.write("#ifndef APP_CONFIG\n")
f.write("#define APP_CONFIG\n\n")
f.write('#include "arm_math.h"\n\n')
f.write("#define USE_DCACHE\n\n")
f.write("/*Defines: CMW_MIRRORFLIP_NONE; CMW_MIRRORFLIP_FLIP; CMW_MIRRORFLIP_MIRROR; CMW_MIRRORFLIP_FLIP_MIRROR;*/\n")
f.write("#define CAMERA_FLIP CMW_MIRRORFLIP_NONE\n\n")
f.write("")
f.write("#define ASPECT_RATIO_CROP (1) /* Crop both pipes to nn input aspect ratio; Original aspect ratio kept */\n")
f.write("#define ASPECT_RATIO_FIT (2) /* Resize both pipe to NN input aspect ratio; Original aspect ratio not kept */\n")
f.write("#define ASPECT_RATIO_FULLSCREEN (3) /* Resize camera image to NN input size and display a fullscreen image */\n")
f.write("#define ASPECT_RATIO_MODE {}\n".format(aspect_ratio_dict[params.preprocessing.resizing.aspect_ratio]))
f.write("\n")
f.write("/* Postprocessing type configuration */\n")
if params.model.model_type == "st_yolod":
f.write("#define POSTPROCESS_TYPE POSTPROCESS_OD_ST_YOLOD_UI\n")
else:
raise TypeError("Please select one of the supported model_type")
f.write("\n")
f.write("#define COLOR_BGR (0)\n")
f.write("#define COLOR_RGB (1)\n")
f.write("#define COLOR_MODE {}\n".format(color_mode_n6_dict[params.preprocessing.color_mode]))
f.write("/* Classes */\n")
f.write("#define NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define CLASSES_TABLE const char* classes_table[NB_CLASSES] = {}\n".format(classes))
if params.model.model_type == "st_yolod":
f.write("\n/* Postprocessing ST_YOLOD configuration */\n")
f.write("#define AI_OD_YOLO_D_PP_NB_CLASSES ({})\n".format(len(class_names)))
f.write("#define AI_OD_YOLO_D_PP_IMG_WIDTH ({})\n".format(int(image_size[0])))
f.write("#define AI_OD_YOLO_D_PP_IMG_HEIGHT ({})\n".format(int(image_size[1])))
f.write("#define AI_OD_YOLO_D_PP_STRIDE_0 ({})\n".format(8))
f.write("#define AI_OD_YOLO_D_PP_STRIDE_1 ({})\n".format(16))
f.write("#define AI_OD_YOLO_D_PP_STRIDE_2 ({})\n".format(32))
f.write("#define AI_OD_YOLO_D_PP_MAX_BOXES_LIMIT ({})\n".format(int(params.postprocessing.max_detection_boxes)))
f.write("#define AI_OD_YOLO_D_PP_CONF_THRESHOLD ({})\n".format(float(params.postprocessing.confidence_thresh)))
f.write("#define AI_OD_YOLO_D_PP_IOU_THRESHOLD ({})\n".format(float(params.postprocessing.NMS_thresh)))
f.write('#define WELCOME_MSG_1 "{}"\n'.format(os.path.basename(params.model.model_path)))
# @Todo retieve info from stedgeai output
if config.deployment.hardware_setup.board == 'NUCLEO-N657X0-Q':
f.write('#define WELCOME_MSG_2 ((char *[2]) {"Model Running in STM32 MCU", "internal memory"})')
else:
f.write('#define WELCOME_MSG_2 "{}"\n'.format("Model Running in STM32 MCU internal memory"))
f.write("\n")
f.write("#endif /* APP_CONFIG */\n")
return None, quantized_model_path