本文简介K210的图像识别
首先要明确的是图像识别 ,我们需要识别的是什么 ,如何让机器代替我们识别 ,我们人类认识世界万物 知道这个是玫瑰花 这个是豆腐 这个是火车 这个是我们一步一步的学习所得的,当然如何让机器识别出物品 也需要机器学习
关于具体的机器学习 神经网络 不再这里讲解
本例程所用的硬件环境是最近新出的K210开发板模块 maixpy
https://maixpy.sipeed.com/zh/course/ai/train/maixhub.html
网站导航 | 地址 |
---|---|
MaixPy 唯一官方文档官网 | 官网: maixpy.sipeed.com 中文站: cn.maixpy.sipeed.com |
MaixPy 例程仓库 | github: github/maixpy_script 国内:gitee/maixpy_scripts |
MaixPy 源码 | github: MaixPy |
开发板资料下载 | dl.sipeed.com |
Sipeed WIKI | wiki.sipeed.com |
MaixPy 是将 Micropython 移植到 K210(一款 64 位双核带硬件 FPU、卷积加速器、FFT、Sha256 的 RISC-V CPU ) 的一个项目, 支持 MCU 常规操作, 更集成了硬件加速的 AI
机器视觉和麦克风阵列,1TOPS
算力 核心模块却不到¥50
, 以快速开发具有极低成本和体积实用的 AIOT
领域智能应用。
MicroPython 是基于 Python3 的语法做的一款解析器,包含了 Python3 的大多数基础语法, 主要运行在性能和内存有限的嵌入式芯片上。(注意 Micropython 不包含 Python3 的所有语法)
MaixPy 让我们在 K210 上编程更加简单快捷, 我们也将源代码开源在 github 上
使用 MaixPy 可以做很多有趣的事情, 具体可以看这里
比如我们需要扫描 I2C 总线上的设备,不需要复杂的开发环境和工程,只需要通过串口发送如下代码即可实现:
from machine import I2C # 导入内置库
i2c = I2C(I2C.I2C0, freq=100000, scl=28, sda=29) # 定义一个I2C对象, 使用I2C0, 频率100kHz,SCL引脚是IO28, SDA 引脚是IO29
devices = i2c.scan() # 调用函数扫描设备
print(devices) # 打印设备
同样,我们需要实现一个呼吸灯,只需要如下代码:
from machine import Timer,PWM
from board import board_info
import time
tim = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
ch = PWM(tim, freq=500000, duty=50, pin=board_info.LED_G)
duty=0
dir = True
while True:
if dir:
duty += 10
else:
duty -= 10
if duty>100:
duty = 100
dir = False
elif duty<0:
duty = 0
dir = True
time.sleep(0.05)
ch.duty(duty)
实时拍照:
import sensor
import image
import lcd
lcd.init()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
while True:
img=sensor.snapshot()
lcd.display(img)
我们本次例程 就以识别汽车为例
我们要识别汽车要有足够的图片来训练模型 然后将模型装载在K210中
关于模型的训练
我们如何获取汽车的图片 可以从网络上选择下载 这是不方便的, 合理的运用爬虫是最合理的
下面是我下载好的图片
K210训练的模型 需要修改图片的格式 (224*224)
https://www.maixhub.com/index/mtrain/help.html
采用下面的工具将图片大小修改 (最后都会放上百度网盘链接)
或者
如果想要拍照训练模型这里有拍照脚本
上面标注的两个脚本文件是 加载在K210进行拍照的 拍下的照片自动保存在内存卡中
#本程序用于使用Maixpy系列开发板进行图像数据收集
#使用方法:
#0.为Maixpy系列开发板插入一张TF卡
#1.将本程序烧入Maixpy系列开发板中,上电运行
#2.阅读用户说明后,按下BOOT按钮进行拍照,LCD屏左下角会显示当前存储目录
#3.拍照后的图片默认存储在TF卡目录./0/下面,拍摄成功后,会在LCD屏正下方显示"OK!"字样
#长按BOOT按钮可切换拍照后图片的默认存储路径(默认支持./1/~./10/,也就是十分类,如有其他需求,请修改Classes_num变量)
#4.完成所有分类的图像采集后,将TF卡中的数据移动到电脑上,并自行按需修改存储图片的文件夹名称(标签名),
#并删除没有用到的标签文件夹
#5.用DataAssitant数据集与处理软件进行处理。
#6.将得到的.zip压缩文件上传MaixHub进行训练。
#PS:
#DataAssitant数据集与处理软件下载地址:
#https://cdn.sipeed.com/donwload/12kj346465hjkv68g7c/DataAssitantV1.0.zip
#数据集收集教程:https://www.maixhub.com/index.php/index/mtrain/help.html
##################################################################################
#This program is used for image data collection using Maixpy series development boards#Instructions:
# 0. Insert a TF card for Maixpy series development board
# 1. Burn the program into the Maixpy series development board and power on
# 2. After reading the user's instructions, press the BOOT button to take a picture,
# the current storage directory will be displayed in the lower left corner of the LCD screen
# 3. The picture after taking the picture is stored in the TF card directory by default.
# Long press the BOOT button to switch the default storage path of pictures after taking pictures
# (default supports ./1/~./10/, which is very class, if you have other needs, please modify the Classes_num variable)
# 4. After completing the collection of all the classified images, move the data in the TF card to the computer,
# and modify the folder name (tag name) where the pictures are stored as needed,
# And delete unused tag folders# 5. Use DataAssitant dataset and processing software for processing.
# 6. Upload the obtained .zip compressed file to MaixHub for training.
#PS:
#DataAssitantDataset and processing software download address:
#https: //cdn.sipeed.com/donwload/12kj346465hjkv68g7c/DataAssitantV1.0.zip
#Dataset Collection Tutorial: https://www.maixhub.com/index.php/index/mtrain/help.html
##################################################################################
import sensor, image, time, lcd
import utime
import uos
import sys
from Maix import GPIO
from board import board_info
from fpioa_manager import fm
##################################################################################
Classes_num = 10 #十分类(10个标签文件夹)|Tier 10 (10 tag folders)
##################################################################################
bg = lcd.RED
text = lcd.WHITE
boot_press_flag = 1
start = time.ticks_ms()
end = time.ticks_ms()
ui_num = 0
image_save_path = "/sd/image/"#图片保存目录头
claass = 0#文件夹名
image_num = 0#图像文件保存名
#完整图片保存路径image_save_path+claass+"/"
image_data = image.Image()#图像
shoot_flag = 0
##################################################################################
def boot_key_irq(pin_num):#
global ui_num
global boot_press_flag,start,end
global claass
global image_num
global shoot_flag
#utime.sleep_ms(100)
if(boot_press_flag == 1):
start = time.ticks_ms()
boot_press_flag = 0
elif(boot_press_flag == 0):
end = time.ticks_ms()
boot_press_flag = 1
time_diff = time.ticks_diff(end, start)
if(time_diff >120 and time_diff <500):
print("短按拍摄",time_diff)
ui_num = 1
if(ui_num == 1):#已进入拍摄
image_num = image_num + 1
#shoot_flag = 1
image_data.save("/sd/image/"+str(claass)+"/"+str(utime.ticks_us())+str(image_num)+".jpg")
lcd.draw_string(160, 224,"ok!"+str(image_num))
utime.sleep_ms(500)
#print(str(utime.ticks_us()))
elif(time_diff >=500 and time_diff <=2000):
print("长按切换文件夹",time_diff)
if(ui_num == 1):#已进入拍摄
claass = claass + 1
if(claass >Classes_num-1):#让保存路径始终有效
claass = 0
elif():
boot_press_flag = 1
start = 0
end = 0
#print("key", pin_num)
fm.register(board_info.BOOT_KEY, fm.fpioa.GPIOHS0, force=True)
boot_key=GPIO(GPIO.GPIOHS0, GPIO.IN, GPIO.PULL_UP)
boot_key.irq(boot_key_irq, GPIO.IRQ_BOTH, GPIO.WAKEUP_NOT_SUPPORT, 7)
##################################################################################
def draw_help_ui():#显示帮助页面
lcd.draw_string(60, 10, "Data Collection Assistant", text, bg)
lcd.draw_string(20, 30, "1.Press the BOOT button to take a pi-", text, bg)
lcd.draw_string(10, 50, "cture. The current storage directory", text, bg)
lcd.draw_string(10, 70, "will be displayed in the lower left corner of the LCD screen.", text, bg)
lcd.draw_string(10, 90, "corner of the LCD screen.", text, bg)
lcd.draw_string(20, 120, "2.Long press BOOT button to switch ", text, bg)
lcd.draw_string(10, 140, "the default storage folder path of pi-", text, bg)
lcd.draw_string(10, 160, "ctures after taking photos.", text, bg)
lcd.draw_string(10, 200, "--Press the BOOT button to start shoot", text, bg)
##################################################################################
def not_found_tf():#没有找到TF卡
lcd.clear(bg)
lcd.draw_string(10, 90, "ERROR: ", text, bg)
lcd.draw_string(20, 110, "No TF card found", text, bg)
lcd.draw_string(20, 130, "The Reason:", text, bg)
lcd.draw_string(20, 150, "1.No TF card inserted", text, bg)
lcd.draw_string(20, 170, "2.TF card model is not supported", text, bg)
lcd.draw_string(20, 190, "3.TF card format is not FAT", text, bg)
##################################################################################
def init():#初始化相关
i = 0
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_vflip(1)
sensor.run(1)
lcd.init(type=1, freq=15000000, color=bg)
try:
uos.mkdir("/sd/image")#创建image文件夹,顺路TF卡检测
for i in range(Classes_num):
uos.mkdir("/sd/image/"+str(i))#创建image/1-10文件夹,顺路TF卡检测
print("/sd/image/"+str(i))
except Exception as e:
if(str(e) == "[Errno 17] EEXIST"):
pass
else:
not_found_tf()
sys.exit(0)
finally:
try:
for i in range(Classes_num):
uos.mkdir("/sd/image/"+str(i))#创建image/1-10文件夹,顺路TF卡检测
#print("/sd/image/"+str(i))
lcd.draw_string(0, 224,str(claass))
except Exception as e:
if(str(e) == "[Errno 17] EEXIST"):
pass
else:
not_found_tf()
sys.exit(0)
draw_help_ui()
##################################################################################
def image_ui():
global image_data
image_data = sensor.snapshot() # Take a picture and return the image.
lcd.display(image_data,oft=(0,0)) # Display on LCD
lcd.draw_string(0, 224,"/sd/image/"+str(claass)+"/")
##################################################################################
def main():#主函数
init()
while(True):
if(ui_num == 1):
image_ui()
if __name__ == '__main__':
try:
main()
except Exception as e:
print(e)
lcd.clear(bg)
lcd.draw_string(10, 90, "ERROR:unknown mistake", text, bg)
lcd.draw_string(20, 110, "Please contact sipeed for help.", text, bg)
上述拍照的效果并不太好
在电脑上加载摄像仪进行拍照脚本
import cv2
import time
import os
cap = cv2.VideoCapture(1)
cap.set(3,320)
cap.set(4,240)
def snapShotCt():
# camera_idx的作用是选择摄像头。如果为0则使用内置摄像头,比如笔记本的摄像头,用1或其他的就是切换摄像头。
# ret, frame = cap.read() # cao.read()返回两个值,第一个存储一个bool值,表示拍摄成功与否。第二个是当前截取的图片帧。
count = 100
while True:
# 从摄像头读取图片
success, img = cap.read()
cv2.imwrite("save_pic/" + str(count) + '.jpg', img)
cv2.imshow('image', img)
time.sleep(0.5) # 休眠一秒 可通过这个设置拍摄间隔,类似帧。
count += 1
# ret, frame = cap.read() # 下一个帧图片
# 保持画面的连续。waitkey方法可以绑定按键保证画面的收放,通过q键退出摄像
# k = cv2.waitKey(1)
# if k == '27':
# break
# 或者得到800个样本后退出摄像,这里可以根据实际情况修改数据量,实际测试后800张的效果是比较理想的
if count >= 100:
break
# 关闭摄像头,释放资源
snapShotCt()
cap.realease()
cv2.destroyAllWindows()
# camera.release()
# cv2.destroyAllWindows()
有了照片 开始标注
vott标注和 labimag
标注400张
labeIimg 标注
标注完之后 打包上传到云端
注意上传的格式要求 labeIimg
VOTT
确定了方案,就可以采集照片了, 目标是采集目标分辨率的照片, 以下都以224x224
为例, 将他们按照目录进行分类收集, 比如 采集整理好的目录结构:
采集照片有以下几种方式:
使用开发板采集到SD
卡, 直接采集成需要的分辨率 (推荐)
手机拍照, 然后使用预处理工具处理成需要的分辨率, 注意, 处理完后一定要手动检查数据是否符合要求, 不然可能影响训练精准度
使用现成的图片, 使用预处理工具处理成需要的分辨率, 注意, 处理完后一定要手动检查数据是否符合要求, 不然可能影响训练精准度
使用 这个 脚本来进行采集图片
按照 图片采集脚本使用说明 采集图片到SD
卡
将SD
卡中的图片拷贝到电脑, 整理成上面的目录结构, 所有图片的分辨率为224x224
注意 SD
卡需要硬件支持SPI
通信, 并使用MBR(msdos)
分区方案,并格式化为FAT32
格式
整理成上面的目录结构, 最后所有图片分辨率已经是224x224
, 如果不是, 后面还需要处理
如果所有图片已经都是224x224
的分辨率,本步骤跳过
如果有图片不是224x224
, 那么需要先把所有图片处理成224x224
, 稍后Maixhub
会推出预处理工具
对于不同的训练类型:
跳过此步骤
注意, 一定要先保证分辨率正确, 再标注
标注有以下两种工具:
vott: 点击这里下载
labelimg : 点击这里下载
如下图, 选择图片目录, 以及存放(输出)目录,(左边PascalVOC
格式不要改动)按下W
按键后, 用鼠标框出图片中的物体, 并且给它给一个标签, 这里使用了ball
作为标签(一张图中可以有多个物体)
保存后会生成xml
格式的文件(即PascalVOC
格式),每张图对应一个xml
文件
结果文件: 完成后目录结构和文件如下:
或者两级目录
将前面处理好的数据集进行打包, 使用zip
压缩,暂不支持其它格式, 而且文件不要超过20MiB
一个文件夹一个分类, 分类名(标签/label)就是文件夹名
vott的输出zip
文件结构:
labelimg 的输出zip
文件结构
新建了一个labels.txt
, 输入标记的标签, 每行一个, 比如这里:
ball
toy
这是必须的, 否则数据无效
然后目录结构如下
或者两级labelimg输出
到 https://www.maixhub.com/mtrain.html 创建训练任务
选择训练类型
填写邮箱地址, 用于接收结果, 包括成功(模型等文件) 和 失败 (失败原因)结果
如果要求填写机器码(不要求则跳过此步骤):
Please Send Bellow Data to Sipeed --> [email protected]:
6f80dccbe29**********cc7e9d69f92
Generate key end
其中 6f80dccbe29**********cc7e9d69f92
就是对应开发板的机器码
上传打包好的zip
格式的数据集
点击创建训练任务
训练结果(成功或者失败) 会发送到邮箱。 是一个zip
压缩文件, 解压后仔细阅读README.txt
,使用了中英文对使用方法进行了说明
默认是在有最新版固件的情况下, 将结果文件全部拷贝到SD
卡根目录, 断电插入开发板, 然后上电就可以运行了
如果需要将模型放到flash
, 烧录方法参考MaixPy
教程
以管理员的方式运行
下载模式 高速改为低速模式
# -*-encoding=utf-8-*-
import cv2
import os
# path = 'switch.avi'
# k = input('输入要打开的摄像头索引:')
# path = int(k)
# if path < 0:
# path = 0
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
cv2.namedWindow('mycamera')
num = -1
start = False
imagePath = 'JPEGImages'
fileKey = [str(i) for i in range(1, 10)] + ['b', 'c', 'd', 'x', 'y']
while True:
success, frame = cap.read()
if success:
# frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)
# frame = cv2.flip(frame, 1)
cv2.imshow('mycamera', frame)
key = cv2.waitKey(4) & 0xff
if start is False and key == ord('a'):
print('按下:a, 开始')
start = True
if start is True and key == ord('p'):
print('按下:p, 暂停')
start = False
# index = str(key - ord('0'))
index = chr(key)
if index in fileKey:
# name = int(index) * 1000000
name = index + '0'*6
k = input("请输入照片对上次于%s的偏移:" % name)
# name = name + int(k)
name = index + k.zfill(6)
num = int(k)
print('按下:' + index + ', name:' + name)
imagePath = 'JPEGImages' + index
elif key == ord('q'):
break
elif key not in [255, ord('a'), ord('p')]:
print('无效按键')
if start is False or num < 0:
continue
currentPath = 'VOCdevkit/VOC2007/' + imagePath
if not os.path.exists(currentPath):
print('create image directory', currentPath)
os.makedirs(currentPath)
cv2.imwrite('VOCdevkit/VOC2007/' + imagePath + '/' + name + '.jpg',
frame)
# cv2.imwrite('VOCdevkit/VOC2007/JPEGImages/' + name + '.jpg', frame)
num += 1
name = name[0] + '%06d' % num
cv2.destroyWindow('mycamera')
cap.release()