选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2020-10-10
  • 更新:2021-11-06
MaixPy 精简版入门教程(AI视觉向)


MaixPy为边沿计算打造的一款超级容易上手的SDK(软件开发套件),目前支持带有硬件AI加速的K210作为硬件载体(1TOPS算力,双核RISC-V 64位400MHz+8MB内存核心, 开发板¥100就能买到)

本篇是MaixPy文档的入门精简版教程,AI视觉向,没有其它干扰, 如果你没有接触过 K210 和 MaixPy, 想要用它来做机器视觉,看我!!

有什么疑惑或者要查询API可以再看完全版的MaixPy文档

各种链接

购买开发板

Sipeed 淘宝店选一款,比如这款(Bit),
注意开发需要一根Type-C线,现在安卓手机都是用这个线,尽量买质量好的,不然压降太低导致无法识别或者不稳定
另外别忘了购买屏幕和摄像头!

maix_bit2
type_c

开发环境安装

开发环境搭建如果看文字遇到问题,也可以看视频教程

获取最新固件并烧录到开发板

  • 下载最新的固件, 默认编译的有很多个固件,主要是因为为了满足内存要求同时满足功能的妥协,也可以在在线编译页面自定义功能并获取固件, 其中需要注意的是minimum代表不兼容openmv的函数,with_ide_support则支持IDEkmodelV4则代表支持kmodel v4版本.
    这里下这个minimum_with_ide_support版本
    minimum_ide

  • 打开flash_gui, 选择前面下载的这个固件,选择串口,如果有两个串口先选第一个看能不能下载,不能再换另一个
    kflash_gui

  • 如果下载出错了,尝试把开发板设置从自动变成对应的开发板型号, 降低波特率到115200尝试

使用终端进入REPL交互模式

  • 打开设备管理器,看串口号,比如这里是COM7COM8
    com
    putty

  • 打开后按开发板的reset按钮,可以看到终端打印了启动信息
    boot_info

  • 接下来就和PC上使用python一样了,如果你还不会python,那么请先花点时间学一下python基础语法,如果你有一门语言基础,这很简单,如果有学过面向对象语言比如C++/Java/Js等,也可以看我另外一篇简单的python总结

  • 有一点和PCpython不同,十分方便的功能是按ctrl+E,然后可以粘贴整块(多行)代码,然后按ctrl+D代码就会运行了

MaixPy IDE 运行代码

IDE 是从开源的 OpenMV 的 IDE 适配过来,功能一样

  • 打开 IDE, 上面任务栏工具->选择开发板选择对应的开发板型号
  • 点击左下角连接,如果连接不上,检查开发板型号选择是否正确,以及串口号是否选错,以及是否有软件占用了串口,比如前面的putty是否已经关闭
  • 然后点击运行即可将代码发送到开发板运行

基本的摄像头图像实时显示

执行摄像头测试代码, 可以使用前面说的终端或者 IDE 的方式执行

  1. import sensor, lcd
  2. sensor.reset() # 初始化摄像头
  3. sensor.set_pixformat(sensor.RGB565) # 设置图像格式为RGB565
  4. sensor.set_framesize(sensor.QVGA) # 设置图像分辨率为 320x240
  5. sensor.set_hmirror(False) # 设置左右镜像
  6. sensor.set_vflip(False) # 设置上下翻转
  7. sensor.run(1) # 摄像头开始运行,也可以不调用,参数设置好后会自动运行
  8. sensor.skip_frames() # 跳过一些帧数,因为摄像头启动时图像没稳定
  9. lcd.init(type=1, freq=15000000) # 初始化显示屏,如果颜色反色了,设置type=2
  10. lcd.rotation(0) # 设置 LCD 显示旋转, 取值范围:[0,3]
  11. while(True):
  12. img = sensor.snapshot() # 从摄像头取一张图片
  13. lcd.display(img) # 把图片显示到 LCD

然后就可以在屏幕看到摄像头的内容了,如果提示初始化失败,如果摄像头确认时支持的,那可能要检查硬件连接,或者摄像头损坏

存数据到 flash 或者 SD 卡

已经支持了文件系统,所以不需要我们去担心读写 flash 或者 SD 卡了, 直接操作文件系统,就是在操作 flash 或者 SD 卡,如下:

  1. import os
  2. # 列出 flash 内容
  3. os.listdir("/flash")
  4. # 列出 SD 卡内容, 注意SD卡要格式化成FAT格式,且要有MBR分区
  5. os.listdir("/sd")
  6. # 写入文件
  7. with open("/flash/test.txt", "w") as f:
  8. f.write("test words: hello!")
  9. # 读取文件
  10. with open("/flash/test.txt", "r") as f:
  11. print(f.read())

