官方资料

新手上路:10分钟快速上手 · OpenMV中文入门教程

官方文档:OpenMV 库函数(中文文档)

OpenMV 库函数(官方文档)

相关社群:OpenMV官网

GitHub - OpenMV 开源项目库


疑难解答

看到不会的代码 –> 复制到 OpenMV 库函数(中文文档) 搜索栏里粘贴搜索

(PS:中文文档较老旧,有些新固件新函数查询不到,建议跳转至OpenMV 库函数(官方文档))查找;如果电脑上有OpenMV IDE的,也可以按此路径[C:/Users/你的用户名/AppData/Roaming/OpenMV/openmvide/html/index.html]拖动到浏览器本地查询(内容与英文官方文档一致))

写代码时不知道函数意义的,将光标停留其上,会显示定义

善用快捷键

  • Ctrl + F 查找
  • Ctrl + / 批量注释/取消注释
  • Tab / Shift +Tab 批量缩进/取消缩进

性能优化

一般情况下,除法运算比乘法运算更消耗性能。


如果你想用OpenMV拼一台简单追小球车:

拼装图例

https://book.openmv.cc/project/zhui-xiao-qiu-de-xiao-8f665d28-project-pan-tilt-md.html


LAB

LAB含义

Lab:

L-亮度

a:正红负绿;

b:正黄负蓝


感光元件

sensor模块,用于设置感光元件的参数。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
import sensor#引入感光元件的模块

# 设置摄像头
sensor.reset()#初始化感光元件
sensor.set_pixformat(sensor.RGB565)#设置为彩色
sensor.set_framesize(sensor.QVGA)#设置图像的大小
sensor.skip_frames()#跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定。

# 一直拍照
while(True):
img = sensor.snapshot()#拍摄一张照片,img为一个image对象

使用图像的统计信息

如果我想知道一个区域内的平均颜色或者占面积最大的颜色?

使用统计信息——Statistics!

ROI感兴趣的区域

ROI感兴趣的区域
roi的格式是(x, y, w, h)的tupple.

  • x:ROI区域中左上角的x坐标
  • y:ROI区域中左上角的y坐标
  • w:ROI的宽度
  • h:ROI的高度

OpenMV 自动计算阈值+找色块代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sensor, image, time

sensor.reset ()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA) #分辨率160*120
#sensor.set_auto_gain (False) #颜色跟踪必须关闭自动增益
#sensor.set_auto_whitebal (False) #颜色跟踪必须关闭白平衡
sensor.skip_frames (time = 2000)

Range = (0,0,160,120) #设定感兴趣区域

#【0】记忆色块阈值
for i in range (200):
img = sensor.snapshot()
img.draw_rectangle((75,55,10,10),color=(255,0,0)) #圈出搜寻区域
Statistics = img.get_statistics (roi=(75,55,10,10)) #得到搜寻区域内的图像统计信息
Threshold = [Statistics.l_min(),Statistics.l_max(), #从统计信息中得到色块阈值
Statistics.a_min(),Statistics.a_max(),
Statistics.b_min(),Statistics.b_max()]
print(Threshold)

while(True):
img = sensor.snapshot()
#【1】找色块
for blob in img.find_blobs([Threshold],roi=Range, pixels_threshold=100, area_threshold=100, merge=True, margin=10):
#【2】输出
img.draw_rectangle(blob.rect()) #画框
img.draw_cross(blob.cx(), blob.cy()) #画十字
print(blob.cx(), blob.cy()) #打印坐标

寻找色块

视频教程4 - 颜色识别:https://singtown.com/learn/49993/

find_blobs函数

追踪小球是OpenMV用的最多的功能了,在10分钟快速上手
通过find_blobs函数可以找到色块.我们来讨论一下,find_blobs的细节。

1
image.find_blobs(thresholds, roi=Auto, x_stride=2, y_stride=1, invert=False, area_threshold=10, pixels_threshold=10, merge=False, margin=0, threshold_cb=None, merge_cb=None)

这里的参数比较多。

  • thresholds是颜色的阈值,注意:这个参数是一个列表,可以包含多个颜色。如果你只需要一个颜色,那么在这个列表中只需要有一个颜色值,如果你想要多个颜色阈值,那这个列表就需要多个颜色阈值。注意:在返回的色块对象blob可以调用code方法,来判断是什么颜色的色块。
1
2
3
4
5
6
7
8
red = (xxx,xxx,xxx,xxx,xxx,xxx)
blue = (xxx,xxx,xxx,xxx,xxx,xxx)
yellow = (xxx,xxx,xxx,xxx,xxx,xxx)

img=sensor.snapshot()
red_blobs = img.find_blobs([red])

color_blobs = img.find_blobs([red,blue, yellow])
  • roi是“感兴趣区”。在使用统计信息中已经介绍过了。

    left_roi = [0,0,160,240]
    blobs = img.find_blobs([red],roi=left_roi)

  • x_stride 就是查找的色块的x方向上最小宽度的像素,默认为2,如果你只想查找宽度10个像素以上的色块,那么就设置这个参数为10:

    blobs = img.find_blobs([red],x_stride=10)

  • y_stride 就是查找的色块的y方向上最小宽度的像素,默认为1,如果你只想查找宽度5个像素以上的色块,那么就设置这个参数为5:

    blobs = img.find_blobs([red],y_stride=5)

  • invert 反转阈值,把阈值以外的颜色作为阈值进行查找

  • area_threshold 面积阈值,如果色块被框起来的面积小于这个值,会被过滤掉

  • pixels_threshold 像素个数阈值,如果色块像素数量小于这个值,会被过滤掉

  • merge 合并,如果设置为True,那么合并所有重叠的blob为一个。
    注意:这会合并所有的blob,无论是什么颜色的。如果你想混淆多种颜色的blob,只需要分别调用不同颜色阈值的find_blobs。

