MCUXpresso_MIMXRT1052xxxxB/boards/evkbimxrt1050/eiq_examples/mpp_camera_mobilenet_view_tflm/camera_mobilenet_view.c
Yilin Sun 6baf4427ce
Updated to v2.15.000
Signed-off-by: Yilin Sun <imi415@imi.moe>
2024-03-18 23:15:10 +08:00

506 lines
20 KiB
C

/*
* Copyright 2022-2023 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/* @brief This example application shows usage of MultiMedia Pipeline to build a simple graph:
* 2D camera -> split -> image converter -> draw labeled rectangle -> display
* +-> image converter -> inference engine (model: MobileNet v1)
* The camera view finder is displayed on screen
* The model performs classification among a list of 1000 object types
*(see models/mobilenet_v1_0.25_128_quant_int8_cm7/mobilenetv1_labels.h),
* the model output is displayed on UART console by application */
/* FreeRTOS kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
#include "string.h"
#include "stdbool.h"
#include "atomic.h"
/* NXP includes. */
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "board_init.h"
/* MPP includes */
#include "mpp_api.h"
#include "mpp_config.h"
#include "models/mobilenet_v1_0.25_128_quant_int8/mobilenetv1_output_postproc.h"
/*******************************************************************************
* Definitions
******************************************************************************/
typedef struct _user_data_t {
int inference_frame_num;
mpp_t mp;
mpp_elem_handle_t elem;
mpp_labeled_rect_t labels[1];
mobilenet_post_proc_data_t inf_out;
uint32_t accessing; /* boolean protecting access to user data */
} user_data_t;
/*******************************************************************************
* Variables declaration
******************************************************************************/
/* Use TensorFlowLite-Micro as an inference engine by default */
#if !defined(INFERENCE_ENGINE_TFLM) && !defined(INFERENCE_ENGINE_DeepViewRT) && !defined(INFERENCE_ENGINE_GLOW)
#define INFERENCE_ENGINE_TFLM
#endif
/* Model data input (depends on inference engine) */
#if defined(INFERENCE_ENGINE_TFLM)
#ifdef APP_USE_NEUTRON16_MODEL
#include "models/mobilenet_v1_0.25_128_quant_int8/mobilenetv1_model_data_tflite_npu16.h"
#else // APP_USE_NEUTRON16_MODEL
#include "models/mobilenet_v1_0.25_128_quant_int8/mobilenetv1_model_data_tflite.h"
#endif // APP_USE_NEUTRON16_MODEL
#elif defined(INFERENCE_ENGINE_GLOW)
#include "models/mobilenet_v1_0.25_128_quant_int8/mobilenet_v1_weights_glow_cm7.h"
#include "models/mobilenet_v1_0.25_128_quant_int8/mobilenet_v1_glow_cm7.h"
#elif defined(INFERENCE_ENGINE_DeepViewRT)
#include <models/mobilenet_v1_0.25_128_quant_int8/mobilenet_model_data_dvrt.h>
#else
#error "ERROR: An inference engine must be selected"
#endif
/*
* SWAP_DIMS = 1 if source/display dims are reversed
* SWAP_DIMS = 0 if source/display have the same orientation
*/
#ifdef APP_SKIP_CONVERT_FOR_DISPLAY
#define SWAP_DIMS 0
#else
#define SWAP_DIMS (((APP_DISPLAY_LANDSCAPE_ROTATE == ROTATE_90) || (APP_DISPLAY_LANDSCAPE_ROTATE == ROTATE_270)) ? 1 : 0)
#endif
/* display small and large dims */
#define DISPLAY_SMALL_DIM MIN(APP_DISPLAY_WIDTH, APP_DISPLAY_HEIGHT)
#define DISPLAY_LARGE_DIM MAX(APP_DISPLAY_WIDTH, APP_DISPLAY_HEIGHT)
#define RECT_LINE_WIDTH 2
#define MODEL_ASPECT_RATIO (1.0f * MODEL_WIDTH / MODEL_HEIGHT)
/* output is displayed in landscape mode */
#define DISPLAY_ASPECT_RATIO (1.0f * DISPLAY_LARGE_DIM / DISPLAY_SMALL_DIM)
/* camera aspect ratio */
#define CAMERA_ASPECT_RATIO (1.0f * APP_CAMERA_WIDTH / APP_CAMERA_HEIGHT)
/*
* The detection zone is a rectangle centered on the display. It has the same shape as the model input.
* The rectangle dimensions are calculated based on the display small dim and respecting the model aspect ratio
* The detection zone width and height depend on the display_aspect_ratio compared to the model aspect_ratio:
* if the display_aspect_ratio >= model_aspect_ratio then :
* (width, height) = (display_small_dim * model_aspect_ratio, display_small_dim)
* if the display_aspect_ratio < model_aspect_ratio then :
* (width, height) = (display_small_dim, display_small_dim / model_aspect_ratio)
*
* */
#define DETECTION_ZONE_RECT_HEIGHT ((DISPLAY_ASPECT_RATIO >= MODEL_ASPECT_RATIO) ? \
DISPLAY_SMALL_DIM : (DISPLAY_SMALL_DIM / MODEL_ASPECT_RATIO))
#define DETECTION_ZONE_RECT_WIDTH ((DISPLAY_ASPECT_RATIO >= MODEL_ASPECT_RATIO) ? \
(DISPLAY_SMALL_DIM * MODEL_ASPECT_RATIO) : DISPLAY_SMALL_DIM)
/* detection zone top/left offsets */
#define DETECTION_ZONE_RECT_TOP (DISPLAY_SMALL_DIM - DETECTION_ZONE_RECT_HEIGHT)/2
#define DETECTION_ZONE_RECT_LEFT (DISPLAY_LARGE_DIM - DETECTION_ZONE_RECT_WIDTH)/2
/*
* The computation of the crop size(width and height) and the crop top/left depends on the detection
* zone dims and offsets and on the camera-display scaling factor SF which is calculated differently
* depending on 2 constraints:
* * Constraint 1: display aspect ratio compared to the camera aspect ratio.
* * Constraint 2: SWAP_DIMS value.
* if the display_aspect_ratio < camera_aspect_ratio :
* - SWAP_DIMS = 0: SF = APP_DISPLAY_WIDTH / APP_CAMERA_WIDTH
* - SWAP_DIMS = 1: SF = APP_DISPLAY_HEIGHT / APP_CAMERA_HEIGHT
* if the display_aspect_ratio >= camera_aspect_ratio:
* - SWAP_DIMS = 0: SF = APP_DISPLAY_HEIGHT / APP_CAMERA_HEIGHT
* - SWAP_DIMS = 1: SF = APP_DISPLAY_WIDTH / APP_CAMERA_WIDTH
* the crop dims and offsets are calculated in the following way:
* CROP_SIZE_TOP = DETECTION_ZONE_RECT_HEIGHT / SF
* CROP_SIZE_LEFT = DETECTION_ZONE_RECT_WIDTH / SF
* CROP_TOP = DETECTION_ZONE_RECT_HEIGHT / SF
* CROP_LEFT = DETECTION_ZONE_RECT_LEFT / SF
* */
#if ((DISPLAY_LARGE_DIM * APP_CAMERA_HEIGHT) < (DISPLAY_SMALL_DIM * APP_CAMERA_WIDTH))
#define CROP_SIZE_TOP ((DETECTION_ZONE_RECT_HEIGHT * APP_CAMERA_WIDTH) / (SWAP_DIMS ? APP_DISPLAY_HEIGHT : APP_DISPLAY_WIDTH))
#define CROP_SIZE_LEFT ((DETECTION_ZONE_RECT_WIDTH * APP_CAMERA_WIDTH) / (SWAP_DIMS ? APP_DISPLAY_HEIGHT : APP_DISPLAY_WIDTH))
#define CROP_TOP ((DETECTION_ZONE_RECT_TOP * APP_CAMERA_WIDTH) / (SWAP_DIMS ? APP_DISPLAY_HEIGHT : APP_DISPLAY_WIDTH))
#define CROP_LEFT ((DETECTION_ZONE_RECT_LEFT * APP_CAMERA_WIDTH) / (SWAP_DIMS ? APP_DISPLAY_HEIGHT : APP_DISPLAY_WIDTH))
#else /* DISPLAY_ASPECT_RATIO() >= CAMERA_ASPECT_RATIO() */
#define CROP_SIZE_TOP ((DETECTION_ZONE_RECT_HEIGHT * APP_CAMERA_HEIGHT) / (SWAP_DIMS ? APP_DISPLAY_WIDTH : APP_DISPLAY_HEIGHT))
#define CROP_SIZE_LEFT ((DETECTION_ZONE_RECT_WIDTH * APP_CAMERA_HEIGHT) / (SWAP_DIMS ? APP_DISPLAY_WIDTH : APP_DISPLAY_HEIGHT))
#define CROP_TOP ((DETECTION_ZONE_RECT_TOP * APP_CAMERA_HEIGHT) / (SWAP_DIMS ? APP_DISPLAY_WIDTH : APP_DISPLAY_HEIGHT))
#define CROP_LEFT ((DETECTION_ZONE_RECT_LEFT * APP_CAMERA_HEIGHT) / (SWAP_DIMS ? APP_DISPLAY_WIDTH : APP_DISPLAY_HEIGHT))
#endif /* DISPLAY_ASPECT_RATIO() < CAMERA_ASPECT_RATIO() */
static const char s_display_name[] = APP_DISPLAY_NAME;
static const char s_camera_name[] = APP_CAMERA_NAME;
#define STATS_PRINT_PERIOD_MS 1000
/*******************************************************************************
* Prototypes
******************************************************************************/
static void app_task(void *params);
/*******************************************************************************
* Code
******************************************************************************/
/*!
* @brief Application entry point.
*/
int main()
{
BaseType_t ret;
TaskHandle_t handle = NULL;
/* Init board hardware. */
BOARD_Init();
ret = xTaskCreate(
app_task,
"app_task",
configMINIMAL_STACK_SIZE + 1000,
NULL,
MPP_APP_MAX_PRIO,
&handle);
if (pdPASS != ret)
{
PRINTF("Failed to create app_task task");
while (1);
}
vTaskStartScheduler();
for (;;)
vTaskSuspend(NULL);
return 0;
}
int mpp_event_listener(mpp_t mpp, mpp_evt_t evt, void *evt_data, void *user_data) {
status_t ret;
const mpp_inference_cb_param_t *inf_output;
mobilenet_post_proc_data_t out_data;
/* user_data handle contains application private data */
user_data_t *app_priv = (user_data_t *)user_data;
switch(evt) {
case MPP_EVENT_INFERENCE_OUTPUT_READY:
/* cast evt_data pointer to correct structure matching the event */
inf_output = (const mpp_inference_cb_param_t *) evt_data;
ret = MOBILENETv1_ProcessOutput(
inf_output,
app_priv->mp,
app_priv->elem,
app_priv->labels,
&out_data);
if (ret != kStatus_Success)
PRINTF("mpp_event_listener: process output error!");
/* check that we can modify the user data (not accessed by other task) */
if (Atomic_CompareAndSwap_u32(&app_priv->accessing, 1, 0) == ATOMIC_COMPARE_AND_SWAP_SUCCESS)
{
/* copy inference output */
app_priv->inf_out = out_data;
__atomic_store_n(&app_priv->accessing, 0, __ATOMIC_SEQ_CST);
}
app_priv->inference_frame_num++;
break;
case MPP_EVENT_INVALID:
default:
/* nothing to do */
break;
}
return 0;
}
static void app_task(void *params)
{
user_data_t user_data = {0};
int ret;
PRINTF("[%s]\r\n", mpp_get_version());
#if defined(INFERENCE_ENGINE_TFLM)
PRINTF("Inference Engine: TensorFlow-Lite Micro \r\n");
#elif defined (INFERENCE_ENGINE_GLOW)
PRINTF("Inference Engine: Glow \r\n");
#elif defined(INFERENCE_ENGINE_DeepViewRT)
PRINTF("Inference Engine: DeepViewRT \r\n");
#else
#error "Please select inference engine"
#endif
mpp_api_params_t api_params = {0};
mpp_stats_t api_stats;
memset(&api_stats, 0, sizeof(api_stats));
api_params.stats = &api_stats;
ret = mpp_api_init(&api_params);
if (ret)
goto err;
mpp_t mp;
mpp_stats_t mpp_stats;
mpp_params_t mpp_params;
memset(&mpp_params, 0, sizeof(mpp_params));
mpp_params.evt_callback_f = &mpp_event_listener;
mpp_params.mask = MPP_EVENT_ALL;
mpp_params.cb_userdata = &user_data;
mpp_params.exec_flag = MPP_EXEC_RC;
mpp_params.stats = &mpp_stats;
mp = mpp_create(&mpp_params, &ret);
if (mp == MPP_INVALID)
goto err;
user_data.mp = mp;
mpp_camera_params_t cam_params;
memset(&cam_params, 0 , sizeof(cam_params));
cam_params.height = APP_CAMERA_HEIGHT;
cam_params.width = APP_CAMERA_WIDTH;
cam_params.format = APP_CAMERA_FORMAT;
cam_params.fps = 30;
ret = mpp_camera_add(mp, s_camera_name, &cam_params);
if (ret) {
PRINTF("Failed to add camera %s\n", s_camera_name);
goto err;
}
/* split the pipeline into 2 branches */
mpp_t mp_split;
mpp_stats_t split_stats;
mpp_params.exec_flag = MPP_EXEC_PREEMPT;
mpp_params.stats = &split_stats;
ret = mpp_split(mp, 1 , &mpp_params, &mp_split);
if (ret) {
PRINTF("Failed to split pipeline\n");
goto err;
}
/* On the preempt-able branch run the ML Inference (using a mobilenet_v1 TF-Lite model) */
/* First do crop + resize + color convert */
mpp_element_params_t elem_params;
memset(&elem_params, 0, sizeof(elem_params));
/* pick default device from the first listed and supported by Hw */
elem_params.convert.dev_name = NULL;
/* set output buffer dims */
elem_params.convert.out_buf.width = MODEL_WIDTH;
elem_params.convert.out_buf.height = MODEL_HEIGHT;
/* color convert */
elem_params.convert.pixel_format = MPP_PIXEL_RGB;
elem_params.convert.ops = MPP_CONVERT_COLOR;
/* crop center of image */
elem_params.convert.crop.top = CROP_TOP;
elem_params.convert.crop.bottom = CROP_TOP + CROP_SIZE_TOP - 1;
elem_params.convert.crop.left = CROP_LEFT;
elem_params.convert.crop.right = CROP_LEFT + CROP_SIZE_LEFT - 1;
elem_params.convert.ops |= MPP_CONVERT_CROP;
/* resize: scaling parameters */
elem_params.convert.scale.width = MODEL_WIDTH;
elem_params.convert.scale.height = MODEL_HEIGHT;
elem_params.convert.ops |= MPP_CONVERT_SCALE;
ret = mpp_element_add(mp_split, MPP_ELEMENT_CONVERT, &elem_params, NULL);
if (ret ) {
PRINTF("Failed to add element CONVERT\n");
goto err;
}
/* configure TFlite element with model */
mpp_element_params_t mobilenet_params;
static mpp_stats_t mobilenet_stats;
memset(&mobilenet_params, 0 , sizeof(mpp_element_params_t));
#if defined(INFERENCE_ENGINE_TFLM)
mobilenet_params.ml_inference.model_data = model_data;
mobilenet_params.ml_inference.model_size = model_data_len;
mobilenet_params.ml_inference.model_input_mean = MODEL_INPUT_MEAN;
mobilenet_params.ml_inference.model_input_std = MODEL_INPUT_STD;
mobilenet_params.ml_inference.type = MPP_INFERENCE_TYPE_TFLITE;
#elif defined(INFERENCE_ENGINE_GLOW)
mobilenet_params.ml_inference.model_data = mobilenet_v1_weights_bin;
mobilenet_params.ml_inference.inference_params.constant_weight_MemSize = MOBILENET_V1_CONSTANT_MEM_SIZE;
mobilenet_params.ml_inference.inference_params.mutable_weight_MemSize = MOBILENET_V1_MUTABLE_MEM_SIZE;
mobilenet_params.ml_inference.inference_params.activations_MemSize = MOBILENET_V1_ACTIVATIONS_MEM_SIZE;
mobilenet_params.ml_inference.inference_params.num_inputs = 1;
mobilenet_params.ml_inference.inference_params.inputs_offsets[0] = MOBILENET_V1_input;
mobilenet_params.ml_inference.inference_params.outputs_offsets[0] = MOBILENET_V1_MobilenetV1_Predictions_Reshape_1;
mobilenet_params.ml_inference.inference_params.model_input_tensors_type = MPP_TENSOR_TYPE_INT8;
mobilenet_params.ml_inference.inference_params.model_entry_point = &mobilenet_v1;
mobilenet_params.ml_inference.type = MPP_INFERENCE_TYPE_GLOW ;
#elif defined(INFERENCE_ENGINE_DeepViewRT)
mobilenet_params.ml_inference.model_data = model_data;
mobilenet_params.ml_inference.model_size = model_data_len;
mobilenet_params.ml_inference.type = MPP_INFERENCE_TYPE_DEEPVIEWRT ;
#endif
mobilenet_params.ml_inference.inference_params.num_inputs = 1;
mobilenet_params.ml_inference.inference_params.num_outputs = 1;
mobilenet_params.ml_inference.tensor_order = MPP_TENSOR_ORDER_NHWC;
mobilenet_params.stats = &mobilenet_stats;
ret = mpp_element_add(mp_split, MPP_ELEMENT_INFERENCE, &mobilenet_params, NULL);
if (ret) {
PRINTF("Failed to add element VALGO_TFLite");
goto err;
}
/* close the pipeline with a null sink */
ret = mpp_nullsink_add(mp_split);
if (ret) {
PRINTF("Failed to add NULL sink\n");
goto err;
}
#ifndef APP_SKIP_CONVERT_FOR_DISPLAY
/* On the main branch of the pipeline, send the frame to the display */
/* First do color-convert + flip */
memset(&elem_params, 0, sizeof(elem_params));
/* pick default device from the first listed and supported by Hw */
elem_params.convert.dev_name = NULL;
/* set output buffer dims */
elem_params.convert.out_buf.width = APP_CAMERA_WIDTH;
elem_params.convert.out_buf.height = APP_CAMERA_HEIGHT;
elem_params.convert.pixel_format = APP_DISPLAY_FORMAT;
elem_params.convert.flip = FLIP_HORIZONTAL;
elem_params.convert.ops = MPP_CONVERT_COLOR | MPP_CONVERT_ROTATE;
ret = mpp_element_add(mp, MPP_ELEMENT_CONVERT, &elem_params, NULL);
if (ret) {
PRINTF("Failed to add element CONVERT\n");
goto err;
}
#endif
/* add one label rectangle */
memset(&elem_params, 0, sizeof(elem_params));
memset(&user_data.labels, 0, sizeof(user_data.labels));
/* params init */
elem_params.labels.max_count = 1;
elem_params.labels.detected_count = 1;
elem_params.labels.rectangles = user_data.labels;
/* first add detection zone box */
user_data.labels[0].top = DETECTION_ZONE_RECT_TOP;
user_data.labels[0].left = DETECTION_ZONE_RECT_LEFT;
user_data.labels[0].bottom = DETECTION_ZONE_RECT_TOP + DETECTION_ZONE_RECT_HEIGHT;
user_data.labels[0].right = DETECTION_ZONE_RECT_LEFT + DETECTION_ZONE_RECT_WIDTH;
user_data.labels[0].line_width = RECT_LINE_WIDTH;
user_data.labels[0].line_color.rgb.B = 0xff;
strcpy((char *)user_data.labels[0].label, "no label");
/* retrieve the element handle while add api */
ret = mpp_element_add(mp, MPP_ELEMENT_LABELED_RECTANGLE, &elem_params, &user_data.elem);
if (ret) {
PRINTF("Failed to add element LABELED_RECTANGLE (0x%x)\r\n", ret);
goto err;
}
#ifndef APP_SKIP_CONVERT_FOR_DISPLAY
/* then rotate if needed */
if (APP_DISPLAY_LANDSCAPE_ROTATE != ROTATE_0) {
memset(&elem_params, 0, sizeof(elem_params));
/* set output buffer dims */
elem_params.convert.out_buf.width = APP_DISPLAY_WIDTH;
elem_params.convert.out_buf.height = APP_DISPLAY_HEIGHT;
elem_params.convert.angle = APP_DISPLAY_LANDSCAPE_ROTATE;
elem_params.convert.ops = MPP_CONVERT_ROTATE;
ret = mpp_element_add(mp, MPP_ELEMENT_CONVERT, &elem_params, NULL);
if (ret) {
PRINTF("Failed to add element CONVERT\r\n");
goto err;
}
}
#endif
mpp_display_params_t disp_params;
memset(&disp_params, 0 , sizeof(disp_params));
disp_params.format = APP_DISPLAY_FORMAT;
disp_params.width = APP_DISPLAY_WIDTH;
disp_params.height = APP_DISPLAY_HEIGHT;
#ifdef APP_SKIP_CONVERT_FOR_DISPLAY
disp_params.rotate = APP_DISPLAY_LANDSCAPE_ROTATE;
#endif
ret = mpp_display_add(mp, s_display_name, &disp_params);
if (ret) {
PRINTF("Failed to add display %s\n", s_display_name);
goto err;
}
mpp_stats_enable(MPP_STATS_GRP_API);
mpp_stats_enable(MPP_STATS_GRP_MPP);
mpp_stats_enable(MPP_STATS_GRP_ELEMENT);
/* start preempt-able pipeline branch */
ret = mpp_start(mp_split, 0);
if (ret) {
PRINTF("Failed to start pipeline");
goto err;
}
/* start main pipeline branch */
ret = mpp_start(mp, 1);
if (ret) {
PRINTF("Failed to start pipeline");
goto err;
}
TickType_t xLastWakeTime;
const TickType_t xFrequency = STATS_PRINT_PERIOD_MS / portTICK_PERIOD_MS;
xLastWakeTime = xTaskGetTickCount();
#if (configGENERATE_RUN_TIME_STATS == 1)
static char task_stats_buf[512];
#endif
for (;;) {
xTaskDelayUntil( &xLastWakeTime, xFrequency );
mpp_stats_disable(MPP_STATS_GRP_API);
mpp_stats_disable(MPP_STATS_GRP_MPP);
mpp_stats_disable(MPP_STATS_GRP_ELEMENT);
PRINTF("API stats ------------------------------\r\n");
PRINTF("rc_cycle = %u ms rc_cycle_max %u ms\r\n",
api_stats.api.rc_cycle, api_stats.api.rc_cycle_max);
PRINTF("pr_slot = %u ms pr_rounds %u app_slot %u ms\r\n",
api_stats.api.pr_slot, api_stats.api.pr_rounds, api_stats.api.app_slot);
PRINTF("MPP stats ------------------------------\r\n");
PRINTF("mpp %p exec_time %u ms\r\n", mpp_stats.mpp.mpp, mpp_stats.mpp.mpp_exec_time);
PRINTF("mpp %p exec_time %u ms\r\n", split_stats.mpp.mpp, split_stats.mpp.mpp_exec_time);
PRINTF("Element stats --------------------------\r\n");
PRINTF("mobilenet : exec_time %u ms\r\n", mobilenet_stats.elem.elem_exec_time);
if (Atomic_CompareAndSwap_u32(&user_data.accessing, 1, 0) == ATOMIC_COMPARE_AND_SWAP_SUCCESS)
{
PRINTF("mobilenet : %s (%d%%)\r\n", user_data.inf_out.label, user_data.inf_out.score);
__atomic_store_n(&user_data.accessing, 0, __ATOMIC_SEQ_CST);
}
mpp_stats_enable(MPP_STATS_GRP_MPP);
mpp_stats_enable(MPP_STATS_GRP_API);
mpp_stats_enable(MPP_STATS_GRP_ELEMENT);
#if (configGENERATE_RUN_TIME_STATS == 1)
vTaskGetRunTimeStats(task_stats_buf);
PRINTF(task_stats_buf);
#endif
}
err:
for (;;)
{
PRINTF("Error building application pipeline : ret %d\r\n", ret);
vTaskSuspend(NULL);
}
}