https://cn.maixpy.sipeed.com/dev/zh/course/ai/basic/maixpy_hardware_ai_basic.html
MaixPy AI 硬件加速基本知识
1. 模型使用和硬件加速原理
前面我们知道了模型是一个数据机构以及很多参数, 最终以一个文件比如kmodel
格式的文件的形式存在。 而这个模型要能在 MaixPy 的程序里面被使用, 首先需要程序能够理解kmodel
这个文件的格式, 并且支持模型里面的算法,这样才能按照模型的描述将输入经过一些裂计算过程后得到输出。
所以,重点就是支持模型里面的算法,称算子, 理论上,我们可以用软件去实现这些算子, 就可以成功运行模型了, 而执行软件的物理器件是CPU
, 神经网络模型的计算量很大,加上我们输入的是图片,图片本身的数据量就挺庞大, 就算是K210
400MHz
的主频, 也无法满足流畅的推算模型。
所以, 要么升级 CPU
,但是成本太高, 要么做一个专用的硬件, 让这个硬件专门去特定的算法,因为不像 CPU
一样要做通用计算, 所以速度会非常快,在电脑上, 我们通常使用专用的图像加速卡即GPU
来加速图形计算, 在K210
上,这个专门的硬件叫做KPU
(kendryte proccess unit),第一个但是是公司名, 其实和其它芯片的NPU
做的事情是一样的。
在 MaixPy 里面,已经集成了推导模型的代码,同时使用了KPU
进行计算加速,使用时无需编写很多代码,只需要调用几个函数即可快速运行模型
2. 关于 KPU
虽然 KPU 是能够加速模型运算了, 但是由于成本、时间、功耗、体积、发热、应用领域定位等各种因素,它的能力并不能像专业领域的强力NPU
一样,包含了每一种算子,它只能处理一部分。
KPU 实现了卷积,批归一化,激活,池化 这4钟基础操作的硬件加速, 但是它们不能分开单独使用,是一体的加速模块。
所以, 在 KPU 上面推理模型, 以下要求(如果不需要训练和设计模型,暂时不需要仔细了解):
- 内存限制
K210 有 6MB 通用 RAM 和 2MB KPU 专用 RAM。模型的输入和输出特征图存储在 2MB KPU RAM 中。权重和其他参数存储在 6MB 通用 RAM 中。
- 哪些算子可以被 KPU 完全加速?
下面的约束需要全部满足。
- 特征图尺寸:输入特征图小于等于 320x240(WxH) 同时输出特征图大于等于 4x4(WxH),通道数在 1 到 1024。
- Same 对称 paddings (TensorFlow 在 stride=2 同时尺寸为偶数时使用非对称 paddings)。
- 普通 Conv2D 和 DepthwiseConv2D,卷积核为 1x1 或 3x3,stride 为 1 或 2。
- MaxPool(2x2 或 4x4) 和 AveragePool(2x2 或 4x4)。
-
任意逐元素激活函数 (ReLU, ReLU6, LeakyRelu, Sigmoid...), KPU 不支持 PReLU。
-
哪些算子可以被 KPU 部分加速?
-
非对称 paddings 或 valid paddings 卷积, nncase 会在其前后添加必要的 Pad 和 Crop。
- 普通 Conv2D 和 DepthwiseConv2D,卷积核为 1x1 或 3x3,但 stride 不是 1 或 2. nncase 会把它分解为 KPUConv2D 和一个 StridedSlice (可能还需要 Pad)。
- MatMul, nncase 会把它替换为一个 Pad(到 4x4)+ KPUConv2D(1x1 卷积和) + Crop(到 1x1)。
- TransposeConv2D, nncase 会把它替换为一个 SpaceToBatch + KPUConv2D + BatchToSpace。
说明来自这里
3. 模型转换
前面说到, 模型其实就是一组结构和参数数据,不同的软件只能识别特定格式的模型, KPU 只认.kmodel
格式的模型, 一般用电脑训练的模型则不是, 比如tensorflow
是.h5
格式或者.tflite
格式, 要给KPU
使用, 必须经过变成kmodel
, 使用nncase这个工具来达到模型转换的目的 如果你需要转换模型, 具体使用方法看这个仓库里面的介绍
4. kmodel V3 模型 和 V4 模型
由于代码更新, 在过程中产生了两个大版本,V3
和 V4
, 其中V3
模型是指用 nncase v0.1.0 RC5 转换出来的模型; V4
模型指用nncase v0.2.0转换出来的模型
两者有一定的不同,所以现在两者公存, V3
代码量更少,占用内存小,效率也高,但是支持的算子少; V4
支持的算子更多,但是都是软件实现的,没有硬件加速,内存使用更多,所以各有所长。 MaixPy 的固件也可以选择是否支持 V4
。
5. MaixPy 中使用模型 kmodel
- 加载 SD 卡 (TF 卡)中的模型
将模型放到 SD 卡, 然后加载
import KPU as kpu
m = kpu.load("/sd/test.kmodel")
- 加载 Flash 中的模型
将模型下载到 Flash, 然后加载
import KPU as kpu
model_addr_in_flash = 0x300000
m = kpu.load(model_addr_in_flash)
此处的 model_addr_in_flash
为模型在 Flash 中的偏移地址,模型可以通过 kflash.py 或者 kflash_gui 烧录到 Flash 对应的地址中
- 准备输入
一般情况下,我们会使用图像作为输入:
- 直接使用摄像头采集的数据作为输入:
这里img = sensor.snapshot()
img
就可以直接作为输入, 这里需要注意:snapshot
函数采集到图片后,会将图片数据放到两个地方 (1)RGB565
内存块, 图像以RGB565
的形式存放在一块内存中,方便图像处理的函数使用,注意在内存中的排序是[像素1 RGB, 像素2 RGB...]
(2)RGB888
内存块, 图像以R8G8B8
的形式存放在另一块内存中,注意在内存中的排序是[所有像素 R, 所有像素 G, 所有像素 B]
, 我们也称之为AI
内存
其中,实际上作为 KPU 输入的数据是RGB888
区域, 这个在前面的文档 MaixPy 图像及常用操作 章节中有仔细讲解过
- 从文件读取,或者修改过的摄像头图像
直接从摄像头采集的图像会自动填充RGB888
区域,但是我们使用图像处理函数比如image.resize()
时,只会修改RGB565
,没有修改RGB888
,因为同时修改两处内存需要耗费大量时间,而 KPU
的输入又是RGB888
内存, 所以在需要进行 KPU
运算时, 需要同步(刷新)一下RGB888
内存块, 使用img.pix_to_ai()
来进行同步,否则修改对 KPU
没有生效。 比如:
img = sensor.snapshot()
img = img.resize(240, 240)
img.pix_to_ai()
img = image.Imag("/sd/test.jpg")
img.pix_to_ai()
- 前向运行模型
前向运行模型,也就是按照 输入到输出的方向走一边模型计算, 通过输入得出输出的值:
feature_map = kpu.forward(m, img)
这里得到了feature_map
, 是一个特征图, 比如我们前面将的小球
和玩具
的分类,输出特征图是两个节点, 每个节点表示了是对应物体的概率,我们将特征图转换为list
对象
p_list = feature_map[:]
print(p_list)
就可以得到类似 [0.9, 0.1]
这样的结果了
6. KPU使用过程中的常见问题
6.1. KPU能够加载多大的模型?
C 语言代码运行模型: 当k210运行 c 代码时,能够加载 < 6MB左右的模型, 具体看 C 代码内容。 MaixPy 运行模型:
* 当运行 MaixPy(minimum版本)时,能够加载4MB左右的模型。 如果不使用摄像头和 LCD, 最大可以加载 5MiB 左右的模型(因为摄像头和 LCD 的缓冲区占用了很多内存,但实际应用也没多大意义了)
* 当运行 MaixPy(完整版)时,能够加载 2MiB 左右的模型
* 另外也支持实时从`Flash`加载模型, 理论上只要单层使用内存不超过 2MiB, 整体模型可以无限大,只不过要牺牲一点运算速度。 使用方法看[这里](https://github.com/sipeed/MaixPy_scripts/tree/master/machine_vision/load_big_model)。 如果对原理和实现感兴趣,可以看[这里](https://neucrack.com/p/313)
6.2. 报错"memory overflow"怎么办?
出现这个问题,根据前面讲到过的系统内存管理可知,一般有两个可能性:
- 报错的地方跟系统堆无关系, 可能是
GC
内存不够导致,可是适当增加GC
的总内存大小 - 由于模型过大引起的。可以依次尝试如下解决方案:
- 更换maixpy mini版本固件
- 进行模型剪枝优化
- 使用
kpu.load_flash
接口运行时实时加载模型,只是执行效率降低一点 - 如果内存不足,而且
kpu.load_flash
性能无法满足, 那么你可能需要使用 C SDK进行开发。
6.3. 报错"load error,only support kmodel v3/v4"怎么办?
出现这个问题可以尝试如下解决方案:
- 如果为加载 Flash 中的模型,请确保
flash offset
填写正确,并保证和 maixpy 固件的地址没有冲突(模型在 Flash 中的地址太靠前,然后往 Flash 烧录入固件时, 固件大小超过了模型所在的起始地址, 导致模型被破坏) - 如果是采用
nncase 0.2.0
进行转换的kmodel V4
,请尝试采用nncase 0.1.0
进行转换,从而生成kmodel V3
6.4. 我想实现不同模型的选择加载(例如按下按钮运行目标分类,再次按下按钮则运行目标检测),应该怎么写程序?
因为内部RAM有限,所以当需要切换不同模型进行kpu.load(address)
前,请先执行kpu.deinit(k210model)
释放之前模型占用的内存,然后再加载新的模型。 也就是分时复用内存
深度神经网络(DNN)基础知识
这里介绍使用 MaixPy AI 相关功能需要了解的知识,让你能够理解后面的内容, 不在本篇中深入介绍。
1. 如何解决一个问题--引出机器解决问题
一个问题, 通常分为 输入 和 输出(结果)
现在提问,假如数据点规律不变, 输入一个 x 坐标 20, y 的值是多少? 按照大家的知识,都知道这是一个一元一次方程(y = kx + b
能解决的, 带入两个点的值,算出方程为y = 3x + 10
, 那么当 x=20
, y
的值为70
, 于是输入是20
, 输出是70
。
这里就是 输入(20
) + 算法(一元一次方程) = 输出(70
), 这就是我们在解决一个问题时的基本方法, 所以关键就是找到这个符合这条线段上数据点规律的一个算法。
人类很强大,会从这些数据中归纳总结学习,最终得到了这个算法(方程),然后其他的人直接使用这个算法就可以快速用于解决同类问题,那么,有没有一种方法, 让机器自动去寻找这个算法呢?
2. 如何让机器总结出算法
要让机器自动总结出算法,即机器学习(ML,Machine Learning), 我们先看看,人类是如何得到这个算法(方程)的。
- 步骤1: 首先,有大量数据点,然后人类根据这些数据点发现了直线都符合
y = kx + b
这个适应所有直线的算法, 但是发现,这里面有两个未知数k
和b
, 这就是适应任何直线的参数 - 步骤2: 然后具体的是什么样的直线,因为方程有两个未知数,即参数,将实际的两个数据点带入这个方程,得到了
k = 3
和b = 10
- 步骤3: 然后我们用在步骤2中没有用到的在线上的数据点,去试试这个算法(方程)是否正确,最终发现都验证正确
- 步骤4: 然后要通过
x
的值知道其它的点的y
的值,只需要代入y = 3x + 10
即可
那么,机器学习是不是也可以利用这个步骤来做呢?
-
我们认为地设计一个算法结构, 加入我们碰巧直接设计成了
y = kx + b
, 我们给具体的直线留下了两个参数,我们暂且称呼这个结构叫 模型结构,因为有未知参数,我们称之为未训练的模型结构。其中x
称为输入,y
称为输出 -
现在,我们将我们这条直线的的几个点代入到这个方程, 我们称这个过程为 训练,得到
y = 3x + 10
这个算法, 已经没有未知参数了, 我们现在称它为模型 或者 训练好的模型,其中k b
是模型内的参数,y = kx + b
是这个模型的结构。 而带入训练的数据点,就叫做训练数据,它们的统称就叫训练数据集 -
然后,我们使用几个在 训练 过程中没有用到的在线段上的数据点作为输入,代入这个模型进行运算,得到结果,比如
x = 10
, 得到y = 40
, 然后对比输出值是否与预期相符,这里我们发现x = 10, y = 40
确实是在图中这条直线上的, 并且训练时没有使用这个点,说明我们得到的模型在此次核验中通过,这个过程叫 验证,x = 10, y = 40
这个数据叫验证数据。 如果我们用多组数据去验证这个模型, 这些数据的统称就叫验证数据集 -
现在, 我们获得了一个模型,并且用验证数据集对这个模型进行了验证,貌似也是很准确了,那我们就可以假设这个模型基本满足了我们以后有一个
x
, 要求着图中线上任意一点的y
值,都可以输入x
给出这条直线上对应点的y
坐标。 这个过程我们其实是在使用模型了,这个过程称之为推理
其实这就算是机器学习了, 我们人类需要的事就是设计y = kx + b
这个结构,以及给出训练数据集和验证数据集,经过训练和验证得到一个我们认为可用的模型,然后使用输入 + 模型
就可以得到认为的正确输出(结果)
了。
3. 什么是深度神经网络?
深度神经网络(DNN)是机器学习(ML)领域中的一种技术。
前面说了一个比较简单的例子, 根据一条直线数据来预测直线上的任何一个点, y = kx + b
这个结构是人为设计的, 很简单,当用于复杂的数据,发现它就不适用了,比如“这张图片里面是球还是玩具”
前面为了模型能够存下一条直线的信息, 用了结构y = kx + b
,直线的特征都存在模型里面了。 现在用来存一张图的特征,光是y = kx + b
这个线性结构, 以及k 和 b
两个参数显然无法满足了, 需要设计一个更好的结构, 这时 神经网络 就出现了, 一种网状结构,能更好地记住图片的特征信息, 而这个网状结构又是多层的,也就是有深度的,所以称之为深度神经网络(DNN, deep neural network), 所以说 DNN 是一种网络结构,是为了实现机器学习的一种手段。 每一层由多个节点组成, 如下图, 一个 DNN 包含了 输入层, 隐藏层, 输出层, 这里隐藏层由三层组成(A[1], A[2], A[3]
层),但是统称隐藏层:
输入层: 图中就是一个深度神经网络结构, x
是输入, 比如x
这里可以是图片, 输入有多个节点,每个节点可以是一个像素点值, 这里输入层画了 7 个节点, 加入我们有一张图片是 10 x 10
的分辨率,则输入层共需要 100
个节点。 这里输入层是一个一维结构,实际情况可能有多维结构, 比如输入如果是一张灰度图片,分辨率3x3
,这其实是一个二维结构,即两行两列的矩阵(关于矩阵的概念请自行学习,或者暂且理解成二维数组),比如:
[[109 138 110]
[220 37 166]
[32 243 67]
]
每个像素点的值取值范围∈[0, 255],然后我们将其平铺后变成共 9 个数据的一维数组给输入层
[109 138 110 220 37 166 32 243 67]
另外, 一般也会将输入层的值归一化到范围
[0, 1]
如果是一张彩色图片,那就是三维,即高、宽、颜色通道
,颜色通道比如RGB
三个颜色通道,即,输入有形状(包含了维度和每个维度的数据数量),比如上面的一维输入形状为(9)
,其它图像通常以(高,宽,通道数)
来表示形状,比如(10, 10, 3)
表示分辨率10 x 10
, 并且有三个颜色通道, 比如RGB
。
这里为了入门好理解,原理只介绍一维的情况
输出层: y
是输出,这里输出有两个值,你可以理解成就是 MaixPy 的两个浮点值的 list
[Y1, Y2]
, Y1
是是小球的概率
,值∈[0, 1], Y2
是是玩具的概率
。 所以最终我们使用这个模型, 就是给它一张图片, 机器按照这个模型规定的结构和算法进行计算后得到一个 list
, 我们根据这个输出的值就知道图中是什么东西了。
隐藏层: 连接输入层和输出层的隐藏层,以及中间的连接,负责了将输入数据推算成合理的输出值。
4. 中间休息,总结
到现在为止, 你知道, 模型是什么:就是一组数据结构,保存了一个网络的形状,以及里面的参数, 通常,这个模型的数据可以被保存成文件,比如.h5 .tflite .kmodel
等文件,都是用来阐述这个模型的形状结构和参数,只不过是不同软件使用。 人们只需要设计模型结构以及参数,用来解决一类问题,比如常见的物体分类, 比如就是上面说的区分一张图里面是小球还是玩具。 这个模型里面有很多参数,具体在需要识别物体的时候,使用已知分类的数据集让机器自动训练得出一套合适的模型参数。 然后我们就可以输入数据,让通过模型推理出来输入的数据时什么类别了。
所以, 如果我们不需要训练模型,直接使用别人训练好的模型,只需要:
- 确认需求,找到现成的模型,因为模型已经是训练好的了,输入和输出的形状的含义都已经定了
- 确认模型的输入形状,比如模型输入分辨率
10x10
的彩图,则使用时需要将符合要求的图片传个输入层 - 确认输出层的含义,比如前面说的识别小球和玩具,最后输出是分别代表是该物体的概率的 list, 比如
[0.9, 0.1]
, 第一个值代表是小球的概率,那我们就知道这张图里有 90% 的概率是小球, 只有 10% 的概率是玩具 - 将模型放到推理程序进行运行。 具体用什么程序先不着急,会在下一章介绍
到这里,应该大致上明白了以下东西:
- 什么是机器学习
- 什么是深度神经网络(简单概念)
- 模型是什么
- 什么是输入层,输出层,在上面举例的分类应用中分别表示什么含义,层形状是什么样的
- 到此为止,我可能还不知道什么是模型训练
- 如果我需要一个模型,我知道如何确认需求
所以,如果你只希望能够使用模型,不需要训练,到此即可, 也不需要知道模型有些什么具体的东西,你就把它当成一个黑盒工具箱使用即可。 如果想要更深的了解,请继续看下面的内容。
5. 继续:深度神经网络(续
既然设计了多层设计,那我们继续深入:
数据流 , 权重, 偏置: 在模型进行推理时,数据从输入层流动到输出层,就是这些网状箭头的方向(第三节网状图),每个箭头前一层到后一层的计算可以用一个熟悉的公式:y = wx + b
, 称w
为权重(weight), b
为偏置(bias), 注意是每个箭头都有一个单独的w, b
, 也就是说后一层节点的值等于前一层节点经过这个公式计算过后的值, 后一层的节点有多个前一层节点指向,那就等于所有前一层节点的值经过这个公式计算后的值的和。 就这样经历了无数次运算后,结果终于在输出层以一个值的形式出现了,整个推理也就完成了
激活函数:
上面的模型虽然可以通过输入得到结果,但是会发现,所有层计算都是线性函数,那么不管有多少层,整体其实还是一个线性函数,即y0 = w1x + b1
+ y = w2y0 + b2
==> y = w2(w1x + b1) + b2
==> y = w2w1x + w2b1 + b2
, 其实还是一个线性函数,那么多层的意义就没有了,于是我们需要在中间加入非线性函数,让网络内部更加复杂一点, 于是就在每个节点上做手脚, 在每个节点输出数据前,先对其用一个非线性函数运算,比如sigmod
或者relu
函数,别听到名字害怕,其实很简单,看下图, 总之就是 x 和 y 不成线性关系:
即到现在为止, 除了输入层,所有节点输出的值都需要经过Sigmod(∑(Wn * x + Bn))
, 输出一个浮点数值
softmax:
输出层在最后输出的时候,因为前面的运算,值的范围不是很统一,虽然我们可以同过比大小,值最大的即认为是答案,但是为了统一而且可以直观地知道每个类别的可能性(另外也为了训练的准确性,这里不讲),正如前面讲到,我们最后输出的一个类别的概率,取值范围∈[0, 1], 且所有输出的值和为1
,所以在输出层后面对输出层的所有值进行处理,公式为
到此,从输入到输出的推理过程就结束了
6. 深度神经网络训练
前面简单介绍了深度神经网络的结构组成, 以及从输入层到输出层的正向过程,在我们使用模型时,就是这个正向过程。 那么,模型定好了,里面的参数(比如w,b
)都是随机的值,怎么让它自动训练得到模型中参数的值呢? 在前面我们讲到, 使用一些我们已知结果的数据输入,来得到参数,同样地,这里我们也输入已知结果的数据,得到第一次的输出结果
判定输出正确性(accuracy)(或者说误差/loss) 和 损失函数:
在输出层得出结果,比如得到了[0.6, 0.4]
代表是小球的概率0.9
, 是玩具的概率0.1
, 但是因为是已知答案的数据, 实际正确答案是[1.0, 0.0]
, 这明显不符合要求。 所以我们得出正确答案和推算的答案的误差为: [0.4, -0.4]
, 但是发现一个问题就是这个误差值的范围不太好看,要是误差的取值范围∈[0, ∞]
就好了。 在高中数学中有个函数y = log10(x)
, 坐标图如下: 发现x
取值∈[0, 1]
时, -y
的取值刚好∈[0, ∞]
, 而我们的输出结果也刚好∈[0, 1]
! 所以,我们直接这样计算误差: error = -log10(输出)
, 也就是输出越接近1
,误差就越接近 0
,这种方法称之为交叉熵损失(CEE, Cross Entropy Error)
, 除了这种方法还有其它的比如均方误差(MSE,Mean Squared Error)等
至此,我们知道了现在结果和实际结果的误差
误差的反向传播 和 参数优化(权重更新): 因为模型的参数还不符合我们的预期, 那我们需要对参数进行修正,我们使用反向传播的方式。 前面我们得出了误差, 因为参数不够正确, 我们用这个误差去修改模型中的参数,来达到微调模型内参数的效果。 就好像你在开一个水龙头, 水打了(即误差大了),就把开关拧紧一点,小了就拧松一点,对其做调整。 就像我们正向推算一样,这次换成了反向,从后往前,可以得到在每个节点处的误差值,然后再根据一定的学习率去更新模型内参数。这里暂时就不仔细展开讲了。
总之,经过一轮反向的调整参数之后,得到了新的模型
衡量模型好坏:训练集误差和验证集误差:
我们使用训练数据集里面的数据反复去进行正向推理得出误差,然后反向调整这个过程,在使用完训练数据集后,可能会得到误差比较小,但是这只能说明这个模型对这批数据来说比较准确,换一些新的数据可能就不准确了,所以我们要用一些训练集里没有的数据去验证模型的效果: 我们使用 验证数据集 去正向推算,得到误差,因为验证数据集没有参与训练,也就是说现在模型的参数和验证数据集没有任何关系,我们用这个得到的误差来恒定这个模型的好坏,误差越小则认为效果越好
多次迭代:
如果将所有数据集训练完了,发现误差依然很大,那么可以用多次训练的方法来继续训练,即多次迭代,每次迭代完成后都用 验证数据集 去验证效果如何, 如果训练集的误差和验证集的误差都足够小,我们就可以暂且认为模型已经有不错的效果了。
测试集: 这时,我们就可以用又一批新的数据去测试我们的模型效果如何,因为这是全新的数据,没有参与到训练也没参与到验证(即确定什么时候停止训练),理论上更有公信力。如果测试误差较小,那么训练就算成功了
优化训练: 如果最终效果不太好, 有很多地方可以调整, 比如
- 训练迭代的次数,并不是越多越好,过多的在一批数据集上训练可能导致模型只对这批数据有效,泛化能力不够, 也就是过度拟合
- 每次训练的学习率也可以调整
- 检查数据集,是否有一些影响分类的数据存在
- 优化网络结构,不管是输入输出还是内部结构和参数,根据不同的数据和任务可以有更优的设计,也叫特征工程
自学习分类器(self learning classifier)
无需单独训练, 直接在开发板上对物体特征进行学习,然后直接使用
1. 使用方法
- 在这里 下载版本 >= v0.5.0-33 的固件
- 下载 kmodel
- 使用 kflash_gui 下载固件和模型
- 运行 示例脚本
然后运行启动后开始学习物体
- 按开发板上的
boot 按钮
来捕获 3 个类别手机
,小车
,键盘
, 每个类别只需要捕获一次 - 然后捕获 15 张图, 对顺序没有要求, 比如捕获 5 张
手机
, 5 张小车
, 5 张键盘
的图片 - 然后它会自动学习这 15 张图的特征
- 最后识别到的图像类别会展示在左上角
2. 保存/加载学习好的特征
- 使用
classifier.save(path)
来保存学习好的特征到path
文件 - 使用
KPU.classifier.load()
来加载特征, 参考 self_learning_classifier_load.py 文件
模型 0x300000
固件 128
import KPU as kpu
import sensor
import lcd
import gc
############### config #################
#装载分类文件的路径
saved_path = "3_classes.classifier"
THRESHOLD = 11
class_names = ['class1', 'class2', 'class3']
########################################
def draw_string(img, x, y, text, color, scale, bg=None ):
if bg:
img.draw_rectangle(x-2,y-2, len(text)*8*scale+4 , 16*scale, fill=True, color=bg)
img = img.draw_string(x, y, text, color=color,scale=scale)
return img
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
try:
del model
except Exception:
pass
try:
del classifier
except Exception:
pass
gc.collect()
#"/sd/sipeed_learn_model.smodel"0x300000
model = kpu.load("/sd/sipeed_learn_model.smodel")
classifier, class_num, sample_num = kpu.classifier.load(model, saved_path)
while 1:
img = sensor.snapshot()
img.rotation_corr(x_rotation=180)#x_rotation=10,
img.rotation_corr(y_rotation=180)#x_rotation=10,
res_index = -1
try:
res_index, min_dist = classifier.predict(img)
print("{:.2f}".format(min_dist))
except Exception as e:
print("predict err:", e)
if res_index >= 0 and min_dist < THRESHOLD :
print("predict result:", class_names[res_index])
img = draw_string(img, 2, 2, class_names[res_index], color=lcd.WHITE,scale=2, bg=lcd.RED)
else:
print("unknown, maybe:", class_names[res_index])
# img = draw_string(img, 2, 2, 'maybe {}'.format(class_names[res_index]), color=lcd.WHITE,scale=2, bg=lcd.RED)
lcd.display(img)
上述程序烧录了128的openmv_kmodel固件
修改了屏幕显示颠倒的问题 原本烧录的是最小固件不支持屏幕翻转指令 更改了固件
模型存放在内存卡中
使用 upyloader 将模型传输到内存卡的根目录下
import KPU as kpu
import sensor
import lcd
from Maix import GPIO
from fpioa_manager import fm
from board import board_info
import time
import gc
from Maix import GPIO
from Maix import FPIOA
from fpioa_manager import fm
from board import board_info
############### config #################
class_num = 3 #分类
sample_num = 15 #训练次数
THRESHOLD = 11 #信任值
class_names = ['class1', 'class2', 'class3']
board_cube = 0
########################################
def draw_string(img, x, y, text, color, scale, bg=None ):
if bg:
img.draw_rectangle(x-2,y-2, len(text)*8*scale+4 , 16*scale, fill=True, color=bg)
img = img.draw_string(x, y, text, color=color,scale=scale)
return img
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
if board_cube == 1:
sensor.set_vflip(True)
sensor.set_hmirror(True)
lcd.init(type=2)
lcd.rotation(2)
else:
lcd.init()
fpioa = FPIOA()
fpioa = FPIOA()
'''
fpioa.set_function(board_info.BOOT_KEY, fm.fpioa.GPIOHS16)
pin = fpioa.get_Pin_num(fm.fpioa.GPIOHS0)
'''
#fm.register(board_info.BOOT_KEY, fm.fpioa.GPIO16, force=True)
#fm.register(board_info.BOOT_KEY, fm.fpioa.GPIOHS16)
#key = GPIO(GPIO.GPIO16, GPIO.IN)
key = GPIO(GPIO.GPIOHS0, GPIO.PULL_UP)
try:
del model
except Exception:
pass
try:
del classifier
except Exception:
pass
gc.collect()
model = kpu.load(0x300000)
classifier = kpu.classifier(model, class_num, sample_num)
cap_num = 0
train_status = 0
last_cap_time = 0
last_btn_status = 1
while 1:
img = sensor.snapshot()#从摄像头取一帧图像数据,返回值是一张图像的对象
img.rotation_corr(x_rotation=180,y_rotation=180)#x_rotation=10,
#img.rotation_corr()#x_rotation=10,
if board_cube:
# img = img.rotation_corr(x_rotation=0)#img.rotation_corr旋转
# img.rotation_corr(y_rotation=180)#x_rotation=10,
#img=img.rotation_corr([x_rotation=0.0[, y_rotation=0.0[, z_rotation=0.0[, x_translation=0.0[, y_translation=0.0[, zoom=1.0]]]]]])
img.pix_to_ai()
'''
需要执行img.pix_to_ai()来将RGB565的图像手动更新到RGB888的区域,然后才可以调用kpu相关的函数进行模型推理
'''
# capture img
if train_status == 0:
if key.value() == 0:
time.sleep_ms(30)
if key.value() == 0 and (last_btn_status == 1) and (time.ticks_ms() - last_cap_time > 500):
last_btn_status = 0
last_cap_time = time.ticks_ms()
if cap_num < class_num:
index = classifier.add_class_img(img)
cap_num += 1
print("add class img:", index)
elif cap_num < class_num + sample_num:
index = classifier.add_sample_img(img)
cap_num += 1
print("add sample img:", index)
else:
img = draw_string(img, 2, 200, "release boot key please", color=lcd.WHITE,scale=1, bg=lcd.RED)
else:
time.sleep_ms(30)
if key.value() == 1 and (last_btn_status == 0):
last_btn_status = 1
if cap_num < class_num:
img = draw_string(img, 0, 200, "press boot key to cap "+class_names[cap_num], color=lcd.WHITE,scale=1, bg=lcd.RED)
elif cap_num < class_num + sample_num:
img = draw_string(img, 0, 200, "boot key to cap sample{}".format(cap_num-class_num), color=lcd.WHITE,scale=1, bg=lcd.RED)
# train and predict
if train_status == 0:
if cap_num >= class_num + sample_num:
print("start train")
img = draw_string(img, 30, 100, "training...", color=lcd.WHITE,scale=2, bg=lcd.RED)
lcd.display(img)
classifier.train()
print("train end")
train_status = 1
else:
res_index = -1
try:
res_index, min_dist = classifier.predict(img)
print("{:.2f}".format(min_dist))
except Exception as e:
print("predict err:", e)
if res_index >= 0 and min_dist < THRESHOLD :
print("predict result:", class_names[res_index])
img = draw_string(img, 2, 2, class_names[res_index], color=lcd.WHITE,scale=2, bg=lcd.RED)
else:
print("unknown, maybe:", class_names[res_index])
img = draw_string(img, 2, 2, 'maybe {}'.format(class_names[res_index]), color=lcd.WHITE,scale=2, bg=lcd.RED)
lcd.display(img)
# You can save trained data to file system by:
classifier.save("3_classes.classifier")
# Then load :
# model = kpu.load(0x300000)
# classifier = kpu.classifier.load(model, "3_class.classifier")
代码 装载模型
模型分类 self_learning_classifier.py 文件
全部资料在百度网盘里下载
链接:https://pan.baidu.com/s/15RbzG8qLRDYHBxPTFsqSZw
提取码:jxgh
复制这段内容后打开百度网盘手机App,操作更方便哦
K210发热怒跑目标分类