1
2
3
4
5
all_blobs = img.find_blobs([red,blue,yellow],merge=True)

red_blobs = img.find_blobs([red],merge=True)
blue_blobs = img.find_blobs([blue],merge=True)
yellow_blobs = img.find_blobs([yellow],merge=True)
  • margin 边界,如果设置为1,那么两个blobs如果间距1一个像素点,也会被合并。

阈值

一个颜色阈值的结构是这样的:

1
red = (minL, maxL, minA, maxA, minB, maxB)

元组里面的数值分别是L A B 的最大值和最小值。

如果想在IDE的图像里获取这个阈值,见:10分钟快速上手

在新版的IDE,有更方便的阈值选择工具,见下面。

颜色阈值选择工具

OpenMV 的IDE里加入了阈值选择工具,极大的方便了对于颜色阈值的调试。

首先运行hello world.py让IDE里的framebuffer显示图案。
img
然后打开 工具 → Mechine Vision → Threshold Editor
img

点击 Frame Buffer可以获取IDE中的图像,Image File可以自己选择一个图像文件。

img

拖动六个滑块,可以实时的看到阈值的结果,我们想要的结果就是,将我们的目标颜色变成白色,其他颜色全变为黑色。

img

blobs是一个列表

find_blobs对象返回的是多个blob的列表。(注意区分blobs和blob,这只是一个名字,用来区分多个色块,和一个色块)。
列表类似与C语言的数组,一个blobs列表里包含很多blob对象,blobs对象就是色块,每个blobs对象包含一个色块的信息。

1
blobs = img.find_blobs([red])

blobs就是很多色块。

可以用for循环把所有的色块找一遍。

1
2
for blob in blobs:
print(blob.cx())

对于for循环的使用,见python背景知识

blob色块对象

blob有多个方法:

  • blob.rect() 返回这个色块的外框——矩形元组(x, y, w, h),可以直接在image.draw_rectangle中使用。

  • blob.x() 返回色块的外框的x坐标(int),也可以通过blob[0]来获取。

  • blob.y() 返回色块的外框的y坐标(int),也可以通过blob[1]来获取。

  • blob.w() 返回色块的外框的宽度w(int),也可以通过blob[2]来获取。

  • blob.h() 返回色块的外框的高度h(int),也可以通过blob[3]来获取。

  • blob.pixels() 返回色块的像素数量(int),也可以通过blob[4]来获取。

  • blob.cx() 返回色块的外框的中心x坐标(int),也可以通过blob[5]来获取。

  • blob.cy() 返回色块的外框的中心y坐标(int),也可以通过blob[6]来获取。

  • blob.rotation() 返回色块的旋转角度(单位为弧度)(float)。如果色块类似一个铅笔,那么这个值为0180°。如果色块是一个圆,那么这个值是无用的。如果色块完全没有对称性,那么你会得到0360°,也可以通过blob[7]来获取。

  • blob.code() 返回一个16bit数字,每一个bit会对应每一个阈值。举个例子:

    blobs = img.find_blobs([red, blue, yellow], merge=True)

如果这个色块是红色,那么它的code就是0001,如果是蓝色,那么它的code就是0010。注意:一个blob可能是合并的,如果是红色和蓝色的blob,那么这个blob就是0011。这个功能可以用于查找颜色代码。也可以通过blob[8]来获取。

  • blob.count() 如果merge=True,那么就会有多个blob被合并到一个blob,这个函数返回的就是这个的数量。如果merge=False,那么返回值总是1。也可以通过blob[9]来获取。
  • blob.area() 返回色块的外框的面积。应该等于(w * h)
  • blob.density() 返回色块的密度。这等于色块的像素数除以外框的区域。如果密度较低,那么说明目标锁定的不是很好。
    比如,识别一个红色的圆,返回的blob.pixels()是目标圆的像素点数,blob.area()是圆的外接正方形的面积。

# OpenMV串口通信 – 传数据包

OpenMV端
1
2
3
#串口输出数据包
data = bytearray([0xa3,0xb3, obj.classid()+1, pos[0], pos[1], 0xc3]) #帧头 + 帧头 + 数字 + x坐标 + y坐标 + 帧尾
usart3.write(data)
STM32端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//接openmv的串口2的中断服务程序:
void USART2_IRQHandler(void)
{
static int i=0;
if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET)
{
openmv_data[i++] = USART ReceiveData(USART2); //接收数据
if(openmy_data[0]!=0xa3) i=0; //判断第一个帧头
if((i==2)&&(openmv_data[1]!=0xb3)) i=0; //判断第二个帧头
if(i==6) //代表一组数据传输完毕
{
if( openmv_data[5] == 0xc3 ) //判断帧尾
{
/********************更新数据************************/
//【1】识别到的数字:
openmv_num = openmv_data[2];
//【2】数字的x、y坐标:
openmv_x = openmv_date[3];
openmv_y = openmv_date[4];
}
i = 0; //清空数组
}
}
}

多模板匹配


脱机调阈值的实现方法

二值化 - 低于阈值就黑色,高于阈值就白色


UART

UART通信简介

两个UART直接相互通信。发送UART将控制设备(如CPU)的并行数据转换位串行形式,以串行的形式将其发送到接收UART。只需要两条线即可在两个UART之间传输信息:

img

