首頁(yè) > 評(píng)測(cè) > 【NUCLEO-U575ZI-Q測(cè)評(píng)】使用輕量級(jí)AI推理框架TinyMaix實(shí)現(xiàn)手寫數(shù)字識(shí)別

【NUCLEO-U575ZI-Q測(cè)評(píng)】使用輕量級(jí)AI推理框架TinyMaix實(shí)現(xiàn)手寫數(shù)字識(shí)別

  
  • 作者:
  • 來(lái)源:
  • [導(dǎo)讀]
  • 本帖最后由 xusiwei1236 于 2023-3-13 22:02 編輯 【NUCLEO-U575ZI-Q測(cè)評(píng)】使用輕量級(jí)AI推理框架TinyMaix實(shí)現(xiàn)手寫數(shù)字識(shí)別 一、TinyMaix簡(jiǎn)介TinyMaix是國(guó)內(nèi)sipeed團(tuán)隊(duì)開發(fā)一個(gè)輕量級(jí)AI推理框架,官方介紹如下

本帖最后由 xusiwei1236 于 2023-3-13 22:02 編輯

【NUCLEO-U575ZI-Q測(cè)評(píng)】使用輕量級(jí)AI推理框架TinyMaix實(shí)現(xiàn)手寫數(shù)字識(shí)別
一、TinyMaix簡(jiǎn)介

TinyMaix是國(guó)內(nèi)sipeed團(tuán)隊(duì)開發(fā)一個(gè)輕量級(jí)AI推理框架,官方介紹如下:
TinyMaix 是面向單片機(jī)的超輕量級(jí)的神經(jīng)網(wǎng)絡(luò)推理庫(kù),即 TinyML 推理庫(kù),可以讓你在任意單片機(jī)上運(yùn)行輕量級(jí)深度學(xué)習(xí)模型。
根據(jù)官方介紹,在僅有2K RAM的 Arduino UNO(ATmega328, 32KB Flash, 2KB RAM) 上,都可以基于 TinyMaix 進(jìn)行手寫數(shù)字識(shí)別。對(duì),你沒(méi)有看錯(cuò),2K RAM + 32K Flash的設(shè)備上都可以使用TinyMaix進(jìn)行手寫數(shù)字識(shí)別!TinyMaix官網(wǎng)提供了詳細(xì)介紹,可以在本文末尾的參考鏈接中找到。
首先,我們看看這次試用的NUCLEO-U575ZI-Q開發(fā)板主控芯片STM32U575ZIT6QU的主要參數(shù):
  • CPU: Cortex-M33內(nèi)核,160 MHz
  • Flash: 2 MB
  • RAM: 786 KB
所以,在我們這次試用的主角NUCLEO-U575ZI-Q開發(fā)板上運(yùn)行TinyMaix完全是沒(méi)有任何壓力的。
接下來(lái),我將介紹如何為STM32U575移植TinyMaix框架,以及如何運(yùn)行手寫數(shù)字識(shí)別示例。


1.1 TinyMaix開源項(xiàng)目

TinyMaix項(xiàng)目源碼時(shí)以 Apache-2.0協(xié)議開源的,
GitHub代碼倉(cāng):https://github.com/sipeed/tinymaix


1.2 TinyMaix核心API

TinyMaix框架對(duì)上層應(yīng)用程序提供的核心API主要位于代碼倉(cāng)的tinymaix.h文件中,核心API如下:
  1. /******************************* MODEL FUNCTION ************************************/
  2. tm_err_t tm_load  (tm_mdl_t* mdl, const uint8_t* bin, uint8_t*buf, tm_cb_t cb, tm_mat_t* in);   //load model
  3. void     tm_unload(tm_mdl_t* mdl);                                      //remove model
  4. tm_err_t tm_preprocess(tm_mdl_t* mdl, tm_pp_t pp_type, tm_mat_t* in, tm_mat_t* out);            //preprocess input data
  5. tm_err_t tm_run   (tm_mdl_t* mdl, tm_mat_t* in, tm_mat_t* out);         //run model
  6.  
  7. /******************************* UTILS FUNCTION ************************************/
  8. uint8_t TM_WEAK tm_fp32to8(float fp32);
  9. float TM_WEAK tm_fp8to32(uint8_t fp8);
  10.  
  11. /******************************* STAT FUNCTION ************************************/
  12. #if TM_ENABLE_STAT
  13. tm_err_t tm_stat(tm_mdlbin_t* mdl);                    //stat model
  14. #endif
