【嵌入式AI部署神经网络】STM32CubeIDE上部署神经网络之指纹识别(Pytorch)——篇一|环境搭建与模型初步部署篇

news/2024/5/19 9:50:09

前言:本篇主要讲解搭建所需环境,以及基于pytorch框架在stm32cubeide上部署神经网络,部署神经网络到STM32单片机,本篇实现初步部署模型,没有加入训练集与验证集,将在第二篇加入。篇二详细讲解STM32CubeIDE上部署神经网络之指纹识别(Pytorch)的数据准备和模型训练过程等,进行实战,第二篇在本专栏查阅。

目录

1. 环境安装和配置

2. AI神经网络模型搭建

2.1 数据集介绍

2.2 网络模型

2.3 训练

3. STM32CubeIDE上进行模型转换与模型部署到单片机

4. STM32 CubeUDE上进行模型验证

5. 结果统计与分析


1. 环境安装和配置

本文介绍在STM32cubeIDE上部署AI模型,开发板型号STM32F429IGT6。

与AI加速器不同,ST支持神经网络计算是因为之前的芯片已经内置了DSP处理器,可以执行高精度浮点运算,正好可以拿来做神经网络计算。如何判断自己准备购买的板子适不适合做AI计算,最好也按以下步骤在CUBE-AI上模拟部署一遍,若模拟成功,所选开发板就是可以的。

STM32cubeIDE可直接在ST官网下载,下载链接

https://www.st.com/zh/development-tools/stm32cubeide.html

默认安装即可,不懂可自行上网查教程。

2. AI神经网络模型搭建

2.1 数据集介绍

针对tinyML开发了自己的指纹识别数据集,数据集和完整代码见文末下载链接。指纹识别数据集包含100个类别,大小为260*260,训练集30张,测试集5张。在测试时使用128*128与64*64的分辨率。

数据集在如下文件夹中

生成测试集的方法:

import os
import numpy as np
from PIL import Image
import torchvision.transforms as transformsnormalize = transforms.Normalize(mean=[0.5],std=[0.5])
test_transforms = transforms.Compose([# transforms.RandomResizedCrop(224),transforms.Resize(128),transforms.ToTensor(),normalize])def prepare_eval_data(data_file, transform=None):datas = os.listdir(data_file)imgs=[]labels=[]for  img_path in datas:data = Image.open(data_file + '/' + img_path)  # 260*260*1label, _ = img_path.split('_')label = int(label) - 1label_ohot=np.zeros(100)label_ohot[label]=1# print(data.shape, label)data1 = transform(data)data2 = transform(data)data3 = transform(data)data = np.concatenate([data1, data2, data3], 0)labels.append(label_ohot)imgs.append(data)imgs = np.array(imgs)labels = np.array(labels)print(imgs.shape,labels.shape)return imgs,labelsif __name__ == '__main__':data_path = '/disks/disk2/dataset/fingerprint/'testx,testy=prepare_eval_data(data_path+'test',test_transforms )print(testx.shape,testy.shape)print(testy[0])np.save('fpr100*5_testx_128.npy', testx)np.save('fpr100*5_testy_128.npy', testy)

2.2 网络模型

  测试了多个轻量化神经网络模型,如压缩后的MobileNet v2:

class MobileNetV2Slim(nn.Module):def __init__(self, num_classes=1000):super(MobileNetV2Slim,self).__init__()self.first_conv = Conv3x3BNReLU(3,4,2,groups=1)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)# self.layer1 = self.make_layer(in_channels=8, out_channels=4, stride=1, block_num=1)self.layer2 = self.make_layer(in_channels=4, out_channels=6, stride=1, block_num=1)self.layer3 = self.make_layer(in_channels=6, out_channels=8, stride=2, block_num=2)self.layer4 = self.make_layer(in_channels=8, out_channels=16, stride=2, block_num=3)self.layer5 = self.make_layer(in_channels=16, out_channels=24, stride=1, block_num=3)self.layer6 = self.make_layer(in_channels=24, out_channels=32, stride=2, block_num=3)self.layer7 = self.make_layer(in_channels=32, out_channels=64, stride=1, block_num=1)self.last_conv = Conv1x1BNReLU(64,128)self.avgpool = nn.AvgPool2d(kernel_size=2,stride=1)self.dropout = nn.Dropout(p=0.2)self.linear = nn.Linear(in_features=128,out_features=num_classes)def make_layer(self, in_channels, out_channels, stride, block_num):layers = []layers.append(InvertedResidual(in_channels, out_channels, stride))for i in range(1, block_num):layers.append(InvertedResidual(out_channels,out_channels,1))return nn.Sequential(*layers)def init_params(self):for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight)nn.init.constant_(m.bias, 0)elif isinstance(m, nn.Linear) or isinstance(m, nn.BatchNorm2d):nn.init.constant_(m.weight, 1)nn.init.constant_(m.bias, 0)def forward(self, x):x = self.first_conv(x)x=self.maxpool(x)# x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.layer5(x)x = self.layer6(x)x = self.layer7(x)x = self.last_conv(x)# print(x.size())x = self.avgpool(x)# x = x.view(x.size(0),-1)x=x.reshape(int(x.size(0)), -1)x = self.dropout(x)out = self.linear(x)return out

2.3 训练

基于pytorch搭建模型进行训练,核心代码如下:


'''***********- trainer -*************'''
class trainer:def __init__(self, loss_f,loss_dv,loss_fn, model, optimizer, scheduler, config):self.loss_f = loss_fself.loss_dv = loss_dvself.loss_fn = loss_fnself.model = modelself.optimizer = optimizerself.scheduler = schedulerself.config = configdef batch_train(self, batch_imgs, batch_labels, epoch):predicted = self.model(batch_imgs)loss = self.myloss(predicted, batch_labels)predicted = softmax(predicted, dim=-1)del batch_imgs, batch_labelsreturn loss, predicteddef train_epoch(self, loader,epoch):self.model.train()tqdm_loader = tqdm(loader)# acc = Accuracy_score()losses = AverageMeter()top1 = AverageMeter()top5 = AverageMeter()print("\n************Training*************")for batch_idx, (imgs, labels) in enumerate(tqdm_loader):#print("data",imgs.size(), labels.size())#[128, 3, 32, 32]) torch.Size([128]imgs, labels=imgs.cuda(), labels.cuda()#permute(0,3,1,2).# print(self.optimizer.param_groups[0]['lr'])loss, predicted = self.batch_train(imgs, labels, epoch)losses.update(loss.item(), imgs.size(0))# print(predicted.size(),labels.size())self.optimizer.zero_grad()loss.backward()self.optimizer.step()# self.scheduler.step()err1, err5 = accuracy(predicted.data, labels, topk=(1, 5))top1.update(err1.item(), imgs.size(0))top5.update(err5.item(), imgs.size(0))tqdm_loader.set_description('Training: loss:{:.4}/{:.4} lr:{:.4} err1:{:.4} err5:{:.4}'.format(loss, losses.avg, self.optimizer.param_groups[0]['lr'],top1.avg, top5.avg))# if batch_idx%1==0:#     breakreturn 100-top1.avg, losses.avgdef valid_epoch(self, loader, epoch):self.model.eval()# acc = Accuracy_score()# tqdm_loader = tqdm(loader)losses = AverageMeter()top1 = AverageMeter()print("\n************Evaluation*************")for batch_idx, (imgs, labels) in enumerate(loader):with torch.no_grad():batch_imgs = imgs.cuda()#permute(0,3,1,2).batch_labels = labels.cuda()predicted= self.model(batch_imgs)loss = self.myloss(predicted, batch_labels).detach().cpu().numpy()loss = loss.mean()predicted = softmax(predicted, dim=-1)losses.update(loss.item(), imgs.size(0))err1, err5 = accuracy(predicted.data, batch_labels, topk=(1, 5))top1.update(err1.item(), imgs.size(0))return 100-top1.avg, losses.avgdef myloss(self,predicted,labels):#print(predicted.size(),labels.size())#[128, 10]) torch.Size([128])loss = self.loss_f(predicted,labels,)# loss = loss1+loss2return lossdef run(self, train_loder, val_loder,model_path):best_acc = 0start_epoch=0# model, optimizer, start_epoch=load_checkpoint(self.model,self.optimizer,model_path)for e in range(self.config.epochs):e=e+start_epoch+1print("------model:{}----Epoch: {}--------".format(self.config.model_name,e))self.scheduler.step(e)# torch.cuda.empty_cache()train_acc, train_loss = self.train_epoch(train_loder,e)val_acc, val_loss=self.valid_epoch(val_loder,e)#print("\nval_loss:{:.4f} | val_acc:{:.4f} | train_acc:{:.4f}".format(val_loss, val_acc,train_acc))if val_acc > best_acc:best_acc = val_accprint('Current Best (top-1 acc):',val_acc)#new_model = quant_dorefa.prepare(self.model, inplace=False, a_bits=8, w_bits=8)x = torch.rand(1, 3,data_config.input_size, data_config.input_size).float().cuda()save_path=data_config.MODEL_PATH+data_config.model_name+'_epoch{}_params.onnx'.format(e)torch.onnx.export(self.model, x, save_path, export_params=True, verbose=False,opset_version=10)# 支持Opset 7, 8, 9 and 10 of ONNX 1.6 is supported.print("saving model sucessful !",save_path)print('\nbest score:{}'.format(data_config.model_name))print("best accuracy:{:.4f}  ".format(best_acc))

模型训练后得到***.onnx模型、训练和测试数据,这是我们后续步骤要用到的。

3. STM32CubeIDE上进行模型转换与模型部署到单片机

    步骤2得到了torch神经网络框架训练好的“***.onnx”模型文件,下一步需要把该AI模型转换成C程序代码,并嵌入到整个项目工程中。整个项目工程包括硬件代码可以直接通过 STM32CubeIDE工具生成。

具体步骤如下:

打开安装好的STM32CubeIDE软件。

新建,stm32 project。

搜索,选择目标开发板型号STM32F429IGT6,点击next。

填写项目名,点击finish。

点击Yes 。

点击I hava read ....   ;然后点击Finish。

等待初始化完成,左边是IDE编译器中是自动生成的底板代码,右边是CUBEMX软件界面,可对开发板硬件资源进行配置。

 下载X-CUBE-AI,需在software packs点manage software packs找到X-CUBE-AI下载。

 

选取ai开发需要支持的软件包cue-ai,在software packs点select components。


 

在X-CUBE-AI行下core勾选,application选validation,双核开发板建议选M7系列,点OK完成

回到引脚配置页面,最下方就是上面导入的CUBE-AI工具包,点击进入配置,选中上方两个√号,然后在configration下方platform setting 选择并配置串口,用来进行数据交互的

回到cube-ai,选择添加网络

根据训练产生的文件进行配置:选择模型文件、压缩比、加载验证数据

点击分析:工具会根据模型与模型参数推断出模型占用(ROM、RAM),进而判断神经网络能否部署到开发板。

若出现以下报错 

错误信息建议通过将注册表中的LongPathsEnabled键设置为1来启用长路径。

以下是具体操作步骤:

  1. 通过在Windows搜索栏中键入“regedit”并按Enter来打开注册表编辑器。
  2. 导航至以下键:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem
  3. 在右侧窗格上右键单击,然后选择“新建” -> “DWORD (32位) 值”。
  4. 将新值命名为LongPathsEnabled
  5. 双击LongPathsEnabled并将其值设置为1
  6. 单击“确定”以保存更改。
  7. 关闭注册表编辑器,然后尝试重新运行命令。

网络分析运行成功

根据ROM、RAM判断出模型可部署 

 点击,validation on desktop 在pc上进行模型验证。(也可以实现原模型与转换后模型的对比。)

在前面步骤基础上,点时钟配置栏,系统会自动进行时钟配置

点项目管理栏,进行配置

在IDE编译器中,选择project,选择生成代码

生成代码后,右键工程,选择build project

修改代码,等待编译完成后,若没有错误则下一步。

重新编译后,右下侧可以看到占用的flash与RAM,表明模型是否成功部署,

右键工程名运行。

配置,默认即可

烧录完成

至此,模型部署完成,下面开始进行板上模型验证。

4. STM32 CubeUDE上进行模型验证

基于上述步骤,下面进行模型验证,验证AI模型在目标板上的运行情况。验证原理如下:

现在tinyML解决方案厂商都在做AutoML,就是为方便初学者不需要掌握专门的硬件知识就能够自动部署到MCU开发板。因此开发者只需要按照指定的格式准备好数据,validate on target 就是将准备好的数据通过串口传输到RAM内存中,一般是神经网络指定的输入缓冲块,经神经网络后再将结果通过串口传回前端程序显示。

运行前,不要忘记连接开发板,以及检测端口号

双击.ioc文件,会自动返回cubeMX页面,与validate on desktop相同的配置进行validate on target

自动即可

 validation on target结果如下

重新 validation on desktop和validation on target,结果是一样的,

参数分析

运行时间

5. 结果统计与分析

结果分析如下:

当前暂时没有加入测试与验证数据集,将在第二篇加入。

更多详细信息需参考txt报告:


http://www.mrgr.cn/p/48388100

相关文章

4.19作业

1、总结二进制信号量和计数型信号量的区别,以及他们的使用场景。 二进制信号量:信号量的数值只能是0和1,用于共享资源的访问 计数型信号量:信号量的值都是大于或者等于2,实现生产者和消费者模型 2、使用技术型信号量…

手撕netty源码(一)- NioEventLoopGroup

文章目录 前言一、NIO 与 netty二、NioEventLoopGroup 对象的创建过程2.1 创建流程图 前言 本文是手撕netty源码系列的开篇文章,会先介绍一下netty对NIO关键代码的封装位置,主要介绍 NioEventLoopGroup 对象的创建过程,看看new一个对象可以做…

快速新建springboot项目

一、初始化 1.打开IDEA,在Spring initializer这里按照下图项目进行配置。注意:如果jdk是1.8建议将Server URL这里替换为图中的阿里云服务器,否则容易找不到对应的java8,然后点击next 2.在这里提前配置一些需要使用的依赖&#xf…

软考 系统架构设计师系列知识点之大数据设计理论与实践(13)

接前一篇文章:软考 系统架构设计师系列知识点之大数据设计理论与实践(12) 所属章节: 第19章. 大数据架构设计理论与实践 第4节 Kappa架构 19.4.2 Kappa架构介绍 Kappa架构由Jay Kreps提出(Lambda由Storm之父Nayhan M…

react引入iconfont的svg图标

react引入iconfont的svg图标 本文目录 react引入iconfont的svg图标普通图标通过link引入css组件内引入css使用 svg图标通过script引入js组件内引入js使用 通过封装组件自定义封装组件中调用 通过antd封装使用 普通图标 通过link引入css <link rel"stylesheet" h…

C语言 字符类型

下面 我们来说字符类型 我们来看这个 保险单 金额 和 总额 都可以用数字类型 而性别则需要字符型 字符数据的存储 – ASCI码 字符类型 char 就是专为存储字符(如字母&#xff0c;标点和数字)而设计的类型。 使用单引号包含单个字符或转义字符去表示一个 char 类型的常量。 …

【QT学习】9.绘图,三种贴图,贴图的转换,不规则贴图(透明泡泡)

一。绘图的解释 Qt 中提供了强大的 2D 绘图系统&#xff0c;可以使用相同的 API 在屏幕和绘图设备上进行绘制&#xff0c;它主要基于QPainter、QPaintDevice 和 QPaintEngine 这三个类。 QPainter 用于执行绘图操作&#xff0c;其提供的 API 在 GUI 或 QImage、QOpenGLPaintDev…

ZYNQ--PL读写PS端DDR数据

PL 和PS的高效交互是zynq 7000 soc开发的重中之重&#xff0c;我们常常需要将PL端的大量数 据实时送到PS端处理&#xff0c;或者将PS端处理结果实时送到PL端处理&#xff0c;常规我们会想到使用DMA 的方式来进行&#xff0c;但是各种协议非常麻烦&#xff0c;灵活性也比较差&am…

CDN、边缘计算与云计算:构建现代网络的核心技术

在数字化时代&#xff0c;数据的快速传输和处理是保持竞争力的关键。内容分发网络&#xff08;CDN&#xff09;、边缘计算和云计算共同构成了现代互联网基础架构的核心&#xff0c;使内容快速、安全地到达用户手中。本文将探讨这三种技术的功能、相互关系以及未来的发展趋势。 …

网络 (基础概念, OSI 七层模型, TCP/IP 五层模型)

网络互连 网络互连: 将多台计算机连接在一起, 完成数据共享 数据共享的本质是网络数据传输, 即计算机之间通过网络来传输数, 也叫做网络通信 根据网络互连的规模不同, 将网络划分为局域网和广域网 注意: 局域网和广域网是相对的概念 局域网LAN 又称内网, 局域网和局域网之间在没…

【快速入门 LVGL】-- 5、Gui Guider界面移植到STM32工程

上篇&#xff0c;我们已学习&#xff1a;【快速入门 LVGL】-- 4、显示中文 工程中添加了两个按钮作示范。运行效果如图&#xff1a; 本篇&#xff1a;把Gui Guider设计好的界面&#xff0c;移植到STM32工程。 特别地&#xff1a; 在使用Gui Guider进行界面设计时&#xff0c;应…

读天才与算法:人脑与AI的数学思维笔记08_生物的创造力

读天才与算法:人脑与AI的数学思维笔记08_生物的创造力1. 生物的创造力 1.1. 在进化树中是否有其他的物种已经具有与我们人类相当的创造力水平 1.2. 20世纪50年代中期,动物学家德斯蒙德莫里斯(Desmond Morris)在伦敦动物园做了这样一个试验 1.2.1. …

[转帖]18--k8s之Nginx ingress

https://www.cnblogs.com/caodan01/p/15142709.html 目录一、介绍 二、安装nginx ingress 三、http部署1.编写一个service准备实验 2.编写http的ingress 3.部署四、https部署 五、常用配置 一、介绍 ingress为kubernetes集群中的服务提供了入口,可以提供负载均衡,ssl终止和基…

基于Vue+ElementPlus自定义带历史记录的搜索框组件

前言 基于Vue2.5ElementPlus实现的一个自定义带历史记录的搜索框组件 效果如图&#xff1a; 基本样式&#xff1a; 获取焦点后&#xff1a; 这里的历史记录默认最大存储10条&#xff0c;同时右侧的清空按钮可以清空所有历史记录。 同时搜索记录也支持点击搜索&#xff0c;按…

SpringCloud系列(11)--将微服务注册进Eureka集群

前言&#xff1a;在上一章节中我们介绍并成功搭建了Eureka集群&#xff0c;本章节则介绍如何把微服务注册进Eureka集群&#xff0c;使服务达到高可用的目的 Eureka架构原理图 1、分别修改consumer-order80模块和provider-payment8001模块的application.yml文件&#xff0c;使这…

【七】jmeter5.5+influxdb2.0+prometheus+grafana

参考文章&#xff1a;https://blog.csdn.net/wenxingchen/article/details/126892890 https://blog.csdn.net/Zuo19960127/article/details/119726652 https://blog.csdn.net/shnu_cdk/article/details/132182858 promethus参考 由于自己下载的是infuldb2.0&#xff0c;所以按照…

【目标检测】FPN特征金字塔完整流程详解

学习视频&#xff1a;1.1.2 FPN结构详解 对比 可以看到FPN是自上而下、自下而上并且可以进行多尺度特征融合的的层级结构。 具体结构 1x1 conv: 对通道数进行调整&#xff0c;不同大小的特征图通道数不同&#xff0c;越高层次的特征图通道数越大&#xff0c;论文中使用256个1…

OpenTelemetry-2.Go接入Jaeger(grpc,gin-http)

目录 1.什么是OpenTelemetry 2.搭建jaeger 3.链路追踪 本地调用 远程调用 GRPC proto server端 client端 Gin-HTTP 调用流程 api1 api2 grpc 4.完整代码 1.什么是OpenTelemetry 参考&#xff1a;OpenTelemetry-1.介绍-CSDN博客 2.搭建jaeger 参考&#xff1a;…

【深度学习】烟雾和火焰数据集,野外数据集,超大量数据集,目标检测,YOLOv5

标注了2w张数据集&#xff0c;是目标检测yolo格式的&#xff0c;有火焰、烟雾两个目标&#xff0c;下图是训练时候的样子&#xff1a; 训练方法看这里&#xff1a; https://qq742971636.blog.csdn.net/article/details/138097481 数据集介绍 都是博主辛苦整理和标注的&…

java 学习一

jdk下载地址 配置环境变量