UART属于异步通信,没有时钟信号。它会在数据包中增加开始和停止位。这些位定义了数据包的开始和结束,因此接收UART知道何时读取这些数据。
当接收UART检测到起始位时,它将以特定的波特率的频率读取(数据传输速度的度量),以每秒比特数(bps)表示。两个UART必须以大约相同的波特率工作,发送的接收UART之间的波特率只能相差约10%。

原文链接:https://blog.csdn.net/qq_52608074/article/details/122297014

1
2
3
4
5
from pyb import UART

uart = UART(3, 9600)
uart.write('hello')
uart.read(5) # read up to 5 bytes

UART 3 RX -> P5 (PB11)
UART 3 TX -> P4 (PB10)

OpenMV3 M7 / OpenMV4 H7/ OpenMV4 H7 Plus上增加:
UART 1 RX -> P0 (PB15)
UART 1 TX -> P1 (PB14)

在 OpenMV RT 上不能用pyb模块,只能使用以下machine模块:

1
2
3
4
5
from machine import UART

uart = UART(1, 9600)
uart.write('hello')
uart.read(5) # read up to 5 bytes

OpenMV RT1062只有串口1,对应 P4 P5 引脚。 UART 1 RX -> P5 (PB11)
UART 1 TX -> P4 (PB10)


OpenMV配置图及引脚图

H7Plus引脚图

Tables OpenMV2 M4 OpenMV3 M7 OpenMV4 H7 OpenMV4 H7 Plus OpenMV RT1062
Pin 9 10 10 10 14
ADC 1 1 1 1 1
DAC 1 1 1 1 0
SPI 1 1 1 1 1
I2C 1 2 2 2 1
UART 1 2 2 2 1
Servo 2 3 3 2 4
CAN bus 0 1 1 1 1
电源按键 0 0 0 0 1
自定义按键 0 0 0 0 1
引脚耐受 5V 5V 5V 5V 3.3V
引脚电平 3.3V 3.3V 3.3V 3.3V 3.3V
IC STM32F427 STM32F765 STM32H743 STM32H743 IMXRT1062
RAM 256KB 512KB 1MB 32MB + 1MB 32MB + 1MB
Flash 1MB 2MB 2MB 32MB + 2MB 16MB
频率 180MHz 216MHZ 480MHZ 480MHZ 600MHZ
标配感光元件 OV7725(30W像素) OV7725(30W像素) OV7725(30W像素) OV5640(500W像素) OV5640(500W像素)

tf.classify(path, img[, roi[, min_scale=1.0[, scale_mul=0.5[, x_overlap=0[, y_overlap=0]]]]])¶

img 上运行TensorFlow Lite图像分类模型,并返回一个 tf_classification 对象列表。 这个方法对图像使用可控滑动方式,执行多次模型(默认算法在整个图像帧上只执行网络一次)。

path 是OpenMV Cam的磁盘上的 .tflite 模型的路径。 为了节省内存,只通过这一个函数,模型被加载到内存中,执行并释放所有内存。 传递 “person_detection” 可以从你的OpenMV Cam的内部FLASH加载内置的人检测模型。

roi 是感兴趣区域矩形元组(x, y, w, h)。如果没有指定时,它等于图像的整个大小。 只有在 roi 内的像素才被操作。

min_scale 控制网络的缩放尺度。在默认值网络不缩放。 当值为0.5时,会允许检测大小为图像roi的50%的目标。

scale_mul 控制有多少种不同的缩放尺度可以检测出来。 滑动窗口方法的工作原理是将默认的尺度1乘以 scale_mul 同时结果大于 min_scale 。 scale_mul 的默认值是0.5,测试出每次变化可以减少50%的大小。但是,0.95只会减少5%的尺寸。

x_overlap 控制与下一个滑动窗口的区域检测器重叠的百分比。 值为0意味着没有重叠,0.95意味着95%的重叠。

y_overlap 控制与下一个滑动窗口的区域检测器重叠的百分比。 值为0意味着没有重叠,0.95意味着95%的重叠。


image.get_statistics([thresholds[, invert=False[, roi[, bins[, l_bins[, a_bins[, b_bins[, difference]]]]]]]****])

计算 roi 中每个颜色通道的平均值、中值、众值、标准偏差、最小值、最大值、下四分值和上四分值,并返回一个数据对象。 请参见 statistics 对象以获取更多信息。您也可以使用 image.get_statsimage.statistics 来调用这一方法。 如果传递 thresholds 列表,则直方图信息将仅从阈值列表中的像素计算得出。

thresholds 必须是元组列表。 [(lo, hi), (lo, hi), ..., (lo, hi)] 定义你想追踪的颜色范围。 对于灰度图像,每个元组需要包含两个值 - 最小灰度值和最大灰度值。 仅考虑落在这些阈值之间的像素区域。 对于RGB565图像,每个元组需要有六个值(l_lo,l_hi,a_lo,a_hi,b_lo,b_hi) - 分别是LAB L,A和B通道的最小值和最大值。 为方便使用,此功能将自动修复交换的最小值和最大值。 此外,如果元组大于六个值,则忽略其余值。相反,如果元组太短,则假定其余阈值处于最大范围。

备注

获取所跟踪对象的阈值,只需在IDE帧缓冲区中选择(单击并拖动)跟踪对象。 直方图会相应地更新到所在区域。然后只需写下颜色分布在每个直方图通道中起始与下降位置。 这些将是 thresholds 的低值和高值。 由于上下四分位数据相差微小,故手动确定阈值为佳。

您还可以通过进入OpenMV IDE中的 工具 ->机器视觉 ->阈值编辑器 并从GUI窗口中拖动滑块来确定颜色阈值。


文件系统

10分钟上手教程中已经简单介绍过OpenMV上的文件系统。这里是一些细节。

MicroPyhon的文件系统是FatFS。

根目录

路径都是以根目录为起点。

当插入sd卡后,根目录就是SD卡;不插入sd卡,根目录就是内置的Flash。

如果需要,你可以在SD卡上,新建一个空文件:/flash/SKIPSD,这会避免挂载SD卡,当然,你可以使用os.mount来手动挂载SD卡。

绝对路径与相对路径

绝对路径是以根目录为起点的,相对路径是以当前目录为起点的。
比如:

1
2
image.save("/example.jpg")
Copy

中的”/example.jpg”就是绝对路径。会存放在根目录/下。
比如:

1
2
3
image.save("./pic/example.jpg")
image.save("pic/example.jpg")
Copy

这就是相对路径,表示当前路径下的pic文件夹下的example.jpg文件。

MicroPython的文件读写

http://www.cnblogs.com/feeland/p/4477535.html

MicroPython的OS模块

在代码中,可以使用os库,来进行新建目录,新建文件之类的操作。

  • os.listdir([dir])
    如果没有参数,列出当前目录。如果给了参数,就列出参数所代表的目录。
  • os.chdir(path)
    改变当前目录
  • os.getcwd()
    获得当前目录
  • os.mkdir(path)
    新建一个新的目录
  • os.remove(path)
    删除文件
  • os.rmdir(path)
    删除目录
  • os.rename(old_path, new_path)
    重命名文件
  • os.stat(path)
    获得文件或者路径的状态

OpenMV的默认文件

img

默认情况下,OpenMV的磁盘有三个文件。

  • main.py
    上电自动运行这个文件的代码。
  • openmv.inf
    windows驱动文件。
  • README.txt
    没什么用,你可以看一下。

你需要了解的相关基础概念:

LAB 阈值 ROI UART IO

你需要实现的相关功能:

find_blogs 巡线



QQ截图对比前后差异


自适应阈值探究:

不同亮度下

理论最深LAB:(0, 0, -128, -128, -128, -128)

理论最亮LAB:(100, 100, 127, 127, 127, 127)

全黑

实际最深LAB:(1, 53, -1, 80, 0, 66)


已实现的方案:

1.巡线、判断十字路口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 巡线(红线) + 判断十字路口
import sensor, image, time, pyb
import display
from pyb import UART, LED
LED(1).on()
LED(2).on()
LED(3).on()
uart = pyb.UART(3, 9600)
uart.init(9600, bits=8, parity=None, stop=1)
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA2)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
lcd = display.SPIDisplay()
sensor.skip_frames (time = 1000)
clock = time.clock()
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
theta_err = 90
rotate_angle = 90
crossroad = 0
while(True):
clock.tick()
img = sensor.snapshot().binary([(4, 70, 18, 127, -125, 127)])
blobs = img.find_blobs([(20, 255)], x_stride=1, y_stride=1, pixels_threshold=180, area_threshold=180, merge=True)
if blobs:
max_blob = find_max(blobs)
rotate_angle = 180 * (max_blob.cx() / img.width())
if max_blob.w()<img.width()*0.7:
crossroad = 0
else:
crossroad = 1
img.draw_rectangle(max_blob.rect())
img.draw_cross(max_blob.cx(), max_blob.cy(),color=(255,0,0))
line = img.get_regression([(20, 255)], pixels_threshold=120, area_threshold=120, robust = True)
if line:
if line.theta()>90:
theta_err = line.theta()-90
else:
theta_err = line.theta()+90
img.draw_line(line.line(), color = 127)
chimera = theta_err*0.7 + rotate_angle*0.3
img.draw_string(10, 10, str(int(chimera)), color=(0,185,255), scale=2, x_spacing=-5)
data=bytearray([0xa3,0xb3,int(chimera),int(crossroad),0xc3])
uart.write(data)
print(" ")
print("当前帧数:", clock.fps(), "fps")
print("是否检测到十字路口:", crossroad)
print("推荐转向", chimera)
lcd.write(img)

2.匿名凌霄无人机识别黑色矩形区域降落:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 无人机降落(黑色矩形区域)
import sensor, image, time, pyb, display
from pyb import UART
import json
import ustruct

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time=2000) # 跳帧
sensor.set_auto_gain(False) # 关闭自动增益
sensor.set_auto_whitebal(False) # 关闭白平衡
lcd = display.SPIDisplay() # 初始化lcd屏幕。
clock = time.clock()
uart3 = UART(3, 500000, timeout_char=1000)
# 色块阈值
#blue_threshold = (31, 66, -37, -9, 5, -32)
#yellow_threshold = (50, 78, -54, 108, 20, 78)
blue_threshold = (0,30,-128,10,-128,127)
yellow_threshold = (58,100,-128,127,-128,127)
# 测距常数
k = 950
moshi=0
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:
clock.tick()
img = sensor.snapshot()
blobs_blue = img.find_blobs([blue_threshold], roi = (0,0,img.width(),img.height()))
if blobs_blue:
# 在矩形内检测到蓝色,说明是己方宝藏
b = find_max(blobs_blue) # 将返回数据赋值给b
if b.density()>0.7 and b.w()>img.width()*0.3:
img.draw_cross(b[5], b[6]) # cx, cy
img.draw_cross(int(img.width()*0.5), int(img.height()*0.5), size=5, color=(0,255,0))
#img.draw_cross(0, 0, size=5, color=(0,255,0))
img.draw_line((int(img.width()*0.5), int(img.height()*0.5),b[5], b[6]), color=(255,0,255))
a_x=b.cx()-int(img.width()*0.5)
a_y=b.cy()-int(img.height()*0.5)
img.draw_rectangle(b.rect(), color = (0, 0, 255))
FH = bytearray([0xAA,0xFF,0xCC,0x00,moshi,a_x,a_y,0x00,0x00])
lens = len(FH)#数据包大小
FH[3] = lens-6;#有效数据个数
i = 0
sum = 0
sum1 = 0
#和校验
while i<(lens-2):
sum = sum + FH[i]
sum1 = sum1 + sum
i = i+1
FH[lens-2] = sum;
FH[lens-1] = sum1;
#传输数据给单片机
#uart_buf = bytearray(row_data)
uart3.write(FH)
print(FH)
print("x偏移:",a_x,"y偏移:",a_y,"\n")
# 在检测到的真宝藏上画蓝框
# 屏幕显示
img.draw_string(10, 10, str(a_x) + "," + str(a_y), color=(255,0,70), scale=2, x_spacing=-5, mono_space=True)
else:
print("未检测")
else:
print("未检测")
img_copy = img.copy(x_scale=128/img.width(),y_scale=160/img.height())
lcd.write(img_copy) # 让LCD屏显示拍到的图像

