非接触物体尺寸形态测量 - OpenMV
文章目录
需求
测试场景布置图
目标板和背景板放置示意图
思路
题目上有三种颜色三种形状的物体, 可以先判断物体的颜色二值化后判断物体形状, 这样可以有效降低误差, 但是后来测试的时候经常会发现有误判的现象, 想到可能是因为背景嘈杂并且二值化后的图像信息会有损耗, 我开始思考为什么人类识别这些物体的成功率如此之高, 自我认为是因为我们会聚焦到一个物体并且不断找出这个物体的特征然后根据经验判断出物体是什么, 因此我设计了一个几率模型,或者说是一种擂台法则, 把在一段时间内检测到的数据进行整合得到一个几率表, 根据擂台法则得出物体的准确信息
大概可分为以下几个要点:
- 聚焦
- 识别图形颜色以及形状
- 建立几率表
- 获取距离
- 根据物体的距离算出物体大小 (好像要用到线性拟合公式但是我当时并不知道有这么个东西, 自己根据大量数据来找规律最后侥幸得出了一个公式)
- 自动寻找目标
- 立体目标测量 (因为时间关系做出来的程序准确率不高, 在这里就不介绍了)
根据题目要求, 程序分为两种运行模式:
flag A:
确认物体颜色,获得距离算出物体大小
flag B:
先确认物体颜色, 然后再跟踪物体, 跟踪物体之后测量出距离, 随后进行尺寸计算
实现
聚焦
所谓聚焦,就是让当前画面中只出现自己所关心的内容, 我使用OpenMV提供的图像二值化方法来实现这一点
根据像素是否在阈值列表 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窗口中拖动滑块来确定颜色阈值。
invert
反转阈值操作,像素在已知颜色范围之外进行匹配,而非在已知颜色范围内。
设置 zero
为True来使阈值像素为零,并使不在阈值列表中的像素保持不变。
mask
是另一个用作绘图操作的像素级掩码的图像。掩码应该是一个只有黑色或白色像素的图像,并且应该与你正在绘制的 image
大小相同。 仅掩码中设置的像素被修改。
to_bitmap
将图像数据转换为二进制位图图像,每个像素以1位的形式存储。 对于非常小的图像,新的位图图像可能无法放入原始图像中,因此需要使用 copy
进行就地操作。
copy
如果为True,则在堆上创建二进制图像的副本,而不是修改源图像。
注解:
位图图像就像只有两个像素值(0和1)的灰度图像一样。 此外,位图图像被打包,这样它们每个像素仅存储1位,因此非常小。 OpenMV图像库允许位图图像在所有位置的
sensor.GRAYSCALE
和sensor.RGB565
中使用。 但是,将许多操作应用于位图图像时没有任何意义,因为位图图像只有2个值。 OpenMV建议在操作中对mask
值使用位图图像,因为它们很容易适合MicroPython堆。 最后,将位图图像像素值0和1应用于sensor.GRAYSCALE
或sensor.RGB565
图像时, 将被解释为黑白。该库自动处理转换。
返回图像对象,以便您可以使用 .
表示法调用另一个方法。
不支持压缩图像和bayer图像。
封装一个工具方法方便下面的程序编写:
# binary filter
def testBinary(img, thresholdValue):
return img.binary([thresholdValue])
分别判断三种形状
圆形判断
使用MicroPython.img对象自带的Hough变换查找圆形
def isCircle(img):
circles = img.find_circles(
threshold=5000, x_margin=10, y_margin=10,
r_margin=10, r_min=8, r_max=100, r_step=2)
print("circles: ", circles)
if (len(circles) < 1):
return False
return circles[0].r() # 像素直径
三角形判断
三角形使用占空比判断, density函数可以自动返回色块面积/外接矩形面积 的值
def isTriangle(img):
sideLength = 0 # 存放测量到的像素块的边长
blobs = img.find_blobs([gray_threshold])
# print(blobs)
blob = find_max(blobs)
if (blob == None):
return False
print('该形状占空比为', blob.density())
# density函数可以自动返回 色块面积/外接矩形面积 的值
if 0.56 > blob.density() > 0.50:
print("检测为三角型 ", end='')
# img.draw_cross(blob.cx(), blob.cy(), color=(255,0,0), size=5)
print('三角型边长', blob.w())
sideLength = blob.w()
print("triangle sideLength: ", sideLength)
return sideLength
return False
矩形判断
矩形同样使用占空比判断, density函数可以自动返回色块面积/外接矩形面积 的值
def isRect(img):
blobs = img.find_blobs([gray_threshold])
print(blobs)
blob = find_max(blobs)
if (blob == None):
return False
# for blob in blobs:
print('该形状占空比为', blob.density())
if 0.99 > blob.density() > 0.87:
print("检测为矩形 ", end='')
print('矩形边长', blob.w())
return blob.w()
return False
物体的距离&大小
最终决定采用激光测距
关于尺寸计算公式实在想不起来当时怎么计算的了, 实验表明只能在200cm~300cm之间并且只能测量圆形的尺寸
使用像素计算
这种方式参考OpenMV官方例程, 倒也可以, 但是误差较大, 像素测距以及计算尺寸的部分代码:
'距离的K值 L=k/像素'
k = 2400
def isCircle(img):
print(img)
res = {'L': 0, 'R': 0} # 距离 直径
circles = img.find_circles(
threshold=5000, x_margin=10, y_margin=10,
r_margin=10, r_min=8, r_max=100, r_step=2)
print("circles: ", circles)
if (len(circles) < 1):
return False
for circle in circles:
# img.draw_circle(circle.x(), circle.y(), circle.r(), color=(255, 0, 0))
L = k / circle.r()
res['L'] = L # 距离
k1 = (L - 200) * 0.04
R = (k1 + circle.r()) * 3.04
res['R'] = R # 直径
print("isCircle:res: ", res)
return res
使用激光测距模块计算
采用这种方式好处就是激光测距本身就带有激光灯, 并且极其准确, 缺点就是需要占用串口
PLS-K-60 激光测距模块:
接线图:
用串口给激光测距发送特定字节命令可以操作它, 部分指令:
详细数据手册稍后上传至CSDN资源: PLS-K-60激光测距模块产品手册.pdf-CSDN下载
激光测距程序实现:
def getDistance():
# uart.write(b'D') # 测距离
while(True): # 因为我需要调用一次这个方法就返回给我准确的数据, 所以这里需要一直等待着获取到有用的数据
uart.write(b'D') # 测距离 0x44
v = uart.read(1024)
# print(v)
if(v != None):
# print(v)
s = v.decode('utf-8')
if(len(s) == 15):
s = s[1:7]
return float(s.strip())*100 # 将距离(cm)反馈出去
else:
continue
# 获得距离&尺寸 Distance & Size
# 根据物体的距离算出物体大小
def getDS(res):
ds = {'d': 0, 's': 0}
L = getDistance()
k1 = (L - 200) * 0.04
S = (k1 + int(res[2])) * 3.04 # size
ds['d'] = L
ds['s'] = S
return ds
建立几率模型
最最最重要的一步就在这里, 首先从threshold
中取出一种颜色对图像进行二值化处理, 然后用二值化后的图像去判断是什么形状, 将会对每个颜色的二值化图片检测5遍, 将每次检测到的图形和颜色都暂存到一个py字典中, 来增加可能是这种物体的几率. 这样做的好处就是既可以获取到当前物体的颜色形状顺便还可以将检测到的物体的边长记录下来.
def areba(img):
# a=形状 c=color s=shape
a = {'c': 0, 'r': 0, 't': 0} # circle rect triangle
c = {'0': 0, '1': 0, '2': 0} # red grenn blue
s = {} # pix_size
count = 0
# 形状字典擂台,规定次数内谁的命中次数最多就获胜
for i in range(3): # 三种颜色
img = sensor.snapshot()
print(threshold[i])
img = testBinary(img, threshold[i])
for x in range(5): # 五次确认形状
count = count + 1
print("count: ", count)
# 纠结到底要不要再次拍照
img = sensor.snapshot()
img = testBinary(img, threshold[i])
# 暂存结果
cir = isCircle(img)
rect = isRect(img)
tria = isTriangle(img)
if (cir != False):
a['c'] = a['c'] + 1 # 增加形状几率
c[str(i)] = c[str(i)] + 1 # 增加颜色几率
if (s.get(str(cir)) != None): # 已经包含这个Key
s[str(cir)] = s[str(cir)] + 1 # 增加像素尺寸机率 k=数据,v=命中次数
else: # 不包含就创建
s[str(cir)] = 0
s[str(cir)] = s[str(cir)]+1
if (rect != False): # -------- 矩形开始 ---------
a['r'] = a['r'] + 1
c[str(i)] = c[str(i)] + 1
if (s.get(str(rect)) != None): # 已经包含这个Key
s[str(rect)] = s[str(rect)] + 1 # 增加像素尺寸机率 k=数据,v=命中次数
else: # 不包含就创建
s[str(rect)] = 0
if (tria != False): # -------- 三角形开始 ---------
a['t'] = a['t'] + 1
c[str(i)] = c[str(i)] + 1
if (s.get(str(tria)) != None): # 已经包含这个Key
s[str(tria)] = s[str(tria)] + 1 # 增加像素尺寸机率 k=数据,v=命中次数
else: # 不包含就创建
s[str(tria)] = 0
# print("颜色: ", c)
# print("形状: ", a)
# dict(a, **c)
return [a, c, s] # 合并字典 https://www.cnblogs.com/lmh001/p/9888156.html
处理结果集
处理结果集, 判断谁获胜 将获胜方的数据返回出去
重要思想: 颜色形状擂台可以和尺寸距离擂台分别对战,最后两方面的获胜者一定说的是同一个物体
def handle_data(data):
shapes = {'r': 'Rect', 'c': 'Circle', 't': 'Tirangle'}
colors = ['Red', 'Green', 'Blue']
shape = ''
color = ''
size = ''
max_value = 0
for (k, v) in data[0].items():
if (v > max_value): # update max_value
max_value = v
shape = shapes[k]
max_value = 0
for (k, v) in data[1].items():
if (v > max_value):
max_value = v
color = colors[int(k)]
max_value = 0
for (k, v) in data[2].items():
if (v > max_value):
max_value = v
size = k
return [shape, color, size]
完整代码
查看我的Gitee:
GFinal.py · Missper/Race_G - 码云 - 开源中国 (gitee.com)
一些参考资料
后续可能会使用机器学习,或者小型神经网络来改善这个项目
G题_辽宁赛区_大连理工大学——非接触物体尺寸形态测量 - 2020年TI杯省赛作品交流区 - 全国大学生电子设计竞赛培训网 (nuedc-training.com.cn)
Jupyter Notebook介绍、安装及使用教程 - 简书 (jianshu.com)
关于TensorFlow | TensorFlow中文官网 (google.cn)
OpenMV Cam H7 Plus (edgeimpulse.com)
让数以百万计的Arduino开发者可以在微控制器中可以轻松使用机器学习,只因为有了它 - 知乎 (zhihu.com)
mask-tflite/README_CN.md at main · SingTown/mask-tflite (github.com)
关于TensorFlow | TensorFlow中文官网 (google.cn)
OpenMV Cam H7 Plus (edgeimpulse.com)
让数以百万计的Arduino开发者可以在微控制器中可以轻松使用机器学习,只因为有了它 - 知乎 (zhihu.com)
mask-tflite/README_CN.md at main · SingTown/mask-tflite (github.com)