OpenMV视觉模块循迹/巡线功能实现(带TFT-LCD屏显示)

  • Post author:
  • Post category:其他




1. 实现原理

对于循迹功能的实现,首先需要设置ROI(Region of Interest)区域,即是图像感兴趣区域,合理的划分ROI区域可以在一定程度上减少OpenMV的计算资源的消耗从而提高系统运行速率。

本次使用的TFT-LCD屏在QQVGA的画质下的尺寸为160

120,其循迹时的ROI区域的设置如图4-4所示。如图所示,总共设置了三个ROI区域,三个区域的大小均是120pix

30pix尺寸的矩形。离视野越近的地方所设置权重值应该更大,所以ROI1、ROI2和ROI3的权值大小分别为0.7、0.3和0.1。

在这里插入图片描述

设置好阈值后,为便于循迹,需要对图像进行灰度处理,同时设置巡线的颜色阈值。本次巡线图的颜色为黑色,设置巡线的灰度颜色阈值为(0,64)。

配置好上述参数后,视觉模块开始循迹,其循迹原理如下图所示。

在这里插入图片描述

首先,摄像头将得到的图像进行转换,得到对应得灰度图。

之后,寻找灰度图ROI区域中的最大黑色色块,并返回三个色块的矩形元组框出位置。如下面公式所示,将三个色块的中心点的横坐标与对应ROI区域的权值相乘再将其求和可以近似得到线的质心和。

在这里插入图片描述

然后,由计算出的质心和和之前设置的权值和根据下述公式可以计算出当前的线心位置CenterPos。

在这里插入图片描述

最后,根据线心位置和图片的像素尺寸可以通过下述公式将线心位置转换成偏角大小。

在这里插入图片描述

本次巡线模式使用的像素尺寸为160*120,所以本次偏角DeflectAngle的计算使用下式。

在这里插入图片描述

不难发现, 的取值在0到160之间,所以偏角DeflectAngle的取值范围为 ,转换为角度即DeflectAngle取值范围为正负53.1度之间。

在得到图像中黑线的偏角之后,即可进行偏角大小的判断来确定小车的动作状态。当偏角绝对值小于角度阈值时,小车直行;当偏角绝对值大于角度阈值且为正偏角时,小车右转;当偏角绝对值值大于角度阈值且为负偏角时,小车左转。



2 循迹功能实现流程图与伪码

在这里插入图片描述

在这里插入图片描述



3 代码实现

#导入函数库
import sensor, image, time, math ,lcd
from pyb import UART
#开启时钟
clock = time.clock()# 跟踪FPS帧率
#变量初始化
#阈值设置 根据实际情况进行更改
TRA_TH= [(128, 255)]           #巡线的灰度值 阈值[(0, 64)][(128, 255)]
TRA_AngTH=30                   #巡线时角度阈值
#ROI区域设置
#(x,y,w,h,weight)=(矩形左上顶点的坐标(x,y),矩形宽度和高度(w,h),权重)
TRA_ROIS = [ # [ROI, weight]
        (0, 100, 160, 20, 0.7), # You'll need to tweak the weights for your app
        (0,  50, 160, 20, 0.3), # depending on how your robot is setup.
        (0,   0, 160, 20, 0.1)
       ]
Weight_Sum = 0                        #权值和初始化
for r in TRA_ROIS: Weight_Sum += r[4] #计算权值和
OBS_ROI = [(30 , 10, 100, 100)] #避障模式ROI区域
#通信数据包封装
Run     = bytearray([0x24,0x4F,0x4D,0x56,0x31,0x26,0x23])# $OMV1&#  直行
Left    = bytearray([0x24,0x4F,0x4D,0x56,0x32,0x26,0x23])# $OMV2&#  左转
Right   = bytearray([0x24,0x4F,0x4D,0x56,0x33,0x26,0x23])# $OMV3&#  右转
Stop    = bytearray([0x24,0x4F,0x4D,0x56,0x34,0x26,0x23])# $OMV4&#  停止
#Fun1:获得最大色块的位置索引函数
#输入:N个色块(blobs) 输出:N个色块中最大色块的索引(int i)
def Get_MaxIndex(blobs):
    maxb_index=0                      #最大色块索引初始化
    max_pixels=0                      #最大像素值初始化
    for i in range(len(blobs)):       #对N个色块进行N次遍历
        if blobs[i].pixels() > max_pixels:#当某个色块像素大于最大值
            max_pixels = blobs[i].pixels()#更新最大像素
            maxb_index = i                #更新最大索引
            return  maxb_index
            