3.识别矩形、返回矩形四角坐标:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 识别矩形 + 返回矩形四角坐标
import sensor, image, time, pyb
import display
from pyb import UART
uart = pyb.UART(3, 9600)
uart.init(9600, bits=8, parity=None, stop=1)
lcd = display.SPIDisplay()
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA2)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
sensor.skip_frames(time = 500)
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
for r in img.find_rects(threshold = 10000):
if r.w() > 15 and r.h() > 15:
img.draw_rectangle(r.rect(), color = (255, 0, 0), scale = 4)
corner = r.corners()
img.draw_circle(corner[0][0], corner[0][1], 3, color = (135, 255, 0), thickness = 2, fill = False)
img.draw_circle(corner[1][0], corner[1][1], 3, color = (0, 70, 255), thickness = 1, fill = False)
img.draw_circle(corner[2][0], corner[2][1], 3, color = (0, 70, 255), thickness = 1, fill = False)
img.draw_circle(corner[3][0], corner[3][1], 3, color = (0, 70, 255), thickness = 1, fill = False)
corner1_str = f"({corner[0][0]},{corner[0][1]})"
corner2_str = f"({corner[1][0]},{corner[1][1]})"
corner3_str = f"({corner[2][0]},{corner[2][1]})"
corner4_str = f"({corner[3][0]},{corner[3][1]})"
print("矩形关键点坐标:")
print("corner1 = " + corner1_str + "\n"
+ "corner2 ="+ corner2_str + "\n"
+ "corner3 ="+ corner3_str + "\n"
+ "corner4 ="+ corner4_str )
dataxy=bytearray([0xa1,0xb1,corner[0][0],corner[1][0],corner[2][0],corner[3][0],corner[0][1],corner[1][1],corner[2][1],corner[3][1],0xc1])
uart.write(dataxy)
img.draw_string(10, 10, corner1_str, color=(255,0,70), scale=2, x_spacing=-5, mono_space=True)
img.draw_string(10, 10, "\n" + corner3_str, color=(255,0,70), scale=2, x_spacing=-5, mono_space=True)
lcd.write(img)
4.延时拍照片并存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# OpenMV 延时拍照片并存储
# 注意:您需要一张TF卡来运行

import sensor
import time
import display # 引入lcd屏幕

sensor.reset() # 初始化sensor
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种
sensor.set_framesize(sensor.QVGA) # 设置图像像素大小 QVGA (320x240)
lcd = display.SPIDisplay() # 初始化lcd屏幕。
sensor.skip_frames(time=2000) # 让新的设置生效

img = sensor.snapshot()
img.save("example.jpg") # or "example.bmp" (or others)
img_copy = img.copy(x_scale=128/img.width(),y_scale=160/img.height())

while(True):
lcd.write(img_copy) # 让LCD屏显示拍到的图像

5.计算指定区域最暗/最亮阈值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import sensor, image, time, pyb
import display # 引入lcd屏幕

# 启用串口 P4,P5 (TX,RX)
from pyb import UART
uart = pyb.UART(3, 9600)
uart.init(9600, bits=8, parity=None, stop=1)

# 初始化lcd屏幕
lcd = display.SPIDisplay()

# 初始化摄像头
sensor.reset()
#sensor.set_vflip(True) # 打开垂直翻转模式
#sensor.set_hmirror(True) # 打开水平镜像模式
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式为RGB565格式
sensor.set_framesize(sensor.QVGA) # 设置图像大小
#sensor.set_framesize(sensor.QQVGA2) # 返回128x160图像帧大小以适配OpenMV配套液晶屏。
sensor.set_auto_gain(False) # 颜色跟踪必须关闭自动增益
sensor.set_auto_whitebal(False) # 颜色跟踪必须关闭白平衡
#sensor.set_brightness(3000) # 设置亮度为3000
sensor.skip_frames(time = 500) # 跳过帧

clock = time.clock()

# 给x毫秒时间准备,计算ROI区域中阈值,作为目标阈值之一;
for i in range (200):
img = sensor.snapshot()
img.draw_rectangle((0,0,img.width(),img.height()),color=(255,0,0)) # 在视野中框出指定ROI区域,用于提取其中颜色阈值
statistics = img.get_statistics (roi=(0,0,img.width(),img.height())) #得到搜寻区域内的图像统计信息#thresholds=(10, 80, -20, 50, -20, 50),

color_L_min = statistics.l_min() #分别赋值LAB的众数
color_L_max = statistics.l_max()
color_A_min = statistics.a_min()
color_A_max = statistics.a_max()
color_B_min = statistics.b_min()
color_B_max = statistics.b_max()

