2022年十月份电赛OpenMV巡线方案详细代码分析(1)

简介: 2022年十月份电赛OpenMV巡线方案详细代码分析(1)

前言

(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
目录
相关文章
|
前端开发 芯片
【芯片前端】保持代码手感——交通灯
【芯片前端】保持代码手感——交通灯
|
5月前
|
存储 编解码 算法
C#.NET逃逸时间算法生成分形图像的毕业设计完成!晒晒功能
该文介绍了一个使用C#.NET Visual Studio 2008开发的程序,包含错误修复的Julia、Mandelbrot和优化过的Newton三种算法,生成色彩丰富的分形图像。作者改进了原始算法的效率,将内层循环的画点操作移至外部,提升性能。程序提供五种图形模式,支持放大缩小及颜色更新,并允许用户自定义画布大小以调整精度。还具备保存为高质JPG的功能。附有四张示例图片展示生成的分形效果。
【期末不挂科-单片机考前速过系列P5】(第五章:11题速过中断系统和中断系统结构)经典例题盘点(带图解析)
【期末不挂科-单片机考前速过系列P5】(第五章:11题速过中断系统和中断系统结构)经典例题盘点(带图解析)
|
6月前
|
传感器 存储 IDE
LabVIEW开发呼吸分析系统
LabVIEW开发呼吸分析系统
33 0
【期末不挂科-单片机考前速过系列P10】(第十章:11题中断系统的工作原理及应用)经典例题盘点(带图解析)
【期末不挂科-单片机考前速过系列P10】(第十章:11题中断系统的工作原理及应用)经典例题盘点(带图解析)
|
6月前
|
计算机视觉 索引 Python
分析”圣诞帽“代码,入门OpenCV
分析”圣诞帽“代码,入门OpenCV
分析”圣诞帽“代码,入门OpenCV
|
6月前
|
传感器 Linux 编译器
不同平台下的点灯代码,你在点灯的哪个段位?
不同平台下的点灯代码,你在点灯的哪个段位?
2021电赛F题之openmv巡线(附代码)
2021电赛F题之openmv巡线(附代码)
244 0
|
算法 Linux Android开发
动手智能小车记(5)-坦克底盘硬件模块大杂烩
动手智能小车记(5)-坦克底盘硬件模块大杂烩
245 0
【单片机期中测试】11.交通灯
【单片机期中测试】11.交通灯
58 0