一、资料导航
“工欲善其事,必先利其器”。在正式学习OpenMV之前,你必须知道一条或几条OpenMV的学习途径。这里推荐星瞳科技的中文官网教程,这个教程里面包括了OpenMV IDE的下载和安装、OpenMV上手教程、OpenMV中文文档、OpenMV详细参数以及OpenMV函数库等,是新手入门OpenMV的一条非常好的途径。
图1OpenMV星瞳科技官网
二、OpenMV简介
OpenMV是一个开源,低成本,功能强大的机器视觉模块。以STM32F427CPU为核心,集成了OV7725摄像头芯片,在小巧的硬件模块上,用C语言高效地实现了核心机器视觉算法,提供Python编程接口。使用者们可以用python语言使用OpenMV提供的机器视觉功能,为自己的产品和发明增加有特色的竞争力。OpenMV上的机器视觉算法包括寻找色块、人脸检测、眼球跟踪、边缘检测、标志跟踪等。可以用来实现非法入侵检测、产品的残次品筛选、跟踪固定的标记物等。使用者仅需要写一些简单的Python代码,即可轻松的完成各种机器视觉相关的任务。小巧的设计,使得OpenMV可以用到很多创意的产品上。比如,可以给自己的机器人提供周边环境感知能力;给智能车增加视觉巡线功能;给智能玩具增加识别人脸功能,提高产品趣味性等;甚至,可以给工厂产品线增加残次品筛选功能等。
OpenMV采用的STM32F427拥有丰富的硬件资源,引出UART,I2C,SPI,PWM,ADC,DAC以及GPIO等接口方便扩展外围功能。USB接口用于连接电脑上的集成开发环境OpenMVIDE,协助完成编程、调试和更新固件等工作。TF卡槽支持大容量的TF卡,可以用于存放程序和保存照片等。
图2 OpenMV实物
三、OpenMV IDE的使用
3.1、IDE功能一览
图3 OpenMV IDE界面
- 代码编辑区 编辑对应的OpenMV的python代码。
- 视频缓冲区实时显示OpenMV上的图像。
- 交互式终端可以与OpenMV交互编程,可将帧率、串口传输内容等打印在该终端上。
- 直方图 可通过直方图来获取图像的阈值。
- 快捷功能区 包含保存、复制、粘贴、剪切等快捷功能。
- 设备连接与运行 用于连接OpenMV与运行代码。
3.2、OpenMV连接及运行代码
先把板子通过USB连接到电脑,等之出现flash闪存盘,然后按下白色连接图标,连接成功会出现绿色图标,点击绿色按钮之后变成红色的X,即可把当前的python代码在OpenMV中运行;若要中断代码或要运行其他代码,点击X再点击绿色按钮即可重新运行。
图4 OpenMV 连接与运行流程
3.3、打开官方例程
OpenMV IDE里有很多官方提供的例程,这里以helloworld例程为例教大家如何去打开这些例程。文件——示例——OpenMV——Basics——helloworld打开官方提供的helloworld例程。
图5 打开OpenMV 官方例程
点击运行之后,IDE右上角就有图像显示,以及右下角的RGB数值显示。并在终端上打印数据。
3.4、菜单栏功能
文件部分:新建文件、打开文件、保存文件等操作功能。
图6 OpenMV IDE菜单栏文件部分
编辑部分:此部分是对编辑区进行设置,比如常用的撤销、复制、粘贴、剪切、重做、全选、查找等功能。也有相对应的快捷键,也比较方便。
图7 OpenMV IDE菜单栏编辑部分
工具部分:此部分有保存脚本到OpenMV、复位OpenMV、还有串口终端的设置、机器视觉设置、读取图像LAB阈值等功能。
图8OpenMV IDE菜单栏工具部分
窗口部分:可对软件进行全屏。
图9 OpenMV IDE菜单栏窗口部分
四、OpenMV理论基础
摄像头
我们都见过各种摄像头,比如:
图10常见摄像头
那么什么是摄像头,说到底,就是一个将光学信号转变成电信号的一个装置。在计算机视觉中,最简单的相机模型是小孔成像模型:
图11小孔成像模型
小孔模型是一种理想相机模型,没有考虑实际相机中存在的场曲、畸变等问题。但是在实际使用时,这些问题可以通过在标定的过程中引入畸变参数解决,所以小孔模型仍然是目前最广泛使用的相机模型。图像透过镜头,照在一个感光芯片上,感光芯片可以把光照的波长和强度等信息转成计算机(数字电路)可以识别的数字信号,OpenMV感光元件是长这样的:
图12OpenMV感光元件
(中间的方形元件就是感光元件)
像素和分辨率
图像由很多个小点构成,这些小点我们称之为像素,也叫像素点。相同物理面积下,像素点越多,显示的图像就越清晰,像素点越少,显示的图像就越模糊。例如,一张人像照片,如果有2000万个像素点,可能连毛孔都看得很清晰,如果只有100万个像素点可能鼻子眼睛都会模糊,这就是用像素的多少来描述图片的清晰程度。我们通常说的1000万像素、2000万像素、一亿像素指的就是图像的像素点总数。
图13由像素构成的人眼图
明白了像素的意义,分辨率就很好理解。一张图像里像素的排列并非杂乱无序,而是横向成行、竖向成列的规则排列。分辨率一般描述为M x N,M代表水平方向即横向像素数,N代表垂直方面即竖向像素数,两者相乘即为此图像的像素总数。例如,一张640乘480分辨率的图片,即代表横向有640个像素点,竖向有480个像素点,这张图的像素总数为640 x 480 = 307200。分辨率其实还是在描述一张图片的像素总数,只不过是换了一种表述方法而已。
帧率
帧率(FPS)就是每秒钟处理的图片数量,如果超过20帧,人眼就基本分辨不出卡顿。当然,如果用在机器上,帧率是越高越好的。
LAB亮度-对比度
Lab颜色空间中,L亮度;a的正端代表红色,负端代表绿色;b的正端代表黄色,负端代表兰色。不像RGB和CMYK色彩空间,Lab颜色被设计来接近人类视觉。因此L分量可以调整亮度对,修改a和b分量的输出色阶来做精确的颜色平衡。而在OpenMV的查找色块的算法中,运用的就是这个LAB模式。
镜头的焦距
因为图像是通过镜头的光学折射,照到感光元件上的。那么镜头就决定了整个画面的大小和远近。一个最重要的参数就是焦距。镜头焦距是指镜头光学后主点到焦点的距离,是镜头的重要性能指标。镜头焦距的长短决定着拍摄的成像大小,视场角大小,景深大小和画面的透视强弱。当对同一距离远的同一个被摄目标拍摄时,镜头焦距长的所成的象大,镜头焦距短的所成的像小。下面是,当OpenMV距离桌面20cm左右时,不同焦距镜头的对比图:
图14镜头的焦距
镜头调焦
有时当你的OpenMV连接到电脑上会发现在OpenMV帧缓冲区里的图像比较模糊,这时你需要去手动调节OpenMV摄像头的焦距,使图像变的更加清晰。
镜头的畸变
因为光学原理,在感光芯片上不同的位置,与镜头的距离不同的,简单说就是近大远小,所以在边缘会出现鱼眼效果(桶型畸变)。为了解决这个问题,可以在代码中使用算法来矫正畸变,注:OpenMV中使用image.lens_corr(1.8)来矫正2.8mm焦距的镜头。也可以直接使用无畸变镜头。
光源的选择
如果你的机器是在工业上,或者24小时长时间运行的设备,保持一个稳定的光源是至关重要的,尤其在颜色算法中。亮度一变,整个颜色的值会变化的很大!
SD卡
如果代码太多,Flash装不下,就可以使用SD卡,这个是OpenMV自带的,SD卡也是一个文件系统,当上电的时候,如果插入SD卡,那么SD卡的文件系统就会自动取代内置的Flash文件系统,每次上电,就会运行SD卡中的main.py。SD卡最大支持32G的容量。
脱机运行
OpenMV把内置Flash虚拟成一个文件系统,当你插入OpenMV到电脑上的时候,电脑会弹出一个U盘,里面就是OpenMV的文件系统。当你想烧录固件的时候,直接把脚本文件复制到这个“U盘”的main.py中。每次上电的时候,OpenMV会自动运行里面的main.py,这样就实现了脱机运行。
一键下载
在工具栏里,点击将打开的脚本保存到OpenMV Cam(作为main.py), IDE就会自动将当前文件保存到main.py,很方便。
图15OpenMV一键下载流程
电源
OpenMV有两个电源输入端:VIN有时也会标识为VCC)和USB输入。VIN输入为3.6V~5V,推荐5V。USB和VIN可以同时供电。OpenMV有一个电源输出端:3.3V,这个电压是OpenMV的稳压器输出的,用于给其他传感器供电。注意:不要给3.3V直接供电,没有内部芯片的保护,很容易烧毁。
五、案例讲解
5.1、小球追踪
1、第一步:配置感光元件并完成图像的拍摄
简介
在这个案例中,我们需要在黄、红、蓝三个小球中识别蓝色小球,并将蓝色小球的位置坐标通过串口发送给STM32单片机。
感光元件
sensor模块,用于设置感光元件的参数。
初始化
sensor.reset() 初始化感光元件
设置彩色/黑白
sensor.set_pixformat() 设置像素模式。
sensor.GRAYSCALE: 灰度,每个像素8bit。
sensor.RGB565: 彩色,每个像素16bit。
设置图像的大小
sensor.set_framesize() 设置图像的大小
sensor.QQVGA: 160×120
sensor.QVGA: 320×240
sensor.VGA: 640×480
跳过一些帧
sensor.skip_frames(n=20) 跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定。
获取一张图像
sensor.set_auto_gain() 自动增益开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动增益,默认打开。
自动增益/白平衡
sensor.set_auto_whitebal() 自动白平衡开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动白平衡,默认打开。
【第一步 程序】
/********************************************************************说明:完成此部分程序并在OpenMV IDE中运行,帧缓冲区中即可出现图像。*********************************************************************/import sensor, image, time # 导入传感器模块、图像模块、时间模块/********************************************************************感光元件配置*********************************************************************/sensor.reset() # 初始化感光元件sensor.set_pixformat(sensor.RGB565)# 设置像素模式为RGB565格式sensor.set_framesize(sensor.QVGA)# 设置图像的大小为QVGA (320x240)sensor.skip_frames(20) # 跳过一些帧,等待感光元件变得稳定sensor.set_auto_gain(False)# 关闭自动增益sensor.set_auto_whitebal(False)# 关闭白平衡clock = time.clock()# 创建一个时钟对象以便去追踪帧率/********************************************************************主函数*********************************************************************/while(True): # 无限循环clock.tick()# 更新图像的帧率img = sensor.snapshot()# 拍一张照片并返回图像print(clock.fps())# 打印帧率/********************************************************************结束*********************************************************************/
2、第二步:识别蓝色小球并将中心坐标打印在串行终端
寻找色块函数——find_blobs函数
寻找色块是OpenMV用的最多的功能,find_blobs函数可以找到色块.以下是find_blobs函数的细节:
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])
a、thresholds是颜色的阈值。注意:这个参数是一个列表,可以包含多个颜色。如果你只需要一个颜色,那么在这个列表中只需要有一个颜色值,如果你想要多个颜色阈值,那这个列表就需要多个颜色阈值。在返回的色块对象blob可以调用code方法,来判断是什么颜色的色块。
b、roi是“感兴趣区”。
c、x_stride 是查找的色块的x方向上最小宽度的像素,默认为2,如果你想查找宽度10个像素以上的色块,那么就设置这个参数为10。
d、y_stride 是查找的色块的y方向上最小宽度的像素,默认为1,如果你想查找宽度5个像素以上的色块,那么就设置这个参数为5。
e、invert 反转阈值,把阈值以外的颜色作为阈值进行查找。
f、area_threshold 面积阈值,如果色块被框起来的面积小于这个值,会被过滤掉。
g、pixels_threshold 像素个数阈值,如果色块像素数量小于这个值,会被过滤掉。
h、merge 合并,如果设置为True,那么合并所有重叠的blob为一个。 注意:这会合并所有的blob,无论是什么颜色的。如果你想混淆多种颜色的blob,只需要分别调用不同颜色阈值的find_blobs。
i、margin 边界,如果设置为1,那么两个blobs如果间距1一个像素点,也会被合并。
手动调节阈值
一个颜色阈值的结构是这样的:
threshold= (minL,maxL,minA,maxA,minB,maxB)
元组里面的数值分别是L A B 的最大值和最小值。如果需要获取自己需要的色块的阈值,可以使用如下操作:
- 首先运行hello world.py让IDE里的右上角帧缓冲区显示图案。
- 在IDE中的菜单栏-工具中找到机器视觉->阈值编辑器中可以选取需要的阈值。
- 手动调整到一个合适的值。
图16手动调节阈值
blobs是一个列表
find_blobs对象返回的是多个blob的列表。(注意区分blobs和blob,这只是一个名字,用来区分多个色块,和一个色块)。 列表类似与C语言的数组,一个blobs列表里包含很多blob对象,blobs对象就是色块,每个blobs对象包含一个色块的信息。blobs就是很多色块。可以用for循环把所有的色块找一遍。
blob色块对象
blob有多个方法,这里介绍三个常用的方法:
(1)、blob.rect() 返回这个色块的外框——矩形元组(x, y, w, h),可以直接在image.draw_rectangle()中使用。
(2)、blob.cx() 返回色块的外框的中心x坐标(int),也可以通过blob[5]来获取
(3)、blob.cy() 返回色块的外框的中心y坐标(int),也可以通过blob[6]来获取。
标识函数
画框
image.draw_rectangle(rect_tuple, color=White) 在图像中画一个矩形框。rect_tuple 的格式是 (x, y, w, h)。
画圆
image.draw_circle(x, y, radius, color=White) 在图像中画一个圆。x,y是圆心坐标,radius是圆的半径。
画十字
image.draw_cross(x, y, size=5, color=White) 在图像中画一个十字。x,y是坐标,size是两侧的尺寸。
【第二步程序】
/********************************************************************说明: 完成此部分程序可以识别视野中的蓝色小球,并在OpenMV IDE帧缓冲区中用矩形框和十字将蓝色小球标出。*********************************************************************/import sensor, image, time# 导入传感器模块、图像模块、时间模块blue_threshold = (0,89,-52,-17,-15,38)# 设置蓝色的阈值/********************************************************************感光元件配置*********************************************************************/sensor.reset()# 初始化感光原件sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式sensor.set_framesize(sensor.QVGA) # 设置图像的大小为QVGA (320x240)sensor.skip_frames(20)# 跳过一些帧,等待感光元件变得稳定sensor.set_auto_gain(False) # 关闭自动增益sensor.set_auto_whitebal(False) # 关闭白平衡clock = time.clock()# 创建一个时钟对象以便去追踪帧率/********************************************************************寻找最大色块函数*********************************************************************/def find_max(blobs):# 定义函数max_size=0# 定义色块尺寸变量for blob in blobs:# 遍历色块blobsif 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 = img.find_blobs([blue_threshold])# 调用find_blobs()函数blob = find_max(blobs)# 调用 find_max()函数if blob:# 如果找到了目标颜色img.draw_rectangle(blob[0:4]) # 用矩形标记出目标颜色区域img.draw_cross(blob[5], b[6]) # 在目标颜色区域的中心画十字形标记print(blob[5], blob[6])# 输出颜色目标中心坐标print(clock.fps())# 打印帧率/********************************************************************结束*********************************************************************/
3、第三步:将蓝色小球的中心通过串口发送给STM32单片机
创建串口对象
uart = UART(3, 115200),使用串口三,波特率115200。
串口初始化
uart.init(115200, bits=8, parity=None, stop=1),8位数据位,无校验位,1位停止位。
字节数组
data = bytearray([0xb3,0xb3,OpenMV_X,OpenMV_Y,0x5b])。bytearray()方法返回一个新字节数组。这个数组里的元素是可变的,每个元素的值范围: 0 <= x < 256。
串口发送
函数uart.write(data)
接线
TTL串口至少需要3根线:TXD,RXD,GND。TXD是发送端,RXD是接收端,GND是地线。 连线的时候,需要把OpenMV的RXD连到另一个MCU的TXD,TXD连到RXD。
【第三步程序】
/********************************************************************说明: 完成此部分程序可以识别视野中的蓝色小球,并在OpenMV IDE帧缓冲区中用矩形框和十字将蓝色小球标出,同时将蓝色小球的中心坐标通过串口发送给STNM32单片机。*********************************************************************/import sensor, image, time# 导入传感器模块、图像模块、时间模块from pyb import UART# 导入串口模块blue_threshold = (0,89,-52,-17,-15,38)# 设置蓝色的阈值/********************************************************************感光元件配置*********************************************************************/sensor.reset()# 初始化感光原件sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式sensor.set_framesize(sensor.QVGA)# 设置图像的大小为QVGA (320x240)sensor.skip_frames(20)# 跳过一些帧,等待感光元件变得稳定sensor.set_auto_gain(False) # 关闭自动增益sensor.set_auto_whitebal(False) # 关闭白平衡clock = time.clock()# 创建一个时钟对象以便去追踪帧率 /********************************************************************创建串口对象并初始化*********************************************************************/uart = UART(3, 115200)# 创建串口对象uart.init(115200, bits=8, parity=None, stop=1)# 串口初始化/********************************************************************寻找最大色块函数*********************************************************************/def find_max(blobs):# 定义函数max_size=0# 定义色块尺寸变量for blob in blobs:# 遍历色块blobsif 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 = img.find_blobs([blue_threshold]) # 调用find_blobs函数blob = find_max(blobs) # 调用 find_max函数if blob: # 如果找到了目标颜色img.draw_rectangle(blob[0:4]) # 用矩形标记出目标颜色区域img.draw_cross(blob[5], b[6]) # 在目标颜色区域的中心画十字形标记OpenMV_X = int(blob[5])# 对色块横坐标取整OpenMV_Y = int(blob[6]) # 对色块纵坐标取整data = bytearray([0xb3,0xb3,OpenMV_X , # 返回字节数组OpenMV_Y,0x5b])uart.write(data) # 调用uart.write()函数发送数据print(blob[5], blob[6])# 打印目标物体中心坐标print(clock.fps()) # 打印帧率/********************************************************************结束*********************************************************************/
5.2、植保飞行器(21电赛G题)视觉设计
1、任务
设计一基于四旋翼飞行器的模拟植保飞行器,能够对指定田块完成“撒药”作业。如图 1 所示作业区中,灰色部分是非播撒区域,绿色部分是待 “播撒农药”的区域,分成多个 50cm×50cm 虚线格区块,用1~28 数字标识,以全覆盖
飞行方式完成“撒药”作业。
作业中播撒区域不得漏撒、重复播撒,非播撒区域不得播撒,否则将扣分;播撒作业完成时间越短越好。
图 1 中,黑底白字的“十”字是飞行器起降点标识;“21”是播撒作业起点区块,用“A”标识;飞行器用启闭可控、垂直向下安装的激光笔的闪烁光点表示播撒动作,光点在每个区块闪烁1~3 次视正常播撒;同一区块光点闪烁次数大于3次,将被认定为重复播撒。激光笔光点闪烁周期。
2、基本要求
(1)飞行器在“十”字起降点垂直起飞,升空至 150 ± 10cm 巡航高度。
(2)寻找播撒作业起点,从“A”所在区块开始“撒药”作业。
(3)必须在 360 秒内完成对图 1 中所有绿色区块进行全覆盖播撒。
(4)作业完成后稳定准确降落在起降点;飞行器几何中心点与起降点中心距离的偏差不大于
± 10cm。
3、视觉设计说明
(1)识别“十”字起降点。
(2)识别播撒作业起点区标识“A”。
(3)识别两条黑线的交汇点辅助飞行器的路径设计。
(4)获取识别物体的坐标信息,并通过串口发送给飞行器进行处理。
说明:学习完前面的内容,掌握小球追踪例程,你已经初步入门OpenMV了,植保飞行器视觉设计对于初学者来说有一定的难度,因此这里仅做介绍。想要参考此部分代码及讲解请见OpenMV零基础教程