復(fù)制代碼

 

主要分為三類:
  • 模型函數(shù),包括模型加載、卸載、預(yù)處理、推理;
  • 工具函數(shù),包含F(xiàn)P32和uint8的互轉(zhuǎn);
  • 統(tǒng)計(jì)函數(shù),用于輸出模型中間層信息;
這里的模型,通常是預(yù)訓(xùn)練模型經(jīng)過(guò)腳本轉(zhuǎn)換生成的TinyMaix格式的模型;

 

另外,TinyMaix還提供了單獨(dú)的層函數(shù),用于實(shí)現(xiàn)單層計(jì)算功能,可以通過(guò)這些函數(shù),用C代碼的形式編寫出一個(gè)模型。
  1. /******************************* LAYER FUNCTION ************************************/
  2. tm_err_t tml_conv2d_dwconv2d(tm_mat_t* in, tm_mat_t* out, wtype_t* w, btype_t* b, \
  3.     int kw, int kh, int sx, int sy, int dx, int dy, int act, \
  4.     int pad_top, int pad_bottom, int pad_left, int pad_right, int dmul, \
  5.     sctype_t* ws, sctype_t in_s, zptype_t in_zp, sctype_t out_s, zptype_t out_zp);
  6. tm_err_t tml_gap(tm_mat_t* in, tm_mat_t* out, sctype_t in_s, zptype_t in_zp, sctype_t out_s, zptype_t out_zp);
  7. tm_err_t tml_fc(tm_mat_t* in, tm_mat_t* out,  wtype_t* w, btype_t* b, \
  8.     sctype_t* ws, sctype_t in_s, zptype_t in_zp, sctype_t out_s, zptype_t out_zp);
  9. tm_err_t tml_softmax(tm_mat_t* in, tm_mat_t* out, sctype_t in_s, zptype_t in_zp, sctype_t out_s, zptype_t out_zp);
  10. tm_err_t tml_reshape(tm_mat_t* in, tm_mat_t* out, sctype_t in_s, zptype_t in_zp, sctype_t out_s, zptype_t out_zp);
  11. tm_err_t tml_add(tm_mat_t* in0, tm_mat_t* in1, tm_mat_t* out, \
  12.     sctype_t in_s0, zptype_t in_zp0, sctype_t in_s1, zptype_t in_zp1, sctype_t out_s, zptype_t out_zp);
復(fù)制代碼




1.3 TinyMaix底層依賴

TinyMaix可以簡(jiǎn)單理解為一個(gè)矩陣和向量計(jì)算庫(kù),目前已支持如下幾種計(jì)算硬件:
  1. #define TM_ARCH_CPU         (0) //default, pure cpu compute
  2. #define TM_ARCH_ARM_SIMD    (1) //ARM Cortex M4/M7, etc.
  3. #define TM_ARCH_ARM_NEON    (2) //ARM Cortex A7, etc.
  4. #define TM_ARCH_ARM_MVEI    (3) //ARMv8.1: M55, etc.
  5. #define TM_ARCH_RV32P       (4) //T-head E907, etc.
  6. #define TM_ARCH_RV64V       (5) //T-head C906,C910, etc.
  7. #define TM_ARCH_CSKYV2      (6) //cskyv2 with dsp core
  8. #define TM_ARCH_X86_SSE2    (7) //x86 sse2
復(fù)制代碼

 