#基于色块容错空间
relative_darkest_threshold = (color_L_min, color_L_min+1, color_A_min, color_A_min+1, color_B_min, color_B_min+1)
relative_brightest_threshold = (color_L_max-1, color_L_max, color_A_max-1, color_A_max, color_B_max-1, color_B_max)
print(relative_darkest_threshold)
print(relative_brightest_threshold)
print("\n")

while True:
clock.tick()
img = sensor.snapshot()
print(relative_darkest_threshold)
print(relative_brightest_threshold)
print("\n")


整理已经弄清楚的知识点:

(不明白的去官方函数库查询)

1.初始化:
摄像头(感光元件)初始化:
1
2
3
4
5
6
7
8
9
10
11
import sensor

sensor.reset() #重启感光元件
#sensor.set_vflip(True) #打开垂直翻转模式
#sensor.set_hmirror(True) #打开水平镜像模式
sensor.set_pixformat(sensor.RGB565) # or sensor.GRAYSCALE # 设置相机模块的像素模式:RGB565
#sensor.set_framesize(sensor.QQVGA) #设置返回图像的帧大小,分辨率160*120
sensor.set_framesize(sensor.QQVGA2) # 返回128x160图像帧大小以适配OpenMV配套液晶屏。
sensor.set_auto_gain(False) #颜色跟踪必须关闭自动增益
sensor.set_auto_whitebal(False) #颜色跟踪必须关闭白平衡
sensor.skip_frames (time = 1000) #跳过启动后的前x毫秒以求稳定
图像处理(机器视觉)初始化:
1
import image
时钟(跟踪运行时间)初始化:
1
2
3
import time

clock = time.clock() # 创建一个计时器对象,用于计算每秒钟的帧数。
屏幕显示驱动初始化:
1
2
3
4
import display # 引入lcd屏幕

lcd = display.SPIDisplay() # 初始化lcd屏幕。
lcd.write(img) # 让LCD屏显示拍到的图像(在while中)
串口通信(UART)初始化:
1
2
3
4
5
6
7
8
import pyb
# 启用串口 P4,P5 (TX,RX)
# OpenMV4 H7 Plus, OpenMV4 H7, OpenMV3 M7, OpenMV2 M4 的UART(3)是P4-TX P5-RX
# OpenMV RT 只有串口UART(1),对应P4-TX P5-RX; OpenMV4 H7 Plus, OpenMV4 H7, OpenMV3 M7 的UART(1)是P0-RX P1-TX
from pyb import UART # 从pyb库中启用uart串口通信

uart = UART(3, 9600, timeout_char=1000) #创建UART对象,使用第3个UART端口,设置波特率为9600
uart.init(9600, bits=8, parity=None, stop=1, timeout_char=1000) #初始化UART总线,时钟速率9600,每个字符占8位,不进行奇偶化校验,停止位数量为1
LED(补光灯)初始化:
1
2
3
4
5
from pyb import LED 

LED(1).on() #红
LED(2).on() #绿
LED(3).on() #蓝
引脚高低电平初始化:
1
2
3
4
5
from pyb import Pin # pyb模块包含与插件相关的特定函数,引入引脚

# 初始化P0引脚,输出高电平
p0 = Pin('P0', Pin.OUT)
p0.value(1)
2.全局变量定义、自定义函数定义:
最大色块更新函数:
1
2
3
4
5
6
7
8
# 最大色块更新函数
def find_max(blobs): # 定义函数 find_max , 接受参数 blobs , 意在从分析出的色块中找出面积最大的
max_size=0 # 定义一个变量 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
ROI(感兴趣区域):
1
ROI = (x,x,x,x)
Thresholds(阈值列表):
1
2
3
4
5
thresholds  = [(4, 70, 18, 127, -125, 127), # for red1
(0, 35, 20, 75, -25, 45), # for red2
(13, 70, 3, 55, 4, 40), # for red3
(30, 100, -64, -8, -32, 32), # for green
(0, 30, 0, 64, -128, 0)] # for blue
3.启动任务进程:
标准进程框架:
1
2
3
4
5
6
while(True):
clock.tick() # 计算从上一次调用tick()到现在的时间,以确定每秒钟的帧数
img = sensor.snapshot() # 拍照片,返回图像
# img = sensor.snapshot().binary([(4, 70, 18, 127, -125, 127)]) # 拍二值化图片(阈值自定义),返回图像
x'x'x'x'x'x'x'x'x (其他任务、算法)
lcd.write(img) # 让LCD屏显示拍到的图像(一般放在进程末尾)
算法:
1.最大色块算法(包含十字路口检测):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 最大色块算法
# 在图像中寻找符合指定条件的色块。找到的色块将被存储在变量blobs中,在调用find_max函数找出最大色块
blobs = img.find_blobs([(20, 255)], x_stride=1, y_stride=1, pixels_threshold=180, area_threshold=180, merge=True)#, suit,[thresholds[threshold_index]]
if blobs:
max_blob = find_max(blobs)

# #定义横纵坐标偏移量,意为探测计算出的色块的中心点距离视野中心点的偏移量,旨在引导小车左右转和加速减速
# x_error = max_blob.cx()-img.width()/2 # 左右偏移量 = 色块中心点横坐标-视野中心点横坐标,范围[-64,64]
# y_error = max_blob.cy()-img.height()/2 # 纵偏移量 = 色块中心点纵坐标-视野中心点纵坐标,范围[-80,80]

rotate_angle = 180 * (max_blob.cx() / img.width())

#定义变量crossroad,判断是否为十字路口
if max_blob.w()<img.width()*0.7:
crossroad = 0
else:
crossroad = 1

