- 作者:
- 分类:知识&开发->AI->k210
- 阅读:39550
- 点赞:736
- 版权: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, lcdsensor.reset() # 初始化摄像头sensor.set_pixformat(sensor.RGB565) # 设置图像格式为RGB565sensor.set_framesize(sensor.QVGA) # 设置图像分辨率为 320x240sensor.set_hmirror(False) # 设置左右镜像sensor.set_vflip(False) # 设置上下翻转sensor.run(1) # 摄像头开始运行,也可以不调用,参数设置好后会自动运行sensor.skip_frames() # 跳过一些帧数,因为摄像头启动时图像没稳定lcd.init(type=1, freq=15000000) # 初始化显示屏,如果颜色反色了,设置type=2lcd.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 utilsdata = 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 sensorimport imageimport lcdimport KPU as kpulcd.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 sensorimport imageimport lcdimport KPU as kpuimport timefrom Maix import FPIOA, GPIOimport gcfrom fpioa_manager import fmfrom board import board_infoimport utimetask_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 = FalseBOUNCE_PROTECTION = 50def set_key_state(*_):global start_processingstart_processing = Trueutime.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 detectdst_point = [(44, 59), (84, 59), (64, 82), (47, 105),(81, 105)] # standard face key point positiona = 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 = 85while (1):img = sensor.snapshot()clock.tick()code = kpu.run_yolo2(task_fd, img)if code:for i in code:# Cut face and resize to 128x128a = 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 pointsfmap = 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 positionsrc_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 vectorfmap = kpu.forward(task_fe, img_face)feature = kpu.face_encode(fmap[:])reg_flag = Falsescores = []for j in range(len(record_ftrs)):score = kpu.face_compare(record_ftrs[j], feature)scores.append(score)max_score = 0index = 0for k in range(len(scores)):if max_score < scores[k]:max_score = scores[k]index = kif 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 = featurerecord_ftrs.append(record_ftr)start_processing = Falsebreakfps = 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 gcprint(gc.mem_free())
GC 的总内存大小可以通过下面的代码来设置, GC 的大了, 系统的就小了,如果模型大这里就不要设置太大了。 另外注意!!要重启才生效。
from Maix import utilsimport machineold = utils.gc_heap_size()print(old)new = 512*1024utils.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 tffrom tensorflow import kerasfrom tensorflow.keras import layersinput_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转换)可以通过,但是是使用软件运算的, 会消耗大量内存和时间, 会发现内存占用大了很多!!! 所以设计模型时也需要注意