對(duì)于ARM-Cortex系列MCU,可以支持純CPU計(jì)算和SIMD計(jì)算。其中CPU計(jì)算部分無(wú)特殊依賴(計(jì)算代碼均使用標(biāo)準(zhǔn)C實(shí)現(xiàn))。SIMD部分,部分計(jì)算代碼使用了C語(yǔ)言內(nèi)嵌匯編實(shí)現(xiàn),需要CPU支持相應(yīng)的匯編指令,才可以正常編譯、運(yùn)行。
TinyMaix的示例代碼依賴于精準(zhǔn)計(jì)時(shí)打印輸出能力,具體是項(xiàng)目的tm_port.h中的幾個(gè)宏定義:
  1. #define  TM_GET_US()       ((uint32_t)((uint64_t)clock()*1000000/CLOCKS_PER_SEC))
  2.  
  3. #define TM_DBGT_INIT()     uint32_t _start,_finish;float _time;_start=TM_GET_US();
  4. #define TM_DBGT_START()    _start=TM_GET_US();
  5. #define TM_DBGT(x)         {_finish=TM_GET_US();\
  6.                             _time = (float)(_finish-_start)/1000.0;\
  7.                             TM_PRINTF("===%s use %.3f ms\n", (x), _time);\
  8.                             _start=TM_GET_US();}
復(fù)制代碼




二、計(jì)時(shí)和打印支持
2.1 創(chuàng)建CubeMX項(xiàng)目

首先,打卡STM32CubeMX,通過(guò)“Access to board selector”進(jìn)入開發(fā)板選擇界面。

在開發(fā)板選擇界面中,首先在左上角Commercial Part Number中輸入NUCLEO-U575ZI-Q,此時(shí)右下角的開發(fā)列表將會(huì)只有一個(gè)NUCLEO-U575ZI-Q開發(fā)板。然后,鼠標(biāo)點(diǎn)擊NUCLEO-U575ZI-Q開發(fā)板所在行,選中此款開發(fā)板。最后,點(diǎn)擊右上角的Start Project按鈕。

彈出“Initialize all peripherals with their default Mode?”對(duì)話框,選擇Yes繼續(xù)。然后,彈出的TrustZone選擇對(duì)話框,選擇without TrustZone actived,點(diǎn)擊OK繼續(xù)。
此時(shí),已進(jìn)入CubeMX的配置界面,按Ctrl+S或通過(guò)File->Save Project菜單可將當(dāng)前CubeMX配置保存為獨(dú)立的ioc文件。



2.2 使用CubeMX生成Keil項(xiàng)目

選擇NUCLEO-U575ZI-Q開發(fā)板后,可以在CubeMX中看到,默認(rèn)已經(jīng)將主控芯片STM32U575ZIT6的PA9、PA10引腳分別配置為USART1_TX、USART1_RX功能,如下圖所示:
查閱原理圖,可以看到板載STLink的串口和主控芯片的PA9、PA10已連接:
因此,我們可以通過(guò)板載STLink接收USART1的輸入和輸出。

 

在CubeMX中,切換到Project Manager標(biāo)簽頁(yè),將Minimum Heap Size和Minimum Stack Size的值分別修改為:0x8000(32K)和0x2000(8KB)。

接下來(lái),點(diǎn)擊Code Generator,然后:
  • STM32Cube MCU packages and embedded software packs中,選擇Copy only the necessary library files(只拷貝必要的庫(kù)文件);
  • Generated files中,選擇Generate peripheral initialization as a pair of '.c/.h' filers per peripheral(每個(gè)外設(shè)的初始化生成獨(dú)立的.c/.h文件對(duì));
接下來(lái),點(diǎn)擊右上角的Generate Code,開始生成代碼。


生成的Keil項(xiàng)目中,文件結(jié)構(gòu)如下:

為了方便調(diào)試,在調(diào)試器設(shè)置中,勾選Reset and Run,如下圖所示:


2.3 添加printf打印支持

生成代碼后,在項(xiàng)目的Target配置界面中,勾選Use MicroLIB,如下圖所示:
然后,在項(xiàng)目的usart.c代碼文件中,添加如下代碼:
  1. int fputc(int ch, FILE* f)
  2. {
  3.   (void) f;
  4.  
  5.   uint8_t data[] = {ch};
  6.   if (HAL_UART_Transmit(&huart1, data, sizeof(data), 1000) != HAL_OK)
  7.   {
  8.     return EOF;
  9.   }
  10.  
  11.   return ch;
  12. }
復(fù)制代碼
添加完如上代碼后,將項(xiàng)目可以支持printf打印。


2.4 基本功能測(cè)試

