项目介绍和创意介绍
当前人体姿态识别技术主要依赖云端服务器或高性能芯片,存在高延迟、高功耗、隐私泄露风险和成本高昂等问题,难以在工业安全、家庭健康监护等场景中规模化落地。基于此我设计了一款基于STM32N6设计的人体姿态识别系统,通过运行神经网络,可用于体感游戏动作捕捉、体操运动姿势纠正、老人跌倒检测等。通过“低成本硬件+轻量化算法+场景化设计”三位一体的创 新,直击边缘AI落地的核心痛点——“既要高性能,又要白菜价”。
方案框图和项目设计思路介绍
硬件框图
设计思路
- 设计原理图,对使用到的电源、flash等芯片进行选型,确保能发挥STM32N6最大性能。
- 绘制PCB,做好高频线等长设计,确保参考面完整,阻抗连续。
- 焊接并测试,完成板卡焊接与基本功能验证,实现LCD、摄像头驱动开发。
- 训练模型,在COCO数据集上训练模型。
- 部署模型,利用ST Edge AI 云服务对模型进行量化并转化为N6可运行的代码。使用STM32Cube.AI对模型进行部署。
硬件介绍
STM32N6核心板
- STM32N6x7xx器件基于高性能Arm® Cortex®-M55,工作频率高达800 MHz。
- Cortex®-M55内核采用Arm® Helium™矢量处理技术。除了标准微控制器任务外,该内核还能实现高能效的数字信号处理。Cortex®-M55配备浮点运算单元 (FPU),支持单精度和半精度(符合IEEE 754标准)数据处理。
- ST Neural-ART加速器运行频率高达1 GHz,可提供600 Gops,利用优化的硬件单元实现DNN(深度神经网络)推理功能,同时优化功耗效率。
- Neo-Chrom图形加速器通过提供缩放、高质量插值、自由旋转、Alpha混合、纹理映射和透视变换等功能的硬件加速,确保高效的2.5D图形处理。
- 对于摄像头应用,集成了并行和CSI接口,并前瞻性地配备了硬件ISP。
STM32N6底板
搭配核心板使用,引出两路USB,RGB接口,CSI摄像头接口。
RGB屏幕
4.3寸电容触摸屏,分辨率为800×480, 支持24位RGB接口显示,可显示1678万种色彩,支持I2C接口控制电容触摸,5点触控。
IMX335模块
高分辨率5-Mpx IMX335LQN CMOS RGB图像传感器、ISM330DLC惯性运动单元和VL53L5CX ToF传感器。它可与任何具有MIPI CSI-2®接口和22引脚FFC连接器的STM32开发板一起使用,在STM32微控制器和微处理器上轻松实现全功能计算机视觉。
整体图
原理图和PCB介绍
核心板
Top部分
核心供电部分
为N6核心部分提供供电,通过PWR_LP脚控制输出电压,当核心供电为0.81V时,芯片可运行在600MHz,当核心供电为0.89V时,芯片可运行在800MHz。
其他供电部分
为芯片提供3.3V和1.8V供电电压,其中VDDIO和VDDA1V8_AON需要提前上电,之后PWR_ON控制VDD3V3、VDDA1V8和VDDCORE进行上电。
PSRAM部分
采用APS256XXN芯片,运行在x16 DDR 200MHz模式。
Flash部分
采用MX66UW1G45GXD100T芯片,1.8V电压,8线1Gbit容量。
接口部分
基本引出所有IO,CSI和USB接口尽量最近引出。
PCB部分
采用6层板设计,3个走线层、2个电源层、1个地层。尺寸为3.94*3.31cm。
底板
CSI摄像头接口
兼容树莓派5CSI接口设计,可直接连接树莓派5摄像头进行使用。
屏幕接口
RGB屏幕接口,支持PWM背光控制,电容触摸。
PCB部分
采用4层板设计,2个走线层、1个电源层、1个地层。尺寸为7.67*6.07cm。
软件流程图和关键代码介绍
流程图
NN初始化部分
LL_ATON_DECLARE_NAMED_NN_INSTANCE_AND_INTERFACE(Default);
const LL_Buffer_InfoTypeDef *nn_in_info = LL_ATON_Input_Buffers_Info_Default();
const LL_Buffer_InfoTypeDef *nn_out_info = LL_ATON_Output_Buffers_Info_Default();
nn_in = (uint8_t *) LL_Buffer_addr_start(&nn_in_info[0]);
float32_t *nn_out[MAX_NUMBER_OUTPUT];
int32_t nn_out_len[MAX_NUMBER_OUTPUT];
int number_output = 0;
/* Count number of outputs */
while (nn_out_info[number_output].name != NULL)
{
number_output++;
}
assert(number_output <= MAX_NUMBER_OUTPUT);
for (int i = 0; i < number_output; i++)
{
nn_out[i] = (float32_t *) LL_Buffer_addr_start(&nn_out_info[i]);
nn_out_len[i] = LL_Buffer_len(&nn_out_info[i]);
}
uint32_t nn_in_len = LL_Buffer_len(&nn_in_info[0]);
uint32_t pitch_nn = 0;
UNUSED(nn_in_len);
图像识别
while (1)
{
CAM_IspUpdate();
if (pitch_nn != (NN_WIDTH * NN_BPP))
{
/* Start NN camera single capture Snapshot */
CAM_NNPipe_Start(dcmipp_out_nn, CMW_MODE_SNAPSHOT);
}
else
{
/* Start NN camera single capture Snapshot */
CAM_NNPipe_Start(nn_in, CMW_MODE_SNAPSHOT);
}
while (cameraFrameReceived == 0) {};
cameraFrameReceived = 0;
uint32_t ts[2] = { 0 };
if (pitch_nn != (NN_WIDTH * NN_BPP))
{
SCB_InvalidateDCache_by_Addr(dcmipp_out_nn, sizeof(dcmipp_out_nn));
img_crop(dcmipp_out_nn, nn_in, pitch_nn, NN_WIDTH, NN_HEIGHT, NN_BPP);
SCB_CleanInvalidateDCache_by_Addr(nn_in, nn_in_len);
}
ts[0] = HAL_GetTick();
/* run ATON inference */
LL_ATON_RT_Main(&NN_Instance_Default);
ts[1] = HAL_GetTick();
int32_t ret = app_postprocess_run((void **) nn_out, number_output, &pp_output, &pp_params);
assert(ret == 0);
Display_NetworkOutput(&pp_output, ts[1] - ts[0]);
/* Discard nn_out region (used by pp_input and pp_outputs variables) to avoid Dcache evictions during nn inference */
for (int i = 0; i < number_output; i++)
{
float32_t *tmp = nn_out[i];
SCB_InvalidateDCache_by_Addr(tmp, nn_out_len[i]);
}
}
识别信息显示
显示识别到的人体姿势与单帧识别时间。
static void Display_NetworkOutput(void *p_postprocess, uint32_t inference_ms)
{
#if POSTPROCESS_TYPE == POSTPROCESS_MPE_YOLO_V8_UF
mpe_pp_outBuffer_t *rois = ((mpe_pp_out_t *) p_postprocess)->pOutBuff;
uint32_t nb_rois = ((mpe_pp_out_t *) p_postprocess)->nb_detect;
#elif POSTPROCESS_TYPE == POSTPROCESS_SPE_MOVENET_UF
spe_pp_outBuffer_t *roi = ((spe_pp_out_t *) p_postprocess)->pOutBuff;
#endif
int ret;
ret = HAL_LTDC_SetAddress_NoReload(&hlcd_ltdc, (uint32_t) lcd_fg_buffer[lcd_fg_buffer_rd_idx], LTDC_LAYER_2);
assert(ret == HAL_OK);
/* Draw bounding boxes */
UTIL_LCD_FillRect(lcd_fg_area.X0, lcd_fg_area.Y0, lcd_fg_area.XSize, lcd_fg_area.YSize, 0x00000000); /* Clear previous boxes */
#if POSTPROCESS_TYPE == POSTPROCESS_MPE_YOLO_V8_UF
for (int i = 0; i < nb_rois; i++)
Display_mpe_Detection(&rois[i]);
UTIL_LCD_SetBackColor(0x40000000);
UTIL_LCDEx_PrintfAt(0, LINE(2), CENTER_MODE, "Objects %u", nb_rois);
UTIL_LCDEx_PrintfAt(0, LINE(20), CENTER_MODE, "Inference: %ums", inference_ms);
UTIL_LCD_SetBackColor(0);
#elif POSTPROCESS_TYPE == POSTPROCESS_SPE_MOVENET_UF
Display_spe_Detection(roi);
UTIL_LCD_SetBackColor(0x40000000);
UTIL_LCDEx_PrintfAt(0, LINE(20), CENTER_MODE, "Inference: %ums", inference_ms);
UTIL_LCD_SetBackColor(0);
#endif
Display_WelcomeScreen();
SCB_CleanDCache_by_Addr(lcd_fg_buffer[lcd_fg_buffer_rd_idx], LCD_FG_FRAMEBUFFER_SIZE);
ret = HAL_LTDC_ReloadLayer(&hlcd_ltdc, LTDC_RELOAD_VERTICAL_BLANKING, LTDC_LAYER_2);
assert(ret == HAL_OK);
lcd_fg_buffer_rd_idx = 1 - lcd_fg_buffer_rd_idx;
}
运行模型
void LL_ATON_RT_Main(NN_Instance_TypeDef *network_instance)
{
LL_ATON_RT_RetValues_t ll_aton_rt_ret;
/*** Start of user initialization code ***/
/*** End of user initialization code ***/
LL_ATON_ASSERT(network_instance != NULL);
LL_ATON_ASSERT(network_instance->network != NULL);
LL_ATON_RT_RuntimeInit(); // Initialize runtime
LL_ATON_RT_Init_Network(network_instance); // Initialize passed network instance object
do
{
/* Execute first/next step of Cube.AI/ATON runtime */
ll_aton_rt_ret = LL_ATON_RT_RunEpochBlock(network_instance);
/*** Start of user event handling code ***/
/*** End of user event handling code ***/
/* Wait for next event */
if (ll_aton_rt_ret == LL_ATON_RT_WFE)
{ /*** subject to change to fit also user code requirements ***/
LL_ATON_OSAL_WFE();
}
} while (ll_aton_rt_ret != LL_ATON_RT_DONE); /*** subject to change to fit also user code requirements ***/
LL_ATON_RT_DeInit_Network(network_instance); // De-initialize the network instance object
LL_ATON_RT_RuntimeDeInit(); // De-initialize runtime
/*** Start of user de-initialization code ***/
/*** End of user de-initialization code ***/
}
功能展示图及说明
识别人体姿势,并显示到屏幕上,单帧处理时间约为18ms
设计中遇到的难题和解决方法
问题:焊接完PCB测试时,上电后发现可以识别到SWD接口,但是无法下载
解决办法:测试发现有一种电容的型号弄错了,全部更换后正常。
对本次竞赛的心得体会
比赛很好,盲盒很喜欢,建议多多举办。