img.draw_rectangle(max_blob.rect()) # 画矩形,根据计算出的最大色块大小
img.draw_cross(max_blob.cx(), max_blob.cy(),color=(255,0,0)) # 画交叉十字,根据计算出的最大色块中心点位置
2.直线算法:
1
2
3
4
5
6
7
8
9
# 直线算法
line = img.get_regression([(20, 255)], pixels_threshold=120, area_threshold=120, robust = True)
if line:
# rho_err = abs(line.rho())-img.width()/2
if line.theta()>90:
theta_err = line.theta()-90
else:
theta_err = line.theta()+90
img.draw_line(line.line(), color = 127)
3.最大色块+直线融合算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    # 色块算法
# 在图像中寻找符合指定条件的色块。找到的色块将被存储在变量blobs中,在调用find_max函数找出最大色块
blobs = img.find_blobs([(20, 255)], x_stride=1, y_stride=1, pixels_threshold=180, area_threshold=180, merge=True)#, suit,[thresholds[threshold_index]]
if blobs:
max_blob = find_max(blobs)

# #定义横纵坐标偏移量,意为探测计算出的色块的中心点距离视野中心点的偏移量,旨在引导小车左右转和加速减速
# x_error = max_blob.cx()-img.width()/2 # 左右偏移量 = 色块中心点横坐标-视野中心点横坐标,范围[-64,64]
# y_error = max_blob.cy()-img.height()/2 # 纵偏移量 = 色块中心点纵坐标-视野中心点纵坐标,范围[-80,80]

rotate_angle = 180 * (max_blob.cx() / img.width())

#定义变量crossroad,判断是否为十字路口
if max_blob.w()<img.width()*0.7:
crossroad = 0
else:
crossroad = 1

img.draw_rectangle(max_blob.rect()) # 画矩形,根据计算出的最大色块大小
img.draw_cross(max_blob.cx(), max_blob.cy(),color=(255,0,0)) # 画交叉十字,根据计算出的最大色块中心点位置

# 直线算法
line = img.get_regression([(20, 255)], pixels_threshold=120, area_threshold=120, robust = True)
if line:
# rho_err = abs(line.rho())-img.width()/2
if line.theta()>90:
theta_err = line.theta()-90
else:
theta_err = line.theta()+90
img.draw_line(line.line(), color = 127)

chimera = theta_err*0.7 + rotate_angle*0.3
# 屏幕显示参数区(如需节约性能,请写固定数值)
# img.draw_string(int(img.width()*0.1), int(img.width()*0.1), str(int(chimera)), color=(0,185,255), scale=int(img.width()*0.016), x_spacing=int(img.width()*0.016)*-2)
img.draw_string(10, 10, str(int(chimera)), color=(0,185,255), scale=2, x_spacing=-5)

# 串口数据定义并发送区
# 要发送的数据:[帧头 a3 b3,中间*个十六位数据,帧尾 c3]
data=bytearray([0xa3,0xb3,int(chimera),int(crossroad),0xc3])
# 发送数据
uart.write(data)

# 串行终端发送区
print(" ")
print("当前帧数:", clock.fps(), "fps")
## print("目标横坐标(0-128):", max_blob.cx())
## print("目标纵坐标(0-160):", max_blob.cy())
## print("横坐标偏离值[-64,64]:", x_error)
## print("纵坐标偏离值[-80,80]:", y_error)
## print("色块转向角度:", max_blob.rotation())
print("是否检测到十字路口:", crossroad)
# print("色块偏离值:", rotate_angle) #[0,180]
# print("线角度:",theta_err) #[0,180]
print("推荐转向", chimera)
4.矩形检测+返回矩形四角坐标算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# -----矩形框部分-----
# 在图像中寻找矩形
for r in img.find_rects(threshold = 10000):
# 判断矩形边长是否符合要求
if r.w() > 15 and r.h() > 15:
# 在屏幕上框出矩形
img.draw_rectangle(r.rect(), color = (255, 0, 0), scale = 4)
# 获取矩形角点位置
corner = r.corners()
# 在屏幕上圈出矩形角点(第一个点通常为左下角那个)
img.draw_circle(corner[0][0], corner[0][1], 3, color = (135, 255, 0), thickness = 2, fill = False)
img.draw_circle(corner[1][0], corner[1][1], 3, color = (0, 70, 255), thickness = 1, fill = False)
img.draw_circle(corner[2][0], corner[2][1], 3, color = (0, 70, 255), thickness = 1, fill = False)
img.draw_circle(corner[3][0], corner[3][1], 3, color = (0, 70, 255), thickness = 1, fill = False)

# 打印四个角点坐标, 角点1的数组是corner[0], 坐标就是(corner[0][0],corner[0][1])
# 角点检测输出的角点排序每次不一定一致,矩形左上的角点有可能是corner0,1,2,3其中一个
corner1_str = f"({corner[0][0]},{corner[0][1]})"
corner2_str = f"({corner[1][0]},{corner[1][1]})"
corner3_str = f"({corner[2][0]},{corner[2][1]})"
corner4_str = f"({corner[3][0]},{corner[3][1]})"
print("矩形关键点坐标:")
print("corner1 = " + corner1_str + "\n"
+ "corner2 ="+ corner2_str + "\n"
+ "corner3 ="+ corner3_str + "\n"
+ "corner4 ="+ corner4_str )

# data1=bytearray([0xa1,0xb1,corner[0][0],corner[0][1],0xc1]) # 定义点坐标1(通常为左下角那个)
# uart.write(data1)

# data2=bytearray([0xa2,0xb2,corner[1][0],corner[1][1],0xc2]) # 定义点坐标2
# uart.write(data2)

