目录
一、OpenMV是什么
OpenMV是一款具有图像处理功能的可编程摄像头模块,可以实现简单的机器视觉应用,例如人脸检测、Apriltag追踪、圆形检测、矩形检测等。
二、OpenART mini与OpenMV对比
(这里介绍另一款可编程摄像头模块OpenART mini,在功能上与OpenMV几乎无差别,但性能和价格都优于OpenMV)
OpenART mini是
逐飞科技
在
恩智浦
的OpenART套件的基础上,去除非视觉部分而制作出来的迷你版。虽说只是迷你版,但“麻雀虽小,五脏俱全”。OpenART mini 不仅可以很轻松的完成机器视觉应用,还可以完成神经网络模型的部署。
比较项目 | OpenMV4 H7 Plus | OpenART mini |
---|---|---|
价格 | 599 | 380 |
处理器 | STM32H743II | MIMXRT1064 |
主频 | 480MHz | 600MHz |
RAM | 1M | 1M |
FLASH | 2M | 4M |
编程环境 | Micropython | Micropython |
OpenMV视觉库 | √ | √ |
三、图像处理背景知识
1.像素和分辨率
-
像素:像素是数字图像中的
最小单元
。 -
分辨率:分辨率指
单位长度内像素点的数量
,常用的度量为ppi(pixels per inch),即每英寸有多少像素点。
感光元件是由很多个感光点构成的,比如有
640
×
480
640×480
640
×
480
个点,每个点就是一个像素,把每个点的像素收集整理起来,就是一副图片,那么这张图片的分辨率就是
640
×
480
640×480
640
×
480
:
2. 帧率
帧率(FPS,Frames Per Second)就是每秒钟处理的图片数量,如果超过20帧,人眼就基本分辨不出卡顿。电影的帧率一般是24FPS,游戏的帧率一般都大于30FPS。
3.RGB三原色
RGB指的是红绿蓝三种颜色,是光学三原色,所有的颜色都可以由这三种色彩混合而成。
(三原色是依据人类视觉定义的,不存在绝对的三原色)
4.LAB颜色空间
Lab是由一个亮度通道和两个颜色通道组成的。在Lab颜色空间中,每个颜色用L、a、b三个数字表示,各个分量的含义是这样的:
-
L
代表亮度 -
a
代表从绿色到红色的分量 -
b
代表从蓝色到黄色的分量
Lab是基于人对颜色的感觉来设计的,更具体地说,它是感知均匀(perceptual uniform)的。Perceptual uniform的意思是,如果数字(即前面提到的L、a、b这三个数)变化的幅度一样,那么它给人带来视觉上的变化幅度也差不多。
Lab相较于RGB与CMYK等颜色空间更符合人类视觉,也更容易调整:想要调节
亮度
就调节
L通道
,想要调节
色彩平衡
就分别调
a
和
b
。
四、OpenMV图像处理方法
1.感光元件
OpenMV中使用sensor模块来设置感光元件的参数,具体使用方法如下:
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对象
自动增益/白平衡/曝光
-
sensor.set_auto_gain()
自动增益开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动增益。 -
sensor.set_auto_whitebal()
自动白平衡开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动白平衡。 -
sensor.set_auto_exposure(enable[, exposure_us])
- enable 打开(True)或关闭(False)自动曝光。默认打开。
- 如果 enable 为 False,则可以用 exposure_us 设置一个固定的曝光时间(以微秒为单位)。
窗口ROI
ROI:Region Of Interest,图像处理中的术语“感兴趣区”。就是在要处理的图像中提取出的要处理的区域。
-
sensor.set_windowing(roi)
roi的格式是(x, y, w, h)- x:ROI区域中左上角的x坐标
- y:ROI区域中左上角的y坐标
- w:ROI的宽度
- h:ROI的高度
2.画图
视觉系统通常需要给使用者提供一些反馈信息。直接在图像中显示出来,很直观。
画线
-
image.draw_line(line_tuple, color=White)
在图像中画一条直线。- line_tuple的格式是(x0, y0, x1, y1),意思是(x0, y0)到(x1, y1)的直线。
- 颜色可以是灰度值(0-255),或者是彩色值(r, g, b)的tupple。默认是白色
画框
-
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是两侧的尺寸
写字
-
image.draw_string(x, y, text, color=White)
在图像中写字 8×10的像素- x,y是坐标。
- text是要写的字符串。
示例
# Hello World Example
#
# Welcome to the OpenMV IDE! Click on the green run arrow button below to run the script!
import sensor, image, time
sensor.reset() # 初始化摄像头
sensor.set_pixformat(sensor.RGB565) # 格式为 RGB565.
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(10) # 跳过10帧,使新设置生效
while(True):
img = sensor.snapshot() # Take a picture and return the image.
img.draw_line((20, 30, 40, 50))
img.draw_line((80, 50, 100, 100), color=(255,0,0))
img.draw_rectangle((20, 30, 41, 51), color=(255,0,0))
img.draw_circle(50, 50, 30)
img.draw_cross(90,60,size=10)
img.draw_string(10,10, "hello world!")
3. 寻找色块(颜色识别)
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)
- thresholds是颜色的阈值,在返回的色块对象blob可以调用code方法,来判断是什么颜色的色块。
- x_stride(查找的色块的x方向上最小宽度的像素,默认为2)
- y_stride(查找的色块的y方向上最小宽度的像素,默认为1)
- invert(反转阈值,把阈值以外的颜色作为阈值进行查找)
- area_threshold(面积阈值,如果色块被框起来的面积小于这个值,会被过滤掉)
- pixels_threshold(像素个数阈值,如果色块像素数量小于这个值,会被过滤掉)
-
merge(合并,如果设置为True,那么合并所有重叠的blob为一个)
注意:这会合并所有的blob,无论是什么颜色的。如果你想混淆多种颜色的blob,只需要分别调用不同颜色阈值的find_blobs。 - margin(边界,如果设置为1,那么两个blobs如果间距1一个像素点,也会被合并)
find_blobs()返回值
-
for blob in img.find_blobs(thresholds):
- blob.rect() 返回这个色块的外框——矩形元组(x, y, w, h)
- blob.x() 返回色块的外框的x坐标(int)
- blob.y() 返回色块的外框的y坐标(int)
- blob.w() 返回色块的外框的宽度w(int)
- blob.h() 返回色块的外框的高度h(int)
- blob.pixels() 返回色块的像素数量(int)
- blob.cx() 返回色块的外框的中心x坐标(int)
- blob.cy() 返回色块的外框的中心y坐标(int)
- blob.rotation() 返回色块的旋转角度(单位为弧度)(float)
- blob.code() 返回一个16bit数字,每一个bit会对应每一个阈值
- blob.count() 如果merge=True,那么就会有多个blob被合并到一个blob,这个函数返回的就是这个的数量
- blob.area() 返回色块的外框的面积
- blob.density() 返回色块的密度
示例
# Single Color RGB565 Blob Tracking Example
#
# This example shows off single color RGB565 tracking using the OpenMV Cam.
import sensor, image, time, math
threshold_index = 0 # 0 for red, 1 for green, 2 for blue
# Color Tracking Thresholds (L Min, L Max, A Min, A Max, B Min, B Max)
# The below thresholds track in general red/green/blue things. You may wish to tune them...
thresholds = [(30, 100, 15, 127, 15, 127), # generic_red_thresholds
(30, 100, -64, -8, -32, 32), # generic_green_thresholds
(0, 30, 0, 64, -128, 0)] # generic_blue_thresholds
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
clock = time.clock()
# Only blobs that with more pixels than "pixel_threshold" and more area than "area_threshold" are
# returned by "find_blobs" below. Change "pixels_threshold" and "area_threshold" if you change the
# camera resolution. "merge=True" merges all overlapping blobs in the image.
while(True):
clock.tick()
img = sensor.snapshot()
for blob in img.find_blobs([thresholds[threshold_index]], pixels_threshold=200, area_threshold=200, merge=True):
# These values depend on the blob not being circular - otherwise they will be shaky.
if blob.elongation() > 0.5:
img.draw_edges(blob.min_corners(), color=(255,0,0))
img.draw_line(blob.major_axis_line(), color=(0,255,0))
img.draw_line(blob.minor_axis_line(), color=(0,0,255))
# These values are stable all the time.
img.draw_rectangle(blob.rect())
img.draw_cross(blob.cx(), blob.cy())
# Note - the blob rotation is unique to 0-180 only.
img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20)
print(clock.fps())
4. 测距
OpenMV采用的是单目摄像头,想要实现测距,就需要选参照物,利用参照物的大小比例来计算距离。
# Measure the distance
#
# This example shows off how to measure the distance through the size in imgage
# This example in particular looks for yellow pingpong ball.
import sensor, image, time
# For color tracking to work really well you should ideally be in a very, very,
# very, controlled enviroment where the lighting is constant...
yellow_threshold = ( 56, 83, 5, 57, 63, 80)
# You may need to tweak the above settings for tracking green things...
# Select an area in the Framebuffer to copy the color settings.
sensor.reset() # Initialize the camera sensor.
sensor.set_pixformat(sensor.RGB565) # use RGB565.
sensor.set_framesize(sensor.QQVGA) # use QQVGA for speed.
sensor.skip_frames(10) # Let new settings take affect.
sensor.set_auto_whitebal(False) # turn this off.
clock = time.clock() # Tracks FPS.
K=5000#the value should be measured
while(True):
clock.tick() # Track elapsed milliseconds between snapshots().
img = sensor.snapshot() # Take a picture and return the image.
blobs = img.find_blobs([yellow_threshold])
if len(blobs) == 1:
# Draw a rect around the blob.
b = blobs[0]
img.draw_rectangle(b[0:4]) # rect
img.draw_cross(b[5], b[6]) # cx, cy
Lm = (b[2]+b[3])/2
length = K/Lm
print(length)
#print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while
# connected to your computer. The FPS should increase once disconnected.
四、串口通道
在项目中,OpenMV通常只用来处理图像数据,而处理结果要传到MCU中做下一步处理。这个过程中需要用到串口来传送数据。
from machine import UART
uart = UART(1, baudrate=115200) # 初始化串口 波特率设置为115200 TX是B12 RX是B13
uart.write("uart test\r\n") # 发送字符串
uart_num = 0 # 定义一个变量
uart_array = [48,49,50,51,52,53,54,55,56,57] # 定义一个列表 保存数字
uart.write(bytearray(uart_array)) # 发送列表
uart.write(bytearray([0x41])) # 发送一个十六进制数据
while(True):
uart_num = uart.any() # 获取当前串口数据数量
if(uart_num):
uart_str = uart.read(uart_num) # 读取串口数据
uart.write(uart_str) # 将读取到的串口数据发回
# uart.read会自动在数据末尾添加\n的操作,如果想去掉可以用strip()去掉
# 例如 uart_str = uart.read(uart_num).strip()
# 这样 uart_str得到的数据就不会被自动添加\n
五、条形码、矩形码、二维码
1.条形码
-
find_barcodes([roi])
查找 roi 内所有一维条形码并返回一个 image.barcode 对象列表
# 条形码识别例程
#
# 这个例子展示了使用OpenMV Cam M7来检测条形码是多么容易。条形码检测不适用于M4相机。
import sensor, image, time, math
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.VGA) # High Res!
sensor.set_windowing((640, 80)) # V Res of 80 == less work (40 for 2X the speed).
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # 必须关闭此功能,以防止图像冲洗…
sensor.set_auto_whitebal(False) # 必须关闭此功能,以防止图像冲洗…
clock = time.clock()
# 条形码检测可以在OpenMV Cam的OV7725相机模块的640x480分辨率下运行。
# 条码检测也将在RGB565模式下工作,但分辨率较低。 也就是说,
# 条形码检测需要更高的分辨率才能正常工作,因此应始终以640x480的灰度运行。
def barcode_name(code):
if(code.type() == image.EAN2):
return "EAN2"
if(code.type() == image.EAN5):
return "EAN5"
if(code.type() == image.EAN8):
return "EAN8"
if(code.type() == image.UPCE):
return "UPCE"
if(code.type() == image.ISBN10):
return "ISBN10"
if(code.type() == image.UPCA):
return "UPCA"
if(code.type() == image.EAN13):
return "EAN13"
if(code.type() == image.ISBN13):
return "ISBN13"
if(code.type() == image.I25):
return "I25"
if(code.type() == image.DATABAR):
return "DATABAR"
if(code.type() == image.DATABAR_EXP):
return "DATABAR_EXP"
if(code.type() == image.CODABAR):
return "CODABAR"
if(code.type() == image.CODE39):
return "CODE39"
if(code.type() == image.PDF417):
return "PDF417"
if(code.type() == image.CODE93):
return "CODE93"
if(code.type() == image.CODE128):
return "CODE128"
while(True):
clock.tick()
img = sensor.snapshot()
codes = img.find_barcodes()
for code in codes:
img.draw_rectangle(code.rect())
print_args = (barcode_name(code), code.payload(), (180 * code.rotation()) / math.pi, code.quality(), clock.fps())
print("Barcode %s, Payload \"%s\", rotation %f (degrees), quality %d, FPS %f" % print_args)
if not codes:
print("FPS %f" % clock.fps())
2.矩形码
# 矩形码识别例程
#
# 这个例子展示了使用OpenMV Cam M7来检测数据矩阵是多么容易。数据矩阵检测不适用于M4相机。
import sensor, image, time, math
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) # 必须关闭此功能,以防止图像冲洗…
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
img.lens_corr(1.8) # 1.8的强度对于2.8mm的镜头来说是好的。
matrices = img.find_datamatrices()
for matrix in matrices:
img.draw_rectangle(matrix.rect(), color = (255, 0, 0))
print_args = (matrix.rows(), matrix.columns(), matrix.payload(), (180 * matrix.rotation()) / math.pi, clock.fps())
print("Matrix [%d:%d], Payload \"%s\", rotation %f (degrees), FPS %f" % print_args)
if not matrices:
print("FPS %f" % clock.fps())
3.二维码
# 二维码例程
#
# 这个例子展示了OpenMV Cam使用镜头校正来检测QR码的功能(请参阅qrcodes_with_lens_corr.py脚本以获得更高的性能)。
import sensor, image, time
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # 必须关闭此功能,以防止图像冲洗…
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
img.lens_corr(1.8) # 1.8的强度参数对于2.8mm镜头来说是不错的。
for code in img.find_qrcodes():
img.draw_rectangle(code.rect(), color = (255, 0, 0))
print(code)
print(clock.fps())
参考文章
OpenMV嵌入式图像处理