- 作者:
- 分类:知识&开发->AI->k210
- 阅读:30520
- 点赞:692
- 版权:CC BY-SA 4.0
- 创建:2020-10-10
- 更新:2021-11-06
原文链接(持续更新):https://neucrack.com/p/325
MaixPy
为边沿计算打造的一款超级容易上手的SDK(软件开发套件)
,目前支持带有硬件AI
加速的K210作为硬件载体(1TOPS
算力,双核RISC-V 64位400MHz+8MB内存
核心, 开发板¥100
就能买到)
本篇是MaixPy文档的入门精简版教程,AI视觉向,没有其它干扰, 如果你没有接触过 K210 和 MaixPy, 想要用它来做机器视觉,看我!!
有什么疑惑
或者要查询API
可以再看完全版的MaixPy文档
各种链接
- 官方文档: maixpy.sipeed.com
- 例程: github.com/sipeed/MaixPy_scripts
- 硬件资料(原理图等): dl.sipeed.com/MAIX/HDK
- 固件源码: github.com/sipeed/MaixPy
购买开发板
去Sipeed 淘宝店选一款,比如这款(Bit
),
注意开发需要一根Type-C
线,现在安卓手机都是用这个线,尽量买质量好的,不然压降太低导致无法识别或者不稳定
另外别忘了购买屏幕和摄像头!
开发环境安装
开发环境搭建如果看文字遇到问题,也可以看视频教程
- windows需要先安装串口驱动:参考文档里的安装方法
- 下载 kflash_gui,或者这里下载, 解压
kflash_gui_v*_windows.7z
(解压需要7z),双击里面的kflash_gui.exe
即可运行 - 下载终端工具putty;
linux
请下载minicom
,可以看看文档 - 为了方便发送文件到开发板,下载MaixPy IDE
获取最新固件并烧录到开发板
下载最新的固件, 默认编译的有很多个固件,主要是因为为了满足内存要求同时满足功能的妥协,也可以在在线编译页面自定义功能并获取固件, 其中需要注意的是
minimum
代表不兼容openmv
的函数,with_ide_support
则支持IDE
,kmodelV4
则代表支持kmodel v4
版本.
这里下这个minimum_with_ide_support
版本打开
flash_gui
, 选择前面下载的这个固件,选择串口,如果有两个串口先选第一个看能不能下载,不能再换另一个如果下载出错了,尝试把开发板设置从自动变成对应的开发板型号, 降低波特率到115200尝试
使用终端进入REPL交互模式
打开设备管理器,看串口号,比如这里是
COM7
和COM8
打开后按开发板的
reset
按钮,可以看到终端打印了启动信息接下来就和PC上使用
python
一样了,如果你还不会python
,那么请先花点时间学一下python
基础语法,如果你有一门语言基础,这很简单,如果有学过面向对象语言比如C++/Java/Js
等,也可以看我另外一篇简单的python总结有一点和
PC
的python
不同,十分方便的功能是按ctrl+E
,然后可以粘贴整块(多行)代码,然后按ctrl+D
代码就会运行了
MaixPy IDE 运行代码
IDE 是从开源的 OpenMV 的 IDE 适配过来,功能一样
- 打开 IDE, 上面任务栏
工具->选择开发板
选择对应的开发板型号 - 点击左下角连接,如果连接不上,检查开发板型号选择是否正确,以及串口号是否选错,以及是否有软件占用了串口,比如前面的
putty
是否已经关闭 - 然后点击运行即可将代码发送到开发板运行
基本的摄像头图像实时显示
执行摄像头测试代码, 可以使用前面说的终端或者 IDE 的方式执行
import sensor, lcd
sensor.reset() # 初始化摄像头
sensor.set_pixformat(sensor.RGB565) # 设置图像格式为RGB565
sensor.set_framesize(sensor.QVGA) # 设置图像分辨率为 320x240
sensor.set_hmirror(False) # 设置左右镜像
sensor.set_vflip(False) # 设置上下翻转
sensor.run(1) # 摄像头开始运行,也可以不调用,参数设置好后会自动运行
sensor.skip_frames() # 跳过一些帧数,因为摄像头启动时图像没稳定
lcd.init(type=1, freq=15000000) # 初始化显示屏,如果颜色反色了,设置type=2
lcd.rotation(0) # 设置 LCD 显示旋转, 取值范围:[0,3]
while(True):
img = sensor.snapshot() # 从摄像头取一张图片
lcd.display(img) # 把图片显示到 LCD
然后就可以在屏幕看到摄像头的内容了,如果提示初始化失败,如果摄像头确认时支持的,那可能要检查硬件连接,或者摄像头损坏
存数据到 flash 或者 SD 卡
已经支持了文件系统,所以不需要我们去担心读写 flash 或者 SD 卡了, 直接操作文件系统,就是在操作 flash 或者 SD 卡,如下:
import os
# 列出 flash 内容
os.listdir("/flash")
# 列出 SD 卡内容, 注意SD卡要格式化成FAT格式,且要有MBR分区
os.listdir("/sd")
# 写入文件
with open("/flash/test.txt", "w") as f:
f.write("test words: hello!")
# 读取文件
with open("/flash/test.txt", "r") as f:
print(f.read())
另外,也支持直接读取flash
内容,这样就可以用 kflash_gui 下载内容到 flash,然后调用API
读取了,在某些场景更灵活,读取方法:
from Maix import utils
data = utils.flash_read(0x300000, 16)
print(data)
# b'\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00'
# 可以看到把烧录进去的模型内容给读出来了
什么是模型
如果你要了解更多的神经网络的知识,请自行学习
这里只为了让你更好的理解接下来的内容,可以简单理解为:
- 传统算法中,
输入 + 人为研究出来的算法 ==> 结果
- 神经网络学习中,分成了两步:
- 第一步,训练:
输入 + 结果 + 设计的网络结构(和参数) ==训练==> 网络结构的参数值
, 而这一步中的网络结构和参数和训练出来的值也就是所谓的模型
,在磁盘上的表现就是一个文件,保存了一堆结构和参数数据,而不同的软件他们的描述不一样,也就有了不同的格式,比如.h5
.tflite
.pb
.kmodel
,理论上都可以互相转换 - 第二步,推理:
输入 + 训练好的模 ==> 结果
。
- 第一步,训练:
其中, 神经网络学习的训练这一步一般都在算力强力的计算机上进行;第二步也就是 MaixPy 做的事情,将训练好的模型加载后,输入数据(比如摄像头的图像),软件配合硬件根据模型对输入数据进行推算,得出结果
阅读MaixPy 文档: 深度神经网络(DNN)基础知识 和 MaixPy AI 硬件加速基本知识
运行人脸检测
- 下载face_model_at_0x300000.kfpkg
- 打开 kflash_gui, 选择这个文件,并下载, 记得先关闭其它软件和串口的连接,不然会下载失败
- 运行代码
import sensor
import image
import lcd
import KPU as kpu
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
task = kpu.load(0x300000) # 加载 flash 中的模型
# task = kpu.load("/sd/face.kmodel") # 也可以选择加载SD卡的模型
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025) # 模型参数,不同模型不一样,例示人脸检测模型用这组参数
kpu.init_yolo2(task, 0.5, 0.3, 5, anchor) # 初始化模型
while(True):
img = sensor.snapshot() # 从摄像头获取一张照片
code = kpu.run_yolo2(task, img) # 推理,得出结果
if code: # 如果检测到人脸
for i in code: # 多张人脸
print(i) # 打印人脸信息
a = img.draw_rectangle(i.rect()) # 在图上框出人脸
lcd.display(img) # 将图片显示到屏幕
kpu.deinit(task) # 释放模型占用的内存
del task # 删除变量,释放变量
另外,如果从 flash 或者 SD 卡读出照片来作为模型推理输入,如下方法:
img = image.Image("/sd/test.jpg")
img.pix_to_ai()
这句话很重要,一定要调用,是把图像复制一份给硬件(KPU
)使用,如果是来自sensor.snapshot()
就不需要执行这一步了,内部已经执行了(硬件做的)
尝试人脸对着摄像头,会发现人脸被框出来了,如果没框出来,可以尝试换一下人的方向,可以通过lcd.rotaion()
函数设置lcd
方向
人脸识别
和人脸检测不同的是,可以通过录制一张图片来记住这个人的特征,下次就能认识这个人是谁嘞
可以先看看使用效果: https://www.bilibili.com/video/av77466790?zw
- 这里使用的模型来自maixhub人脸识别模型
这是一个加密模型,格式为smodel
,下载这个模型稍微麻烦一点,注册账号,然后根据页面的说明下载一个ken_gen
的固件,烧录这个固件,打开终端,按复位按钮,会发现打印了一串32
字节的key
- 点击下载模型时需要这个 key,输入即可得到模型
- 同样,得到模型后用 kflash_gui 下载到开发板
- 然后运行代码,代码在这里
这里也贴一下, 但是尽量看链接里的代码,保证是最新的
代码看起来会多一些,与上面不同的是,这里用了三个模型,耐心花点时间看一下也不是很难,改改逻辑就能达到想要的功能了
import sensor
import image
import lcd
import KPU as kpu
import time
from Maix import FPIOA, GPIO
import gc
from fpioa_manager import fm
from board import board_info
import utime
task_fd = kpu.load(0x200000)
task_ld = kpu.load(0x300000)
task_fe = kpu.load(0x400000)
clock = time.clock()
fm.register(board_info.BOOT_KEY, fm.fpioa.GPIOHS0)
key_gpio = GPIO(GPIO.GPIOHS0, GPIO.IN)
start_processing = False
BOUNCE_PROTECTION = 50
def set_key_state(*_):
global start_processing
start_processing = True
utime.sleep_ms(BOUNCE_PROTECTION)
key_gpio.irq(set_key_state, GPIO.IRQ_RISING, GPIO.WAKEUP_NOT_SUPPORT)
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_hmirror(1)
sensor.set_vflip(1)
sensor.run(1)
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437,
6.92275, 6.718375, 9.01025) # anchor for face detect
dst_point = [(44, 59), (84, 59), (64, 82), (47, 105),
(81, 105)] # standard face key point position
a = kpu.init_yolo2(task_fd, 0.5, 0.3, 5, anchor)
img_lcd = image.Image()
img_face = image.Image(size=(128, 128))
a = img_face.pix_to_ai()
record_ftr = []
record_ftrs = []
names = ['Mr.1', 'Mr.2', 'Mr.3', 'Mr.4', 'Mr.5',
'Mr.6', 'Mr.7', 'Mr.8', 'Mr.9', 'Mr.10']
ACCURACY = 85
while (1):
img = sensor.snapshot()
clock.tick()
code = kpu.run_yolo2(task_fd, img)
if code:
for i in code:
# Cut face and resize to 128x128
a = img.draw_rectangle(i.rect())
face_cut = img.cut(i.x(), i.y(), i.w(), i.h())
face_cut_128 = face_cut.resize(128, 128)
a = face_cut_128.pix_to_ai()
# a = img.draw_image(face_cut_128, (0,0))
# Landmark for face 5 points
fmap = kpu.forward(task_ld, face_cut_128)
plist = fmap[:]
le = (i.x() + int(plist[0] * i.w() - 10), i.y() + int(plist[1] * i.h()))
re = (i.x() + int(plist[2] * i.w()), i.y() + int(plist[3] * i.h()))
nose = (i.x() + int(plist[4] * i.w()), i.y() + int(plist[5] * i.h()))
lm = (i.x() + int(plist[6] * i.w()), i.y() + int(plist[7] * i.h()))
rm = (i.x() + int(plist[8] * i.w()), i.y() + int(plist[9] * i.h()))
a = img.draw_circle(le[0], le[1], 4)
a = img.draw_circle(re[0], re[1], 4)
a = img.draw_circle(nose[0], nose[1], 4)
a = img.draw_circle(lm[0], lm[1], 4)
a = img.draw_circle(rm[0], rm[1], 4)
# align face to standard position
src_point = [le, re, nose, lm, rm]
T = image.get_affine_transform(src_point, dst_point)
a = image.warp_affine_ai(img, img_face, T)
a = img_face.ai_to_pix()
# a = img.draw_image(img_face, (128,0))
del (face_cut_128)
# calculate face feature vector
fmap = kpu.forward(task_fe, img_face)
feature = kpu.face_encode(fmap[:])
reg_flag = False
scores = []
for j in range(len(record_ftrs)):
score = kpu.face_compare(record_ftrs[j], feature)
scores.append(score)
max_score = 0
index = 0
for k in range(len(scores)):
if max_score < scores[k]:
max_score = scores[k]
index = k
if max_score > ACCURACY:
a = img.draw_string(i.x(), i.y(), ("%s :%2.1f" % (
names[index], max_score)), color=(0, 255, 0), scale=2)
else:
a = img.draw_string(i.x(), i.y(), ("X :%2.1f" % (
max_score)), color=(255, 0, 0), scale=2)
if start_processing:
record_ftr = feature
record_ftrs.append(record_ftr)
start_processing = False
break
fps = clock.fps()
print("%2.1f fps" % fps)
a = lcd.display(img)
gc.collect()
# kpu.memtest()
# a = kpu.deinit(task_fe)
# a = kpu.deinit(task_ld)
# a = kpu.deinit(task_fd)
其它模型
自学习模型,用户对准物体按按键学习物体,然后就可以认识这个物体了
演示视频:MaixPy自学习分类器演示
另外:代码 和 模型口罩识别, 和人脸识别类似, 这里可以用 这里提供的训练好的模型,步骤和上面都一样,运行代码在博客中也有,可以尝试
数字识别(2021电赛F题): https://neucrack.com/p/384
更多的,可以在maixhub找到
报错不支持的模型版本
一般有几个可能
- 固件不够新
- 固件不对,比如用了 v4的模型,但是固件不支持v4只支持v3,换一个支持v4的固件就好了
- 加载时的模型地址和实际烧录的地址不一样
- 烧录错了模型
内存不够 (MemoryError: Out of normal MicroPython Heap Memory!)
k210 有 6MiB 通用内存, 需要用到内存的有固件(K210 是一次性将所有代码加载到内存的….),一些功能所需比如摄像头缓冲区等,还有存放模型, 另外有 2MiB 给 KPU 专用的内存(如果使用 KPU 的话)
如果你不需要使用 KPU, 想给更多的内存给 CPU使用, 则最多可以用 8MiB 连续的内存, 方法是不初始化 KPU (时钟), 则可以有连续的 8MiB通用内存使用. 当然,在本文必须要用到 KPU, 所以只作为知识补充
所以需要总内存 = 固件大小 + 静态变量申请内存 + 其它功能动态申请(比如摄像头 屏幕显示申请的缓冲区, RGB888 缓冲区, Micropython 的GC 内存块) + 模型使用 + 剩余内存
, 模型使用的内存在使用nncase
转换的时候会有输出, V3 输出了 main memory usage
, V4 输出了working memory usage
.
另外,因为 KPU 专用内存只有 2MiB, 会用来放输入和输出层的数据, 所以在设计模型时, 一个层的输入和输出层, 以及上一层的输出层和下一层的输入层的大小和必须小于 2MiB
MaixPy 固件内有两个内存池,一个是系统内存,一个是 GC 内存,前者主要用来给模型还有系统内的一些功能使用,包括摄像头和屏幕的缓冲区等都来自这里;后者是 maixpy 解析器层面的内存,可以给代码的变量使用。
GC 剩余内存可以用下面的代码来打印
import gc
print(gc.mem_free())
GC 的总内存大小可以通过下面的代码来设置, GC 的大了, 系统的就小了,如果模型大这里就不要设置太大了。 另外注意!!要重启才生效。
from Maix import utils
import machine
old = utils.gc_heap_size()
print(old)
new = 512*1024
utils.gc_heap_size(new)
machine.reset()
另外,可以用kpu.memtest()
查看一下大致上还剩多少,这个现实的系统内存不一定准确,只作参考
所以,解决内存不足有以下几种方法:
最基础的方法就是减少内存的使用,比如全局变量,不使用了尽量删除(通过
del 变量名
),删除之后还可以手动回收 GC 内存(通过gc.collect()
)。图片分辨率也可以尽量不要用太大(一般QVGA)另一个方法就是压缩固件体积,通过裁减功能来减少内存占用,这个在前面固件升级部分有说明,使用在线编译定制固件,或者自己本机编译,方法见这里
还有一个方法就是设置 GC 内存池总大小,如果 GC 内存不够,就设置大点;如果系统内存不够用比如模型加载不了,在够用的范围内减小 GC 的大小, 留给加载模型使用
另外,如果模型太大,可以使用
kpu.load_flash()
函数来加载模型(只支持kmodel
):
这会在需要模型时实时从flash读取内容,这样就可以装载大模型了,效率会低一点,帧率会有所降低(原理有兴趣可以见另一篇文章K210 从flash实时加载大模型)。
使用方法见这里,注意,模型需要先用脚本转一下大小端,别漏了!!另外, 如果你在操作
image
时或者lcd
画图时遇到这个问题,可以合理利用lcd
的display(img, oft=(x,y))
的oft
参数来实现在lcd
指定区域画图,而不是画整副图。比如,有个
img
的大小为200x200
,现在需要画到屏幕,周围填充颜色,但是屏幕是320x240
,可以先创建一个320x240
的image
填充颜色,然后拷贝这个200x200
的图到这张图上,然后lcd.dispaly
这张320x240
的图,但是会需要浪费一个320x240
图像的内存。可以直接lcd.clear(color)
一次,或者lcd.fill_rectangle(0, 0, 320, 20)
这样填充四周的颜色, 这个操作只需要一次,然后不断调用lcd.display(img_200x200, oft=(60, 20))
刷新中间的区域的图像就好了,周围会保持最开始画的颜色。 画图片同理。
同时运行多个模型
其实也不是同时运行,就是分时运行,就像前面的人脸识别模型一样
- 如果有足够内存,就一次性把几个模型加载到内存, 然后分别分时运行推理
- 如果内存不足:
- 加载第一个模型,运行后注销(kpu.deinit),再加载运行第二个模型
- 部分或全部模型 使用
load_flash
的方式加载模型,实时从 flash 读取内容
MaixHub 训练模型
MaixHub 支持训练分类模型
(输入是一张图片,输出类别) 和 分类检测器
(输入一张图片,输出物体的坐标和框大小,并输出类别)
使用 maixhub 训练的好处就是不用搭建训练环境,以及调试训练代码,只要在本地处理好数据集,上传,点击训练即可,训练完成会邮箱通知,对于常规任务来说非常合适
另外,也可以在 maixhub 上分享训练的模型或者自己训练的模型
到miaxhub 模型训练页面 看使用说明 ,制作符合要求的数据集,并创建训练任务, 任务训练完成(成功或者失败)会发送邮件通知,注意查收,有可能被识别成垃圾邮件在邮箱回收站
maixhub 要求的数据集也可以看这里的实例
训练一个分类模型
源码在这里: https://github.com/sipeed/maix_train
只支持在 Linux 上运行, 按照README.md
的说明进行使用
大致上如下,具体一定要看 README.md
!!!!
- 安装依赖
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
- 下载模型转换工具 nncase
下载 nncase 并解压到 tools/ncc/ncc_v0.1, 可执行程序的路径为:tools/ncc/ncc_v0.1/ncc
- 初始化
python3 train.py init
- 编辑
instance/config.py
配置文件, 可以使用默认 - 制作数据集, 要求和
maixhub
的数据集要求是一样的,具体可以看datasets
目录下的例子,第一次测试可以直接使用这些测试数据集, 当然,这些测试数据集只是随便弄的图片,训练出来的结果不准确
其实就是把不同类别的图片(分辨率为224x224
)放到不同的文件夹下, 文件夹名就是分类名,然后打包 - 训练
或者把数据集解压到文件夹,然后:python3 train.py -t classifier -z datasets/test_classifier_datasets.zip train
python3 train.py -t classifier -d datasets/test_classifier_datasets train
- 然后在
out
目录有训练结果,包括了一个结果 zip 压缩文件
训练一个分类检测器模型
同上, 最后训练命令不一样
python3 train.py -t detector -z datasets/test_detector_xml_format.zip train
然后在 out
目录有训练结果,包括了一个结果 zip 压缩文件
相比分类器, 检测器的数据集需要标注位置, 使用 labelimg 或者 vott 进行标注即可
分类检测这里用了 YOLOV2, 这里初学者可能都有疑问, anchor
是什么,怎么取值?
可以简单理解成给模型的几个估计框大小,比如这里anchor
是10 个浮点数,共5组,2个一组, 每组是宽和高,运行时,先用这5组框去尝试是否能框出物体, 是根据训练时的数据得出的一组经验框。
它们的取值是从训练数据中算出来的,给程序在这份训练代码中,anchor
会被自动写入到输出文件boot.py
,不需要大家手动填写
关于YOLO 的原理,有兴趣就自己去学习了,有空会写篇文章,在这里, 踩好坑,具体哪天, 就是这个链接有内容的那天hhhhh
为什么训练结果精确度低, 怎么提高
- 如果数据集数量小, 尽量使用时候的摄像头采集图片,参考这里的采集方法
- 数据集里面保证每个类的数据是正确的,别混入了奇怪的数据
- 训练次数得当
- 优化训练代码和模型结构
自己写一个 K210 可以使用的简单模型
需要先了解 nncase 支持的算子
nncase v0.2.0 支持的算子: https://github.com/kendryte/nncase/blob/master/docs/tflite_ops.md
nncase v0.1.0 支持的算子: https://github.com/kendryte/nncase/tree/v0.1.0-rc5
tensorflow 举个例子, 两分类, 这里是随便叠的层结构
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
input_shape = (240, 320, 3)
model = tf.keras.models.Sequential()
model.add(layers.ZeroPadding2D(input_shape = input_shape, padding=((1, 1), (1, 1))))
model.add(layers.Conv2D(32, (3,3), padding = 'valid', strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu')); #model.add(MaxPool2D());
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(64, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(64, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(64, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(2))
model.add(layers.Activation('softmax'))
model.summary()
model.compile(
loss ='sparse_categorical_crossentropy',
optimizer = 'adam',
metrics =['accuracy'])
# mode.fit(...)
这里你可能注意到了, 在 conv
层中stride != 1
时, 都加了一个 zeropadding
层, 这是 K210 硬件支持的模式, 如果不这样做, 转换成 V3 模型时(使用 nncase v0.1.0 RC5) 则直接报错, 使用 V4 模型(nncase V0.2.0转换)可以通过,但是是使用软件运算的, 会消耗大量内存和时间, 会发现内存占用大了很多!!! 所以设计模型时也需要注意