序、活动介绍
Funpack活动是硬禾学堂联合DigiKey发起的“玩成功就全额退”活动。第-季和第二季已圆满结束,现在是第三季,本季共6期,每两个月我们都会推出一款全球顶级半导体厂商的开发板/仪器套件。参与者需先从“得捷购”购买规定板卡,并在规定时间内完成指定任务,就可以获得购板返还,项目优秀者另有额外奖励。
第三季第四期的必购板卡是来自NXP的FRDM-MCXN947,N947是一款紧凑且可扩展的开发板其外部闪存和板载MCU-Link调试器
这里是N947的结构框图
我们首先来看看这块开发板的性能
MCX-N947有两个Arm™ Cortex™-M33内核,每个内核的主频均为150MHz,优化了性能效率,高达2MB的双块Flash存储器,带可选的全ECC RAM,外部闪存
加速器:神经处理单元、PowerQuad、Smart DMA等,这确保了神经网络高速计算的可靠性
然后是板载的连接器
以太网PHY HS USB Type-C SPI/I2C/UART(PMOD/mikroBUS、DNP) WIFI(PMOD/mikroBUS、DNP) CAN-FD
同时板载两个传感器P3T1755 I3C/I2C温度传感器与touch电路板
本次活动中我使用到的高级外设有
SmartDMA
eIQ®Neutron N1-16神经处理单元
DSP加速器(PowerQUAD,带协处理器接口)
32位标准通用异步定时器/计数器
我这里做的是任务3因为之前在FastBond提出过一个创意
(这里正好通过恩智浦的MCXN947来学习一下关于神经网络的使用)
这里推荐李宏毅老师的机器学习课程(强推)李宏毅2021/2022春机器学习课程_哔哩哔哩_bilibili
任务三具体如下
•使用eIQ NPU实现机器学习加速。可以自己搭配摄像头模组,若无模组可以使用调试器的串口向MCU直接发送图像或者相应的音频数据,进行测试。要求使用eIQ实现图像分类或者音频分类,自行搭建可用的NN结构,实现鞋子or帽子检测或者音频的鼓声or钢琴检测
我实现了实现鞋子or帽子检测
接下来我将会带大家一步步的复刻这个工程
一、开发环境的搭建
恩智浦有一套自己的完善开发环境可以选择MCUXpresso IDE或MCUXpresso for VS Code。这取决与您是否依赖vscode,本文我将以MCUXpresso IDE来介绍这部分。
1、MCUXpresso IDE简介
MCUXpresso IDE为开发人员带来一个易于使用的基于Eclipse的开发环境,适用于基于Arm®Cortex®-M内核的恩智浦MCU,包括通用、跨界和无线MCU。MCUXpresso IDE提供高级编辑、编译和调试功能。
2、MCUXpresso IDE安装
本文该部分省略请自行百度
3、构建、运行项目代码
3.1使用MCUXpresso IDE导入示例代码
打开MCUXpresso IDE从Application Code Hub导入Label CIFAR10 image
至此我们的工程导入成功限于文章篇幅限制本文将不会详细的讲解本工程的具体代码细节
3.2根据示例修改代码为我们所需要求的代码
请注意这两个文件
source->model->labers.h
source->model->model_data.s
在labers.h文件中我们需要修改labers[ ]为我们需要检测的物体标签
在model_data.s修改为我们导入模型的名称
注意导入的模型应存放在model文件夹下
此处提供我修改后的代码请根据自己需求进行修改
static const char* labels[] = {
"maozi",
"xiezi",
" "
};
npu_model_data:
.incbin "../source/model/mobilenet_v1.tflite"
npu_model_data_end:
3.3构建调试到MCXN947
如图所示为构建与烧录调试,在烧录开始前确保您的MCXN947与计算机之间的链接 请使用具有数据传输的数据线来链接您的开发板点击上方的“调试”图标,或者在快速入门面板中点击“调试”,将应用下载到板上。
选择MCU-Link CMSIS-DAP硬件调试器打开一个串行终端,以便查看应用的输出。选择“终端”窗口,然后点击“新建终端”图标
选择“串行终端”,然后将UART设置为115200波特率,8位数据大小,无校验位,1个停止位。按下OK(确认)键
4、构建属于自己的模型文件
在此之前请先确保您有合适且足够多的数据集并且依照如下方式放置到磁盘根目录下
这里简单介绍下恩智浦的神经网络开发过程
首先我们要拥有完备的数据集与分类并且选择一个模型进行训练根据损耗函数与图像是否收敛来评估模型质量,然后通过测试集对模型进行测试调整Softmax函数的阈值得到一个合适的二进制模型文件(人不可读)依C语言数组int8的形式烧录进MCXN947后通过原生传感器(摄像头)获取数据 并对数据进行分析
4.1安装eIQ Portal工具链
本文省略请自行百度
4.2将数据集按分类好的方式导入eIQ
选择create project->import dataset:
4.3 将数据集导入为eiq工程
Structured Folders->SELECT DATASTE FOLDER->选择我们准备的数据集->IMPORT->选择工程文件目录
4.4分配测试集与训练集
导入成功后点击右上角SHUFFLE可以自动分配测试集与数据集比例为8:2
4.5选择合适的模型与模型输入尺寸
select model->base models->input size ->128,128,3
4.6 开始训练
Epochs To Train->150次 注意训练次数应该根据模型收敛程度 点击STAR TRAINING开始训练
观察损耗函数收敛如果精度一直不达标,可以通过修改各训练参数,点击CONTINUE TRAINING继续进行训练。
4.7对训练后的模型进行测试
点击VALIDATE对训练集做测试设置参数Softmax,input DataType和output Data Type,目前MCXN系列Neutron NPU只支持int8类型 这里我给到一个参考值为0.15点击VALIDATE进行测试得到一个测试样本
通过混淆矩阵我们可以清晰看出不同类别的分类情况,图中x轴是预测的标签,y轴是实际的标签,可以看到每一张图片预测标签和实际标签的对应情况:
在此处我所用到的模型训练了400次卷积计算混淆矩阵测试为百分之百通过
4.8 对模型进行导出
TensorFlow Lite -> DEPLOY ->Export file Type -> input Data Type ->output Data-> TypeExport Quantized Model->Export Model 如图所示
5、将模型导入开发板 请参考本文
3、构建、运行项目代码
6、对项目进行验证
链接开发板到电脑打开串口观察到串口输出N/A及为未检测到任何物体 当前是background
放置一张帽子图片串口输出如下括号内为神经网络的肯定程度
此处在插入一张带板卡+图像+输出结果的照片
功能实现 活动任务完成
7、关于本次活动的心得体会
7.1 硬件选择与架构设计
首先,选择了合适的摄像头模组,这是整个项目的硬件基础。摄像头模组的分辨率、帧率及其与MCU的兼容性都是需要仔细斟酌的因素。最终决定使用OV7670,是因为NXP库中直接有现成的驱动库,避免了重复造轮子的环节。
7.2 图像数据采集与预处理
图像数据质量对于机器学习模型的训练和推理效果至关重要。在数据采集阶段,我尽量确保图像的多样性和代表性,这样可以提高模型的泛化能力。在预处理阶段,进行了图像的裁剪、缩放和标准化处理,以保证数据的一致性和质量,这对于机器学习有很大帮助,譬如我将一张图片水平翻转、去色、提高饱和度,带着这样的数据集去问机器 这是一张图片嘛,由此训练出的模型,对重复问题能做出高质量回答。
7.3 神经网络结构搭建
基于现有的神经网络设计指南,我选择了适合嵌入式系统的轻量级神经网络架构,如MobileNet。这种网络在计算资源有限的设备上表现良好,同时可以保持较高的分类准确率。针对鞋子和帽子的特征,对网络结构进行了微调,以更好地适配具体的分类任务。
7.4 在eIQ NPU上的部署
本次活动的部署难度真的很低 学习成本也几乎为零,很适合我这种对神经网络理解不透彻的人来参与学习再次感谢恩智浦公司与硬禾科技给我本次学习的平台
8、活动代码的简单解释
首先是对主函数中代码的分析
BOARD_Init();
TIMER_Init();
DEMO_PrintInfo();
Ov7670_Init();
display_init();
ezh_start();
cifar10_recognize();
通过这些初始化函数对项目进行初始化这里最重要的函数是cifar10_recognize
用于运行CIFAR-10图像识别模型的函数。它的主要任务是初始化模型,准备输入数据,运行推理操作,并处理输出结果
if (MODEL_Init() != kStatus_Success)
{
PRINTF("Failed initializing model");
for (;;) {}
}
这里调用 MODEL_Init()
函数进行模型初始化。如果初始化失败,它会打印错误信息并进入无限循环
size_t usedSize = MODEL_GetArenaUsedBytes(&arenaSize);
PRINTF("\r\n%d/%d kB (%0.2f%%) tensor arena used\r\n", usedSize / 1024, arenaSize / 1024, 100.0*usedSize/arenaSize);
inputData = MODEL_GetInputTensorData(&inputDims, &inputType);
outputData = MODEL_GetOutputTensorData(&outputDims, &outputType);
这里,它首先获取模型使用的内存,并打印相关信息。接着获取模型输入和输出的张量数据。
while(1)
{
if (g_isImgBufReady == 0)
continue;
uint8_t *buf = 0;
memset(inputData,0,inputDims.data[1]*inputDims.data[2]*inputDims.data[3]);
buf = inputData + (inputData,inputDims.data[1] - MODEL_IN_H) /2 * MODEL_IN_W * MODEL_IN_C;
memcpy(buf, model_input_buf, MODEL_IN_W*MODEL_IN_H*MODEL_IN_C);
auto startTime = TIMER_GetTimeInUS();
MODEL_RunInference();
auto endTime = TIMER_GetTimeInUS();
auto dt = endTime - startTime;
s_infUs = (uint32_t)dt;
MODEL_ProcessOutput(outputData, &outputDims, outputType, dt);
}
该部分代码为一个无限循环。首先,会检测 g_isImgBufReady
是否为0,如果是,则继续等待。如果准备好图像数据( g_isImgBufReady
不为0),则执行如下操作:
- 清零
inputData
的内容。 - 根据偏移量,将
model_input_buf
复制到inputData
中。 - 记录开始时间
startTime
。 - 调用
MODEL_RunInference()
运行模型推理。 - 记录结束时间
endTime
,并计算推理时间dt
。 - 处理推理输出数据,调用
MODEL_ProcessOutput()
方法。
再来看看一切接口函数
void Rgb565StridedToBgr888(...)
- 输入: 包含RGB565格式数据的指针
pIn
,源宽度srcW
,窗口宽度和高度wndW
,wndH
,起始坐标wndX0
,wndY0
,输出888格式数据的指针p888
,步幅stride
,是否减128的标志isSub128
。 - 功能: 将RGB565格式的图像数据转换为BGR888格式,并处理步幅。在必要时,将输出的每个字节减128(通过XOR操作)。
- 内部实现:
- 循环遍历图像窗口的每一行。
- 从源图像(RGB565)中读取数据,并根据公式转换为BGR888格式。
- 根据需要,将每组RGB888数据(24位)减去128。
- 将转换后的数据拷贝到输出缓冲区
p888out
。
void ezh_copy_slice_to_model_input(...)
- 输入:
idx
表示当前切片的索引,cam_slice_buffer
是指向摄像头切片缓冲区的指针,cam_slice_width
和cam_slice_height
分别是切片的宽度和高度,max_idx
是最大的切片索引。 - 功能: 将摄像头的图像切片复制到模型输入缓冲区,并根据需要转换图像格式(RGB565到BGR888或RGB888)。
- 内部实现:
- 计算切片在整体图像中的位置。
- 根据需要将一部分图像数据从摄像头缓冲区拷贝到模型输入缓冲区。
- 根据
MODEL_IN_COLOR_BGR
的值选择使用Rgb565StridedToBgr888
或Rgb565StridedToRgb888
函数进行格式转换。
这些代码的核心功能是图像格式转换和数据拷贝。在进行实际应用时,可以通过优化内存访问和数据处理来提高性能。此外,可以考虑提升代码的可读性和可维护性,便于后续调试和扩展。