此次电赛我负责的部分主要是视觉,所以我着重和详细讲解一下视觉部分,不止限于此次电赛,而是从这次电赛视觉部分更好得学习和了解视觉,以及如何去运用视觉,以便在下次比赛中能更好的理解和使用!
这次电赛我使用的视觉开发板是K210
,这里先给出官方教程文档:MaixPy 文档
这是openmv
的官方文档,其实它和k210都是使用python进行编写的,代码部分其实大差不差(串口方面书写会稍有差异),openmv的文档会更加详细,这里也给出:MicroPython 和 OpenMV Cam 中文文档
注意,下面的代码是根据功能分模块写的,实际结合运用时要稍作修改和整合。由于是视觉新手,可能会存在少许问题或者值得优化的地方,希望各位能在评论区或者私信指出,我会很高兴接收和与大家交流!
识别激光的大体思路不难:使用find_blobs(
函数即可,这里的难点是如何使识别到的激光灵敏且准确!
对图像进行了哪些处理可以看看以下代码,给出了详细解释!但是还是要根据具体情况来调,在我当时的场景下识别是非常准确和灵敏的。(绿色圈中的就是激光正中心,左边红色的是光影)
# Untitled - By: Yaoyao - 周四 8月 3 2023
import sensor, image, time, lcd
from machine import UART #串口库函数
from Maix import GPIO
from fpioa_manager import fm # GPIO重定向函数(引脚映射
fm.register(GPIO.GPIOHS10, fm.fpioa.UART1_TX)
fm.register(GPIO.GPIOHS11, fm.fpioa.UART1_RX)
uart = UART(UART.UART1, 115200, 8, None, 1, timeout=1000, read_buf_len=4096)
lcd.init(freq=15000000)
sensor.reset() # Reset and initialize the sensor. It will
# run automatically, call sensor.run(0) to stop
sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.QVGA) # Set frame size to QVGA (320x240)
sensor.set_windowing((176,176))
sensor.skip_frames(time = 2000) # Wait for settings take effect.
#sensor.set_auto_whitebal(False) # turn this off..
sensor.set_auto_exposure(True, 80)#在这里调节曝光度,调节完可以比较清晰地看清激光点
#sensor.set_contrast(1) #设置对比度范围为[-2,+2]
sensor.set_auto_gain(False)#关闭自增益
sensor.set_brightness(-1) #摄像头亮度,范围为[-2,+2]
#水平方向翻转
sensor.set_hmirror(True)
# 垂直方向翻转
sensor.set_vflip(True)
clock = time.clock() # Create a clock object to track the FPS.
clock = time.clock()
green_threshold=[(90, 100, -85, 29, 3, 90)]
red_threshold = [(60, 255, -20, 20, -20, 20)]
#red_threshold = [(44, 93, 7, 45, -8, 14)]
#识别激光点代码
def color_blob(threshold):
blobs = img.find_blobs(threshold,x_stride=1, y_stride=1, area_threshold=0, pixels_threshold=0,merge=False,margin=1)
if len(blobs)>=1 :#有色块
# Draw a rect around the blob.
b = blobs[0]
#img.draw_rectangle(b[0:4]) # rect
cx = b[5]
cy = b[6]
for i in range(len(blobs)-1):
#img.draw_rectangle(b[0:4],color = (0,255,0)) # rect
cx = blobs[i][5]+cx
cy = blobs[i][6]+cy
cx=int(cx/len(blobs))
cy=int(cy/len(blobs))
img.draw_cross(cx, cy,color = (255,0,0)) # cx, cy
return int(cx), int(cy)
return -1, -1 #表示没有找到
while(True):
clock.tick()
img = sensor.snapshot()
img.draw_cross((90,90),color = (0,0,255),size = 10, thickness=1)
cx, cy = color_blob(red_threshold)
dot_str = str(cx) + ',' + str(cy)+'+'
uart.write( "@" + dot_str + "!") # 使用换行符作为消息的结束,方便 Arduino 一次读取一个坐标
#time.sleep_ms(20)
#print("@" + dot_str + "!")
#print("cx:",cx,"cy",cy)
img.draw_circle(cx, cy, 5, color=127, thickness=2)
lcd.display(img, x_scale=1, y_scale=1)
sensor.set_auto_exposure(True, 80
\ sensor.set_brightness(-1)
:通过调整曝光度\亮度,可以控制图像的明暗程度,从而创造出不同的视觉效果sensor.set_auto_whitebal(False)和sensor.set_auto_gain(False)
。但是如果调节出来没结果。可以尝试打开看看(所以还是说要看具体环境,根据具体环境进行调节!)场景 : 在这次比赛中,我们的摄像头是始终固定不动的,其中k210一项重要的功能就是识别黑色方框矩形的四个顶点,并且把坐标发送给主控板。
此处先上代码
import sensor, image, lcd, time
from machine import UART #串口库函数
from Maix import GPIO
from fpioa_manager import fm # GPIO重定向函数(引脚映射
fm.register(GPIO.GPIOHS10, fm.fpioa.UART1_TX)
fm.register(GPIO.GPIOHS11, fm.fpioa.UART1_RX)
uart = UART(UART.UART1, 9600, 8, None, 1, timeout=1000, read_buf_len=4096)
lcd.init(freq=15000000)
sensor.reset() # Reset and initialize the sensor. It will
# run automatically, call sensor.run(0) to stop
sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.QQVGA) # Set frame size to 160x120 分辨率的相机传感器。
sensor.set_windowing((120,120))
#水平方向翻转
sensor.set_hmirror(True)
# 垂直方向翻转
sensor.set_vflip(True)
sensor.skip_frames(time = 10000) # Wait for settings take effect.
clock = time.clock() # Create a clock object to track the FPS.
flag = 0
x1 = 0
y1 = 0
x2 = 0
y2 = 0
x3 = 0
y3 = 0
x4 = 0
y4 = 0
while(True):
clock.tick() # 更新FPS时钟
img = sensor.snapshot() # 拍摄图片并返回图像
img.draw_cross((60,60),color = (255,0,0),size = 5, thickness=1)
# -----矩形框部分-----
print("寻找矩形")
# 在图像中寻找矩形
for r in img.find_rects():
# 判断矩形边长是否符合要求
if r.w() >20 and r.h() > 20 and r.w() < 100 and r.w() < 100:
flag += 1
# 在屏幕上框出矩形
print("找到了矩形",flag)
#img.draw_rectangle(r.rect(), color = (255, 0, 0), thickness = 2, fill = False)
# 获取矩形角点位置
corner = r.corners()
## 在屏幕上圆出矩形角点;四个角点坐标, 角点1的数组是corner[0], 坐标就是(corner[0][0],corner[0][1])
#img.draw_circle(corner[0][0], corner[0][1], 5, color = (0, 0, 255), thickness = 2, fill = False)
#img.draw_circle(corner[1][0], corner[1][1], 5, color = (0, 0, 255), thickness = 2, fill = False)
#img.draw_circle(corner[2][0], corner[2][1], 5, color = (0, 0, 255), thickness = 2, fill = False)
#img.draw_circle(corner[3][0], corner[3][1], 5, color = (0, 255, 0), thickness = 2, fill = False)
print("左下角:",corner[0][0], corner[0][1],",右下角 ",corner[1][0], corner[1][1],",右上角:",corner[2][0], corner[2][1],",左上角:",corner[3][0], corner[3][1])
x1 += corner[0][0]
y1 += corner[0][1]
x2 += corner[1][0]
y2 += corner[1][1]
x3 += corner[2][0]
y3 += corner[2][1]
x4 += corner[3][0]
y4 += corner[3][1]
# 在屏幕上显示图像,此部分会降低帧率
lcd.display(img)
if(flag == 3):
x1 = x1/3
y1 = y1/3
x2 = x2/3
y2 = y2/3
x3 = x3/3
y3 = y3/3
x4 = x4/4
y4 = y4/4
# 将四个角点的坐标数据打包成一个字符串b
corner_str = str(x1) + ',' + str(y1) + ',' + str(x2) + ',' + str(y2) + ',' + str(x3) + ',' + str(y3) + ',' + str(x4) + ',' + str(y4)
uart.write( "@" + corner_str + "!") # 使用换行符作为消息的结束,方便 Arduino 一次读取一个坐标
print("@" + corner_str + "!")
# 停止1秒钟
time.sleep(1)
break
# 打印帧率
#print(clock.fps())
如何寻找矩形并且得到四个坐标角点这个不难,这里的问题是:因为在实际中我们除了运行找矩形的代码,还要在K210上运行找激光(可能还有其它图像处理的代码),因为k210的算力不是很高,可能直接导致这个错误MemoryError: Out of fast Frame Buffer Stack Memory! Please reduce the resolution of the image you are running this algorithm on to bypass this issue!# Hello World Example
,是因为openmv/k210处理图像部分的内存不够了!
可以通过以下措施来解决看看:
减少图像的分辨率:降低图像分辨率可以减少所需的内存量。您可以使用 sensor.set_framesize()
函数来设置图像帧大小。
减少图像处理的区域:使用 sensor.set_windowing()
函数设置感兴趣的图像区域,只处理所需的部分图像。
减少使用Frame Buffer:Frame Buffer Stack Memory是OpenMV处理图像时使用的内存,因此您可以尝试减少使用Frame Buffer。例如,您可以使用 img.copy() 函数来创建图像的副本,而不是直接在原始图像上进行处理。
优化代码:您可以尝试优化代码以减少内存使用量。例如,您可以使用 img.draw_rectangle() 函数的 merge 参数来减少绘制矩形时所需的内存。
从这个场景和功能下,其实四个坐标顶点只需要获取一次即可,不需要持续运行这个代码,于是在上面代码中加入了一个flag
标志变量,当在最开始运行代码时,识别三次矩形,将坐标平均值求出(包装准确性),然后通过串口发送给主控。这样一来只有最开始很少一段时间在进行“寻找矩形”这部分代码,后面就可以进行其他操作,这样可以减少Frame Buffer的使用。(个人认为上面提到的使用 img.copy() 函数
也是可行的,大家可以自行尝试!)
这次电赛是我第一次参加这种大型比赛,四天三夜的时间学习了很多,有泪水、有压力也有焦虑,但是重要的是坚持自己的内心不后悔和坚定的走下。
同时这次比赛让我学习到理论和实践之间的距离其实是很遥远的,以及经验和实践的重要性。一些看起来很合理的理论,但是实现却和理想差很多,你要考虑到硬件的性能、这个函数的参数有哪些作用,如何去调节、环境的影响…
所以接下来的学习和比赛中,要更深入的学习,去更好的理解,才能更好的运用,并且勤加实践,才能更好的将理论和实践结合!!
最后,借用史铁生先生的一句话结束这篇文章吧:“命定的局限尽可永在,不屈的挑战却不可须臾或缺 ”