另外,也支持直接读取flash内容,这样就可以用 kflash_gui 下载内容到 flash,然后调用API读取了,在某些场景更灵活,读取方法:

  1. from Maix import utils
  2. data = utils.flash_read(0x300000, 16)
  3. print(data)
  4. # b'\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00'
  5. # 可以看到把烧录进去的模型内容给读出来了

什么是模型

如果你要了解更多的神经网络的知识,请自行学习

这里只为了让你更好的理解接下来的内容,可以简单理解为:

  • 传统算法中,输入 + 人为研究出来的算法 ==> 结果
  • 神经网络学习中,分成了两步:
    • 第一步,训练: 输入 + 结果 + 设计的网络结构(和参数) ==训练==> 网络结构的参数值, 而这一步中的网络结构和参数和训练出来的值也就是所谓的模型,在磁盘上的表现就是一个文件,保存了一堆结构和参数数据,而不同的软件他们的描述不一样,也就有了不同的格式,比如 .h5 .tflite .pb .kmodel,理论上都可以互相转换
    • 第二步,推理: 输入 + 训练好的模 ==> 结果

其中, 神经网络学习的训练这一步一般都在算力强力的计算机上进行;第二步也就是 MaixPy 做的事情,将训练好的模型加载后,输入数据(比如摄像头的图像),软件配合硬件根据模型对输入数据进行推算,得出结果

阅读MaixPy 文档: 深度神经网络(DNN)基础知识MaixPy AI 硬件加速基本知识

运行人脸检测

  1. import sensor
  2. import image
  3. import lcd
  4. import KPU as kpu
  5. lcd.init()
  6. sensor.reset()
  7. sensor.set_pixformat(sensor.RGB565)
  8. sensor.set_framesize(sensor.QVGA)
  9. sensor.run(1)
  10. task = kpu.load(0x300000) # 加载 flash 中的模型
  11. # task = kpu.load("/sd/face.kmodel") # 也可以选择加载SD卡的模型
  12. anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025) # 模型参数,不同模型不一样,例示人脸检测模型用这组参数
  13. kpu.init_yolo2(task, 0.5, 0.3, 5, anchor) # 初始化模型
  14. while(True):
  15. img = sensor.snapshot() # 从摄像头获取一张照片
  16. code = kpu.run_yolo2(task, img) # 推理,得出结果
  17. if code: # 如果检测到人脸
  18. for i in code: # 多张人脸
  19. print(i) # 打印人脸信息
  20. a = img.draw_rectangle(i.rect()) # 在图上框出人脸
  21. lcd.display(img) # 将图片显示到屏幕
  22. kpu.deinit(task) # 释放模型占用的内存
  23. del task # 删除变量,释放变量

另外,如果从 flash 或者 SD 卡读出照片来作为模型推理输入,如下方法:
img = image.Image("/sd/test.jpg")
img.pix_to_ai() 这句话很重要,一定要调用,是把图像复制一份给硬件(KPU)使用,如果是来自sensor.snapshot()就不需要执行这一步了,内部已经执行了(硬件做的)

尝试人脸对着摄像头,会发现人脸被框出来了,如果没框出来,可以尝试换一下人的方向,可以通过lcd.rotaion()函数设置lcd方向

face

人脸识别

和人脸检测不同的是,可以通过录制一张图片来记住这个人的特征,下次就能认识这个人是谁嘞

可以先看看使用效果: https://www.bilibili.com/video/av77466790?zw

