一.
openmv适合做一些小项目,比如追踪小球的小车,云台。还有对成本要求较高的嵌入式工业方案,比如流水线物品分拣。不适合复杂的算法,比如车牌识别,猫狗分类,深度学习,ocr识别等。
openmv图像处理的方法:
- 感光元件
- 基本方法
- 使用统计信息
- 滤波
- 画图
- 寻找色块
- AprilTag标记跟踪
- 模板匹配
- 特征点检测
二.感光元件
1.sensor模块,用于设置感光元件的参数
sensor.reset() 初始化感光元件
那么,感光元件有哪些模式设置参数呢?
- sensor.set_pixformat() 设置彩色/黑白 sensor.GRAYSCALE灰度模式,每个像素8bit; sensor.RGB565彩色模式,每个像素16bit。
- sensor.set_framesize() 设置图像大小。有多种参数,表示不同的图像大小,适用于不同的感光元件。
- sensor.skip_frames(n=10) 跳过一些帧。在更改设置之后,跳过n张照片,等待感光元件变稳定。
- sensor.snapshot() 拍摄一张照片,返回一个image对象。
- sensor.set_auto_gain() 自动增益开启(True),关闭(False)。在使用颜色追踪时,要关闭自动增益。
- sensor.set_auto_whitebal() 自动白平衡开启,关闭。在使用颜色追踪时,要关闭自动白平衡。
- sensor.set_auto_exposure(enable[\,exposure_us]) enable打开True自动曝光(默认打开),关闭False可以使用exposure_us设置一个固定曝光时间,单位微秒。
- sensor.set_windowing(roi) 设置窗口ROI。ROI是感兴趣区,从要处理的图像中提取出要处理的区域。roi的格式是(x,y,w,h)。x,y是roi区域左上角的坐标;w,h是图像大小。
- sensor.set_hmirror(True)水平方向翻转
- sensor.set_vflip(True)垂直方向翻转
三.基本方法
image模块,对图像/像素的设置,获取和运算
1.设置/获取像素点
- image.get_pixel(x,y) 对于灰度图,返回像素点的灰度值;对于彩色图,返回RGB值
- image.set_pixel(x,y,pixel) 设置像素点的灰度值或RGB值
2.获取图像的宽度和高度
- image.width()
- image.height()
- image.format() 返回灰度值或者RGB值
- image.size()
3.图像运算
- image.invert() 对于二值化图像,取反,黑变白,白变黑
- image.nand(image) 与另一个图像进行与非运算
- image.nor(image) 或非运算
- image.xor(image) 异或运算
- image.difference(image) 从这张图片中减去另一个图片。常用作移动检测。
四.使用统计信息
image.get_statistics(roi=(x,y,w,h)) 从ROI中获取数据
对于灰度图,有灰度的平均数,众数,中位数等;彩色图有L,A,B三个通道的平均数,众数,中位数等。
- statistics.mean() 返回灰度平均数 statistics.l_mean() 返回L通道的平均数
- statistics.median()中位数 statistics.l_median()
- statistics.mode()众数 statistics.l_mode()
五.画图
可以画线,画框,画圆,画十字,写字。
- img.draw_line(line_tuple, color=White),line_tuple是(x0,y0,x1,y1),表示从0点到1点的直线
- img.draw_rectangle(rect_tuple, color=White),rect_tuple是(x,y,w,h)
- img.draw_circle(x, y, radius, color=White),圆心坐标和半径
- img.draw_cross(x, y, size, color=White),坐标和两侧尺寸
- img.draw_string(x, y, text, color=White)
六.寻找色块
find_blobs函数:对象返回是一个列表,类似于C语言的数组。一个blobs列表包含很多blob对象,每一个对象包含一个色块的信息。
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颜色阈值,这个参数是一个列表,可包含多个颜色。元组里面的数值分别为LAB的最大值和最小值。
- x_stride是查找色块的x方向上最小宽度的像素。y_stride同理。
- invert反转阈值,把阈值之外的颜色作为阈值进行查找。
- area_threshold面积阈值,如果色块被框起来的面积小于这个值,会被过滤掉。
- pixels_threshold像素阈值,与面积阈值同理。
- merge合并所有的blob。
- margin边界,如果设为1,那么两个blobs如果距离一个像素点,也会被合并。
blob色块对象
- blob.rect()返回色块的外框--矩形元组(x,y,w,h)
- blob.x()返回色块外框的x坐标
- blob.y()
- blob.w()
- blob.h()
- blob.pixels()
- blob.cx()返回外框的中心x坐标
- blob.cy()
- blob.rotation()返回色块的旋转角度,弧度制。
- blob.code()可用于查找颜色代码。返回16位数字,每一位代表一个颜色。如果是合并色块,则多个位为1.
- blob.count()返回被合并的色块的数量。没有合并则返回1.
- blob.area()返回w*h
- blob.density()色块密度
七.AprilTag标记跟踪
AprilTag是一个视觉基准系统,可用于AR,机器人,相机校准。Tag可以用打印机打印出来。AprilTag检测程序可以计算标签图片相对于相机的3D位置,方向以及ID。TAG36H11是AprilTag的一种。使用TAG36H11比较好,因为方块数量比较多,看得距离要远,信息更准确,出错率更少。Tag可以在openmvIDE生成。
img.find_apriltags()
八.模板匹配
由于模板图片的大小超过openmv内置的flash,需要SD卡,插到openmv上面。可支持32GB内存卡。模板匹配采用灰度图。只能识别和模板图片类似大小的范围。如果需要识别不同的大小,那就要存储多个不同大小的模板。所以,模板匹配适用于摄像头与目标物体之间距离确定,不需要动态移动的情况。
首先,我们需要创建或者导入一个模板,可以直接从openmv里面截取模板图片。可以先运行helloword.py例程,让frambuffer显示出图像,鼠标右击点击save image selection to pc即可保存。但是格式不对,需要转换成PGM格式。使用的模板图片要求是PGM格式。模板图片建议小于80*60.如果出现:MemoryError.FB Alloc Collision(内存不够),要把QQVGA改成QQCIF。
想要识别多个模板图片则多次调用多个模板识别函数,或者for t in templates。
模板匹配使用NCC算法【计算机视觉】NCC匹配算法_荔枝酱的博客-CSDN博客
find_template(template,thresholds,step,search,roi)不设置roi的话,默认整个图像范围。仅支持灰度图像。
- thresholds是0~1的浮点数。较小的值可以在提高检测速率的同时增加误报率。
- step是查找模板时需要跳过的像素数量。跳过像素可大大提高算法运行的速度。这种方法只适用于SEARCH_EX模式下的算法。
- saerch常常是取SAERCH_EX,表示寻找的方法。其他方法还有:image.SEARCH_DS。image.SEARCH_DS算法较快,但是当模板位于图像边缘的时候,可能无法搜索成功。
- roi的大小要比模板图片大,比frambuffer小。
九.特征点检测
如果是刚开始运行程序,例程提取最开始的图像作为目标物体特征,kpts1保存目标物体的特征。默认会匹配目标特征的多种比例大小和角度,而不仅仅是保存目标特征时的大小角度,比模版匹配灵活,也不需要像多模板匹配一样保存多个模板图像。也可以尝试提前保存目标特征。
特征点检测使用FAST/AGAST算法进行特征提取和目标追踪,仅支持灰度图。在程序最开始的十秒左右,将目标物体放在摄像头中央识别,直至出现特征角点,证明已经识别记录目标特征。匹配过程中,如果画面出现十字和矩形框,说明匹配成功。
image — 机器视觉 — MicroPython 1.9.2 文档 (singtown.com)
image.find_keypoints(roi=Auto, threshold=20, normalized=False, scale_factor=1.5, max_keypoints=100, corner_detector=CORNER_AGAST)
- threshold是0~255的一个阈值,用来控制特征点检测的角点数量。用默认的AGAST特征点检测,这个阈值大概是20。用FAST特征点检测,这个阈值大概是60~80。阈值越低,获得的角点越多。
- normalized是一个布尔数值,默认是False,可以匹配目标特征的多种大小(比ncc模版匹配效果灵活)。如果设置为True,关闭特征点检测的多比例结果,仅匹配目标特征的一种大小(类似于模版匹配),但是运算速度会更快一些。
- scale_factor是一个大于1.0的浮点数。这个数值越高,检测速度越快,但是匹配准确率会下降。一般在1.35~1.5左右最佳。
- max_keypoints是一个物体可提取的特征点的最大数量。如果一个物体的特征点太多导致RAM内存爆掉,减小这个数值。
- corner_detector是特征点检测采取的算法,默认是AGAST算法。FAST算法会更快但是准确率会下降。
image.match_descriptor(descritor0, descriptor1, threshold=70, filter_outliers=False)
本函数返回kptmatch对象。
- threshold阈值设置匹配的准确度,用来过滤掉有歧义的匹配。这个值越小,准确度越高。阈值范围0~100,默认70
- filter_outliers默认关闭。
pyb各种外设
常用函数:
- pyb.delay(50)延时50ms
- pyb.millis()获取从启动开始计时的毫秒数
from pyb import LED
- led.toggle()
- led.on() led.off()
- LED(1)红色 LED(2)绿色 LED(3)蓝色 LED(4)红外LED两个
from pyb import Pin
- p_in=Pin('P7',Pin.in,Pin.OUT_PP)
- p_out.heigh() p_outlow()
- value= p_in.value()
from pyb import Servo
- s1=Servo(1)
- Servo(1)->P7(PD12)
- s1.angle(angle,time) move to xx degrees in xx time
- s1.speed(speed)
from pyb import Pin,ExtInt
- callback=lambda e:print("intr") 中断触发时调用的函数。
- ext=ExtInt(Pin('P7'),ExtInt.IRQ_RISING,Pin.PULL_NONE,callback)
from pyb import Timer
- tim=Timer(4,freq=1) 使用定时器4,1Hz触发
- tim.counter() 获取或设置定时器
- tim.freq() 获取或设置定时器频率
- tim.callback() 设置定时器触发时调用的函数
- Timer 1 Channel 3 Negative->P0
from pyb import Pin, Timer
- p = Pin('P7') P7 has TIM4, CH1
- tim = Timer(4, freq=1000)
- ch = tim.channel(1, Timer.PWM, pin=p)
- ch.pulse_width_percent(50)
from pyb import Pin, ADC
- adc = ADC('P6')
- adc.read() read value, 0-4095
from pyb import Pin, DAC
- dac = DAC('P6')
- dac.write(120) output between 0 and 255
from pyb import UART
- uart = UART(3, 9600)
- uart.write('hello')
- uart.read(5) # read up to 5 bytes
from pyb import SPI
- spi = SPI(2, SPI.MASTER, baudrate=200000, polarity=1, phase=0)
- spi.send('hello')
- spi.recv(5) receive 5 bytes on the bus
- spi.send_recv('hello') send a receive 5 bytes
from machine import I2C, Pin
- i2c = I2C(sda=Pin('P5'),scl=Pin('P4'))
- i2c.scan()
- i2c.writeto(0x42, b'123') write 3 bytes to slave with 7-bit address 42
- i2c.readfrom(0x42, 4) read 4 bytes from slave with 7-bit address 42
- i2c.readfrom_mem(0x42, 8, 3) read 3 bytes from memory of slave 42, starting at memory-address 8 in the slave
- i2c.writeto_mem(0x42, 2, b'\x10') write 1 byte to memory of slave 42 starting at address 2 in the slave
openmv常使用的功能:颜色追踪,模板匹配,特征点匹配,二维码条形码识别
常应用的场景:十字路口识别,数字识别,红线追踪,二维码条码识别
1.关于颜色追踪 find_blobs
颜色追踪,先要完成颜色识别。
single_color_rgb565_blob_tracking例程,单颜色识别
multi_color_rgb565_blob_tracking例程,多颜色识别
颜色识别,首当其冲的就是颜色阈值。颜色阈值在工具--阈值工具里通过LAB模式将需要追踪的颜色的部分调整为白色,复制颜色LAB数字即为颜色阈值。也可以在直方图中,直接选择LAB色域,在显示区直接选出需要的颜色区域,然后看LAB的最大值和最小值。
其次就是区域阈值,像素阈值,合并。阈值表示低于阈值的面积大小或者像素大小会被过滤,按照实际情况定下区域阈值和像素阈值的大小即可。合并为true时,多个颜色框框会合并为一个框框。
识别颜色之后,还要进行追踪,那么我们在硬件上必然需要运动和驱动的部件,比如电机。此时,需要颜色识别的结果来确定电机的运动方向。
需要电机,那么必然需要PID算法。PID的各参数在线调试和脱机运行是有差别的。因为使用了IDE的显示图像的帧率和不使用IDE的帧率会有差别。在线调试的参数大小会大于脱机运行的参数。除此之外,PID需要有一个error值,这个值与颜色方块在x/y方向的质心位置与整个图像的中心位置的偏差有关。这个error值代表了方块在整个图像中中心的偏差位置,进入PID函数中获得两个电机在x/y方向的旋转角度。
openmv提供的库代码,颜色追踪的程序是否有哪些局限?
在灯光变化时,框框的区域会发生变化。因为颜色本身就会受光照影响而发生变化。由于使用IDE会导致PID参数调了之后,在脱机时会有差别。如果不使用IDE查看图像,可以选择使用LCD。是需要导入LCD模块,初始化LCD,然后lcd.display(img)。注意需要使用和LCD一样的分辨率。
2.关于模板匹配NCC find_template
外接内存卡,最大32GB。只支持灰度图模式。
创建模板图片文件,匹配阈值,寻找方法,step。
模板图片可以直接在显示区截取。将bmp图像保存在pc里。bmp格式转为pgm格式后保存到openmv的u盘或者内存卡中。多个模板匹配,使用for循环。
模板匹配NCC的局限?
NCC算法只能匹配近似大小,如果需要识别不同的大小或者角度,那么就需要保存多个大小和角度的模板图片。
3.关于小车的速度与方向控制
小车看成两轮,使用差速法来控制方向。差速法,那必然会有一个基础速度,再加上方向速度。基础速度就是小车的速度,使用追踪物体的大小来判断,原理是近大远小。左右轮的基础速度是一样的,同样需要PID来计算,使得基础速度的变化较为稳定。差速的实现是为了变化方向,方向的变化同样也需要是稳定平滑的,所以也需要PID来计算。此时,是根据物体质心位置与视野中心位置的偏移为error值进入PID。得出的结果为差速,左右两轮一正一负与基础速度相加。
做一个巡线小车:
巡线小车需要实现巡线功能,那就必然需要小车速度和方向控制,路线识别。当巡线速度没有特别要求时,那么基础速度可以是一直不变的。方向由差速来实现。需要根据路线与视野的中心位置的偏移为error值进入PID得到差速值。左右轮是一正一负与基础速度相加。
由路线来得到偏移值,使用get_regression函数。将直线信息返回,由rho值和theta值获得小车的转弯角度。其实,主要的思路就是获得直线中心与视野中心位置的偏移大小。
4.关于扫码识别
只有openmv2以上的版本才可以扫码。扫码识别使用灰度图模式。openmv的二维码识别采用的是Apriltag的四元检测算法。条形码识别是不需要考虑畸变,是一维信息。对于二维码识别和矩形识别需要考虑镜头畸变问题。镜头畸变问题如果使用软件算法来解决,那会导致帧率降低。使用无畸变的镜头可以有效避免畸变问题和帧率下降问题。
find_barcodes()条形码识别 find_qrcodes()二维码识别
在线二维码生成器 ~ 二维工坊 (2weima.com) 二维码生产器网址
5.关于特征点检测 find_keypoints,match_descriptor
特征点检测经常用于目标追踪,适用于对象的大小和角度不断变化的情况。在检测开始前保存物体的特征。使用灰度图模式。
1.事先保存特征点,然后进行特征点匹配:save_keypoint在开始时就会保存特征点,之后需要重新连接然后重置openmv,此时u盘上可以看到保存的特征图片。然后使用特征点匹配的例程,需要将key1改为在保存的文件里寻找特征点。
2.程序开始运行时,保存特征点之后直接匹配:直接使用特征点匹配例程,key1是none。
6.SD卡的使用
外接SD卡可拓展存储空间。在断电后,插入SD卡。内存卡会替代原来的flash。将文件保存到SD卡中,重置openmv。如果SD卡不兼容,则格式化为FAT32。
7.数字识别LENAT,minst
LENAT卷积识别网络,可以识别打印或者手写的数字。之前的模板匹配也可以识别数字。由于模板匹配的局限性,lenat数字识别预先保存lenat神经网络的模型文件到flash里面,运行例程就可以实现数字识别,大小或者角度变化可识别。openmv只能识别较大的数字。lenat识别在openmv4上使用。
目标检测:
训练识别物体。适用于openmv4h7/(plus)。在edge impulse网站上训练模型。FOMO模型只能检测到目标的位置,无法获知的物体的尺寸。与YOVLO相比,FOMO的效果更好。
采集并上传数据集;标注目标;在线网站模型训练;模型测试;模型文件放到flash中
在采集过程中,每一个分类至少100张图片。工具-数据集编辑器-新数据集-新建文件夹-保存分类图片(连接openmv-点击左侧保存数据集照片,保存多个角度的照片)。将数据集上传到网站上。数据集编辑器-export-upload to edge impulse by API Key-复制API Key-上传中。
8.脱机运行
将在打开的文件的界面,点击工具-将打开的文件保存到openmv,保存之后将U盘弹出。重新上电之后可以看到保存的代码。在U盘里面就可以找到保存的文件。
如果没有正确地保存代码,可以格式化,然后重新保存。右键u盘,点击格式化,文件系统选择FAT,点击确定。关闭IDE。
openmv与STM32的通信
下面是openmv与STM32使用uart协议的通讯。
在数据发送和接收之前,我们需要设置openmv和STM32的uart协议:8位数据位,1位停止位,无校验位,波特率设置得低一点,为9600.
Openmv数据发送:
在openmvIDE的示例里面,提供了uart的例程,较为简单,使用uart.write来发送数据。自己调试的时候,发现uart.write只能发送字符串,无法以十六进制的形式发送数据。
openmv与STM32的uart通信有两种方式。
- bytearray( [ 0x2C,0x12,data,ox5B] ) ,使用bytearray函数。它是python的内置函数,返回一个新字节数组,数组元素可变,每个元素的值范围是0~255.然后uart.write( bytearray( ) )发送出去。【需要import math】如果需要发送两个字节的数据,那么把这个数据分为高8位和低8位两段来发送出去:bytearray( [ ox2c , ox2c , A>>8 , A , ox5b ] )
- ustruct.pack()。根据格式字符串fmt,将数据打包,返回一个解码该值的字节对象。然后uart.write( ustruct.pack() )发送出去。数据包一般会有帧头和帧尾,主要是为了保证数据传输的准确性。fmt格式有规定。【需要import ustruct】
#pack各字母对应类型 字节大小
#x pad byte no value 1
#c char string of length 1 1
#b signed char integer 1
#B unsigned char integer 1
#? _Bool bool 1
#h short integer 2
#H unsigned short integer 2
#i int integer 4
#I unsigned int integer or long 4
#l long integer 4
#L unsigned long long 4
#q long long long 8
#Q unsilong long long 8
#f float float 4
#d double float 8
#s char[] string 1
#p char[] string 1
#P void * long
调试方法:
在使用以上两种方式写程序的时候,openmv使用USB转串口,打开电脑的串口助手并连接后,串口助手上看不到uart.write的内容。只有使用print()才能显示内容。所以,如果想要看到uart传输的结果,那么就连接到单片机上,中断函数读取到数据之后将数据发送到串口助手上显示。
STM32数据接收:
在openmvIDE中写完发送函数之后,主控单片机上需要接收数据。需要特别注意中断函数。数据接收与数据的发送格式紧密相关。
数据的发送格式是8位数据位,意味着数据是1个字节1个字节地发送过来的,每次中断只能接收到8位数据位,即1个字节。openmv发送的数据包里有帧头和帧尾。在中断函数中需要一一识别帧头帧尾,判断这个数据是否是正确的,获得我们需要的数据。如果data是2个字节的,那么data的传输需要两次中断完成,我们需要设计相应的程序正确接收到数据。
数据转换和处理
在发送和接收,以及数据接收后的处理过程中,需要注意数据的内存大小,数据的类型。防止出现截断,溢出等改变数据的情况。
如果openmv需要发送较多的数据给其他单片机,可以借鉴一下下面这篇文章: