前言
(1)马上要进行电赛了,机器识别是铁定会使用到的。为了防止出现去年十月份那种特殊的巡线方案。我在此分享出OpenMV巡线方案,并且进行讲解和分析如何更改。
(2)学习本文之前,需要学习:OpenMV串口通讯详解;OpenMV图像处理之后给单片机通讯;OpenMV的单颜色识别讲解;
(3)版权申明:此代码借鉴了淘宝商家—无名创新的代码,已获得许可。未经允许,禁止商用!
(4)注意:他的代码自己创建了一个比较复杂的通讯协议,为了方便新手快速入门,我在他的代码上进行了微调。
(5)先给一颗定心丸,本文学习最多20分钟就能掌握。(前提,OpenMV的颜色识别,感兴趣区,串口通讯能够熟练使用)
(6)主控代码将会在下一篇中给出,因为C站编辑器字数超过1W字就卡的一批,没办法,只能分成两篇来写。
OpenMV全部代码
(1)先直接上代码,因为可能很多人都是开赛了才看到这一篇博客的,没多少时间了。
(2)OpenMV的函数入口是main.py,从第一行往下执行。所以这个文件要命名为main.py!!!
#main.py -- put your code here! import cpufreq import pyb import sensor,image, time,math from pyb import LED,Timer,UART sensor.reset() # 重置感光元件,重置摄像机 sensor.set_pixformat(sensor.RGB565) # 设置颜色格式为RGB565,彩色,每个像素16bit。 sensor.set_framesize(sensor.QQQVGA) # 图像大小为QQQVGA,大小80x60 sensor.skip_frames(time = 2000) #延时跳过一些帧,等待感光元件变稳定 sensor.set_auto_gain(False) #黑线不易识别时,将此处写False sensor.set_auto_whitebal(False) #颜色识别必须关闭白平衡,会影响颜色识别效果,导致颜色的阈值发生改变 clock = time.clock() # 创建一个时钟对象来跟踪FPS。 #sensor.set_auto_exposure(True, exposure_us=5000) # 设置自动曝光sensor.get_exposure_us() red_led = pyb.LED(1) #下面这三个就是OpenMV上的LED初始化 green_led = pyb.LED(2) blue_led = pyb.LED(3) uart=UART(3,115200) #初始化串口3,波特率为115200,P4为TX连接单片机RX,P5为RX连接单片机TX class target_check(object): x=0 #int16_t,横线上被标记黑点的地方,从左到右依次减少 y=0 #int8_t,竖线上被标记黑点的地方,从上到下依次减少 target=target_check() # 绘制水平线 def draw_hori_line(img, x0, x1, y, color): for x in range(x0, x1): img.set_pixel(x, y, color) # 绘制竖直线 def draw_vec_line(img, x, y0, y1, color): for y in range(y0, y1): img.set_pixel(x, y, color) # 绘制矩形 def draw_rect(img, x, y, w, h, color): draw_hori_line(img, x, x+w, y, color) draw_hori_line(img, x, x+w, y+h, color) draw_vec_line(img, x, y, y+h, color) draw_vec_line(img, x+w, y, y+h, color) #图像大小为QQQVGA,大小80x60 #roi的格式是(x, y, w, h) track_roi=[(0,25,5,10), (5,25,5,10), (10,25,5,10), (15,25,5,10), (20,25,5,10), (25,25,5,10), (30,25,5,10), (35,25,5,10), (40,25,5,10), (45,25,5,10), (50,25,5,10), (55,25,5,10), (60,25,5,10), (65,25,5,10), (70,25,5,10), (75,25,5,10)] target_roi=[(70,0,10,12), (70,12,10,12), (70,24,10,12), (70,36,10,12), (70,48,10,12)] thresholds =(0, 30, -30, 30, -30, 30) #黑色的颜色阈值 hor_bits=['0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'] #记录横线16个感兴趣区是否为黑线 ver_bits=['0','0','0','0','0',] #记录右边5个感兴趣区是否为黑线 #__________________________________________________________________ def findtrack(): target.x=0 target.y=0 img=sensor.snapshot() #用于检测黑线 for i in range(0,16): hor_bits[i]=0 ''' thresholds表示黑色线阈值,roi为感兴趣区 merge=True,表示所有合并所有重叠的blob为一个 margin 边界,如果设置为10,那么两个blobs如果间距10一个像素点,也会被合并。 ''' blobs=img.find_blobs([thresholds],roi=track_roi[i],merge=True,margin=10) #如果识别到了黑线,hor_bits对应位置1 for b in blobs: hor_bits[i]=1 #用于检测右侧的黑线 for i in range(0,5): ver_bits[i]=0 blobs=img.find_blobs([thresholds],roi=target_roi[i],merge=True,margin=10) for b in blobs: ver_bits[i]=1 #绘制16个横线红色四个角 for k in range(0,16): if hor_bits[k]: target.x=target.x|(0x01<<(15-k)) img.draw_circle(int(track_roi[k][0]+track_roi[k][2]*0.5),int(track_roi[k][1]+track_roi[k][3]*0.5),1,(255,0,0)) #绘制右侧5个红色四个角 for k in range(0,5): if ver_bits[k]: target.y=target.y|(0x01<<(4-k)) img.draw_circle(int(target_roi[k][0]+target_roi[k][2]*0.5),int(target_roi[k][1]+target_roi[k][3]*0.5),3,(0,255,0)) #绘制16个横线感兴趣区 for rec in track_roi: img.draw_rectangle(rec, color=(0,0,255))#绘制出roi区域 #绘制右侧5个横线感兴趣区 for rec in target_roi: img.draw_rectangle(rec, color=(0,255,255))#绘制出roi区域 #大--小 从左到右 从上到下 print((target.x & 0xff00)>>8,target.x & 0xff,target.y) uart.write(str((target.x & 0xff00)>>8)) #uart.write(str(target.x & 0xff)) #uart.write(str(target.y)) #uart.write("\r\n") #__________________________________________________________________ def package_blobs_data(): return bytearray([target.x >> 8, target.x, target.y]) #__________________________________________________________________ i = 0 while True: findtrack() uart.write(package_blobs_data()) uart.write("\r\n") i = i + 1 if i == 50: i = 0 green_led.toggle() pyb.delay(10) #uart.write("Hello World!\r") #uart.write(1+'\n') #计算fps #print(clock.fps()) #__________________________________________________________________
代码分析
关注点1—图像大小设置
(1)下面这一部分代码,我只会讲解你需要进行更改的部分。
(2)sensor.set_framesize(sensor.QQQVGA)
<1>这个是用于设置图像大小的。首先我们需要知道,对于计算机而言,一个张图片到底是什么东西。
<2>大家都打电赛了,肯定是玩过常见的OLED,或者是点阵屏的。那么我们让OLED或者点阵屏画图是怎么做的呢?很简单,一个像素点一个像素点的画。
<3>比如常见的4脚0.96寸的OLED是128*64像素。这是什么意思呢?他说明了,这款OLED纵向有128个小型的LED,横向有64个小型LED。我们想要绘制一个图像,就是一个一个的点亮这些小型LED。
<3>OpenMV同样也是,不过他的像素大小是可以进行设置的。比如我这里是采用的QQQVGA画质,像素就是80x60,也就是说X轴80个像素点,Y轴60个像素点。
<4>这个有什么用呢?很简单,与后面的感兴趣区设置有关。
<5>OpenMV画质设置相关资料:https://book.openmv.cc/image/sensor.html
<6>感兴趣区相关资料:https://book.openmv.cc/image/statistics.html
<7>可能有些人学颜色识别的时候没有注意看感兴趣区的相关资料。我在此进行简单的说明一下。
<8>我们上面说了,OpenMV可以对图像大小进行设置,如下图,我们将图像大小设置为16*16的像素点大小。
<9>我拍摄到了整个图像,是一朵花。但是如果我们进行特征识别,只想识别这朵花的中心部分(途中蓝色线标记的地方),那么在颜色识别的时候,传入感兴趣值roi=(5,2,6,4)。
import cpufreq import pyb import sensor,image, time,math from pyb import LED,Timer,UART sensor.reset() # 重置感光元件,重置摄像机 sensor.set_pixformat(sensor.RGB565) # 设置颜色格式为RGB565,彩色,每个像素16bit。 sensor.set_framesize(sensor.QQQVGA) # 图像大小为QQQVGA,大小80x60 sensor.skip_frames(time = 2000) #延时跳过一些帧,等待感光元件变稳定 sensor.set_auto_gain(False) #黑线不易识别时,将此处写False sensor.set_auto_whitebal(False) #颜色识别必须关闭白平衡,会影响颜色识别效果,导致颜色的阈值发生改变 clock = time.clock() # 创建一个时钟对象来跟踪FPS。 #sensor.set_auto_exposure(True, exposure_us=5000) # 设置自动曝光sensor.get_exposure_us()
关注点2—串口波特率设置
(1)首先先说明,pyb.LED这个是什么。
<1>这个其实就是OpenMV上的那唯一一个RGB灯的初始化函数。
<2>为什么我需要初始化RGB呢?因为,为了防止OpenMV突然因为什么原因停止运行,最终没有给主控反馈数据,在排错过程中,无法定位问题。所以我设置了一个RGB闪烁的程序。如果OpenMV正常运转,那么RGB就会闪烁。这样出现问题,也方便定位问题。
(2)UART函数就是串口初始化函数
<1>注意,OpenMV只有一个串口3!请不要自作聪明改第一个参数!
<2>第二个参数是设置波特率的,这个波特率的值需要和主控的波特率值设置一致。
<3>连接方法是:OpenMV的TX(P4)——单片机RX,RX(P5)——单片机TX,GND——GND,3.3V——3.3V
red_led = pyb.LED(1) #下面这三个就是OpenMV上的LED初始化 green_led = pyb.LED(2) blue_led = pyb.LED(3) uart=UART(3,115200) #初始化串口3,波特率为115200,P4为TX连接单片机RX,P5为RX连接单片机TX
关注点3—巡线数据存储
(1)首先,我们需要知道22年10月份的电赛地图长什么样子。他就是一个倒车入库的题目。
(2)这个题目,明显不能使用光感循迹,否则就会超出车子大小而且不符合题意。
(3)所以我们的OpenMV要斜着放。
(4)如下为OpenMV实际检测到的结果。我们会看到,只要右边那一条Y轴线检测到了黑色,就说明可以开始准备倒车入库了。
(5)当中心位置的X轴线,右边5个识别区域有数据识别,说明已经车子要倒车入库了。
(6)因此,我们这里创建了一个类,如果没有python基础的,可以理解为C语言的一个结构体。这个结构体存放x和y轴的数据。
(7)如果需要进行微调,你就看看你的感兴趣区怎么设置
class target_check(object): x=0 #int16_t,横线上被标记黑点的地方,从左到右依次减少 y=0 #int8_t,竖线上被标记黑点的地方,从上到下依次减少 target=target_check()
无需关注的代码,做一个简单讲解
(1)我们能够看到上面的OpenMV识别代码里面,画有一些框框。这个其实只会出现在OpenMV IDE里面,方便我们进行调试。
# 绘制水平线 def draw_hori_line(img, x0, x1, y, color): for x in range(x0, x1): img.set_pixel(x, y, color) # 绘制竖直线 def draw_vec_line(img, x, y0, y1, color): for y in range(y0, y1): img.set_pixel(x, y, color) # 绘制矩形 def draw_rect(img, x, y, w, h, color): draw_hori_line(img, x, x+w, y, color) draw_hori_line(img, x, x+w, y+h, color) draw_vec_line(img, x, y, y+h, color) draw_vec_line(img, x+w, y, y+h, color)
关注点4—感兴趣区设置
(1)我们上面设置了OpenMV的图像大小为QQQVGA,这里像素点大小就是80x60。
(2)然后我们需要设置只识别规定部分,来进行循迹。
(3)先讲解中间的16个识别点,track_roi。
<1>我们X轴要建立16个识别点,而x轴一共有80个像素点,所以w都是5,而x每次以5进行递增。
<2>因为我的整个图像y轴有60个像素点。h如果选取太小,主控的反应时间太短了,太大的话,又影响y轴的那8个数据识别,所以我们选取h固定为10。因为y轴60个像素点,所以中心位置像素点是30,因为h选取为10,所以y的起始位置是30-(10/2)=25。
(4)现在讲解右侧的5个识别点,target_roi。
<1>理解了上面的讲解之后这里就很好理解了。首先为了防止x轴的宽度w设置太小,不利于识别,太大影响中心16个识别点,所以宽度w都设置为10,而x轴的像素点只有80个,所以x设置为80-10=70。
<2>因为右边是5个识别点,然后y轴有60个像素点,所以h设置为60/6=12个,y轴每次递增12。
#图像大小为QQQVGA,大小80x60 #roi的格式是(x, y, w, h) track_roi=[(0,25,5,10), (5,25,5,10), (10,25,5,10), (15,25,5,10), (20,25,5,10), (25,25,5,10), (30,25,5,10), (35,25,5,10), (40,25,5,10), (45,25,5,10), (50,25,5,10), (55,25,5,10), (60,25,5,10), (65,25,5,10), (70,25,5,10), (75,25,5,10)] target_roi=[(70,0,10,12), (70,12,10,12), (70,24,10,12), (70,36,10,12), (70,48,10,12)]
关注点5—黑线颜色阈值
(1)你们只需要按照下面的颜色阈值设置工具,将最终产生的数据传入给thresholds 即可。
(2)颜色阈值设置工具教程:OpenMV颜色阈值设置。
thresholds =(0, 30, -30, 30, -30, 30) #黑色的颜色阈值
关注点6—感兴趣区数组
(1)你有几种感兴趣区,就建立几个数组。
(2)每种感兴趣区有几个区域,就写几个’0’。
hor_bits=['0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'] #记录横线16个感兴趣区是否为黑线 ver_bits=['0','0','0','0','0',] #记录右边5个感兴趣区是否为黑线
关注点7—循迹部分
(1)这里只会讲解可能需要进行微调的部分
(2)arget.x 和 target.y
<1>arget.x和target.y是用于记录x轴16个感兴趣区和y轴右侧的那5个感兴趣区的数据。每次调用这个函数需要进行清零操作,否则数据会受到上一次结果干扰。
<2>这里需要根据你之前设置的关注点3部分的数据信息有关,你设置了几个量,就需要清零几个。
(3)for i in range(0,16): 和 for i in range(0,5):
<1>range(0,16)这个就是将就是将0到15这16个数据传递给i。这里的两个for语句内容其实是一样的。你有几中类型数据,就写几个for,然后只需要更改一下range(0,x)中的x。
<2>hor_bits[i]=0首先我们需要将这些数组的数据清空,防止上一次数据造成干扰。不用改。
<3>find_blobs()这个函数,只需要更改roi感兴趣区,将roi设置为你要的感兴趣区即可。
(4)for k in range(0,16): 和for k in range(0,5):
<1>这两个for语句作用就是如果识别到了黑线,将数组中的数据存入target.x和target.y。(0x01<<(4-k)和(0x01<<(15-k)部分,这个(0x01<<(x-k)中的x是根据你这个类型感兴趣区有几个决定,比如x轴的有16个,那么x就是15。y轴的有5个,那么x就是4。
<2>draw_circle()函数就是识别到黑线之后OpenMV IDE上就会出现提示,我们如果需要更改,就把传入的四个target_roi[]设置为自己的,如下:
(5)for rec in track_roi: 和 for rec in target_roi:
<1>这个就是画出我们会进行颜色识别的区域,我们如果想要改,只需要将 for语句后面的in 条件改成自己设置的感兴趣区
def findtrack(): target.x=0 target.y=0 img=sensor.snapshot() #这个必须存在,是用于获取图像数据的 #用于检测黑线 for i in range(0,16): hor_bits[i]=0 ''' thresholds表示黑色线阈值,roi为感兴趣区 merge=True,表示所有合并所有重叠的blob为一个 margin 边界,如果设置为10,那么两个blobs如果间距10一个像素点,也会被合并。 ''' blobs=img.find_blobs([thresholds],roi=track_roi[i],merge=True,margin=10) #如果识别到了黑线,hor_bits对应位置1 for b in blobs: hor_bits[i]=1 #用于检测右侧的黑线 for i in range(0,5): ver_bits[i]=0 blobs=img.find_blobs([thresholds],roi=target_roi[i],merge=True,margin=10) for b in blobs: ver_bits[i]=1 #绘制16个横线红色四个角 for k in range(0,16): if hor_bits[k]: target.x=target.x|(0x01<<(15-k)) img.draw_circle(int(track_roi[k][0]+track_roi[k][2]*0.5),int(track_roi[k][1]+track_roi[k][3]*0.5),1,(255,0,0)) #绘制右侧5个红色四个角 for k in range(0,5): if ver_bits[k]: target.y=target.y|(0x01<<(4-k)) img.draw_circle(int(target_roi[k][0]+target_roi[k][2]*0.5),int(target_roi[k][1]+target_roi[k][3]*0.5),3,(0,255,0)) #绘制16个横线感兴趣区 for rec in track_roi: img.draw_rectangle(rec, color=(0,0,255))#绘制出roi区域 #绘制右侧5个横线感兴趣区 for rec in target_roi: img.draw_rectangle(rec, color=(0,255,255))#绘制出roi区域 #大--小 从左到右 从上到下 print((target.x & 0xff00)>>8,target.x & 0xff,target.y) uart.write(str((target.x & 0xff00)>>8))
关注点8—OpenMV给串口发送数据
(1)这个函数就是将OpenMV识别到的黑线信息传递给主控。每次发送8bit的数据。
(2)如果需要更改,只需要根据你的需求,更改bytearray函数中的[]。
(3)这里注意一下,数据发送的内容到底是什么,target.x的数据从大到小,是从左到右。target.y的数据从大到小是从上往下的位置。
(4)可能有些人会问为什么,原因很简单,这个和你设置感兴趣区的时候,放置顺序有关。
def package_blobs_data(): return bytearray([target.x >> 8, target.x, target.y])
无需关注,有一个简单了解
(1)最后就是一个死循环了。我设置了OpenMV上的RGB灯每隔500ms翻转一次。用于查看OpenMV是否在正常运行。
(2)uart.write()就是将循迹数据传递给主控,因为我采用了正点原子的通讯协议,所以数据帧以"\r\n"结尾。主控代码也要同理采用这个方案。
i = 0 while True: findtrack() uart.write(package_blobs_data()) uart.write("\r\n") i = i + 1 if i == 50: i = 0 green_led.toggle() pyb.delay(10) #延时10ms