接下來(lái),修改main.c文件,將其中的while循環(huán)代碼修改為:
  1.   /* Infinite loop */
  2.   /* USER CODE BEGIN WHILE */
  3.   while (1)
  4.   {
  5.     HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
  6.     HAL_Delay(1000);
  7.     printf("Low\r\n");
  8.  
  9.     HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
  10.     HAL_Delay(1000);
  11.     printf("High\r\n");
  12.     /* USER CODE END WHILE */
  13.  
  14.     /* USER CODE BEGIN 3 */
  15.   }
  16.   /* USER CODE END 3 */
復(fù)制代碼
重新編譯整個(gè)項(xiàng)目,燒錄,將會(huì)看到紅色LED閃爍。

 

CubeMX中,默認(rèn)的USART1參數(shù)配置為:
使用串口助手,打開STLINK的虛擬串口,波特率設(shè)置為115200,既可以看到printf的打印輸出:




三、TinyMaix移植3.1 添加TinyMaix源碼

接下來(lái),克隆TinyMaix源碼:

git clone https://github.com/sipeed/TinyMaix.git

在CubeMX生成的文件夾中創(chuàng)建TinyMaix,并將TinyMaix開源代碼的include、src、example目錄拷貝到該目錄中。

 

然后在Keil的項(xiàng)目視圖中,使用Add Group菜單添加源碼組,如下圖所示:
然后,將新創(chuàng)建的組重命名為TinyMaix,如下圖所示:

然后,通過(guò)右鍵Add Exists Files to Group 'TinyMaix'菜單,將剛剛拷貝過(guò)來(lái)的src目錄的.c文件添加到該組中,如下圖所示:



3.2 解決TinyMaix編譯問(wèn)題

此時(shí)直接編譯源碼,會(huì)出現(xiàn)一些找不到tinymaix.h的編譯報(bào)錯(cuò),需要按照如下步驟處理:
  • 右鍵TinyMaix-U575打開Options for Target 'TinyMaix-U575'配置窗口;
  • 點(diǎn)擊C/C++標(biāo)簽頁(yè),找到Include paths欄;
  • 點(diǎn)擊Include paths欄右側(cè)的“...”按鈕,彈出Setup compiler include paths界面;
  • 在此界面中,將項(xiàng)目的TinyMaix/include子目錄添加到搜索路徑列表中。
完成上述步驟后,仍然無(wú)法編譯通過(guò),會(huì)有如下編譯報(bào)錯(cuò):

我們需要修改這段代碼:
將其修改為如下代碼段:
  1. #include "stm32u5xx_hal.h"
  2. #define TM_GET_MS()         HAL_GetTick()
  3. #define TM_DBGT_INIT()      uint32_t _start, _finish; int _time; _start = TM_GET_MS();
  4. #define TM_DBGT_START()     _start = TM_GET_MS();
  5. #define TM_DBGT(x)          _finish = TM_GET_MS(); \
  6.                             _time = (_finish - _start); \
  7.                             TM_PRINTF("===%s use %d ms\n", (x), _time); \
  8.                             _start = TM_GET_MS();
復(fù)制代碼
PS:CubeMX默認(rèn)生成的項(xiàng)目的Tick頻率為1000Hz,1個(gè)Tick即為1毫秒;


3.3 添加手寫數(shù)字識(shí)別示例

TinyMaix項(xiàng)目的examples目錄下,每個(gè)目錄是一個(gè)獨(dú)立的示例程序。其中,minst目錄即為手寫數(shù)字識(shí)別示例的代碼。
由于TinyMaix的examples目錄內(nèi)有多個(gè)main.c文件,并且每個(gè)main.c中都有一個(gè)main函數(shù)。另外,CubeMX生成的項(xiàng)目本身已經(jīng)有了main.c文件,以及對(duì)應(yīng)的main函數(shù)。
因此,如果直接將這些文件添加到項(xiàng)目中,會(huì)導(dǎo)致編譯錯(cuò)誤。所以,接下來(lái)首先需要進(jìn)行如下修改:
  • 將mnist目錄內(nèi)的main.c重命名為mnist_main.c;
  • 將該文件中的main函數(shù)重命名為mnist_main;
完成以上修改之后,使用類似前面的方法,將mnist_main.c文件添加到TinyMaix源碼組中:

