Created: June 26, 2023 11:30 PM
Created by: Luser
Type: MCU
1.Maxipy固件烧录教程
(1)Maxipy简介
[Maxipy](https://github.com/sipeed/MaixPy "MaxiPy" "Maxipy")是一个将Micropython语言移植到K210芯片上的工程,该工程发行的固件名字就叫做Maxipy,Maxipy固件中集成了大量的机器视觉算法,并全部兼容openmv的算法库,然后将这个固件烧录到K210上之后就可以在该单片机上运行python,相对于传统的C语言单片机开发,难度极大降低、效率极大提高。 新买的K210开发板都已经刷好了默认固件,可以直接使用。但是建议刷到最新版固件,新版固件bug少。在连接好摄像头和液晶屏后即可上电测试。首先需要下载安装开发环境Maxipy IDE,下载之后双击exe安装包,根据个人喜好进行安装即可,无特殊要求,安装路径全为英文名是一个良好的习惯。然后使用USB连接开发板和电脑USB口,将开发板连接Maxipy IDE,点击IDE左下角连接,点击左上角文件,打开该K210项目仓库中的scripts文件夹中的hello_world.py,点击左下角三角,即可实现在线运行。上述已经实现了板子的运行,并将摄像头拍摄到的画面显示在液晶屏上。但是还是建议参考下节方法来为板子升级最新固件。
(2)固件烧录方法
首先下载最新MaxiPy固件,然后下载固件烧录工具kflash_gui。MaxiPy固件常用的有如下三种,可根据自己需要选择,建议在不适用openmv库时烧录最小固件,可以节约内存。下载站中官方的readme文档中有对每个固件的介绍。
- maixpy_v*_no_lvgl.bin: MaixPy固件, 不带LVGL版本.(LVGL是嵌入式GUI框架, 写界面的时候需要用到)
- maixpy_v*_full.bin: 完整版的MaixPy固件(MicroPython + OpenMV API + lvgl )
- maixpy_v0.3.1_minimum.bin: MaixPy固件最小集合,不支持 MaixPy IDE, 不包含OpenMV的相关算法
得到MaxiPy固件和烧录工具kflash_gui后,点击进入kflash_gui子文件夹,该文件夹内是固件烧录工具kflash_gui,注意不要删除里边的任何文件,里边的kflash_gui.exe即为可运行的固件烧录程序。
双击kflash_gui.exe,即可打开gui界面。
固件烧录程序打开后,将K210开发板通过USB数据线连接电脑,正常情况下,USB插入电脑后,烧录程序会自动识别并自动填写相关配置。 如果没有自动识别和自动配置,可进行手动配置:
- 开发板选择对应的开发板,我们使用的是MAXI DOCK
- 下载到选择Flash
- 串口设置中的端口选项选择K210开发板连接电脑后对应的端口,可以通过设备管理器查看端口号
- 波特率为1500000
- 速度模式为低速模式
配置完成之后,点击打开文件按钮进行浏览到你存放Maxipy固件的文件夹,找到所要下载的固件。
之后单击下载按钮,即可开始烧录固件,此时只需等待固件烧录完成即可。
之后点击OK按钮即可,烧录成功后开发板会自动重启,到此已完成固件烧录,可以关闭固件烧录软件。
2.IDE使用教程
(1)IDE安装
IDE的下载和安装前述已经说明,和普通软件安装过程无异。安装好之后,桌面会出现名字为MaxiPy IDE的应用程序,双击运行,界面如下所示。左下角有两个按钮,上边的链条形状为连接按钮,用来连接开发板和IDE,下边的三角按钮为运行按钮,用来启动脚本在线运行,在未连接开发板时链接按钮为绿色,运行按钮为灰色。
(2)开发板和IDE连接
安装好IDE后,将K210开发板通过USB数据线和电脑连接,然后点击左下角的连接按钮,此时如果电脑连接了多个串口设备时,IDE会提示你选择要连接的串口,此时需要选择K210对应的串口,和固件烧录时的串口号是一致的。连接成功后,连接按钮变成红色,运行按钮变成绿色。此时点击红色连接按钮可断开连接。
(3)在线运行脚本程序
成功连接开发板和IDE后,即可进行程序的编写和在线运行。可以点击IDE的文件按钮,选择新建脚本文件编写自己的程序,也可以点击打开文件按钮选择已有的脚本程序。这里选择已有的脚本程序,脚本程序存放scripts子文件夹内,通过IDE的文件按钮,浏览到脚本文件夹,全选所有的脚本,点击打开,打开上述脚本文件。
可以点击左上方的下三角按钮切换脚本,如下图所示。
[
此时可以修改程序,之后点击运行按钮即可在线运行脚本。如果提示out of memory可以重启开发板来释放内存。运行界面如下所示。
界面下方为log输出,可以看运行数据;右上方为图像窗口,可以展示摄像头画面。此时运行按钮变为红色,再次点击可停止运行。
(4)脱机运行
上述程序运行是在线运行,只能连接IDE才可以运行。如果要实现脱机运行,需要将脚本程序烧写到K210开发板。烧写方法为:首先停止在线运行,然后点击IDE工具栏,点击工具选项,点击将打开的脚本保存到开发板的boot.py,然后点击是,等待完成即可。之后即可不依赖IDE,实现上电脱机运行。
3.神经网络模型使用方法
神经网络应用除了烧录上述对应的脚本程序之外,还要将训练好的神经网络模型放到K210开发板SD的根目录,训练过程后续会有讲解。例如在人体检测时使用了深度学习方法,其对应的网络模型存放在该仓库的model子文件夹内,名称为class.kmodel。将其拷贝到SD卡根目录即可。其他模型操作方法一样。.kmodel文件是K210支持的网络模型文件。
4.初次运行 Hello World
按照国际惯例,我们都会运行一下hello world程序来作为我们进行开发的开始,通过这个程序可以验证一下K210开发板的基本功能和外设是否正常,例如液晶屏和摄像头。该程序将摄像头采集到的图像显示在LCD液晶屏上,并在IDE串口打印帧率,源码如下:
# Hello World Example
#
# Welcome to the MaixPy IDE!
# 1. Conenct board to computer
# 2. Select board at the top of MaixPy IDE: `tools->Select Board`
# 3. Click the connect buttion below to connect board
# 4. Click on the green run arrow button below to run the script!
import sensor, image, time, lcd
lcd.init(freq=15000000)
sensor.reset() # Reset and initialize the sensor. It will
# run automatically, call sensor.run(0) to stop
sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.QVGA) # Set frame size to QVGA (320x240)
sensor.skip_frames(time = 2000) # Wait for settings take effect.
clock = time.clock() # Create a clock object to track the FPS.
while(True):
clock.tick() # Update the FPS clock.
img = sensor.snapshot() # Take a picture and return the image.
lcd.display(img) # Display on LCD
print(clock.fps()) # Note: MaixPy's Cam runs about half as fast when connected
# to the IDE. The FPS should increase once disconnected.
5.机器视觉应用
硬件平台:K210开发板,软件平台:openmv机器视觉库。机器视觉类应用不能使用最小固件,因为最小固件里没有集成openmv机器视觉库。
(1)最大色块识别
该应用对应scripts文件夹下的find_blob.py,其的目的是认为事先设定一个LAB色系颜色阈值,就可以选中一种颜色,程序会识别出图像中该颜色的最大连通区域,并给出区域中心坐标。该应用主要接住了openmv机器视觉库的find_blob库函数,然后采用任意一种排序算法对检测到的所有与预设颜色一致的色块面积大小进行排序,最终将面积最大的色块框起来就是代求的最大色块。
找最大色块的函数:
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
#寻找两个最大的色块,ID存在max_ID中,便于调用
def find_2_max(blobs):
max_size=[0,0]
max_ID=[-1,-1]
for i in range(len(blobs)):
if blobs[i].pixels()>max_size[0]:
max_ID[1]=max_ID[0]
max_size[1]=max_size[0]
max_ID[0]=i
max_size[0]=blobs[i].pixels()
elif blobs[i].pixels()>max_size[1]:
max_ID[1]=i
max_size[1]=blobs[i].pixels()
return max_ID
源码如下:
# 找最大色块
'''
色块对象是由 image.find_blobs 返回的。
image.find_blobs(thresholds, invert=False, roi, x_stride=2, y_stride=1, area_threshold=10, pixels_threshold=10, merge=False,margin=0, threshold_cb=None, merge_cb=None)
查找图像中指定的色块。返回 image.blog 对象列表;
【thresholds】 必须是元组列表。 [(lo, hi), (lo, hi), ..., (lo, hi)] 定义你想追踪的颜色范围。 对于灰度图像,每个元组需要包含两个值 - 最小灰度值和最大灰度值。 仅考虑落在这些阈值之间的像素区域。 对于 RGB565 图像,每个元组需要有六个值(l_lo,l_hi,a_lo,a_hi,b_lo,b_hi) - 分别是 LAB L,A 和 B通道的最小值和最大值。
【area_threshold】若色块的边界框区域小于此参数值,则会被过滤掉;
【pixels_threshold】若色块的像素数量小于此参数值,则会被过滤掉;
【merge】若为 True,则合并所有没有被过滤的色块;
【margin】调整合并色块的边缘。
对于 RGB565 图像,每个元组需要有六个值(l_lo,l_hi,a_lo,a_hi,b_lo,b_hi)
分别是 LAB中 L,A 和 B 通道的最小值和最大值。
L的取值范围为0-100,a/b 的取值范围为-128到127。
'''
import sensor
import image
import lcd
import time
lcd.init()
sensor.reset(freq=24000000, set_regs=True, dual_buff=True)
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1) #设置摄像头后置
sensor.run(1)
#红色阈值[0],绿色阈值[1],蓝色阈值[2]
rgb_thresholds =[
(30, 100, 15, 127, 15, 127),
(0, 80, -70, -10, -0, 30),
(0, 30, 0, 64, -128, -20)]
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
while True:
img=sensor.snapshot()
blobs = img.find_blobs([rgb_thresholds[0]])
if blobs:
b = find_max(blobs)
img.draw_rectangle(b[0:4]) # 画框
img.draw_cross(b[4], b[5]) # 画中心十字
lcd.rotation(2)
lcd.display(img)
6.串口传输
(1)K210与电脑串口助手进行信息交互
from machine import UART
from fpioa_manager import fm
## 端口映射 IO7→RX1,IO8→TX1
fm.register(7, fm.fpioa.UART1_RX, force=True)
fm.register(8, fm.fpioa.UART1_TX, force=True)
#初始化串口
uart = UART(UART.UART1, 115200, read_buf_len=4096, timeout_char=1000)
uart.write('Experiment start!\r\n')
while True:
text=uart.read() #读取数据
if text: #如果读取到了数据
print(text.decode('utf-8')) #REPL打印
uart.write('I got\r\n'+text.decode('utf-8')) #数据回传
(2)K210使用固定数据格式发送数据给单片机(或上位机)
```
通信数据传输测试
K210按照 <0xb3,0xb3,rect,0x0d,0x0a> 的数据格式向单片机或上位机发送数据
rect是找到的最大色块的x,y,w,h数据,也就是总共应该发送8个16进制数
```
import sensor, image, lcd, time
from machine import UART
from fpioa_manager import fm
# 端口映射 IO6→RX1,IO7→TX1
fm.register(7, fm.fpioa.UART1_RX, force=True)
fm.register(8, fm.fpioa.UART1_TX, force=True)
# 初始化串口
uart = UART(UART.UART1, 115200, read_buf_len=4096, timeout_char=1000)
uart.write('Experiment start!\r\n')
# 设置针头针尾
u_start=bytearray([0xb3,0xb3])
u_over=bytearray([0x0d,0x0a])
# 初始化摄像头
sensor.reset(freq=24000000, set_regs=True, dual_buff=True)
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(30)
sensor.set_vflip(True) #设置摄像头后置
clock = time.clock()
sensor.run(True)
# 找最大的色块
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
#红色阈值[0],绿色阈值[1],蓝色阈值[2]
rgb_thresholds =[
(30, 100, 15, 127, 15, 127),
(0, 80, -70, -10, -0, 30),
(0, 30, 0, 64, -128, -20)]
row_data=[0,0,0,0]
while True:
times=0
clock.tick()
img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)#不断拍照,进行鱼眼校正
blobs = img.find_blobs([rgb_thresholds[0]])
if blobs:
b = find_max(blobs)
row_data[0:4]=b[0:4]
img.draw_rectangle(b[0:4]) # 画框
img.draw_cross(b[4], b[5]) # 画中心十字
print(row_data)
#传输数据给单片机
uart_buf = bytearray(row_data)
uart.write(u_start)
uart.write(uart_buf)
uart.write(u_over)
使用串口助手接收到的数据如下(串口助手中使用16进制显示的方式查看):
7.小车循迹实现
参考文章:
循迹识别小车:(四)OpenMV4部分_openmv循迹_南沐ヾ的博客-CSDN博客
K210中有许多openmv的API可以直接使用,不过细节处还是有些许不同:
import sensor, lcd, image, time, math
from machine import UART
from fpioa_manager import fm
# 端口映射 IO7→RX1,IO8→TX1
fm.register(7, fm.fpioa.UART1_RX, force=True)
fm.register(8, fm.fpioa.UART1_TX, force=True)
# 初始化串口
uart = UART(UART.UART1, 115200, timeout_char=1000)
# 设置针头针尾
u_start=bytearray([0xb3,0xb3])
u_over=bytearray([0x0d,0x0a])
# 初始化摄像头
sensor.reset(freq=24000000, set_regs=True, dual_buff=True)
sensor.set_contrast(1)
sensor.set_pixformat(sensor.RGB565)
# QVGA:320x240 resolution for the camera sensor.
# QQVGA:160x120 resolution for the camera sensor.
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(30)# 让新的设置生效。
clock = time.clock()# 跟踪FPS帧率
sensor.set_vflip(True)
sensor.set_hmirror(True)
sensor.run(True)
# 设置巡线的阈值
GRAYSCALE_THRESHOLD = [(0, 44, -128, 127, -128, 127)]
# 每个roi为(x, y, w, h),线检测算法将尝试找到每个roi中最大的blob的质心。
# 然后用不同的权重对质心的x位置求平均值,其中最大的权重分配给靠近图像底部的roi,
# 较小的权重分配给下一个roi,以此类推。
ROIS = [
(0, 90, 160, 20, 0.7),
(0, 50, 160, 20, 0.35),
(0, 0, 160, 20, 0.08)
]#三个区域
# roi代表三个取样区域,(x,y,w,h,weight),代表左上顶点(x,y)宽高分别为w和h的矩形,
# weight为当前矩形的权值。注意本例程采用的QQVGA图像大小为160x120,roi即把图像横分成三个矩形。
# 三个矩形的阈值要根据实际情况进行调整,离机器人视野最近的矩形权值要最大,
# 如上图的最下方的矩形,即(0, 100, 160, 20, 0.7)
# 计算权值和。遍历上面的三个矩形,r[4]即每个矩形的权值。
weight_sum = 0
for r in ROIS: weight_sum += r[4]# r[4] is the roi weight.
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
# 弧度转角度
def degrees(radians):
return (180 * radians) / math.pi
def car_run():
centroid_sum = 0
for r in range(3): # 三个区域分别寻找色块
# r[0:4] is roi tuple.
#找到视野中的线,merge=true,将找到的图像区域合并成一个
blobs = img.find_blobs(GRAYSCALE_THRESHOLD, roi=ROIS[r][0:4], merge=True,area_threshold=100,margin=3)
if blobs:
b = find_max(blobs)
img.draw_rectangle(b.rect())
img.draw_cross(b.cx(),b.cy())
centroid_sum += b.cx() * ROIS[r][4] # 乘权值
center_pos = 0
center_pos = (centroid_sum / weight_sum)# 中间公式
deflection_angle = 0
deflection_angle = -math.atan((center_pos-80)/60)# 计算角度
deflection_angle = math.degrees(deflection_angle)# 弧度制换成角度制
return [int(deflection_angle)+90] # 要发送的数据哪怕只有一位也需要进行打包,否则发送的数据会是0
# 入口参数:发送数据长度,发送的数据;Transmit_Data(4,[0x00, 0x00, 0x00, 0x00])
def Transmit_Data(TX_Length, TX_Buffer):
global uart
TX_Data = bytearray([0x59, TX_Length]) #bytearray是二进制数据组成的序列,每个元素8bit二进制组成
for i in range(TX_Length):
TX_Data.append(TX_Buffer[i])
uart.write(TX_Data)
while(True):
times=0
clock.tick()
img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)# 不断拍照,进行鱼眼校正
row_data=car_run()
print(row_data)
# 传输数据给单片机
uart_buf = bytearray(row_data)
uart.write(u_start)
uart.write(uart_buf)
uart.write(u_over)