一种基于openmv和触摸屏的动态读取颜色的解决方案
一种基于openmv和触摸屏的动态读取颜色的解决方案
前言:
作为大学生电子设计竞赛控制题选手,常常需要与视觉上位机打交道,openmv作为当前一种开源方案,能够以较低的成本制作,并且官方文档和各种教程丰富,但是苦于光照的影响,程序中预定的阈值往往会出现误差,导致完美运行的工程就此崩塌,故博主以2023年电赛E题为背景,基于B站up主:程欢欢的智能控制集 的开源屏幕方案,设计了一套能够基于触摸屏动态读取阈值的解决方案,该方案成本低廉(屏幕成本大概在25元左右),支持阈值读取,保存,修改,删除等,并可以DIY扩展多阈值
原理:
- 通过阅读星瞳的官方手册 使用统计信息 · OpenMV中文入门教程 ,我们可以使用统计方法来读取某一RIO区域的各种阈值及其平均数,中位数,众数等,基于此,不难想到我们可以通过这个api来设计一个读取RIO阈值的函数:Get_threshold
- 基于B站up主开源的触摸屏方案,我们可以利用其显示摄像头的实时图像,并使用触摸屏操控,设计控件。
- 至此,我们外部控制设备和设计原理都已经掌握,可以开始设计解决方案了
文件结构:
calibration.py :该脚本记录了电阻屏的按压阈值
get_threshold_v3.py(main.py) :该脚本主要实现了动态阈值的读取,保存,修改,删除等操作
screen.py :该脚本实现了SPI总线与屏幕控制IC和触摸IC的初始化
my_threshold.py :该脚本保存了读取的阈值,包括LAB格式,和灰度格式
get_threshold_v3.py的部分函数实现
1. 串口收发函数:
'''
串口发送函数 请根据实际情况更改发送的数据
功能: 以16进制发送一条位置帧
帧结构: 帧头(0x12,0x13),数据段(data1,data2), 帧尾(0x11)|
参数: data1 : 颜色目标X轴量化坐标 范围:0x00 ~ 0x250
data2 : 颜色目标Y轴量化坐标 范围:0x00 ~ 0x250
异常处理: 当找不到色块时, 输出X == 255, Y == 255,表示色块没有找到
'''
def send_data(data1,data2):
uart.write(bytes([12,13])) # 将整数转换为字节类型(16位)并发送
# print([data1,data2])
uart.write(bytes([data1,data2])) # 将整数转换为字节类型(16位)并发送
uart.write(bytes([11])) # 将整数转换为字节类型(16位)并发送
'''
串口接收函数 请根据实际情况更改
'''
def rece_data():
rece_dat = 0
if uart.any():
rece_dat = int.from_bytes(uart.read(1),'little')
print("已接收到数据{0}".format(rece_dat))#打印接收到的数字
while(uart.any() != 0):
print("out")
uart.read(1)
'''
2. 寻找目标色块函数:
'''
寻找最大色块辅助函数
参数: blobs类型
'''
def find_max(blobs):
max_size = 0
max_blob = 0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
'''
寻找最大色块函数
参数: img: 图像
threshold: 颜色阈值
'''
def find_maxblobs(img,threshold):
global cnt_find_fail
print(threshold)
blobs = img.find_blobs(threshold,roi = (40,0,240,240))
if blobs:
#如果找到了目标颜色
cnt_find_fail = 0
max_blob = find_max(blobs)
img.draw_rectangle(max_blob.rect(), color = (255,255,255))
#img.draw_edges(b.min_corners(), color=(0,255,0))
img.draw_cross(max_blob.cx(),max_blob.cy(), color = (255,255,255))
# img.draw_string(10,30,str(max_blob.cx()*max_blob.cy()),color=(255,0,0),mono_space=False)
send_data(max_blob.cx() - 40,max_blob.cy())
else:
cnt_find_fail = cnt_find_fail+1
if cnt_find_fail >= 10:
print("find_out")
send_data(255,255)
3. 文件操作函数:
'''
保存阈值函数
注意: !!! 保存后需要重启器件
会在当前目录下生成一个my_threshold.py文件保存get获取的阈值
'''
def save_threshold():
global text
my_threshold = threshold[0] #读取阈值默认保存到列表第一个
my_grayscale_threshold = (my_threshold[0],my_threshold[1])#灰度值等于LAB中的L即亮度值
file_path = "my_threshold.py" #文件地址
f = open(file_path,mode='w')
f.write('my_threshold=' + str(my_threshold))
f.write('\n')
f.write('my_grayscale_threshold=' + str(my_grayscale_threshold))
f.write(text+' linjiejie and bilibili up: 程欢欢')
#写入LAB和灰度阈值
#延时确保文件被保存
sleep(0.5)
f.close()
print("save sccess")
sleep(0.5)
#重置器件
hard_reset()
'''
删除阈值函数
注意: !!! 删除后需要重启器件
删除当前目录下的my_threshold.py文件, 当前版本未使用该函数
'''
def del_threshold():
sleep(0.5)
os.remove('my_threshold.py')
hard_reset()
'''
读取阈值函数
返回值: 若文件存在,返回文件的LAB阈值(默认),若没有找到则返回(0)元组
如果需要读取的是灰度阈值请自行修改
'''
def read_threshold():
try:
import my_threshold
return my_threshold.my_threshold
except:
print("can't open threshold")
return (0,0,0,0,0,0)
'''
4. 控件绘制函数:
'''
控件绘制函数
绘制clear, get, save, size等控件, 可以自定义
参数: img: image类型
'''
def draw_button(img):
#绘制clear
img.draw_circle(mode_btn_x,mode_btn_y,mode_btn_radius,color=(0,255,0),thickness = 3)
img.draw_string(mode_btn_x-10,mode_btn_y-5,'mode',color=(0,255,0),mono_space=False)
#绘制get
img.draw_circle(get_btn_x,get_btn_y,get_btn_radius,color=(255,255,0),thickness = 3)
img.draw_string(get_btn_x-5,get_btn_y-6,'get',color=(255,255,0),mono_space=False)
#绘制save
img.draw_circle(save_btn_x,save_btn_y,save_btn_radius,color=(0,255,255),thickness = 3)
img.draw_string(save_btn_x-7,save_btn_y-6,'save',color=(0,255,255),mono_space=False)
#绘制size
img.draw_circle(size_btn_x,size_btn_y,size_btn_radius,color=(255,180,255),thickness = 3)
img.draw_string(size_btn_x-6,size_btn_y-6,'size',color=(255,180,255),mono_space=False)
5. 阈值读取函数(核心):
'''
采集阈值函数
采集roi方框中的统计数据, 使用均值滤波
参数 n: 需要连续采集n次
img: 摄像头读取的图像image类型
img_drawing_board: 画板image类型, 用于和img做叠加操作, 在屏幕上显示
返回值: get_thresholds 采集到的阈值元组
'''
def Get_threshold(n,img,img_drawing_board):#采集n次
if(last_x == 0 and last_y == 0):
print('error')
return None
#阈值保存变量初始化
get_thresholds = ()
Lmin = 0
Lmax = 0
Amin = 0
Amax = 0
Bmin = 0
Bmax = 0
for i in range(n):
img = sensor.snapshot()
Statistics = img.get_statistics(roi = (last_x,last_y,get_roi_size,get_roi_size))
img.draw_rectangle(last_x,last_y,get_roi_size,get_roi_size,color=(255,255,255))
img.draw_string(10,10,'getting threshold:',color=(255,0,0),mono_space=False)#打印正在获取阈值
print("{0}...".format(i+1))
Lmin += Statistics.l_min()
Lmax += Statistics.l_max()
Amin += Statistics.a_min()
Amax += Statistics.a_max()
Bmin += Statistics.b_min()
Bmax += Statistics.b_max()
img.b_nor(img_drawing_board) #将画板画布与感光器图像叠加仅支持二值图像(黑白图像)
screen.display(img)
#均值滤波
Lmin //= n
Lmax //= n
Amin //= n
Amax //= n
Bmin //= n
Bmax //= n
get_thresholds = (Lmin,Lmax,Amin,Amax,Bmin,Bmax)
print(get_thresholds)
return get_thresholds
- !!!注意,博主这里仅使用最简单的均值滤波,请根据自己需要修改滤波函数
6. 主函数:
def get_main():
global last_x
global last_y
global rectangle_flag
global threshold
global state
global get_roi_size
highlight_threshold = [(74, 100, -4, 6, -17, 2)] #高光阈值
screen.init()
threshold[0] = read_threshold()#读取文件中的阈值
print(threshold)
img_drawing_board=sensor.alloc_extra_fb(320,240,sensor.RGB565)
img_drawing_board.draw_rectangle(0,0,320,240,fill=True,color=(255,255,255))
#初始化画布
while(True):
#这里state 为0和1都覆盖高光是因为电阻屏四周灵敏度差,容易误触
#这里的状态可以根据自己的需要选择删除, 设置这里的黑色覆盖是为了更加直观的在屏幕中显示目标色块与实际检测结果在图像中的分布情况, 以帮助我们来调试和选择设置方框大小
if state == 0:
img = sensor.snapshot().binary([highlight_threshold[0]], invert=False, zero=True)
elif state == 1:
img = sensor.snapshot().binary([highlight_threshold[0]], invert=False, zero=True)
elif state == 2:
img = sensor.snapshot().binary([threshold[0]], invert=False, zero=True)
# img = sensor.snapshot()#.binary([(86, 254)])二值化操作
# img = img.gaussian(3)
if screen.press:
#判断mode按键
if (mode_btn_x-mode_btn_radius<screen.x<mode_btn_x+mode_btn_radius) and (mode_btn_y-mode_btn_radius<screen.y<mode_btn_y+mode_btn_radius):
img_drawing_board.draw_rectangle(0,0,320,240,fill=True,color=(255,255,255))#将img_drawing_board设置为全黑
rectangle_flag = 0#方框数清零
state = (state + 1)%3
#判断get按键
if (get_btn_x-get_btn_radius<screen.x<get_btn_x+get_btn_radius) and (get_btn_y-get_btn_radius<screen.y<get_btn_y+get_btn_radius):
threshold[0] = Get_threshold(100,img,img_drawing_board)#将读取的阈值保存读阈值列表[0]中, 请根据自己的需要修改逻辑和列表大小
print(threshold[0])
highlight_threshold[0] = Get_threshold(100,img,img_drawing_board)#将读取的阈值保存读高光阈值列表[0]中, 请根据自己的需要修改逻辑和列表大小
# print(highlight_threshold)
#判断save按键
if (save_btn_x-save_btn_radius<screen.x<save_btn_x+save_btn_radius) and (save_btn_y-save_btn_radius<screen.y<save_btn_y+save_btn_radius):
save_threshold()
#判断size按键
if (size_btn_x-save_btn_radius<screen.x<size_btn_x+save_btn_radius) and (size_btn_y-save_btn_radius<screen.y<size_btn_y+save_btn_radius):
get_roi_size = 10 + (get_roi_size + 5) % 35
#长按处理
cnt_press = cnt_press + 1
img.draw_cross(screen.x,screen.y)
if cnt_press > press_limit :
if rectangle_flag > 1:
img_drawing_board.draw_rectangle(last_x,last_y,get_roi_size,get_roi_size,color=(255,255,255))
rectangle_flag = 1
# pass
img_drawing_board.draw_rectangle(screen.x,screen.y,get_roi_size,get_roi_size,color=(0,0,0))
rectangle_flag = rectangle_flag + 1
last_x = screen.x
last_y = screen.y
cnt_press = 0
else:
cnt_press = 0
find_maxblobs(img,threshold) #匹配最大色块
img.b_nor(img_drawing_board) #将画板画布与感光器图像叠加仅支持二值图像(黑白图像)
fps=clock.fps() #获取帧速
img.draw_string(10,10,'FPS:'+str(fps),color=(255,0,0),mono_space=False) #绘制帧速
img.draw_string(10,30,'now_size:'+str(get_roi_size),color=(255,180,255),mono_space=False) #当前采样矩形大小
img.draw_string(10,20,'now_mode:'+str(state+1),color=(255,180,255),mono_space=False) #当前采样矩形大小
draw_button(img)
screen.display(img) #显示图像到屏幕,并获取触摸信息。不运行此函数,不会更新触屏信息。
get_main()
博主这里提供一个简单参考mian函数,请根据自己的需要修改或重写该函数
实现效果

