1. 项目介绍
由于新学期来到学校,发现学校各个教学楼、寝室、实验楼和运动场都安装了自动售卖机。我在自动售卖机购买商品时也进行了观察,在柜门上方有一个摄像头,猜想应该是能够在你拿取商品时摄像头能够识别出物品种类以及数量,之后能够自动结算并将账单推送到你的支付宝上。
图1‑1 智能无人售货柜
刚好看到硬禾学堂网站上开展举办了[max78000第二季]使用max78000实现低功耗的人工智能方案,立刻就来了兴趣,想着能不能基于max78000制作一个简易的类似于自动售卖机的设备,学习一下其中的原理,这也是一个非常好的锻炼自己的机会。
通常人工智能需要极高的计算能力,需要在云端部署计算设备,而MAX78000能够在超低功耗的条件下提供执行神经网络的能力。其上面集成的基于硬件的卷积神经网络 (CNN) 加速器可以在非常低的能量水平下执行 AI 推理(使用预训练的模型)。
2. 项目设计思路
该项目主要功能为识别摄像头采集到的图像信息,并对其中的商品种类进行判断。其工作流程如下:
图2-1 项目设计框图
其中图像采集使用max78000开发板上自带的摄像头,将其采集到的图像传输到max78000芯片。之后使用预训练得到的神经网络模型和参数对输入的图片进行分析识别,并输出分类的结果。最后通过板卡外接的TFT显示屏模块,将摄像头采集到的图片以及神经网络识别到的分类结果和相关信息输出到显示屏上。
此外,识别的结果还将通过串口发送至电脑端,从而不需要显示屏模块也能够得到设备的识别结果,从而使得该项目无需其它外界设备也能够运行和观察结果,提高项目的可复现性。
3. 效果展示
图3-1 识别红枣饴
图3-2 识别薄荷糖
图3-3 识别药片
4. 项目实现流程
4.1 项目框架
该项目主要包括两个部分:神经网络模型的训练及量化、max78000设备端模型部署及编程调试
4.1.1 神经网络模型的训练及量化
模型的训练和量化部分主要参考官方给的github参考仓库:MaximIntegratedAI
其中主要使用的是“ai8x-training”和"ai8x-synthesis"两个代码仓库。其中前一个仓库用于模型的训练,后一个仓库则用于模型的量化和转换成C语言代码文件。
4.1.2 max78000设备端模型部署及编程调试
max78000设备端编程调试部分使用的是官方的Eclipse MaximSDK,该软件易于上手,且配有官方的例程,不需要像vs code一样需要复杂的配置。
4.2 图片素材收集
该基于MAX78000的自动收银台项目需要大量标注的商品图片作为训练样本进行训练,由于在网上没有找到相关较好的数据集,故采用自己拍照片的方式进行收集素材。
首先确定商品的种类,我初步选了如下三种商品进行识别,分别是红枣饴、薄荷糖、药片。分别从不同的角度,对三种商品进行拍摄,从而获得数据集。由于我每张图片中只拍摄了一个商品,所以导致背景要比商品占据的画面还大,所以在拍摄完后还要对图片进行裁剪,把商品本身突出出来。
此外,这里为了预防在识别的时候,当摄像头内没有商品时也会输出识别结果导致识别错误的情况,这里我还拍摄了一组不包含商品的空白图片作为训练素材的一部分,从而保证识别的准确性。
图4.1 待识别物品
本来以为手机拍摄的图片像素会比板卡上的摄像头像素高,会不会影响识别的准确度,但后来发现这种考虑完全是多余的,因为在训练load训练数据的时候,会对数据集的图片进行resize,将训练集的图片像素改为神经网络输入端的像素大小,所以对识别的准确度不会影响太大。
4.3 预训练实现过程及关键代码说明
4.3.1 训练环境搭建
模型预训练我采用的平台是Windoes10下的WSL2,ubuntu版本为20.04。环境的搭建按照github教程上的一步一步来即可。
首先是安装WSL2,由于我之前不知道怎么回事把windos的更新给关了,导致我进入windows store总是报错,不能够在上面安装ubuntu,在尝试数次无果后,本来是打算安装一个ubuntu双系统的,后来想到既然SDK能够离线安装,那ubuntu是否也能够离线安装呢?果然,ubuntu的安装包在微软的官网上是能够下载的,下载完成后双击打开即可进行安装。这里注意要选择安装20.04版本的Ubuntu,它内置的python版本就是3.8,若使用其它版本的ubuntu系统可能还需要修改python的版本,会十分麻烦。
WSL2安装好之后,就是linux下python环境的搭建,具体步骤如下:
- 安装相应的依赖包
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install -y make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev \
libsndfile-dev portaudio19-dev
- 将python3命令改为python
sudo apt-get install python-is-python3
安装虚拟环境
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
- 设置环境变量(add the following to ~/.bashrc:)
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv virtualenv-init -)"
- 安装python3.8.11
pyenv install 3.8.11
- 拉取仓库代码(这里注意一定要加上recursive参数,否则后面训练的时候会出现问题)
git clone --recursive https://github.com/MaximIntegratedAI/ai8x-training.git
git clone --recursive https://github.com/MaximIntegratedAI/ai8x-synthesis.git
- 进入训练文件夹并配置python虚拟环境
cd ai8x-training
pyenv local 3.8.11
python -m venv venv --prompt ai8x-training
source venv/bin/activate
- 之后就是安装python的依赖包,我这里使用了gpu,故使用的命令是
pip3 install -U pip wheel setuptools
pip3 install -r requirements-cu11.txt
注意如果这里使用GPU的话,按照步骤直接走就行,不需要在WSL2内单独安装cuda和环境配置。
- 之后使用以下命令来检测是否安装成功:
python check_cuda.py
如果返回
CUDA acceleration: available in PyTorch
说明安装成功了,并且能够使用GPU进行加速,接下来就能够进行模型的训练啦。
4.3.2 神经网络模型架构
图4-2 神经网络模型架构
4.3.3 模型训练
在模型训练部分我参考github文档中的cats_vs_dogs项目进行训练。
- 构建自己的数据集
数据集需要放到ai8x-training/data/目录下,其具体结构如下所示:
└─GOODS
├─test
│ ├─blank
│ ├─cerealose
│ ├─mint
│ └─pills
├─train
│ ├─blank
│ ├─cerealose
│ ├─mint
│ └─pills
- 编写自己的数据加载器
在制作完数据集之后,还需要在ai8x-training的datasets子目录下添加加载数据集的python文件,这里是参考该目录下的“cats_vs_dogs.py”文件进行修改并将其重命名为“product.py”,主要修改的地方是最后的datasets,如下:
datasets = [
{
'name': 'product',
'input': (3, 64, 64),
'output': ('blank', 'cerealose', 'mint', 'pills'),
'loader': product_get_datasets,
},
]
此外,在读取数据的时候,还要将图片resize为输入大小(64*64),具体代码可参考附件。
- 模型搭建
训练的模型需要保存在“ai8x-training/models”目录下,这里我们新建一个“ai85net-product.py”的文件,并将我们自己的模型代码填入其中。
- 训练模型
首先需要分别在”policies“目录下新建“schedule-product.yaml”和“qat_policy_cd.yaml”来填写我们的训练参数和量化参数,具体如下:
schedule-product.yaml
---
lr_schedulers:
training_lr:
class: MultiStepLR
milestones: [30, 100]
gamma: 0.2
policies:
- lr_scheduler:
instance_name: training_lr
starting_epoch: 0
ending_epoch: 300
frequency: 1
qat_policy_cd.yaml
---
start_epoch: 30
weight_bits: 8
shift_quantile: 1.0
在上述文件都设置好后,就可以开始我们的训练了,训练的具体命令如下:
python train.py --epochs 200 --optimizer Adam --lr 0.001 --wd 0 --deterministic --compress policies/schedule-product.yaml --qat-policy policies/qat_policy_cd.yaml --model ai85net_product --dataset product --confusion --param-hist --pr-curves --embedding --device MAX78000 "$@"
其中的参数在github文档中都有解释,其中值得提的一点是,如果由于特殊原因使得训练中段,我们不用重新头开始训练,而是可以使用参数
--resume-from ./logs/2023.10.24-231528/qat_checkpoint.pth.tar
从上一次中断的地方继续训练,后面的具体参数需要修改为你logs文件夹下对应的文件。
4.3.5 模型量化
量化“ai8x-synthesis”文件夹的虚拟环境配置与训练阶段的配置类似,这里不再赘述。
首先要把训练得到的模型文件复制到“ai8x-synthesis”下的”trained“目录下
cp ../ai8x-training/logs/2023.12.02-211827/qat_checkpoint.pth.tar trained/ai8x-product-qat8.pth.tar
具体命令如下:
#!/bin/sh
python quantize.py trained/ai85-product-qat8.pth.tar trained/ai85-product-qat8-q.pth.tar --device MAX78000 -v "$@"
其中“qat_checkpoint.pth.tar“为训练得到的模型文件。
4.3.6 生成硬件加载模型参数文件
首先我们需要将描述网络的“product.yaml”文件放置到“network”文件夹下,具体编写方法可参考Network Description YAML Files: A Quick Start Guide
arch: ai85net_product
dataset: product
# Define layer parameters in order of the layer sequence
layers:
- out_offset: 0x2000
processors: 0x0000000000000007
operation: conv2d
kernel_size: 3x3
max_pool: 2
pool_stride: 2
pad: 1
activate: ReLU
data_format: HWC
- out_offset: 0x0000
processors: 0x0ffff00000000000
operation: conv2d
kernel_size: 3x3
pad: 1
activate: ReLU
- out_offset: 0x2000
processors: 0x00000000000fffff
output_processors: 0x00000000000fffff
operation: passthrough
write_gap: 1
- in_offset: 0x0000
in_sequences: 1
out_offset: 0x2004
processors: 0x00000000000fffff
operation: conv2d
kernel_size: 3x3
pad: 1
activate: ReLU
write_gap: 1
- in_sequences: [2, 3]
in_offset: 0x2000
out_offset: 0x0000
processors: 0x00000000000fffff
eltwise: add
operation: conv2d
kernel_size: 3x3
pad: 1
activate: ReLU
- out_offset: 0x2000
processors: 0xfffff00000000000
output_processors: 0x000000fffff00000
max_pool: 2
pool_stride: 2
pad: 1
operation: conv2d
kernel_size: 3x3
activate: ReLU
- out_offset: 0x0000
processors: 0x000000fffff00000
output_processors: 0x000000fffff00000
op: passthrough
write_gap: 1
- in_offset: 0x2000
in_sequences: 5
out_offset: 0x0004
processors: 0x000000fffff00000
operation: conv2d
kernel_size: 3x3
pad: 1
activate: ReLU
write_gap: 1
- in_sequences: [6, 7]
in_offset: 0x0000
out_offset: 0x2000
processors: 0x000000fffff00000
eltwise: add
operation: conv2d
kernel_size: 3x3
pad: 1
activate: ReLU
- out_offset: 0x0000
processors: 0x00000fffffffffff
max_pool: 2
pool_stride: 2
pad: 1
operation: conv2d
kernel_size: 3x3
activate: ReLU
- out_offset: 0x2000
processors: 0x0000ffffffffffff
output_processors: 0x0000ffffffffffff
op: passthrough
write_gap: 1
- in_offset: 0x0000
in_sequences: 9
out_offset: 0x2004
processors: 0x0000ffffffffffff
operation: conv2d
kernel_size: 3x3
pad: 1
activate: ReLU
write_gap: 1
- in_sequences: [10, 11]
in_offset: 0x2000
out_offset: 0x0000
processors: 0x0000ffffffffffff
eltwise: add
max_pool: 2
pool_stride: 2
pad: 0
pool_first: false
operation: conv2d
kernel_size: 3x3
activate: ReLU
- out_offset: 0x2000
processors: 0x000000000ffffffff
operation: mlp
flatten: true
output_width: 32
生成硬件加载模型参数文件的具体命令如下:
python ai8xize.py --verbose --test-dir sdk/Examples/MAX78000/CNN --prefix product --checkpoint-file trained/ai85-product-qat8-q.pth.tar --config-file networks/product.yaml --device MAX78000 --compact-data --mexpress --softmax
这里的--prefix参数是保存的文件夹名称,--network就是上面编写的网络描述文件。命令运行结束,得到生成文件如下:
图4-3 生成模板工程文件夹内容
其中对于我们有用的文件就是“cnn.h”、“cnn.c”、“softmax.c”、“weights.h”。另外需要注意的是,我在windows下如果直接使用文件资源管理器在wsl2和windows之间传文件的话,会非常慢,这里可以在wsl2环境下使用cp命令,将windows下的文件拷贝到wsl2指定的文件夹下,这样就不会出现传文件非常慢的情况。
4.4 设备端功能调试及实现
设备端的调试参考示例工程“cats_vs_dogs”
4.4.1 神经网络移植
首先需要将我们训练得到的神经网络替换掉原有的神经网络,只需将我们之前训练得到的样例工程中的“cnn.h”、“cnn.c”、“softmax.c”、“weights.h”覆盖掉原有的文件即可。
此外由于我们的训练的数据输入为(64*64),因此还要对“main.c”文件中的load_input函数进行修改(参考训练得到的样例工程中的"main.c"):
// 3-channel 64x64 data input (12288 bytes total / 4096 bytes per channel):
// HWC 64x64, channels 0 to 2
static const uint32_t input_0[] = SAMPLE_INPUT_0;
void load_input(void)
{
// This function loads the sample data input -- replace with actual data
memcpy32((uint32_t *) 0x50400000, input_0, 4096);
}
4.4.2 图像采集
在代码中调用capture_process_camera函数来读取摄像头采集到的数据并将采集到的数据放入cnn的输入数组input_0中,并将采集到的图像转换为RGB656格式,从而方便显示到TFT显示屏上。
r = data[k];
g = data[k + 1];
b = data[k + 2];
//skip k+3
// change the range from [0,255] to [-128,127] and store in buffer for CNN
input_0[cnt++] = ((b << 16) | (g << 8) | r) ^ 0x00808080;
// convert to RGB656 for display
rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3);
data565[j] = (rgb >> 8) & 0xFF;
data565[j + 1] = rgb & 0xFF;
4.4.3 显示屏调试
我这里显示屏使用的是与示例工程中一致的驱动芯片为ILI9341的显示屏,显示像素为320×240。
- 显示屏硬件连接
图4-4 TFT显示屏硬件接线图
- 显示屏初始化
首先需要注意的是,示例工程默认是不开启显示屏的,需要我们添加
#define ENABLE_TFT
才能够正常开启显示屏显示。
此外,在进行TFT初始化的时候,我们还要加入REST引脚和BLK引脚,才能够成功初始化TFT屏幕,具体代码如下:
mxc_gpio_cfg_t tft_reset_pin = {MXC_GPIO0, MXC_GPIO_PIN_19,MXC_GPIO_FUNC_OUT, MXC_GPIO_PAD_NONE, MXC_GPIO_VSSEL_VDDIOH};
mxc_gpio_cfg_t tft_blk_pin = {MXC_GPIO0, MXC_GPIO_PIN_9,MXC_GPIO_FUNC_OUT, MXC_GPIO_PAD_NONE, MXC_GPIO_VSSEL_VDDIOH};
MXC_TFT_Init(MXC_SPI0, 1, &tft_reset_pin, &tft_blk_pin);
- 显示图片
在图像采集结束后,调用tft_dma_display函数将采集到的图像显示到TFT屏幕上。
tft_dma_display(TFT_X_START, TFT_Y_START + row, w, 1, (uint32_t *)data565);
- 显示识别结果
每按下SW1一次,板卡将采集一次摄像头图像,并将采集到的图像存储到input_0数组中。之后启动CNN对input_0中的数据进行分析推理,得到神经网络的输出ml_softmax。
cnn_start();
cnn_load_input();
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; // SLEEPDEEP=0
while (cnn_time == 0) {
__WFI(); // Wait for CNN interrupt
}
// Unload CNN data
cnn_unload((uint32_t *)ml_data);
cnn_stop();
// Softmax
softmax_q17p14_q15((const q31_t *)ml_data, CNN_NUM_OUTPUTS, ml_softmax);
最终通过转换,得到分类的结果。
printf("Time for CNN: %d us\n\n", cnn_time);
printf("Classification results:\n");
for (i = 0; i < CNN_NUM_OUTPUTS; i++) {
digs = (1000 * ml_softmax[i] + 0x4000) >> 15;
tens = digs % 10;
digs = digs / 10;
result[i] = digs;
printf("[%7d] -> Class %d %8s: %d.%d%%\r\n", ml_data[i], i, classes[i], result[i],tens);
}
printf("\n");
5. 问题及解决方法
起初,在训练平台的搭建上我走了不少弯路,最初我打算是直接在windows下安装puyhon虚拟环境进行训练,但是搭建完成后,在训练KSW20模型时,总是会提示找不到data的路径,最终也没有找到是什么原因,因此放弃了windows平台下的环境搭建,改用WSL2的linux环境。
6. 未来的计划
最初我是希望使用tinyssd实现一个目标检测的系统,能够识别出商品并且能够将其框起来,并且算出每种商品的数量,但由于之前没有接触过神经网络,所以对其具体原理不清楚,学习起来比较困难,故改为商品分类。接下来还要基于这个项目,进一步学习一下神经网络的知识,实现我最初的目标。