OpenMV 从入手到跑TensorFlow Lite神经网络进行垃圾分类
OpenMV 从入手到跑TensorFlow Lite神经网络进行垃圾分类
一、了解OpenMV 4 plus的构成
OpenMV 是一个开源项目,其使用stm32系列单片机作为主控单元,搭载摄像头,将python语言解析器移植到openMV的主控芯片运行,使其可以使用python语言做一些图像处理相关的工作。最常见的从 OpenMV 2 搭载的 stm32f4 到 OpenMV 3 的 stm32f7,再到 OpenMV 4 的 stm32H7 ,主频从180MHz一路升级到480MHz,内存也从256KB升级到1MB。而本次介绍的 OpenMV 4 plus版本在OpenMV 4的基础上外挂了32 MB 的 SRAM 和 32 MB 的 Flash,将可用性再次提升一个大的台阶。
1.OpenMV 4 plus 的特点
1.1 硬件资源
资源 | 性能 |
---|---|
处理器 | STM32H743II ARM Cortex M7 处理器,480 MHz ,1MB RAM,2 MB flash. |
接口 | 全速 USB (12Mbs) 接口,连接到电脑,当插入OpenMV摄像头后,你的电脑会出现一个虚拟COM端口和一个“U盘” |
接口 | 一个μSD卡槽拥有100Mbs读写,这允许你的OpenMV摄像头录制视频和把机器视觉的素材从SD卡提取出来 |
接口 | 一个SPI总线高达100Mbs速度,允许你简单的把图像流数据传给LCD扩展板,WiFi扩展板,或者其他控制器 |
接口 | 一个 I2C总线,CAN总线, 和2两个异步串口总线 (TX/RX) ,用来链接其他控制器或者传感器 |
接口 | 一个12-bit ADC 和一个12-bit DAC |
接口 | 2个 I/O 引脚用于舵机控制 |
接口 | 一个RGB LED(三色), 两个高亮的 850nm IR LED(红外) |
接口 | 所有的IO口都可以用于,中断和PWM(板子上有10个I/O引脚) |
扩展 | 32 MB 外置的 32-bit SDRAM ,100 MHz的时钟,达到 400 MB/s 的带宽 |
扩展 | 32 MB 外置的 quadspi flash, 100 MHz的时钟,4-bit DDR模式达到 100 MB/s 的带宽 |
摄像头 | 默认安装 OV5640 感光元件的摄像头,可处理2592×1944 (5MP)图像,并带有M12标准的2.8mm焦距镜头 |
1.2 引脚图
2.可用的学习资源
名称 | 网址 | 备注 |
---|---|---|
外文官网 | https://openmv.io/ | 唯一原版官网 |
外文文档教程 | https://docs.openmv.io/openmvcam/quickref.html | 强调基础控制 |
中国代理官网 | https://singtown.com/openmv/ | 星瞳科技 |
中文文档教程 | https://docs.singtown.com/micropython/zh/latest/openmvcam/openmvcam/quickref.html# | 外文文档教程的翻译版(星瞳科技) |
中文文档教程 | https://book.openmv.cc/quick-starter.html | 入门推荐 ☆ 星瞳科技 |
中文视频教程 | https://singtown.com/learn/ | 入门推荐 ☆ 星瞳科技 |
开源项目地址 | https://github.com/openmv/openmv | github 固件源码 |
二、建议的学习路线
1.学习python基础语法
python的学习资源非常丰富,但为了使用OpenMv并最终实现跑TensorFlow Lite神经网络进行垃圾分类,仅会使用其基础语法和格式即可,以下为免费的入门教程,自己有其他的教程资源亦可。其他教程推荐去bilibili搜索python,大把的免费视频。
名称 | 网址 | 备注 |
---|---|---|
中文文档简称 | https://www.runoob.com/python3/python3-tutorial.html | 免费简洁的学习网站 |
视频教程 | https://www.bilibili.com/video/av27789609 | 著名小甲鱼教程(幽默有趣,但是有点污) |
前期学习python无需学得很精,如果有其它语言基础如C语言,则只需要看python的基础数据类型、基础条件语句以及基础循环语句等与自己掌握语言的不同之处即可。如没有语言基础,则需要将以上内容在例程的基础上训练几遍,让自己熟悉编程规则。
2.练习OpenMV的基础实验
2.1 安装软件
在开始使用OpenMV之前,您需要下载并安装 OpenMV IDE(点击蓝字即可跳转官网下载),如下图。在Windows下,OpenMV IDE进入安装程序后,它将自动安装IDE以及OpenMV和MicroPython pyboard的驱动程序,只需按照默认的安装程序提示即可,一般情况下一直点击下一步就正常安装完成了。要启动OpenMV IDE,只需点击开始菜单中的快捷方式即可。【安装有问题的点此】
2.2 了解基础环境使用方法并连接设备
通过USB数据线将OpenMV连接至电脑USB口,然后打开资源管理器,找到OpenMv生成的U盘,打开会发现包含一个main.py文件。此文件就是OpenMV在离线(未连接OpenMvIDE)上电时自动执行的第一个代码文件。
之后打开刚安装好的OpenMV IDE,将OpenMV生成的U盘内的main.py文件拖至OpenMV IDE编辑器处打开该文件。
打开文件后,首先点击编辑器下方的连接图标,之后再点击编辑器下方运行按钮,OpenMV就会在OpenMV IDE的控制下执行编辑器打开的main.py文件,并在右侧显示摄像头传输的图像流。
点击下方的串行终端部分可看到OpenMV打印的提示信息,在默认例程中输出为图像流的帧率。
2.3 常用基础例程
2.3.1 查找例程方法
①在OpenMV IDE的 【文件->示例->openMV】中可找到带有的所有官方例程。
②在星瞳给出的教程文档中,有对官方例程的翻译和解释。【点此前往】
③当然是根据自己的需求去万能的百度了。。。
三、一点教程
在学习图像处理操作前,先学习一点基础操作知识。
1.常用的延时写法
延时函数依赖于time模块,所以在使用前应引入time模块。
import time # 导入库文件
time.sleep(1) # 延时1s
time.sleep(0.5) # 延时0.5s [参数以秒为单位,可传入小数或整数]
time.sleep_ms(500) # 延时500ms
time.sleep_us(10) # 延时10us
2.如何 点亮or熄灭 一个LED灯
首先OpenMV 4 plus上搭载了一个全彩RGB灯和两个红外LED,一个RGB LED内部包含3个LED,但是两个红外LED是受一个端口控制的,那么OpenMV 4 plus上就总共有4个控制LED的端口,如下表所示。
LED | 端口编号 | 定义写法 |
---|---|---|
红色LED | 1 | led_R = pyb.LED(1) |
红色LED | 2 | led_G = pyb.LED(2) |
红色LED | 3 | led_B = pyb.LED(3) |
两个红外LED | 4 | led_IR = pyb.LED(4) |
在上表中可以看到在写法中都为“y = pyb.LED(x)”,可知LED的控制所依赖pyb模块,所以在程序的开始应先将pyb模块引入。控制LED灯点亮的方法为led_R.on(),熄灭的方法为led_R.off(),相应的使用示例如下。
import pyb,time # 导入库文件
led_R = pyb.LED(1) # 定义红色LED控制
led_G = pyb.LED(2) # 定义绿色LED控制
led_B = pyb.LED(3) # 定义蓝色LED控制
led_IR = pyb.LED(4) # 定义红外LED控制
while(True): # 死循环
led_R.on() # 红色LED点亮
led_G.on() # 绿色LED点亮
led_B.on() # 蓝色LED点亮
led_IR.on() # 红外LED点亮
time.sleep_ms(500) # 延时0.5S
led_R.off() # 红色LED熄灭
led_G.off() # 绿色LED熄灭
led_B.off() # 蓝色LED熄灭
led_IR.off() # 红外LED熄灭
time.sleep_ms(500) # 延时0.5S
3.控制IO口输入输出
在上一个控制LED示例中并未通过直接控制IO口的方式去控制LED的状态,而是调用了pyb库中已经写好的接口实现控制目的,此讲来说一下如何直接控制一个IO口。首先IO的控制依赖Pin模块,而Pin模块又包含在pyb模块中,所以在程序开始要提前添加Pin模块【from pyb import Pin】。
学习过stm32单片机的同学都知道,在初始化一个IO口时,需要配置IO口的引脚编号、输入输出模式以及上下拉模式等,在使用STM32为主控单元的OpenMV中也是如此。如下表所示。
配置模式 | 输入or输出模式代码 | 上下拉模式代码 |
---|---|---|
下拉输入 | Pin.IN | Pin.PULL_DOWN |
上拉输入 | Pin.IN | Pin.PULL_UP |
浮空输入 | Pin.IN | Pin.PULL_NONE |
开漏输出 | Pin.OUT_OD | 无 |
推挽输出 | Pin.OUT_PP | 无 |
实际使用的例程如下:
from pyb import Pin # 引入Pin库
p0_out = Pin('P0', Pin.OUT_PP) # 设置P0端口为推挽输出模式
p0_out.high() # 设置P0端口输出高电平
p0_out.low() # 设置P0端口输出低电平
p1_in = Pin('P1', Pin.IN, Pin.PULL_UP) # 设置P1端口为上拉输入模式
p1_value = p1_in.value() # 获取P1端口的值,0或1,高电平为1,低电平为0
4.控制串口引脚输出信息
在OpenMV 4 Plus中,有两个串口可以使用,分别为串口1和串口三,引脚对应关系如下表所示。
串口 | 端口号 | 对应功能 |
---|---|---|
UART 1 | P5 | RX |
UART 1 | P4 | TX |
UART 3 | P0 | RX |
UART 3 | P1 | TX |
常用的串口相关函数如下表。
类型 | 函数名 | 示例 | 实现功能 |
---|---|---|---|
配置 | UART() | uart = UART(3, 9600) | 创建串口3对象,并初始化串口3,波特率9600,其他参数默认 |
配置 | uart.init() | uart.init(9600, bits=8, parity=None, stop=1) | 重新配置串口参数【接UART()后使用】 波特率9600,8位数据长度,无校验位,1位停止位 |
读取 | uart.read() | str = uart.read() | 读取所有可用字符 |
读取 | uart.read() | str = uart.read(x) | 读取x位字符 |
读取 | uart.readchar() | num = uart.readchar() | 读取一个字符,并返回其整数形式 |
读取 | uart.readline() | str = uart.read(x) | 读取一行【到回车换行结束】 |
读取 | uart.readinto() | uart.readinto(buf) | 读取并存入缓冲区buf内 |
读取状态 | uart.any() | num = uart.any() | 返回等待的字符数量,用于检查是否有串口数据接收到 |
发送 | uart.write() | uart.write(‘abc’) | 发送字符串‘abc’ |
发送 | uart.writechar() | uart.writechar(42) | 发送一个字符’B’ ,数值42对应ASCLL码的符号B |
对串口功能的使用也是依赖与pyb模块,在程序开始要提前添加pyb模块,使用示例如下。
from pyb import UART # 导入串口支持库文件
uart = UART(3, 115200) # 创建串口3设备,并设置波特率为115200
num = 132 # 随便定义一个数值变量
num_str = "[%d]" % num # 将数据转化为字符串用于输出
uart.write(num_str +"\r\n") # 串口3输出【通过 P4端口】
while(True):
if uart.any()>0: # 如果串口接收到数据【通过 P5端口】
str = uart.read() # 读取接收到的字符
uart.write("redata:"+ str+ "\r\n") # 将收到的数据通过串口3输出【通过 P4端口】
5.第一个图像例程HelloWord
此例程为最基础的HelloWord级例程,内部包含了相关模块的依赖、摄像头的初始化、拍照并存放至缓存变量中以及打印图像的获取帧率。以后的所有有关图像处理的程序都将以此程序为基础进行编写。
import sensor, image, time # 引入此例程依赖的模块,sensor是与摄像头参数设置相关的模块,image是图像处理相关的模块,time时钟控制相关的模块。import相当于c语言的#include <>,模块相当于c语言的库。
sensor.reset() # 复位并初始化摄像头,reset()是sensor模块里面的函数
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种
sensor.set_framesize(sensor.QVGA) # 将图像大小设置为QVGA (320x240)
# 设置图像像素大小,QQVGA: 160x120,QQVGA2: 128x160,QVGA: 320x240,VGA: 640x480, QQCIF: 88x72,QCIF: 176x144,CIF: 352x288
sensor.skip_frames(time = 2000) # 等待设置生效。
clock = time.clock() # 初始化时钟,并创建一个时钟对象来跟踪FPS帧率。
while(True): # python while循环,一定不要忘记加冒号“:”
clock.tick() # 更新FPS帧率时钟。
img = sensor.snapshot() # 拍一张照片并返回图像。截取当前图像,存放于变量img中。注意python中的变量是动态类型,不需要声明定义,直接用即可。
print(clock.fps()) # 打印当前的帧率。注意: 当连接电脑后,OpenMV会变成一半的速度。当不连接电脑,帧率会增加。
6.单颜色识别例程
在OpenMV IDE的菜单【文件->示例->OpenMV-ColotTracking】中依次找到例程,即为单颜色识别程序。
单颜色识别的程序如下所示,可知此程序就是在上一个HelloWord例程的基础上使用了find_blobs()方法对拍摄到的图片进行颜色的识别。
import sensor, image, time
green_threshold = ( 0, 80, -70, -10, -0, 30) # 要识别的颜色LAB值
sensor.reset() # 初始化sensor
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种
sensor.set_framesize(sensor.QVGA) # 使用QVGA的分辨率
sensor.skip_frames(10) # 等待设置生效。
sensor.set_auto_whitebal(False) # 关闭白平衡。白平衡是默认开启的,在颜色识别中,建议关闭白平衡。
while(True):
img = sensor.snapshot() # 拍一张照片并返回图像。
blobs = img.find_blobs([green_threshold]) # 识别并返回结果【返回一个列表】
if blobs: # 如果找到了目标颜色【即列表不为空】
for b in blobs: # 遍历找到的目标颜色区域【因为可能存在不只一个颜色块】
img.draw_rectangle(b[0:4]) # 用矩形标记出目标颜色区域
img.draw_cross(b[5], b[6]) # 在目标颜色区域的中心画十字形标记
print(b[5], b[6]) # 输出目标物体中心坐标【通过串行终端 USB设备】
其中find_blobs()方法的输入参数是一个LAB格式的颜色阈值,作为欲识别的颜色,返回值是一个包括识别到的每个色块对象的列表。返回值具体是个什么意思呢,首先是个列表,列表内的每个一条信息都是找到的一个色块的信息,此信息中又包含10个值,分别代表的意义如下表所示【假设列表中某条信息为b】。列表内信息值的使用除了可以用下标的方式取出,还可以通过[.变量名]的方式使用,如下表所示【假设列表中某条信息为b】:
标号方式使用 | 内部方法方式使用 | 意义 |
---|---|---|
b[0] | b.x() | 识别到的目标颜色区域左上顶点的x坐标 |
b[1] | b.y() | 识别到的目标颜色区域左上顶点的y坐标 |
b[2] | b.w() | 识别到的目标颜色区域的宽 |
b[3] | b.h() | 识别到的目标颜色区域的高 |
b[4] | b.pixels() | 识别到的目标颜色区域的像素点的个数 |
b[5] | b.cx() | 识别到的目标颜色区域的中心点x坐标 |
b[6] | b.cy() | 识别到的目标颜色区域的中心点y坐标 |
b[7] | b.rotation() | 识别到的目标颜色区域的旋转角度(是弧度值,浮点型,列表其他元素是整型) |
b[8] | b.count() | 识别到的所有颜色区域与此目标区域交叉的目标个数 |
b[9] | b.code() | 识别到的目标颜色区域颜色的编号(它可以用来分辨这个区域是用哪个颜色阈值识别出来的) |
无 | blob.rect() | 返回一个矩形元组(x, y, w, h) ,用于如色块边界框的 image.draw_rectangle 等 其他的 image 方法 |
那么具体的待识别LAB颜色阈值如何设置呢,可以使用OpenMV IDE中的“阈值编辑器”工具,对摄像头中拍摄的待识别图像进行LAB颜色阈值的调整。
选择帧缓冲区
比如说要识别图片中红色的颜色,那么拖动下面的LAB最大最小值,使右侧的串口中仅剩红色部分为白色,此时窗口下方的LAB阈值即为需要识别的颜色的LAB阈值。此处我调整的经验是,先将全部的最小值拖至最左侧,全部的最大值拖至最右侧,然后依次拖动LAB值,仅需一次完整过程即可将右侧图像仅剩待处理颜色块。【注意,白色部分是待识别的颜色】
将获得的LAB值复制,并粘贴值代码中的green_threshold=()括号内,即完成颜色的识别设置。此时运行程序,即可准确的将摄像头捕获的图像中的红色标注出来。
如果欲让OpenMV在不连接OpenMV IDE时也可以进行此程序功能,则可以在编辑器的菜单栏【工具->将打开的脚本文件保存到OpenMV Cam】中将程序代码保存到OpenMV中去。
7.多颜色识别例程
多颜色识别与单颜色识别基本上是一致的,都是使用了同一个方法find_blobs(),只不过将输入的阈值从一个增加至了多个,阈值的设置也与单颜色的设置相同,使用“阈值编辑器”依次将每种颜色的图像进行处理即可,调整后的识别效果如下。
多颜色识别代码,其中find_blobs()方法比单颜色识别程序中多了两个参数,pixels_threshold和area_threshold。其中pixels_threshold是设置边界框区域阈值,若一个色块的边界框区域小于 area_threshold ,则会被过滤掉。 pixel_threshold是设置像素数阈值,若一个色块的像素数小于 pixel_threshold ,则会被过滤掉。
import sensor, image, time, math
# 多颜色识别最多可同时识别16种颜色
thresholds = [(0, 100, 13, 127, 34, 67), # 待识别颜色LAB阈值1
(81, 100, -128, -13, 13, 53), # 待识别颜色LAB阈值2
(38, 100, -87, 22, -98, -33)] # 待识别颜色LAB阈值3
sensor.reset() # 初始化摄像头,reset()是sensor模块里面的函数
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种
sensor.set_framesize(sensor.QVGA) # 设置图像像素大小302*240
sensor.skip_frames(time = 2000) # 等待设置生效
sensor.set_auto_gain(False) # 颜色跟踪建议关闭自动增益
sensor.set_auto_whitebal(False) # 颜色跟踪建议关闭白平衡
while(True):
img = sensor.snapshot() # 拍照并获取图像
for blob in img.find_blobs(thresholds, pixels_threshold=200, area_threshold=200): # 进行多颜色识别
img.draw_rectangle(blob.rect()) # 框出识别到的色块
img.draw_cross(blob.cx(), blob.cy()) # 标出色块中心点
8.【串口】多颜色识别-返回色块中心坐标
在上个示例中已实现颜色的识别和色块的框选,在教程的开始处还讲解了串口的使用方法,本示例将其组合至一起,实现将识别到每个颜色色块中心点的坐标通过串口发送出去。代码如下:
import sensor, image, time, math # 导入串口支持库文件
from pyb import UART # 导入串口支持库文件
# 多颜色识别最多可同时识别16种颜色
thresholds = [(0, 100, 13, 127, 34, 67), # 待识别颜色LAB阈值1
(81, 100, -128, -13, 13, 53), # 待识别颜色LAB阈值2
(38, 100, -87, 22, -98, -33)] # 待识别颜色LAB阈值3
sensor.reset() # 初始化摄像头,reset()是sensor模块里面的函数
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种
sensor.set_framesize(sensor.QVGA) # 设置图像像素大小302*240
sensor.skip_frames(time = 2000) # 等待设置生效
sensor.set_auto_gain(False) # 颜色跟踪建议关闭自动增益
sensor.set_auto_whitebal(False) # 颜色跟踪建议关闭白平衡
uart = UART(3, 115200) # 创建串口3设备,并设置波特率为115200
while(True):
uart.write("-------------------------\r\n") # 串口3输出【通过 P4端口】
img = sensor.snapshot() # 拍照并获取图像
for blob in img.find_blobs(thresholds, pixels_threshold=200, area_threshold=200): # 进行多颜色识别
img.draw_rectangle(blob.rect()) # 框出识别到的色块
img.draw_cross(blob.cx(), blob.cy()) # 标出色块中心点
Str_x = "[%3d]" % blob.cx() # 将中心点X坐标转为长度为3的字符串
Str_y = "[%3d]" % blob.cy() # 将中心点y坐标转为长度为3的字符串
if blob.code()==1: # 如果识别
uart.write("red X:"+Str_x+" Y:"+Str_y+"\r\n") # 串口3输出【通过 P4端口】
if blob.code()==2:
uart.write("green X:"+Str_x+" Y:"+Str_y+"\r\n") # 串口3输出【通过 P4端口】
if blob.code()==4:
uart.write("blue X:"+Str_x+" Y:"+Str_y+"\r\n") # 串口3输出【通过 P4端口】
uart.write("-------------------------\r\n\r\n") # 串口3输出【通过 P4端口】
在以上程序中,对于当前信息是什么颜色的,使用code()方法配合判断,code()方法返回的是用于识别的阈值列表中的颜色位置。即,本次识别使用的LAB颜色阈值在上面程序中thresholds列表中是第几个颜色。在上面程序中thresholds列表中有三个颜色,第一个是红色(0, 100, 13, 127, 34, 67),第二个是绿色(81, 100, -128, -13, 13, 53),第三个是蓝色(38, 100, -87, 22, -98, -33)。【(感谢网友的指导)所以,当code()方法返回1证明本次识别到的是红色,返回2证明本次识别到的是绿色,返回3证明本次识别到的是蓝色。但是在实际的使用中发现,识别蓝色的时候返回值为4,此处本人尚未查明原因,若有朋友知道诚邀指导。】
所以,当code()方法返回二进制00000001->十进制1,证明本次识别到的是红色;返回00000010->2,证明本次识别到的是绿色;返回00000100->4,证明本次识别到的是蓝色。
识别后在串口软件中打印的效果如下:
四、开启TensorFlow Lite神经网络之旅
本章对应星瞳视频教程:OpenMV 4 Plus 训练神经网络进行口罩识别
1.TensorFlow Lite介绍
人工智能、神经网络以及机器学习等应用皆需要大量的计算资源以及高性能的处理器加成,动辄就是上GPU群或者云服务器集群来处理,想要在一般的嵌入式芯片或者单片机上执行是想都不要想的事情。
但是当前的边缘处理场景越来越多,需求也越来越大,所以一些针对IOT设备和嵌入式设备的迁移学习网络应运而生,而Tensorflow Lite就是在这样的情境下诞生的,是在完整的Tensorflow上进行裁剪、优化以及量身定做的,是针对移动设备和嵌入式设备的专用轻量化解决方案,其占用空间小且延迟低。
①主要特点有:
✦支持浮点运算和量化模型,并已针对移动平台进行优化,可以用来创建和运行自定义模型。开发者也可以在模型中添加自定义操作。
✦FlatBuffer格式
✦具有在移动设备运行更快的内核解释器
✦支持通过Tensorflow训练好的模型转换为Tensorflow Lite格式(pd,h5等都可以)
✦当支持所有优化操作时,模型小于300k,当仅支持inception v3和mobilenet模型优化时,模型小于200k
②预训练模型:
✦inception v3:用于目标检测
✦MobileNets:专门针对移动端的模型,具有低延迟,高速度,低内存,可用于图像识别,目标检测,图像分割,但是精度小于inception v3
✦量化版本的MobileNets,通过将float-32转为int-8,在CPU上拥有更快的速度
✦支持java,c++API
以上谈到的预训练模型基于ImageNet数据集训练,支持1000种类别。如果此数据集不能满足你的项目需要,你需要准备自己的数据集和标签,使用迁移学习重新训练模型。
2.使用支持
但是尽管Tensorflow Lite已经足够小、足够快速,但是作为资源非常紧俏的单片机来说,尤其是作图像处理,还是有点力不从心。好在OpenMV 4 Plus使用的单片机是当前stm32单片机中性能最强的H7系列,而且外扩了较大的SDRAM作为数据处理内存使用,已基本满足进行一些数据量小、图像像素低的识别任务要求。
但是满足了基本的硬件资源支持,进行一款神经网络的搭建、训练和调试,还是需要付出极大的学习成本的。如果让一个单片机从业者为了产品需求,进军人工智能方面的学习,无论从产品周期来说、还是学习能力的考验方面来说,都不是个好的方法。
为了进一步降低神经网络模型在嵌入式设备部署的难度,在线训练模型网站Edge Impulse诞生了,此网站专门针对各种设备创建了各种的训练场景,以创建工程、设置参数的方式完成网络模型的训练。基本步骤:添加训练数据->选择模型->设置训练参数->点击开始训练->训练结果准确度达到要求->生成指定设备的可执行代码->直接将代码下载到设备运行。直达网址:https://studio.edgeimpulse.com/login
3.使用OpenMV采集训练数据
在进行神经网络模型训练前需要先将训练用的数据准备好,本次训练目的为垃圾分类,那么训练用的数据为图片。打开OpenMV IDE,在菜单处选择【工具->数据集编辑器->新数据集】。
自己随便选择一个位置创建文件夹命名为【数据集】(随便),并点击选择文件夹。
之后会进入如下界面,首先可以创建你想要分类的所有文件夹(也称为标签),之后依次在每个标签下拍下此类物品的各角度照片,建议在100张左右即可。
具体的操作如下(动图,因为有5MB大小限制,动图较模糊):
根据自己的所需,创建对应的标签并采集完足够的素材,展现效果如下:
4.上传Edge Impulse进行训练
4.1注册账号并创建Edge Impulse工程
打开Edge Impulse网站,注册就是一般流程没有什么特殊性,就不描述了。登陆成功后会跳转到创建工程页面,按下图步骤创建工程。
之后会进入指引页面,因为是外文网站,所以整个网页内容都是英文的,英语不好的小伙伴建议使用带网页翻译功能的浏览器为好,我用的是Google Chrome浏览器自带翻译插件。翻译后页面效果如下。
4.2 上传训练集
虽然OpenMV IDE带有直接上传至Edge Impulse工程的功能,但是经过实测,并不是那么好用,会因为各种原因如防火墙等影响导致无法上传,所以建议直接手动上传就可以了。点击左侧“数据采集”导航栏,进入上传数据页面,点击上传图标进入上传页面。
按照以下步骤,依次选择文件->设置输入标签->点击上传,完成一组标签内容的上传,重复此步骤,直至将采集到的全部的照片上传完毕。
上传完之后,再次点击左侧“数据采集”导航栏,重新进入上传数据页面,将会看到已上传的数据详情。
4.3 生成特征
首先点击左侧“创建冲动”导航按钮,进行输入数据对象、训练模型的选择,并点击保存,如下动图所示。【为方便处理,一般建议图像大小为96×96或160×160】
点击右侧图像导航栏按钮,选择RGB模式,点击保存,保存后会自动跳转至生成特征界面。
点击生成特征按钮,等待生成完毕,会显示右侧的生成特征的3维图像。如果显示失败,则证明采集的图像特征不明显,导致特征有重叠部分,则需要重新对训练的图像进行筛选后上传训练。
4.4 进行训练
至此万事具备只欠东风,进入迁移学习训练界面,依次设置训练的参数,如训练周数:越大训练次数越多、时间越长,相应的结果准确率也越有可能高。学习率:此值决定了训练的速度,如果过拟合过快,则要降低此值。最低置信度:比较好理解,就是准确率达到百分之多少,标记为识别成功。然后选择神经网络架构,可根据自己设置冲动是设置的图片大小进行选择,也可以使用默认的,每个神经网络架构训练出来的模型结果也是不同的,可以自己实验最符合自己要求的结构。之后点击开始训练,等待训练完成即可。
如果最后的结果准确率不满意,可尝试更改参数重新训练,如果一直不够理想,可返回之前的步骤重新设置图片大小以及训练参数等,甚至进一步考虑自己采集的训练集图像是不是特征不明显,导致的准确率低。在结果准确率满意的情况下,可进行下一步。
5.在OpenMV上部署
进入部署界面,选择生成OpenMV固件,点击构建,会自动下载生成的OpenMV代码和模型。
下载下来的压缩包内包含训练完的模型“trained.tflite”,OpemMV执行模型的代码“ei_image_classification.py”,以及模型内部包含的所有分类标签“labels.txt”。将OpenMV生成的U盘内的文件全部删除掉,然后将下载下来的压缩包内的文件全部复制进去,为保证OpenMV离线状态下可自动执行识别程序,将复制进去后的“ei_image_classification.py”重命名为“main.py”。
之后将U盘内的main.py文件在OpenMV IDE内打开,并运行,此时观察串口终端内会输出识别结果以及准确率。输出的标签后面都跟着一个数值,此数值代表识别结果认为是此标签的可能性,=1表示识别程序认为100%是此标签的物品。
如果想让其输出的结果为中文,则可打开labels.txt文件,并将其内部的英文标签对应位置翻译为中文保存即可,修改完毕后效果如下。
识别出结果后,可修改代码通过串口输出识别到的结果给其他单片机使用,串口相关的具体代码已在文中有过说明,自己修改吧。😄😄😄
五、错误情况问题 【更新时间2021-8-04】
这段时间以来,很多小伙伴在实践此文章的神经网络部署方面出现了各种各样的问题,再此做一个汇总和解决方案。
1.“OSError” 错误
出现最多的无非是OSError:,如下图样式。翻译过来呢是说:目前,仅支持float32输入类型。当然也有出现float16错误的。
最早出现此情况的一个朋友在我们共同的努力下,成功解决了此问题,虽然解决方法比较玄学,但是最终锁定了原因出在训练模型的参数配置上。下图是问他最终什么原因他说的原话,他说把所有图片放到了open mv 的flash中,不过我觉得这个应该是不需要的,根本原因应该是在训练的配置上。
解决思路如下:
①首先要查看自己的open MV是否为4Plus,若不是,则无法使用此例程。
②之后连接open MV IDE进行固件升级,确保为最新的固件。
③下载我提供的,我训练好并且经过验证没有任何问题的固件【点此下载-gitee】,部署到自己的open MV上运行,看是否还有异常问题。
④-1如果运行还有异常,说明您的open MV有问题,请更换官方正版open MV 4Plus再次尝试。
④-2如果运行无异常,则说明您训练的模型有问题,请重新训练。
⑤如果锁定是训练问题,请仔细检查自己采集的训练图像,看特征是否明显。请重新创建一个新的训练工程,按照步骤重新配置,可以尽量使用默认参数,这里因为本人并不是专业的人工智能人员,故这里的参数也无法给您提供建议,自己多尝试尝试吧,只要思路正确,找到问题所在,总能够解决。
2.运行速度慢
其实本来作为openMV来说,运行此类程序就是比较勉强的,所以出现帧率很低的情况也属正常,但是在注意一些细节的情况下也有优化的空间。比如调低图像的生成特征的分辨率,适当调整训练参数,以及更改为更适合的模型结构等,自己多试试总结总结吧。
3.“MemoryError” 内存不足
还有小伙伴出现了以下的错误,提示说内存不足,注意这里的内存指的是RAM,是运行内存空间,而不是flash或者SD大小。出现这种情况,一般表明您的open MV不是4Plus,所以是无法运行此垃圾识别的程序的。
4.其他问题
如出现“trained.tflite – 无法读取压缩包数据”,这样的问题,请仔细检查是否将模型放置到了openMV的代码空间内,或者查看名称是否有写错误等。
其实除了上述3个问题外,其他问题基本都属于细节性问题了,自己静下心来再仔细检查检查吧。
5.解决问题一般思路
其实如何解决问题,如何找到问题的关键所在,是一种非常重要的能力。在自己的项目出现问题时,首先要将思路理清。一般首先将项目分为几个大的部分,通过控制变量法依次排除掉可以确定没问题的部分,将问题锁定在某一个范围内,再次重复此过程直至找到问题的根源所在。
比如咱们这个项目,可以首先将其分为两大块:①open MV ②训练的模型。
接下来就要大致锁定问题出在①部分还是②部分。在这里有两个思路:
(1)将自己训练好的模型放到其他运行过此类代码并且确认完好的openMV上去执行,这样可以确认自己训练的模型是否出现问题;
(2)去下载别人训练好并且经过验证的模型,然后在自己的openMV上执行,这样可以查看自己openMV是否出现问题。
如果是模型出现问题,则要考虑是采集的训练集有问题?特征生成配置有问题?训练参数有问题?还是最后的模型结构不匹配?
如果是openMV有问题,则考虑固件是否为最新版?是否硬件出了问题?是否为正版设备?还是说型号搞错了?等等。
当然,本项目比较简单,可以比较容易的锁定问题所在,如果是比较庞大的项目,则可能需要更多的排除次数,但是思路是一致的。
来源:超级网吧