这是一个加密模型,格式为smodel,下载这个模型稍微麻烦一点,注册账号,然后根据页面的说明下载一个ken_gen的固件,烧录这个固件,打开终端,按复位按钮,会发现打印了一串32字节的key

  • 点击下载模型时需要这个 key,输入即可得到模型
  • 同样,得到模型后用 kflash_gui 下载到开发板
  • 然后运行代码,代码在这里
    这里也贴一下, 但是尽量看链接里的代码,保证是最新的
    代码看起来会多一些,与上面不同的是,这里用了三个模型,耐心花点时间看一下也不是很难,改改逻辑就能达到想要的功能了
  1. import sensor
  2. import image
  3. import lcd
  4. import KPU as kpu
  5. import time
  6. from Maix import FPIOA, GPIO
  7. import gc
  8. from fpioa_manager import fm
  9. from board import board_info
  10. import utime
  11. task_fd = kpu.load(0x200000)
  12. task_ld = kpu.load(0x300000)
  13. task_fe = kpu.load(0x400000)
  14. clock = time.clock()
  15. fm.register(board_info.BOOT_KEY, fm.fpioa.GPIOHS0)
  16. key_gpio = GPIO(GPIO.GPIOHS0, GPIO.IN)
  17. start_processing = False
  18. BOUNCE_PROTECTION = 50
  19. def set_key_state(*_):
  20. global start_processing
  21. start_processing = True
  22. utime.sleep_ms(BOUNCE_PROTECTION)
  23. key_gpio.irq(set_key_state, GPIO.IRQ_RISING, GPIO.WAKEUP_NOT_SUPPORT)
  24. lcd.init()
  25. sensor.reset()
  26. sensor.set_pixformat(sensor.RGB565)
  27. sensor.set_framesize(sensor.QVGA)
  28. sensor.set_hmirror(1)
  29. sensor.set_vflip(1)
  30. sensor.run(1)
  31. anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437,
  32. 6.92275, 6.718375, 9.01025) # anchor for face detect
  33. dst_point = [(44, 59), (84, 59), (64, 82), (47, 105),
  34. (81, 105)] # standard face key point position
  35. a = kpu.init_yolo2(task_fd, 0.5, 0.3, 5, anchor)
  36. img_lcd = image.Image()
  37. img_face = image.Image(size=(128, 128))
  38. a = img_face.pix_to_ai()
  39. record_ftr = []
  40. record_ftrs = []
  41. names = ['Mr.1', 'Mr.2', 'Mr.3', 'Mr.4', 'Mr.5',
  42. 'Mr.6', 'Mr.7', 'Mr.8', 'Mr.9', 'Mr.10']
  43. ACCURACY = 85
  44. while (1):
  45. img = sensor.snapshot()
  46. clock.tick()
  47. code = kpu.run_yolo2(task_fd, img)
  48. if code:
  49. for i in code:
  50. # Cut face and resize to 128x128
  51. a = img.draw_rectangle(i.rect())
  52. face_cut = img.cut(i.x(), i.y(), i.w(), i.h())
  53. face_cut_128 = face_cut.resize(128, 128)
  54. a = face_cut_128.pix_to_ai()
  55. # a = img.draw_image(face_cut_128, (0,0))
  56. # Landmark for face 5 points
  57. fmap = kpu.forward(task_ld, face_cut_128)
  58. plist = fmap[:]
  59. le = (i.x() + int(plist[0] * i.w() - 10), i.y() + int(plist[1] * i.h()))
  60. re = (i.x() + int(plist[2] * i.w()), i.y() + int(plist[3] * i.h()))
  61. nose = (i.x() + int(plist[4] * i.w()), i.y() + int(plist[5] * i.h()))
  62. lm = (i.x() + int(plist[6] * i.w()), i.y() + int(plist[7] * i.h()))
  63. rm = (i.x() + int(plist[8] * i.w()), i.y() + int(plist[9] * i.h()))
  64. a = img.draw_circle(le[0], le[1], 4)
  65. a = img.draw_circle(re[0], re[1], 4)
  66. a = img.draw_circle(nose[0], nose[1], 4)
  67. a = img.draw_circle(lm[0], lm[1], 4)
  68. a = img.draw_circle(rm[0], rm[1], 4)
  69. # align face to standard position
  70. src_point = [le, re, nose, lm, rm]
  71. T = image.get_affine_transform(src_point, dst_point)
  72. a = image.warp_affine_ai(img, img_face, T)
  73. a = img_face.ai_to_pix()
  74. # a = img.draw_image(img_face, (128,0))
  75. del (face_cut_128)
  76. # calculate face feature vector
  77. fmap = kpu.forward(task_fe, img_face)
  78. feature = kpu.face_encode(fmap[:])
  79. reg_flag = False
  80. scores = []
  81. for j in range(len(record_ftrs)):
  82. score = kpu.face_compare(record_ftrs[j], feature)
  83. scores.append(score)
  84. max_score = 0
  85. index = 0
  86. for k in range(len(scores)):
  87. if max_score < scores[k]:
  88. max_score = scores[k]
  89. index = k
  90. if max_score > ACCURACY:
  91. a = img.draw_string(i.x(), i.y(), ("%s :%2.1f" % (
  92. names[index], max_score)), color=(0, 255, 0), scale=2)
  93. else:
  94. a = img.draw_string(i.x(), i.y(), ("X :%2.1f" % (
  95. max_score)), color=(255, 0, 0), scale=2)
  96. if start_processing:
  97. record_ftr = feature
  98. record_ftrs.append(record_ftr)
  99. start_processing = False
  100. break
  101. fps = clock.fps()
  102. print("%2.1f fps" % fps)
  103. a = lcd.display(img)
  104. gc.collect()
  105. # kpu.memtest()
  106. # a = kpu.deinit(task_fe)
  107. # a = kpu.deinit(task_ld)
  108. # a = kpu.deinit(task_fd)

其它模型

  • 比如 mobilenet 1000分类模型,可以识别不同的物体
    代码在这里,模型在这里

  • 自学习模型,用户对准物体按按键学习物体,然后就可以认识这个物体了
    演示视频: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 剩余内存可以用下面的代码来打印

  1. import gc
  2. print(gc.mem_free())