如何使用
1.初始化:
当我们拿到代码时,只有get_threshold_v3.py(main.py)和screen.py,我们缺少calibration.py和calibration.py
-
缺少calibration.py :我们会进入下面的触摸IC初始化界面,根据提示按压初始化IC压力参数(强行重启openmv)即可
![]()
-
缺少my_threshold.py :没有影响,按照下面的控件使用教程读取阈值即可
2.控件:
- size控件:单击生效,用来修改采集阈值RIO区域的大小
- save控件:单击生效,保存已经采集的阈值(openmv会强行重启)
- mode控件:单击切换,上机默认mode1(使用黑色覆盖高光区域),可以单击切换mode2(将目标颜色和实际颜色相同的像素点用黑色覆盖)
- get 控件:单击生效,在使用前需要在想要采集的屏幕位置上长按一段时间,出现一个小矩形白框,白框就是采集阈值RIO区域,然后单击get即可,值得注意的是博主这里实现的长按是基于openmv帧率的(mcpy在openmv上的定时器太难用了),如果觉得长按时间太长可以修改 press_limit 变量来修改长按时间
- Q&A:为什么有方框指示了还要用黑色覆盖呢?
- 为了能够更直观的看见目标颜色与实际情况,方便我们进行调试,方框显示会有偏差
触摸屏硬件获取
版本限制
该脚本是在博主大二(2024)时编写的,受限与当时的知识储备,和当时程欢欢屏幕固件的版本限制,该屏幕固件仅支持openmv固件4.3.3之前版本,如果想要使用,可以下载旧版的openmvIDE为手上的openmv烧录旧版本固件(IDE版本大概为3.3左右),或者下载程欢欢的新版屏幕固件,并根据代码修改即可使用
程欢欢 gitee仓库地址
2023电赛E题: 2023电赛E题,关注B站‘程欢欢的智能控制集’
程欢欢/OpenMV-resistive_touch_screen - 码云 - 开源中国
【开源】OpenMV触屏扩展板,只要26块!额外预留三路舵机+一路UART
源码获取
通过网盘分享的文件:openmv动态读取脚本
链接: https://pan.baidu.com/s/1rgaN4LVZOnnrtR-iTfycAw 提取码: mcpy
如果有疑问可以通过邮箱与我交流
1309909195@qq.com


浙公网安备 33010602011771号