FuShenxiao 发表于 2024-10-2 22:05

STM32H7S78-DK测评(二)——用串口实现手写数字体识别(失败)

<div class='showpostmsg'> 本帖最后由 FuShenxiao 于 2024-10-2 22:49 编辑

<p>本次测试基于b站教程:<a href="https://www.bilibili.com/video/BV1eg4y167G6/?spm_id_from=333.337.search-card.all.click&amp;vd_source=30af65e26f8054bfda43260a9879957f">教程来了!!STM32手写数字识别!!!_哔哩哔哩_bilibili</a></p>

<p>up主工程开源地址:<a href="https://github.com/colin2135/STM32G070_AI_TEST.git" target="_blank">https://github.com/colin2135/STM32G070_AI_TEST.git</a></p>

<p>上位机测试软件地址:<a href="https://github.com/colin2135/HandWriteApp.git" target="_blank">https://github.com/colin2135/HandWriteApp.git</a></p>

<p><span style="font-size:18px;"><strong>模型训练与保存</strong></span></p>

<p>作为深度学习的入门教程,现在网上介绍MNIST手写数字体识别的教程已经很多了。这里贴一段用keras生成.h5文件的代码,不过为了和跟随up主的教程,我最后用了GitHub上的.tflite模型文件。</p>

<pre>
<code class="language-python">from keras.datasets import mnist
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras.utils import np_utils
import tensorflow as tf

config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.compat.v1.Session(config=config)

# 设定随机数种子,使得每个网络层的权重初始化一致
# np.random.seed(10)

# x_train_original和y_train_original代表训练集的图像与标签, x_test_original与y_test_original代表测试集的图像与标签
(x_train_original, y_train_original), (x_test_original, y_test_original) = mnist.load_data()

"""
数据可视化
"""

# 原始数据量可视化
print('训练集图像的尺寸:', x_train_original.shape)
print('训练集标签的尺寸:', y_train_original.shape)
print('测试集图像的尺寸:', x_test_original.shape)
print('测试集标签的尺寸:', y_test_original.shape)

"""
数据预处理
"""

# 从训练集中分配验证集
x_val = x_train_original
y_val = y_train_original
x_train = x_train_original[:50000]
y_train = y_train_original[:50000]
# 打印验证集数据量
print('验证集图像的尺寸:', x_val.shape)
print('验证集标签的尺寸:', y_val.shape)
print('======================')

# 将图像转换为四维矩阵(nums,rows,cols,channels), 这里把数据从unint类型转化为float32类型, 提高训练精度。
x_train = x_train.reshape(x_train.shape, 28, 28, 1).astype('float32')
x_val = x_val.reshape(x_val.shape, 28, 28, 1).astype('float32')
x_test = x_test_original.reshape(x_test_original.shape, 28, 28, 1).astype('float32')

# 原始图像的像素灰度值为0-255,为了提高模型的训练精度,通常将数值归一化映射到0-1。
x_train = x_train / 255
x_val = x_val / 255
x_test = x_test / 255

print('训练集传入网络的图像尺寸:', x_train.shape)
print('验证集传入网络的图像尺寸:', x_val.shape)
print('测试集传入网络的图像尺寸:', x_test.shape)

# 图像标签一共有10个类别即0-9,这里将其转化为独热编码(One-hot)向量
y_train = np_utils.to_categorical(y_train)
y_val = np_utils.to_categorical(y_val)
y_test = np_utils.to_categorical(y_test_original)

"""
定义网络模型
"""