最后,修改main.c,在while循環(huán)之前添加兩行代碼,分別聲明和調(diào)用minst_main函數(shù):
  1.   int mnist_main(int argc, char** argv);
  2.   mnist_main(0, 0);
復(fù)制代碼
完成以上修改后,編譯、燒錄、運(yùn)行,將會(huì)在STLink虛擬串口中看到如下輸出:
可以看到,成功識(shí)別了手寫數(shù)字。


四、原理解讀4.1 手寫數(shù)字識(shí)別示例源碼

mnist_main.c文件中,開始的幾行用于根據(jù)tm_port.h中定義的數(shù)據(jù)使用對(duì)應(yīng)的模型:
  1. #if TM_MDL_TYPE == TM_MDL_INT8
  2. #include "../../tools/tmdl/mnist_valid_q.h"
  3. //#include "../../tools/tmdl/mnist_resnet_q.h"
  4. #elif TM_MDL_TYPE == TM_MDL_FP32
  5. #include "../../tools/tmdl/mnist_valid_f.h"
  6. //#include "../../tools/tmdl/mnist_resnet_f.h"
  7. #elif TM_MDL_TYPE == TM_MDL_FP16
  8. #include "../../tools/tmdl/mnist_valid_fp16.h"
  9. #elif TM_MDL_TYPE == TM_MDL_FP8_143
  10. #include "../../tools/tmdl/mnist_fp8_143.h"
  11. #elif TM_MDL_TYPE == TM_MDL_FP8_152
  12. #include "../../tools/tmdl/mnist_fp8_152.h"
  13. #endif
復(fù)制代碼
這些.h文件是由tflite2tmdl.py腳本生成的TinyMaix模型,mnist_valid_f模型的轉(zhuǎn)換命令為:

python3 tflite2tmdl.py tflite/mnist_valid_f.tflite tmdl/mbnet_fp8.tmdl fp8_152 1 28,28,1 10

接下來(lái)定義了一個(gè)數(shù)組,uint8_t mnist_pic[28*28],保存一張測(cè)試圖片,數(shù)組每個(gè)元素對(duì)應(yīng)一個(gè)像素的灰度值。

接下來(lái),mnist_main中使用模型,主要使用了一下幾個(gè)TinyMaix的API:
  • tm_stat 打印模型結(jié)構(gòu)等信息;
  • tm_load 將模型加載到內(nèi)存;
  • tm_preprocess 輸入數(shù)據(jù)預(yù)處理;
  • tm_run 模型推理,得到輸出;
  • tm_unload 模型卸載,釋放內(nèi)存;
使用起來(lái)還是非常簡(jiǎn)單的,具體接口參數(shù)和返回值可以參考TinyMaix代碼注釋。

 

本篇內(nèi)容就到這里了,感謝你的閱讀,下次再會(huì)。
完整移植代碼倉(cāng)(感興趣的同學(xué)可以下載下來(lái)自行實(shí)驗(yàn)):https://gitee.com/swxu/tiny-maix-u575.git


五、參考鏈接

  • 【ST官網(wǎng)】NUCLEO-U575ZI-Q開發(fā)板產(chǎn)品頁(yè): https://www.st.com/en/evaluation-tools/nucleo-u575zi-q.html
  • 【ST官網(wǎng)】NUCLEO-U575ZI-Q開發(fā)板原理圖:https://www.st.com/resource/en/schematic_pack/mb1549-u575ziq-c03_schematic.pdf
  • 【ST官網(wǎng)】STM32U575ZI芯片產(chǎn)品頁(yè): https://www.st.com/en/microcontrollers-microprocessors/stm32u575zi.html
  • 【Keil官網(wǎng)】Pack包下載:https://www.keil.com/dd2/pack/
  • Keil中如何重定向printf/scanf到UART: http://www.ocfreaks.com/retarget-redirect-printf-scanf-uart-keil/










  • 本文系21ic原創(chuàng),未經(jīng)許可禁止轉(zhuǎn)載!

網(wǎng)友評(píng)論

  • 聯(lián)系人:巧克力娃娃
  • 郵箱:board@21ic.com
  • 我要投稿
  • 歡迎入駐,開放投稿

熱門標(biāo)簽
項(xiàng)目外包 more+