详情请参考https://book.openmv.cc/python-background.html 官方教程
https://docs.singtown.com/micropython/zh/latest/openmvcam/index.html 官方函数库
此处仅作备忘录,官方文档已经非常详尽了。
——
openmv算是我第一次接触到的图像处理入门设备,对于专业不涉及图像处理领域,但是需要应用相关功能的人来说,这小玩意算是物超所值,学习成本很低(相比树莓派+摄像头),你要做的只是熟悉一下mircopython语法。
想来openmv接触了有一个月(我觉得挺长了),也实现了不少小功能(可惜只是用了裸机操作)。 这篇文章算是对openmv的回顾,因为有相当一段时间没摸它了,毕竟花了这么常时间研究它,怕把这些经验遗忘了。
在开始之前请先注意,openmv和opencv是两个完全不同的概念,几乎不存在相互移植的可能性。
openmv是一个可以通过MicroPython语言进行编程的摄像头(实际上也只能使用MicroPython,并不能使用C语言,但是其运算速度是可以忍受的),内置了一些图像处理算法,使用时可以直接调用库实现功能,所有的功能基本上都是通过堆叠库实现的。
openmv通常用于DIY相关的项目制作(也就是说适合学生和爱好者)。
openmv常用的功能有板级控制(略去不讲,但很重要)、画图(drawing)、图像滤波(image-filters)、拍摄(snapshot)、特征检测(feature-detection)、颜色追踪(color-tracking)、codes、测距等。
尽可能选择OPENMV3 M7摄像头及以上版本的(如果出了的话),这是因为stm32系列处理图像能力有限,低版本会有很多功能无法实现,掉帧情况也会变得难以忍受。
由于是开源项目,对于硬件来说实际上不存在盗版产品,所以不需要特意追求授权商产品,但是建议购买正版key,用于支持国外研发团队。key用于激活IDE,没有key仍能够正常使用产品,但会出现恼人的提示弹窗。
python是面向对象的图形语言(c语言是面向过程)。Python 的发展以 CPython 为主,MicroPython 和 CPython 在 Python3 语法上保持高度的一致性,包含Python标准库的一小部分,但常用的标准语法命令都已经支持。相比于python,mircopython通常用于资源有限的嵌入式设备。
对mircopython的学习点到即止,因为它不是用于嵌入式开发的常用语言。
OV(omnivision,豪威科技)为美商半导体公司,专业开发高度集成CMOS影像技术。
openmv3/openmv4采用OV7725 sensor,其具体参数见下:
项目 | 指标 |
---|---|
焦距 | 2.8mm |
光圈 | f2.0 |
尺寸 | 1/3’’ |
视场角 | H视角:115° V视角:90° |
在这里要特别关注视场角FOV指标,H视角指水平视场角,V视角指垂直视场角,示意图如下:
当我们获取摄像头视场角参数后,即可将其套入公式计算被测物大小(已知距离情况下)亦或是被测物距离(已知大小情况下)。详见文末5.6物体测距与直径计算
LAB模式
Lab颜色空间中,L代表亮度;a的正数代表红色,负端代表绿色;b的正数代表黄色,负端代表l蓝色。
因此修改L分量可以调整亮度,修改a和b分量的输出色阶来做精确的颜色平衡。
在与颜色处理有关的功能实现中,openmv使用的就是LAB模式或灰度模式。
噪声
噪声即噪点,是图像中一种亮度或颜色信息的随机变化,而被拍摄物体本身并没有,这给图像带来了错误和额外的信息。
只有当图像噪声影响到图像处理时,才需要进行图像滤波。
图像滤波
总结自 http://www.ruanyifeng.com/blog/2017/12/image-and-wave-filters.html 图像与滤波 阮一峰
简洁明了。
图像本质上就是各种色彩波的叠加,可以用波的算法处理图像。色彩剧烈变化的地方,就是图像的高频区域;色彩稳定平滑的地方,就是低频区域。通过使用滤波器(filter)可以过滤掉某些波,保留另一些波。滤波在一定程度上会影响图像的清晰度,清晰图像无需进行滤波,一般都是有明显的噪点时才进行滤波处理。
图像处理算子
在数学中,当映射的作用是把函数映成函数,或者函数映成数的时候,这个映射常常叫做算子.图像处理里都把图像看成R²上的函数,每个像素只是这个函数的采样点。在这个意义下,算子就是把一个R²上的函数变化到另一个R²上的函数的一个变换。
常见的算子有morph算子,主要用于特征检测。
此处为常用功能都会涉及的通用设置。
需引用sensor模块,用于设置感光元件的参数,几乎在所有程序中涉及。
import sensor#引入感光元件的模块
# 设置摄像头
sensor.reset()#初始化感光元件
sensor.set_pixformat()#设置像素模式,括号内可选sensor.GRAYSCALE: 灰度or sensor.RGB565: 彩色
sensor.set_framesize()#设置图像的大小
sensor.set_auto_gain()#自动增益开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动增益。
sensor.set_auto_whitebal()#自动白平衡开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动白平衡。
sensor.skip_frames()#跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定。
其中图像大小可设置为:
sensor.QQQVGA: 80×60
sensor.QQVGA: 160x120
sensor.HQVGA: 240x160
sensor.QVGA: 320x240
sensor.VGA: 640x480 (只用于OpenMV Cam M7 的灰度图处理图像,或者彩图采集图像)
图像处理要实现的功能越复杂,分辨率就要设置的越低,或者切换为灰度模式,以此来提高帧数。
img = sensor.snapshot()#image=img
sensor.snapshot().save("example.jpg") # or "example.bmp" (or others)
#需要SD卡
ROI(region of interest),感兴趣区域。机器视觉、图像处理中,从被处理的图像以方框、圆、椭圆、不规则多边形等方式勾勒出需要处理的区域,称为感兴趣区域ROI。
ROI=(x0,y0,x1,y1)#设置roi位置
img.draw_rectangle(ROI)#在图像中标注感兴趣区域
若要获取ROI内图像统计数据,可调用下列代码:
statistics = image.get_statistics(roi=ROI)
print(statistics)
这将输出全部统计数据,实际上通常只需要输出LAB的众数值即可:
statistics=img.get_statistics(roi=ROI)
color_l=statistics.l_mode()
color_a=statistics.a_mode()
color_b=statistics.b_mode()
print(color_l,color_a,color_b)
需引入time模块。
clock = time.clock() # Create a clock object to track the FPS.返回一个时钟对象
#初始化时钟
clock.tick() # Update the FPS clock.开始追踪运行时间
print("FPS:", clock.fps())#可输出帧率.停止追踪运行时间,并返回当前FPS
也可以直接在图像中显示帧率,即构建HUD。
注意: 当连接电脑后,OpenMV会变成一半的速度。当不连接电脑,帧率会增加。
阈值有LAB阈值和灰度阈值。
OpenMV 的IDE里加入了阈值选择工具(位于 工具 → Mechine Vision → Threshold Editor ,选中 Frame Buffer可以获取IDE中的图像)来确定阈值。使用方法为拖动六个滑块,将目标颜色变成白色,其他颜色全变为黑色。
阈值的格式为:
rgb = (minL, maxL, minA, maxA, minB, maxB)#彩色
grayscale = (min,max)#灰度
img = sensor.snapshot()#image=img
img.lens_corr(1.8) # 该参数适用于2.8mm镜头.
#或者
img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)#二者纯粹是语法上的差异
用于扫码,形状识别等。
某些函数省略了不常用的参数设置,详情请参考官方函数库文档。
实现此功能需引入image(机器视觉)模块。
常用的可绘制图形有箭头arrow、圆形circle、十字cross、线段line、矩形rectangle、文字text
image.draw_xxx(x0, y0, x1, y1, color) #颜色可以是灰度值(0-255),或者是彩色值(r, g, b)
xxx内为上述加粗字中的黑体部分。
实现此功能需引入image(机器视觉)模块。
注意与灰度化的区分。
在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。
分为颜色二值化和灰度二值化。
img.binary([threshold])#二值化分割
BINARY_VISIBLE = True or False #设置是否使用img.binary()函数进行图像分割
img = sensor.snapshot().binary([THRESHOLD]) if BINARY_VISIBLE else sensor.snapshot()#用来切换二值化视角或彩色/灰度视角
利用morph算子实现快速边缘检测,速度慢但效果好。
#设置核函数滤波,核内每个数值值域为[-128,127],核需为列表或元组
kernel_size = 1 # kernel width = (size*2)+1, kernel height = (size*2)+1
kernel = [-1, -1, -1,\
-1, +8, -1,\
-1, -1, -1]
img.morph(kernel_size, kernel)
在OV7725 sensor上, 边缘检测可以通过设置sharpness/edge寄存器来增强。
侵蚀函数
img.erode(1, threshold = 2)
#侵蚀函数erode(size, threshold=Auto),去除边缘相邻处多余的点。threshold
#用来设置去除相邻点的个数,threshold数值越大,被侵蚀掉的边缘点越多,边缘旁边白色杂点越少
利用canny算子实现快速边缘检测,编程非常简单,速度快但效果差。
img.find_edges(image.EDGE_CANNY, threshold=(a,b))
特征检测应用较多的有图形检测,如识别圆、识别线段、识别直线、识别矩形。应用上通常把特征检测和颜色追踪结合起来,并通过绘图将检测到的图形在图像中标出。
实现此功能需引入image(机器视觉)模块。
通常是用霍夫变换(Hough transform)完成的。
霍夫变换是用来辨别找出物件中的特征,例如:线条。他的算法流程大致如下,给定一个物件、要辨别的形状的种类,算法会在参数空间(parameter space)中执行投票来决定物体的形状,而这是由累加空间(accumulator space)里的局部最大值(local maximum)来决定。
圆
for c in img.find_circles(threshold = 3500, x_margin = 10, y_margin = 10, r_margin = 10,r_min = 2, r_max = 100, r_step = 2): #threshold 控制从霍夫变换中监测到的圆,只返回大于或等于阈值的圆
#x/y/r_margin 控制所检测的圆的合并;
#r_min,r_max和r_step控制测试圆的半径,缩小测试圆半径的数量可以大大提升性能
img.draw_circle(c.x(), c.y(), c.r(), color = (255, 0, 0)) #找到图像中所有的圆
线段
相比寻找无限长的直线,速度降低不少。
直线
霍夫变换将二维空间中所有的直线映射到以theta&rho为坐标轴的二维空间中,theta代表直线与Y轴负方向的夹角,以Y轴负轴为起始轴,逆时针旋转到直线的角度。rho表示原点到直线的距离,Y轴截距大于0的均为正,Y轴截距小于0则rho为负。
min_degree =
max_degree =
for l in img.find_lines(threshold = 1000, theta_margin = 25, rho_margin = 25):
if (min_degree <= l.theta()) and (l.theta() <= max_degree):
img.draw_line(l.line(), color = (255, 0, 0))
矩形
for r in img.find_rects(threshold = 10000):
img.draw_rectangle(r.rect(), color = (255, 0, 0))
for p in r.corners(): img.draw_circle(p[0], p[1], 5, color = (0, 255, 0)) #标注4个角
rect.corners()
返回一个由矩形对象的四个角组成的四个元组(x,y)的列表。四个角通常是按照从左上角开始沿顺时针顺序返回的。
线性回归分为鲁棒线性回归(linear regression robust)和快速线性回归(linear regression fast)。线性回归通常用于机器人巡线。快速线性回归的原理是使用最小二乘法来拟合线。
line = img.get_regression(threshold, robust = True or False) #robust为1时开启鲁棒线性回归
if (line): img.draw_line(line.line(), color ) #标注检测出的直线
实现此功能需引入image(机器视觉)模块。
分为单灰度识别和单彩色识别。
for blob in img.find_blobs([threshold])
for blob in img.find_blobs([threshold0,threshold1,threshold2,...]) #最多有16个阈值
使用前需进行镜头畸变矫正。
可测量条形码barcodes、二维码qrcodes、矩形码datamatrices等。
条形码的代码相对复杂很多,最简单的是二维码。
for code in img.find_qrcodes(): #识别二维码
img.draw_rectangle(code.rect(), color = (255, 0, 0))
print(code)
虽然可以使用Apriltag进行3D定位,但通常情况下我们只能使用裸机测距(特别是距离目标物较远时,低成本测距传感器将无法派上用场)。
OpenMV采用的是单目摄像头,需要利用参照物的大小比例来计算距离。
其公式为:距离 = 一个常数/直径的像素
该常数的测量方法为:让被测物距离摄像头 n cm,打印出摄像头里直径的像素值,然后相乘,就得到了常数的值。
官网例程已经足够清晰,故本文不再进行公式推算。
仍引用上图,使用单目摄像头实现物体直径测量,需要获取镜头视场角和物距,然后通过三角函数计算尺寸。
由镜头左侧几何关系得:
t a n ( a ) = A p i x 2 L ′ tan(a)=\frac{Apix}{2L'} tan(a)=2L′Apix
t a n ( b ) = B p i x 2 L ′ tan(b)=\frac{Bpix}{2L'} tan(b)=2L′Bpix
故:
t a n ( a ) t a n ( b ) = A p i x B p i x \frac{tan(a)}{tan(b)}=\frac{Apix}{Bpix} tan(b)tan(a)=BpixApix
由镜头右侧几何关系得:
t a n ( b ) = R m L m tan(b)=\frac{Rm}{Lm} tan(b)=LmRm
代入(3)式得:
R m = t a n ( a ) ∗ L m ∗ B p i x A p i x Rm=tan(a)*Lm*\frac{Bpix}{Apix} Rm=tan(a)∗Lm∗ApixBpix