def CNN_model():
    model = Sequential()
    model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    model.add(Conv2D(filters=32, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    model.add(Flatten())
    model.add(Dense(100, activation='relu'))
    model.add(Dense(10, activation='softmax'))

    print(model.summary())
    return model


"""
训练网络
"""

model = CNN_model()

# 编译网络(定义损失函数、优化器、评估指标)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# 开始网络训练(定义训练数据与验证数据、定义训练代数,定义训练批大小)
train_history = model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=10, batch_size=32, verbose=2)

# 模型保存
model.save('model.h5')

# 定义训练过程可视化函数(训练集损失、验证集损失、训练集精度、验证集精度)
def show_train_history(train_history, train, validation):
    plt.plot(train_history.history)
    plt.plot(train_history.history)
    plt.title('Train History')
    plt.ylabel(train)
    plt.xlabel('Epoch')
    plt.legend(['train', 'validation'], loc='best')
    plt.show()

show_train_history(train_history, 'accuracy', 'val_accuracy')
show_train_history(train_history, 'loss', 'val_loss')
</code></pre>

<p><span style="font-size:18px;"><strong>CubeMX配置</strong></span></p>

<p>安装CubeAI</p>

<p style="text-align: center;"> &nbsp;</p>

<p>在CubeMX上方Software Packs下拉选择Select Components,选择其中的X-CUBE-AI</p>

<p style="text-align: center;"> &nbsp;</p>

<p>在左侧菜单栏选择Middleware and Software Packs,选择其中的X-CUBE-AI,导入模型并分析。如果这个模型过大,超过了flash的大小,可能还需要对模型进行压缩,并配置外部flash。</p>

<p style="text-align: center;"> &nbsp;</p>

<p>这里可能有人会问为什么用的是7.3.0版本的CubeAI,而不用更高版本的CubeAI。我尝试了8.1.0和9.0.0两个版本的CubeAI,在模型验证阶段均出现tool&nbsp;error:&nbsp;&#39;gbk&#39;&nbsp;codec&nbsp;can&#39;t&nbsp;encode&nbsp;character的报错,可能高版本的CubeAI对gbk的适配能力不太好吧。</p>

<p><strong><span style="font-size:18px;">串口配置</span></strong></p>

<p>观察开发板原理图可以发现,PD0和PD1可以做虚拟串口使用,对应的是UART4。</p>

<p style="text-align: center;"> &nbsp;</p>

<p>开启UART4并设置为异步模式。由于需要串口收发,所以还要使能串口接收中断。</p>

<p style="text-align: center;"> &nbsp;</p>

<p>最后使能DEBUG功能</p>

<p style="text-align: center;"> &nbsp;</p>

<p><strong><span style="font-size:18px;">代码编写</span></strong></p>

<p>由于此次测试不需要TouchGFX(keil对TouchGFX的适配不是很好,总是缺文件),所以用的keil编写。</p>

<p>首先包含相关头文件</p>

<pre>
<code>#include "stdio.h"
#include "string.h"
#include "ai_platform.h"
#include "network.h"
#include "network_data.h"</code></pre>

<p>由于需要串口收发数据,因此需要对printf进行重定向,并在魔术棒里开启microLIB</p>

<pre>
<code>/* USER CODE BEGIN 0 */
/**
* <a href="home.php?mod=space&amp;uid=159083" target="_blank">@brief </a> 重定向c库函数printf到USARTx
* @retval None
*/
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&amp;huart4, (uint8_t *)&amp;ch, 1, 0xffff);
    return ch;
}
/**
* @brief 重定向c库函数getchar,scanf到USARTx
* @retval None
*/
int fgetc(FILE *f)
{
    uint8_t ch = 0;
    HAL_UART_Receive(&amp;huart4, &amp;ch, 1, 0xffff);
    return ch;
}</code></pre>

<p>定义AI模型相关参数,并声明后续使用到的一些函数</p>

<pre>
<code>ai_handle network;
float aiInData;
float aiOutData;
ai_u8 activations;

ai_buffer * ai_input;
ai_buffer * ai_output;

static void AI_Init(void);
static void AI_Run(float *pIn, float *pOut);
void PictureCharArrayToFloat(uint8_t *srcBuf,float *dstBuf,int len);

void Uart_send(char * str);
#define UART_BUFF_LEN 1024
#define ONE_FRAME_LEN 1+784+2
uint16_t uart_rx_length = 0;
uint8_t uart_rx_byte = 0;
uint8_t uart_rx_buffer;
volatile uint8_t goRunning = 0;

/* USER CODE END 0 */</code></pre>

<p>定义串口中断回调函数</p>

<pre>
<code>/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
        if(goRunning ==0)
        {
                if (uart_rx_length &lt; UART_BUFF_LEN)
                {
                        uart_rx_buffer = uart_rx_byte;
                        uart_rx_length++;

                        if (uart_rx_byte == '\n')
                        {
                                goRunning = 1;
                        }
                }
                else
                {
                        //rt_kprintf("rx len over");
                        uart_rx_length = 0;
                }
        }
        HAL_UART_Receive_IT(&amp;huart4, (uint8_t *)&amp;uart_rx_byte, 1);
}</code></pre>

<p>定义串口发送函数</p>

<pre>
<code>void Uart_send(char * str)
{
        HAL_UART_Transmit(&amp;huart4, (uint8_t *)str, strlen(str),0xffff);
}</code></pre>

<p>定义AI模型初始化函数</p>

<pre>
<code>static void AI_Init(void)
{
ai_error err;

/* Create a local array with the addresses of the activations buffers */
const ai_handle act_addr[] = { activations };
/* Create an instance of the model */
err = ai_network_create_and_init(&amp;network, act_addr, NULL);
if (err.type != AI_ERROR_NONE) {
    printf("ai_network_create error - type=%d code=%d\r\n", err.type, err.code);
    Error_Handler();
}
ai_input = ai_network_inputs_get(network, NULL);
ai_output = ai_network_outputs_get(network, NULL);
}</code></pre>

<p>定义AI模型运行函数</p>

<pre>
<code>static void AI_Run(float *pIn, float *pOut)
{
        char logStr;
        int count = 0;
        float max = 0;
ai_i32 batch;
ai_error err;

/* Update IO handlers with the data payload */
ai_input.data = AI_HANDLE_PTR(pIn);
ai_output.data = AI_HANDLE_PTR(pOut);

batch = ai_network_run(network, ai_input, ai_output);
if (batch != 1) {
    err = ai_network_get_error(network);
    printf("AI ai_network_run error - type=%d code=%d\r\n", err.type, err.code);
    Error_Handler();
}
for (uint32_t i = 0; i &lt; AI_NETWORK_OUT_1_SIZE; i++) {

          sprintf(logStr,"%d%8.6f\r\n",i,aiOutData);
          Uart_send(logStr);
          if(max&lt;aiOutData)
          {
                  count = i;
                  max= aiOutData;
          }
}
sprintf(logStr,"current number is %d\r\n",count);
Uart_send(logStr);
}</code></pre>

<p>定义将串口收到的uint8_t类型数据转换为float类型函数</p>

<pre>
<code>void PictureCharArrayToFloat(uint8_t *srcBuf,float *dstBuf,int len)
{
        for(int i=0;i&lt;len;i++)
        {
                dstBuf = srcBuf;//==1?0:1;
        }
}
/* USER CODE END 4 */</code></pre>

<p>主函数部分,需要完成外设初始化以及模型运行逻辑的书写</p>

<pre>
<code>int main(void)
{

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Update SystemCoreClock variable according to RCC registers values. */
SystemCoreClockUpdate();

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_CRC_Init();
MX_FLASH_Init();
MX_UART4_Init();
/* USER CODE BEGIN 2 */
__HAL_RCC_CRC_CLK_ENABLE();
AI_Init();
memset(uart_rx_buffer,0,784);
HAL_UART_Receive_IT(&amp;huart4, (uint8_t *)&amp;uart_rx_byte, 1);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    char str;
    if(goRunning&gt;0)
    {
      if(uart_rx_length == ONE_FRAME_LEN)
      {
            PictureCharArrayToFloat(uart_rx_buffer+1,aiInData,28*28);
            AI_Run(aiInData, aiOutData);
      }
      memset(uart_rx_buffer,0,784);
      goRunning = 0;
      uart_rx_length = 0;
    }
}
/* USER CODE END 3 */
}</code></pre>

<p>至此,代码部分就完成了。但是当我烧录运行程序的时候,发生了如下报错,定位到报错的函数为 ai_network_init_and_create() ,说明模型创建失败。</p>

<pre>
<code>ai_network_create error - type=51 code=65</code></pre>

<p>在网上诸多类似问题的帖子中,存在类似问题的大多都是H7系列芯片,而一般建议的方案都是检查是否开启CRC,以下两张图是ST员工对该问题的回复</p>

<p style="text-align: center;"> &nbsp;</p>

<p style="text-align: center;"> &nbsp;</p>

<p>然而,当我在AI模型初始化在 AI_Init() 之前加入 __HAL_RCC_CRC_CLK_ENABLE() 用于CRC开启使能,还是发生了如上的报错。</p>

<p>如果有了解这个问题的大佬,还请指导一下,我将不胜感激。</p>

<p>整个工程如下:</p>

<div></div>

<p><strong><span style="font-size:18px;">STM32G431RBT6实现</span></strong></p>

<p>依照该up主的做法,我看网上已有用F4,F7实现的,而该up主用的是G0的芯片。我宿舍里刚好有一块蓝桥杯嵌入式的板子,上面搭载的是STM32G431RBT6,接下来就尝试一下G4是否能实现手写数字体识别。</p>

<p>AI模型导入流程与上文一致,引脚配置如下。其中PF0和PF1用于晶振信号输入,PA9和PA10用于串口收发,PA13和PA14用于DEBUG。</p>

<p style="text-align: center;"> &nbsp;</p>

<p>&nbsp;</p>

<p>代码部分也与上文一致,仅需将huart4改为huart1即可。</p>

<p>编写烧录,运行成功!说明G4也是能部署AI模型用于MNIST手写数字体识别的。</p>

<p style="text-align: center;"> &nbsp;</p>

<div></div>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

cc1989summer 发表于 2024-10-3 10:35

<p>比较有创意的设计。</p>

<p>&nbsp;</p>

<p>STM32H7S78-DK有触摸屏,要是能直接脱离电脑直接在开发板上运行就好了(这也是我想做的事)</p>

FuShenxiao 发表于 2024-10-3 13:08

cc1989summer 发表于 2024-10-3 10:35
比较有创意的设计。

&nbsp;

STM32H7S78-DK有触摸屏,要是能直接脱离电脑直接在开发板上运行就好了( ...

<p>对的,我下一步就想这么做了,不过深度学习的办法似乎行不通,我得尝试用别的识别手段</p>

zxcvbnm751120 发表于 2024-10-5 16:45

页: [1]
查看完整版本: STM32H7S78-DK测评(二)——用串口实现手写数字体识别(失败)