# data3=bytearray([0xa3,0xb3,corner[2][0],corner[2][1],0xc3]) # 定义点坐标3
# uart.write(data3)

# data4=bytearray([0xa4,0xb4,corner[3][0],corner[3][1],0xc4]) # 定义点坐标4
# uart.write(data4)

dataxy=bytearray([0xa1,0xb1,corner[0][0],corner[1][0],corner[2][0],corner[3][0],corner[0][1],corner[1][1],corner[2][1],corner[3][1],0xc1]) # 四个点的x坐标和y坐标(通常左下角那个是第一个)
uart.write(dataxy)
# print(dataxy)

img.draw_string(10, 10, corner1_str, color=(255,0,70), scale=2, x_spacing=-5, mono_space=True) # 屏幕显示
img.draw_string(10, 10, "\n" + corner3_str, color=(255,0,70), scale=2, x_spacing=-5, mono_space=True) # 屏幕显示
俯视检测追踪特定颜色矩形算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
blobs_blue = img.find_blobs([blue_threshold], roi = (0,0,img.width(),img.height()))
if blobs_blue:
# 在矩形内检测到蓝色,说明是己方宝藏
b = find_max(blobs_blue) # 将返回数据赋值给b
if b.density()>0.7 and b.w()>img.width()*0.3:
img.draw_cross(b[5], b[6]) # cx, cy
img.draw_cross(int(img.width()*0.5), int(img.height()*0.5), size=5, color=(0,255,0))
#img.draw_cross(0, 0, size=5, color=(0,255,0))
img.draw_line((int(img.width()*0.5), int(img.height()*0.5),b[5], b[6]), color=(255,0,255))
a_x=b.cx()-int(img.width()*0.5)
a_y=b.cy()-int(img.height()*0.5)
img.draw_rectangle(b.rect(), color = (0, 0, 255))
FH = bytearray([0xAA,0xFF,0xCC,0x00,moshi,a_x,a_y,0x00,0x00])
lens = len(FH)#数据包大小
FH[3] = lens-6;#有效数据个数
i = 0
sum = 0
sum1 = 0
#和校验
while i<(lens-2):
sum = sum + FH[i]
sum1 = sum1 + sum
i = i+1
FH[lens-2] = sum;
FH[lens-1] = sum1;
#传输数据给单片机
#uart_buf = bytearray(row_data)
uart3.write(FH)
print(FH)
print("x偏移:",a_x,"y偏移:",a_y,"\n")
# 在检测到的真宝藏上画蓝框
# 屏幕显示
img.draw_string(10, 10, str(a_x) + "," + str(a_y), color=(255,0,70), scale=2, x_spacing=-5, mono_space=True)
else:
print("未检测")
else:
print("未检测")
指定区域阈值检测算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 给x毫秒时间准备,计算ROI区域中阈值,作为目标阈值之一;
for i in range (200):
img = sensor.snapshot()
img.draw_rectangle((0,0,img.width(),img.height()),color=(255,0,0)) # 在视野中框出指定ROI区域,用于提取其中颜色阈值
statistics = img.get_statistics (roi=(0,0,img.width(),img.height())) #得到搜寻区域内的图像统计信息#thresholds=(10, 80, -20, 50, -20, 50),

color_L_min = statistics.l_min() #分别赋值LAB的众数
color_L_max = statistics.l_max()
color_A_min = statistics.a_min()
color_A_max = statistics.a_max()
color_B_min = statistics.b_min()
color_B_max = statistics.b_max()

#基于色块容错空间
relative_darkest_threshold = (color_L_min, color_L_min+1, color_A_min, color_A_min+1, color_B_min, color_B_min+1)
relative_brightest_threshold = (color_L_max-1, color_L_max, color_A_max-1, color_A_max, color_B_max-1, color_B_max)
屏幕图像缩放显示:
1
img_copy = img.copy(x_scale=128/img.width(),y_scale=160/img.height())
屏幕显示参数:
1
2
3
4
5
    # 屏幕显示参数区(如需节约性能,请写固定数值)
# img.draw_string(int(img.width()*0.1), int(img.width()*0.1), str(int(chimera)), color=(0,185,255), scale=int(img.width()*0.016), x_spacing=int(img.width()*0.016)*-2)
img.draw_string(10, 10, str(int(chimera)), color=(0,185,255), scale=2, x_spacing=-5)
img.draw_string(10, 10, corner1_str, color=(255,0,70), scale=2, x_spacing=-5, mono_space=True) # 屏幕显示
mg.draw_string(10, 10, "\n" + corner3_str, color=(255,0,70), scale=2, x_spacing=-5, mono_space=True) # 屏幕显示
串口数据定义并发送:
1
2
3
4
5
# 串口数据定义并发送区
# 要发送的数据:[帧头 a3 b3,中间*个十六位数据,帧尾 c3]
data=bytearray([0xa3,0xb3,int(chimera),int(crossroad),0xc3])
# 发送数据
uart.write(data)
串行终端发送:
1
2
3
4
5
6
7
8
9
10
11
12
# 串行终端发送区
print(" ")
print("当前帧数:", clock.fps(), "fps")
## print("目标横坐标(0-128):", max_blob.cx())
## print("目标纵坐标(0-160):", max_blob.cy())
## print("横坐标偏离值[-64,64]:", x_error)
## print("纵坐标偏离值[-80,80]:", y_error)
## print("色块转向角度:", max_blob.rotation())
print("是否检测到十字路口:", crossroad)
# print("色块偏离值:", rotate_angle) #[0,180]
# print("线角度:",theta_err) #[0,180]
print("推荐转向", chimera)