def Recive_Data():
    global OVSys_State
    if uart.any():                  #如果串口接收到数据
      OVSys_State=int(uart.read())  #将读到的数据强制转换为整数
      print(OVSys_State)            #用于串口通信调试
#TFT-LCD初始化
lcd.init()                #lcd函数初始化
lcd.set_direction(1)      #设置LCD显示方向 0和2是竖屏 1和3是横屏
#摄像头初始化
sensor.reset()                     #初始化相机传感器
sensor.set_pixformat(sensor.RGB565)#设置相机模块的像素模式 16 bits/像素 GRAY为8
sensor.set_framesize(sensor.QQVGA) #设置相机模块的帧大小 160x120
sensor.skip_frames(30)             #跳过30帧 让相机图像在改变相机设置后稳定下来
sensor.set_auto_gain(False)        #关闭自动增益
sensor.set_auto_whitebal(False)    #关闭默认的白平衡
#主函数
while (1):
    clock.tick()
    if (OVSys_State==1)or (OVSys_State==3):#循迹模式或者循迹避障模式
            Hor_servo.angle(0)          #循迹模式舵机姿态调整
            Ver_servo.angle(90)           #水平-10 垂直向下70°
            sensor.set_pixformat(sensor.GRAYSCALE)#循迹模式 设置摄像头为灰度图
            TRA_img = sensor.snapshot().histeq()#截一帧图像 加强对比度好分割
            TRA_img.mean(1)
            TRA_img.binary([(0,35)])
            #在LCD上打印帧率和OpenMV工作模式
            TRA_img.draw_string(0, 60,"OpenMv\rMode:\rTracking\rMode")
            TRA_img.draw_string(0, 100, "FPS:\r%.2f"%(clock.fps()))
            #偏移角度计算
            Centroid_Sum = 0    #初始化质心和
            for r in TRA_ROIS:  #是ROI的元组
                #找到视野中ROI区域的色块,merge=true,将找到的图像区域合并
                blobs = TRA_img.find_blobs(TRA_TH, roi=r[0:4], pixels_threshold=100, area_threshold=100,merge=True)
                if blobs:#如果找到了多个色块 计算质心和
                    maxb_index = Get_MaxIndex(blobs)#找到多个色块中的最大色块返回索引值
                    #返回最大色块外框元组(x,y,w,h) 绘制线宽为2的矩形框 不填充矩形
                    TRA_img.draw_rectangle(blobs[maxb_index].rect(), thickness = 2, fill = False)
                    #最大色块的中心位置标记十字
                    TRA_img.draw_cross(blobs[maxb_index].cx(),blobs[maxb_index].cy())
                    #计算质心和=(ROI中最大颜色块的中心点横坐标)cx*(ROI权值)w
                    Centroid_Sum += blobs[maxb_index].cx() * r[4]

            #中间公式 确定线心位置=质心和/权值和
            Center_Pos = (Centroid_Sum / Weight_Sum)
            Deflection_Angle = 0#需要将线心Center_Pos转换为偏角 偏角初始化为0
            Deflection_Angle = -math.atan((Center_Pos-80)/60)#计算偏角 限制输出为正负53.13°
            Deflection_Angle = math.degrees(Deflection_Angle)#弧度值转换为角度
            #当偏角大于偏角阈值 且小于最大偏角 和STM32通信 小车左转
            Angle_err=Deflection_Angle
            if abs(Deflection_Angle) > TRA_AngTH:
                if Deflection_Angle>0:
                        uart.write(Right)#和STM32通信 小车左转
                #time.sleep(10)
                        TRA_img.draw_string(0,0,"Car:\rRight")#LCD显示小车运动状态
                        print("Turn Right")#用于程序终端调试
                        print("Turn Angle: %f" % Deflection_Angle)
            #当偏角小于负的偏角阈值 且大于最小偏角
                if Deflection_Angle < 0:
                        uart.write(Left)#和STM32通信 小车右转
                #time.sleep(10)
                        TRA_img.draw_string(0,0,"Car:\rLeft")#LCD显示小车运动状态
                        print("Turn :Left")#用于程序终端调试
                        print("Turn Angle: %f" % Deflection_Angle)
            #当小车角度绝对值小于阈值
            if abs(Deflection_Angle) <= TRA_AngTH:
                        uart.write(Run)#和STM32通信 小车直行
                        TRA_img.draw_string(0,0,"Car:\rRun")#LCD显示小车运动状态
                        print("Run")#用于程序终端调试
                        print("Turn Angle: %f" % Deflection_Angle)
            lcd.display(TRA_img)#在LCD上显示img图像



4 实现效果

图像缓冲区:

在这里插入图片描述

终端输出:

在这里插入图片描述



版权声明:本文为ediykk原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。