GC 的总内存大小可以通过下面的代码来设置, GC 的大了, 系统的就小了,如果模型大这里就不要设置太大了。 另外注意!!要重启才生效

  1. from Maix import utils
  2. import machine
  3. old = utils.gc_heap_size()
  4. print(old)
  5. new = 512*1024
  6. utils.gc_heap_size(new)
  7. machine.reset()

另外,可以用kpu.memtest()查看一下大致上还剩多少,这个现实的系统内存不一定准确,只作参考

所以,解决内存不足有以下几种方法:

  • 最基础的方法就是减少内存的使用,比如全局变量,不使用了尽量删除(通过del 变量名),删除之后还可以手动回收 GC 内存(通过gc.collect())。图片分辨率也可以尽量不要用太大(一般QVGA)

  • 另一个方法就是压缩固件体积,通过裁减功能来减少内存占用,这个在前面固件升级部分有说明,使用在线编译定制固件,或者自己本机编译,方法见这里

  • 还有一个方法就是设置 GC 内存池总大小,如果 GC 内存不够,就设置大点;如果系统内存不够用比如模型加载不了,在够用的范围内减小 GC 的大小, 留给加载模型使用

  • 另外,如果模型太大,可以使用kpu.load_flash()函数来加载模型(只支持kmodel):
    这会在需要模型时实时从flash读取内容,这样就可以装载大模型了,效率会低一点,帧率会有所降低(原理有兴趣可以见另一篇文章K210 从flash实时加载大模型)。
    使用方法见这里,注意,模型需要先用脚本转一下大小端,别漏了!!

  • 另外, 如果你在操作 image时或者lcd画图时遇到这个问题,可以合理利用lcddisplay(img, oft=(x,y))oft参数来实现在lcd指定区域画图,而不是画整副图。

    比如,有个img的大小为200x200,现在需要画到屏幕,周围填充颜色,但是屏幕是320x240,可以先创建一个320x240image填充颜色,然后拷贝这个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!!!!

  1. 安装依赖
    1. pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
  2. 下载模型转换工具 nncase
    下载 nncase 并解压到 tools/ncc/ncc_v0.1, 可执行程序的路径为: tools/ncc/ncc_v0.1/ncc
  3. 初始化
    1. python3 train.py init
  4. 编辑instance/config.py配置文件, 可以使用默认
  5. 制作数据集, 要求和 maixhub的数据集要求是一样的,具体可以看datasets目录下的例子,第一次测试可以直接使用这些测试数据集, 当然,这些测试数据集只是随便弄的图片,训练出来的结果不准确
    其实就是把不同类别的图片(分辨率为224x224)放到不同的文件夹下, 文件夹名就是分类名,然后打包
  6. 训练
    1. python3 train.py -t classifier -z datasets/test_classifier_datasets.zip train
    或者把数据集解压到文件夹,然后:
    1. python3 train.py -t classifier -d datasets/test_classifier_datasets train
  7. 然后在 out目录有训练结果,包括了一个结果 zip 压缩文件

训练一个分类检测器模型

同上, 最后训练命令不一样

  1. 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 举个例子, 两分类, 这里是随便叠的层结构

  1. import tensorflow as tf
  2. from tensorflow import keras
  3. from tensorflow.keras import layers
  4. input_shape = (240, 320, 3)
  5. model = tf.keras.models.Sequential()
  6. model.add(layers.ZeroPadding2D(input_shape = input_shape, padding=((1, 1), (1, 1))))
  7. model.add(layers.Conv2D(32, (3,3), padding = 'valid', strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu')); #model.add(MaxPool2D());
  8. model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
  9. model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  10. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  11. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  12. model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
  13. model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  14. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  15. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  16. model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
  17. model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  18. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  19. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  20. model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
  21. model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  22. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  23. model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  24. model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
  25. model.add(layers.Conv2D(64, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  26. model.add(layers.Conv2D(64, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  27. model.add(layers.Conv2D(64, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
  28. model.add(layers.Flatten())
  29. model.add(layers.Dropout(0.5))
  30. model.add(layers.Dense(2))
  31. model.add(layers.Activation('softmax'))
  32. model.summary()
  33. model.compile(
  34. loss ='sparse_categorical_crossentropy',
  35. optimizer = 'adam',
  36. metrics =['accuracy'])
  37. # mode.fit(...)

这里你可能注意到了, 在 conv 层中stride != 1 时, 都加了一个 zeropadding 层, 这是 K210 硬件支持的模式, 如果不这样做, 转换成 V3 模型时(使用 nncase v0.1.0 RC5) 则直接报错, 使用 V4 模型(nncase V0.2.0转换)可以通过,但是是使用软件运算的, 会消耗大量内存和时间, 会发现内存占用大了很多!!! 所以设计模型时也需要注意

文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-43lej9.png