一种基于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. 主函数:
[code]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 |