【OpenMV】 学习笔记+历程
【OpenMV】 学习笔记+历程
本文最后更新于335 天前,其中的信息可能已经过时,如有错误请发送邮件到273925452@qq.com

OpenMV与STM32串口/蓝牙通信识别颜色控制RGB

OpenMV+USART2+PC

# Multi Color Blob Tracking Example
#
# This example shows off multi color blob tracking using the OpenMV Cam.




import sensor, image, time, math
from pyb import UART
uart = UART(3, 19200)
arr=[0]
counter=0
# Color Tracking Thresholds (L Min, L Max, A Min, A Max, B Min, B Max)
# The below thresholds track in general red/green things. You may wish to tune them...
thresholds = [(30, 100, 15, 127, 15, 127), # generic_red_thresholds
              (30, 100, -64, -8, -32, 32), # generic_green_thresholds
              (0, 15, 0, 40, -80, -20)] # generic_blue_thresholds
# You may pass up to 16 thresholds above. However, it's not really possible to segment any
# scene with 16 thresholds before color thresholds start to overlap heavily.
(57, 69, 30, 90, -44, 65)



sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
clock = time.clock()




# Only blobs that with more pixels than "pixel_threshold" and more area than "area_threshold" are
# returned by "find_blobs" below. Change "pixels_threshold" and "area_threshold" if you change the
# camera resolution. Don't set "merge=True" becuase that will merge blobs which we don't want here.




while(True):
    clock.tick()
    #img = sensor.snapshot()
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
    for blob in img.find_blobs(thresholds, pixels_threshold=200, area_threshold=200):
        # These values depend on the blob not being circular - otherwise they will be shaky.
        if blob.elongation() > 0.5:
            img.draw_edges(blob.min_corners(), color=(255,0,0))
            img.draw_line(blob.major_axis_line(), color=(0,255,0))
            img.draw_line(blob.minor_axis_line(), color=(0,0,255))
        # These values are stable all the time.
        img.draw_rectangle(blob.rect())
        img.draw_cross(blob.cx(), blob.cy())
        # Note - the blob rotation is unique to 0-180 only.
        img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20)
    #print(clock.fps())
    print(blob[8])
    arr[0] = blob[8]
    counter = (counter+1)
    if counter>=100:
        counter=0
        uart.write(str(arr[0])+'\r\n')

OpenMV(从)+蓝牙+手机

# Untitled - By: 黑尾鹿 - 周六 4月 30 2022


import sensor, image, time,pyb
from pyb import UART
from pyb import LED
red_led   = LED(1)
green_led = LED(2)
blue_led  = LED(3)
ir_led    = LED(4)


sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)


clock = time.clock()


uart = UART(1,38400)
uart.init(9600,bits=8,parity=None,stop=1,timeout_char=1000)#串口初始化




while(True):
    clock.tick()
    img = sensor.snapshot()
    if uart.any():#判断是否接收到数据,返回等待的字节数量(可能为0)




        getrx = uart.readline()
        #time.sleep(150)     #延时150ms


        getcmd = str(getrx.decode('UTF-8'))
        print(getcmd)
        if getcmd=='1':
            print('red')
            red_led.on()
        else:red_led.off()
        if getcmd=='2':
            print('green')
            green_led.on()
        else:green_led.off()
        if getcmd=='3':
            print('blue')
            blue_led.on()
        else:blue_led.off()



OpenMV+串口+STM32

(stm32+蓝牙+蓝牙+OpenMV同理,注意配其中一个蓝牙为主模式)

操作办法为:按住蓝牙模块按钮上电后(蓝牙指示灯慢闪),PC上串口助手发送:AT+ROLE=1加回车(波特率38400),蓝牙模块返回OK就成功

openmv只能传输十六进制的数据给STM32,否则STM32将收不到数据

十六进制数据的实现主要通过 bytearray ()这个函数或者直接放到数组里并且转str,具体的格式如下:

OUT_DATA =bytearray([0x2C,0x12,cx,cy,cw,ch,0x5B])

Python提供了便捷的将数据打包的方法。我使用的方法就是将数据以二进制的方式打包然后通过串口逐字节发送。

struct模块的pack(fmt, v1, v2, …) 函数提供了按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)的功能,关与格式,有一个表定义了所有支持的字符及数据格式,如下:

# Multi Color Blob Tracking Example
#
# This example shows off multi color blob tracking using the OpenMV Cam.


import sensor, image, time, math,ustruct    #模块导入
from pyb import UART


uart = UART(3, 115200)#串口配置
arr=[1,2,4]#存放颜色的代号


#元组里面的数值分别是L A B 的最大值和最小值。
thresholds = [(51, 12, 37, -118, -128, -6),     # generic_blue_thresholds
              (24, 94, -99, -5, 27, 119),     # generic_green_thresholds
             (40, 100, -94, 12, 9, 127)]     # generic_Yellow_thresholds


sensor.reset()     #初始化感光远原件
sensor.set_pixformat(sensor.RGB565)#设置图像的色彩
sensor.set_framesize(sensor.QVGA)#设置图像分辨率
sensor.skip_frames(time = 2000)#设置要跳过的帧率
sensor.set_auto_gain(False) # 颜色追踪时要关闭自动增益
sensor.set_auto_whitebal(False) # 颜色追踪时要关闭白平衡
clock = time.clock()#设置帧率检测




#**************************传输数据的函数********************************************************
'''
ustruct 
打包和解压缩原始数据类型
支持的大小/字节顺序前缀: @, <, >, !.
支持的格式编码: b, B, h, H, i, I, l,L, q, Q, s, P, f, d 后两个取决于浮点支持)。
'''
def sending_data(code):
    global uart;
    #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];
    #data = bytearray(frame)
    data = ustruct.pack("<bbhb",      #格式为4个字符1个短整型(2字节)
                   0x2C,                      #帧头1
                   0x12,                      #帧头2
                   int(code), # up sample by 4   #数据1 分为低八位和高八位,先发低后发高
                   #int(cy), # up sample by 4    #数据2
                   0x5B)
    uart.write(data);   #必须要传入一字节的数组,这个函数似乎不能发送单个字节,必须得一次发送多个字节
#**********************************************************************************************


while(True):
    clock.tick()
    img = sensor.snapshot()#存储摄像头所拍摄的图像并保存在img变量中
    for blob in img.find_blobs(thresholds, pixels_threshold=200, area_threshold=200):#find_blobs函数可以找到色块。
    #若一个色块的边界框区域小于 area_threshold ,则会被过滤掉。若一个色块的像素数小于 pixel_threshold ,则会被过滤掉。
        if blob.elongation() > 0.5:#调用该函数返回一个介于0和1之间的值,该值表示对象的长度(不是圆形)。一条线将是1。
            img.draw_edges(blob.min_corners(), color=(255,0,0))    #在blob.min_corners()返回的角列表之间绘制红线边
            #img.draw_line(blob.major_axis_line(), color=(0,255,0))
            #img.draw_line(blob.minor_axis_line(), color=(0,0,255))
        img.draw_rectangle(blob.rect())         #在blob.rect()返回的矩形元组(x, y, w, h) 图像中画一个矩形框
        img.draw_cross(blob.cx(), blob.cy())    #在blob.cx()返回的中心坐标 画一个十字
       # img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20)
    #print(clock.fps())
    a=blob[8]    #[8]索引得到code的代码
    if a==1:
        sending_data(str(arr[0]))    #存放到数组里面,在转换为str就变成1字节
        print('蓝色')
    elif a==2:
        sending_data(str(arr[1]))
        print('绿色')
    elif a==4:
        sending_data(str(arr[2]))
        print('黄色')




    print(a)
    #uart.write(str(arr[0]))

NCC模板匹配

教程:基于OpenMV的图像识别之数字识别

1.简单模板拍摄

#
#模板匹配简单拍照程序
#


import time, sensor, image
from image import SEARCH_EX, SEARCH_DS
#从imgae模块引入SEARCH_EX和SEARCH_DS。使用from import仅仅引入SEARCH_EX,
#SEARCH_DS两个需要的部分,而不把image模块全部引入。


sensor.reset() #初始化传感器(摄像头)
# 设置传感器
sensor.set_contrast(1)
sensor.set_gainceiling(16)
sensor.set_framesize(sensor.LCD) #分辨率,用LCD屏幕的话需要设为LCD。和模板匹配程序一样。
sensor.set_pixformat(sensor.GRAYSCALE) #照片模式,灰度图像方式
sensor.skip_frames(time = 200) #延时跳过一些帧,等待感光元件变稳定。
sensor.set_auto_gain(False) # 颜色跟踪必须关闭自动增益
sensor.set_auto_whitebal(False) #关闭白平衡。
sensor.set_auto_exposure(False,16777) #设置曝光,需要更改


clock = time.clock() # 跟踪FPS帧率


while(True):
    clock.tick()  # 追踪两个snapshots()之间经过的毫秒数.
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)   #去畸变()    
    print("FPS %f" % clock.fps()) # 注意: 当连接电脑后,OpenMV会变成一半的速度。当不连接电脑,帧率会增加。  

单模板匹配

# NCC模板匹配示例-Normalized Cross Correlation (NCC)
#
# 这个例子展示了如何使用OpenMV的NCC功能将小部分图像与图像的各个部分
# 进行匹配...期望获得极其可控的环境 NCC并不总是有用的。
#
# 警告:NCC支持需要重做!到目前为止,这个功能需要做大量的工作才能有用。
# 这个脚本将重新表明功能的存在,但在目前的状态是不足的。

import time, sensor, image
from image import SEARCH_EX, SEARCH_DS
#从imgae模块引入SEARCH_EX和SEARCH_DS。使用from import仅仅引入SEARCH_EX, 
#SEARCH_DS两个需要的部分,而不把image模块全部引入。

# 重置传感器
sensor.reset()

# 设置传感器
sensor.set_contrast(1)
sensor.set_gainceiling(16)
# Max resolution for template matching with SEARCH_EX is QQVGA
# 模板与SEARCH_EX匹配的最大分辨率是QQVGA
sensor.set_framesize(sensor.QQVGA)
# 你可以设置windowing窗口来减少搜索图片。
#sensor.set_windowing(((640-80)//2, (480-60)//2, 80, 60))
sensor.set_pixformat(sensor.GRAYSCALE)#灰度图像。

# 加载模板。
# 模板应该是一个小的(例如。32x32像素)灰度图像。
template = image.Image("/1.pgm")

clock = time.clock()

#运行模板匹配
while (True):
    clock.tick()
    img = sensor.snapshot()
    
    # find_template(template, threshold, [roi, step, search])
    # ROI: 感兴趣区域元组 (x, y, w, h).
    # Step:使用的循环步长(y+= Step, x+= Step) 使用更大的步长使其更快。
    # search 为image.SEARCH_EX进行详尽搜索,或者为image.SEARCH_DS进行菱形搜索
    #
    # Note1: ROI必须比图像小,比模板大。
    # Note2:在菱形diamond搜索中,step和ROI都被忽略。
    r = img.find_template(template, 0.70, step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))
    # find_template(template, threshold, [roi, step, search]),
    # threshold中的0.7是相似度阈值,roi是进行匹配的区域(左上顶点为(10,0),长80宽60的矩形),
    # 注意roi的大小要比模板图片大,比frambuffer小。
    # 把匹配到的图像标记出来
    if r:
        img.draw_rectangle(r)
        print("匹配")

    print(clock.fps())

多模板匹配

# 使用NCC算法实现模板匹配
#
# 这个例子展示了如何使用OpenMV凸轮的NCC功能将小部分图像与图像的各个部分进行匹配...期望获得极其可控的环境NCC并不是全部有用的。
#
# 警告:NCC支持需要重做!到目前为止,这个功能需要做大量的工作才能有用。
# 这个脚本将重新表明功能的存在,但在目前的状态是不足的。

import time, sensor, image
from image import SEARCH_EX, SEARCH_DS
#从imgae模块引入SEARCH_EX和SEARCH_DS。使用from import仅仅引入SEARCH_EX,
#SEARCH_DS两个需要的部分,而不把image模块全部引入。

sensor.reset() #初始化传感器(摄像头)
# 设置传感器
sensor.set_contrast(1)
sensor.set_gainceiling(16)
sensor.set_framesize(sensor.LCD) #分辨率,用LCD屏幕的话需要设为LCD。SEARCH_EX 最大用 QQVGA
sensor.set_pixformat(sensor.GRAYSCALE) #照片模式,灰度图像方式
sensor.skip_frames(time = 200) #延时跳过一些帧,等待感光元件变稳定。
sensor.set_auto_gain(False) # 颜色跟踪必须关闭自动增益
sensor.set_auto_whitebal(False) #关闭白平衡。
sensor.set_auto_exposure(False,16777)# 设置曝光,需要更改

#sensor.set_windowing(((640-80)//2, (480-60)//2, 80, 60)) #子分辨率。可设置windowing窗口来减少搜索图片

templates1 = ["/300.pgm","/301.pgm","/302.pgm","/303.pgm","/310.pgm","/311.pgm","/312.pgm","/313.pgm","/320.pgm","/321.pgm","/322.pgm","/323.pgm"] #保存三角形多个模板
templates2 = ["/100.pgm","/101.pgm","/102.pgm","/103.pgm","/110.pgm","/111.pgm","/112.pgm","/113.pgm","/120.pgm","/121.pgm","/122.pgm","/123.pgm"] #保存矩形多个模板
templates3 = ["/000.pgm","/001.pgm","/002.pgm","/003.pgm","/010.pgm","/011.pgm","/012.pgm","/013.pgm","/020.pgm","/021.pgm","/022.pgm","/023.pgm"] #保存圆多个模板


clock = time.clock() #跟踪FPS帧率

#运行模板匹配
while (True):
    clock.tick() # 追踪两个snapshots()之间经过的毫秒数.
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0) #去畸变
    
    #初始化计数常量
    n1 = 0
    n2 = 0
    n3 = 0
    
    for t in templates1:  #如果与模板匹配    
        template = image.Image(t) #template获取图片
        r = img.find_template(template, 0.70, step=4, search=SEARCH_EX) #进行相关设置,可以设置roi缩小区域
        if r: #如果有目标
            img.draw_rectangle(r) #画矩形,框出匹配的目标
            n1 = n1 + 1

    for t in templates2:        
        template = image.Image(t)
        r = img.find_template(template, 0.70, step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))
        if r:
            img.draw_rectangle(r)
            n2 = n2 + 1

    for t in templates3:
        template = image.Image(t)
        r = img.find_template(template, 0.70, step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60))
        if r:
            img.draw_rectangle(r)
            n3 = n3 + 1
        
        #打印模板名字
        tuple([n1,n2,n3])
        tu = tuple([n1,n2,n3])
        max_value = max(tu) #获取最大匹配度的模板
        max_value_index = tu.index(max_value) #获取最大匹配度的模板下标
        if max_value_index == 0:
            print("三角形")
        if max_value_index ==1 :
            print("矩形")
        if max_value_index == 2:
            print("圆形")

        print('模板元组{}中最大值为:{},下标为:{}'.format(tu,max_value,max_value_index))

print(clock.fps())

特征点检测

特征点保存

# 特征点保存例程
# 此示例显示如何将关键点描述符保存到文件。向相机显示一个对象,然后运行
# 该脚本。该脚本将提取并保存关键点描述符和图像。
# 您可以使用keypoints_editor.py 来删除不需要的关键点。
#
# 注意:请在运行此脚本后重置摄像头以查看新文件。
import sensor, time, image


# 重置传感器
sensor.reset()


# 传感器设置
sensor.set_contrast(3)
sensor.set_gainceiling(16)
sensor.set_framesize(sensor.VGA)
sensor.set_windowing((320, 240))
sensor.set_pixformat(sensor.GRAYSCALE)


sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False, value=100)


FILE_NAME = "desc"
img = sensor.snapshot()
# 注意:请参阅文档查看其他参数
# 注:默认情况下,find_keypoints返回从图像中提取的多尺度关键点。
kpts = img.find_keypoints(max_keypoints=150, threshold=10, scale_factor=1.2)


if (kpts == None):
    raise(Exception("Couldn't find any keypoints!"))


image.save_descriptor(kpts, "/%s.orb"%(FILE_NAME))
img.save("/%s.pgm"%(FILE_NAME))


img.draw_keypoints(kpts)
sensor.snapshot()
time.sleep_ms(1000)
raise(Exception("Done! Please reset the camera"))

特征点检测

# Untitled - By: 黑尾鹿 - 周五 5月 27 2022


# 利用特征点检测特定物体例程。
# 向相机显示一个对象,然后运行该脚本。 一组关键点将被提取一次,然后
# 在以下帧中进行跟踪。 如果您想要一组新的关键点,请重新运行该脚本。
# 注意:请参阅文档以调整find_keypoints和match_keypoints。
import sensor, time, image


# 重置传感器
sensor.reset()


# 传感器设置
sensor.set_contrast(3)
sensor.set_gainceiling(16)
sensor.set_framesize(sensor.VGA)
sensor.set_windowing((320, 240))
sensor.set_pixformat(sensor.GRAYSCALE)


sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False, value=100)


#画出特征点
def draw_keypoints(img, kpts):
    if kpts:
        print(kpts)
        img.draw_keypoints(kpts)
        img = sensor.snapshot()
        time.sleep_ms(1000)


#kpts1 = None 如果改为开始几秒保存特征点,就打开注释,关闭后两个注释
#kpts1保存目标物体的特征,可以从文件导入特征,但是不建议这么做。
kpts1 = image.load_descriptor("/desc.orb")
img = sensor.snapshot()
#draw_keypoints(img, kpts1)


clock = time.clock()


while (True):
    clock.tick()
    img = sensor.snapshot()
    if (kpts1 == None):
        #如果是刚开始运行程序,提取最开始的图像作为目标物体特征,kpts1保存目标物体的特征
        #默认会匹配目标特征的多种比例大小,而不仅仅是保存目标特征时的大小,比模版匹配灵活。
        # NOTE: By default find_keypoints returns multi-scale keypoints extracted from an image pyramid.
        kpts1 = img.find_keypoints(max_keypoints=150, threshold=10, scale_factor=1.2)
        #image.find_keypoints(roi=Auto, threshold=20, normalized=False, scale_factor=1.5, max_keypoints=100, corner_detector=CORNER_AGAST)
        #roi表示识别的区域,是一个元组(x,y,w,h),默认与framsesize大小一致。
        #threshold是0~255的一个阈值,用来控制特征点检测的角点数量。用默认的AGAST特征点检测,这个阈值大概是20。用FAST特征点检测,这个阈值大概是60~80。阈值越低,获得的角点越多。
        #normalized是一个布尔数值,默认是False,可以匹配目标特征的多种大小(比ncc模版匹配效果灵活)。如果设置为True,关闭特征点检测的多比例结果,仅匹配目标特征的一种大小(类似于模版匹配),但是运算速度会更快一些。
        #scale_factor是一个大于1.0的浮点数。这个数值越高,检测速度越快,但是匹配准确率会下降。一般在1.35~1.5左右最佳。
        #max_keypoints是一个物体可提取的特征点的最大数量。如果一个物体的特征点太多导致RAM内存爆掉,减小这个数值。
        #corner_detector是特征点检测采取的算法,默认是AGAST算法。FAST算法会更快但是准确率会下降。
        draw_keypoints(img, kpts1)
        #画出此时的目标特征
    else:
        # 当与最开始的目标特征进行匹配时,默认设置normalized=True,只匹配目标特征的一种大小。
        # NOTE: When extracting keypoints to match the first descriptor, we use normalized=True to extract
        # keypoints from the first scale only, which will match one of the scales in the first descriptor.
        kpts2 = img.find_keypoints(max_keypoints=150, threshold=10, normalized=True)
        #如果检测到特征物体
        if (kpts2):
            #匹配当前找到的特征和最初的目标特征的相似度
            match = image.match_descriptor(kpts1, kpts2, threshold=85)
            #image.match_descriptor(descritor0, descriptor1, threshold=70, filter_outliers=False)。本函数返回kptmatch对象。
            #threshold阈值设置匹配的准确度,用来过滤掉有歧义的匹配。这个值越小,准确度越高。阈值范围0~100,默认70
            #filter_outliers默认关闭。


            #match.count()是kpt1和kpt2的匹配的近似特征点数目。
            #如果大于10,证明两个特征相似,匹配成功。
            if (match.count()>10):
                # If we have at least n "good matches"
                # Draw bounding rectangle and cross.
                #在匹配到的目标特征中心画十字和矩形框。
                img.draw_rectangle(match.rect())
                img.draw_cross(match.cx(), match.cy(), size=10)


            #match.theta()是匹配到的特征物体相对目标物体的旋转角度。
            print(kpts2, "matched:%d dt:%d"%(match.count(), match.theta()))
            # 不建议draw_keypoints画出特征关键点。
            # 注意:如果你想绘制关键点,取消注释
            #img.draw_keypoints(kpts2, size=KEYPOINTS_SIZE, matched=True)


    #打印帧率。
    img.draw_string(0, 0, "FPS:%.2f"%(clock.fps()))

寻迹

自动颜色跟踪

由于每次[[开启小车之前都需要手动调试阈值,因此,我们可以使用自动颜色追踪,只需要在每次开始时将小车放到正中央进行阈值矫正。

# 自动灰度颜色追踪例程
#
# 这个例子展示了使用OpenMV的单色自动灰度色彩跟踪。

import sensor, image, time
print("Letting auto algorithms run. Don't put anything in front of the camera!")

sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # 颜色跟踪必须自动增益
sensor.set_auto_whitebal(False) # 颜色跟踪必须关闭白平衡
clock = time.clock()

# 捕捉图像中心的颜色阈值。
r = [(320//2)-(50//2), (240//2)-(50//2), 50, 50] # 50x50 center of QVGA.

print("Auto algorithms done. Hold the object you want to track in front of the camera in the box.")
print("MAKE SURE THE COLOR OF THE OBJECT YOU WANT TO TRACK IS FULLY ENCLOSED BY THE BOX!")
for i in range(60):
    img = sensor.snapshot()
    img.draw_rectangle(r)

print("Learning thresholds...")
threshold = [128, 128] # Middle grayscale values.中间灰度值。
for i in range(60):
    img = sensor.snapshot()
    hist = img.get_histogram(roi=r)
    lo = hist.get_percentile(0.01) # 获取1%范围的直方图的CDF(根据需要调整)!
    hi = hist.get_percentile(0.99) # 获取99%范围的直方图的CDF(根据需要调整)!
    # 平均百分位值。
    threshold[0] = (threshold[0] + lo.value()) // 2
    threshold[1] = (threshold[1] + hi.value()) // 2
    for blob in img.find_blobs([threshold], pixels_threshold=100, area_threshold=100, merge=True, margin=10):
        img.draw_rectangle(blob.rect())
        img.draw_cross(blob.cx(), blob.cy())
        img.draw_rectangle(r)

print("Thresholds learned...")
print("Tracking colors...")

while(True):
    clock.tick()
    img = sensor.snapshot()
    for blob in img.find_blobs([threshold], pixels_threshold=100, area_threshold=100, merge=True, margin=10):
        img.draw_rectangle(blob.rect())
        img.draw_cross(blob.cx(), blob.cy())
    print(clock.fps())

快速线性回归

# Untitled - By: 黑尾鹿 - 周一 5月 9 2022
import sensor, image, time
from pyb import UART,LED
#from pid import PID
import ustruct              #打包和解压缩原始数据类型
THRESHOLD = (100, 70, 127, -128, -128, 127)   # 灰度阈值
#rho_pid = PID(p=0.19, i=0 , d=0)        #y=ax+b b截距
#theta_pid = PID(p=0.002, i=0, d=0)      #a斜率


#开启LED灯进行补光
LED(1).on()
LED(2).on()
LED(3).on()


sensor.reset()
sensor.set_vflip(True)#打开摄像头垂直翻转模式
sensor.set_hmirror(True)#水平镜像模式
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQQVGA) # 80x60 (4,800 pixels) - O(N^2) max = 2,3040,000.
sensor.skip_frames(time = 2000)     # 跳过2s帧
clock = time.clock()                # to process a frame sometimes.


uart = UART(3,115200)   #定义串口3变量
uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters
#**************************传输数据的函数********************************************************
def sending_data(theta_err):
    global uart;
    data = ustruct.pack("<bbhb",
                   0x2C,                      #帧头1
                   0x12,                      #帧头2
                   int(theta_err), # up sample by 4   #数据1
                   0x5B)
    uart.write(data);   #必须要传入一个字节数组
#**************************传输数据的函数********************************************************


while(True):
    clock.tick()
    img.dilate(2) #膨胀(去除噪点)
    img.erode(2)# 腐蚀 (可有可无)
    img = sensor.snapshot().binary([THRESHOLD]) #线设置为白色,其他为黑色,binary(二进制)根据像素是否在阈值列表 thresholds 中的阈值内,将图像中的所有像素设置为黑色或白色。
    line = img.get_regression([(100,100,0,0,0,0)], robust = True) #线性拟合(回归)返回直线蓝色 返回的是一个 image.line对象 robust(坚定的)为True,则使用Theil-Sen线性回归算法,它计算图像中所有阈值像素的斜率的中位数
    if (line):
        #rho_err = abs(line.rho())-img.width()/2  #蓝色直线偏移距离,line.rho()返回霍夫变换后的直线p值 img.width()/2图像宽度除以二即视野中线
        if line.theta()> 90:  #返回霍夫变换(特征提取算法,比如投票提取直线)后的直线的角度(0-179度)。
            theta_err = line.theta()-180  #次操作是将坐标180 90 0变为 -90 0 90
        else:
            theta_err = line.theta()
        img.draw_line(line.line(), color = 127)  #画出蓝色直线 line.line()返回一个直线元组(x1, y1, x2, y2) ,用于如 image.draw_line 等其他的 image 方法。


        if line.magnitude()>8:      #线性回归效果,好进行下一步,否则不进行s
            if theta_err<0:#左偏
                sending_data(theta_err+100)
                #print(theta_err+100)
            else:#右偏
                sending_data(theta_err)
                #print(theta_err)
            #print(line.magnitude(),theta_err)#(线性回归效果,直线角度(左偏负右偏正))
            #if -40<b_err<40 and -30<t_err<30:
            #rho_output = rho_pid.get_pid(rho_err,1)
            #theta_output = theta_pid.get_pid(theta_err,1)
            #xw = rho_output+theta_output  #角度,距离PID线性叠加
            #if(xw<0):
                #speed = (rho_output+theta_output+12)  #角度,距离PID线性叠加
           # else:
               # speed = rho_output+theta_output
            #print(rho_output,theta_output,xw)
        #else:
           # speed=0.0

findblobs进行颜色识别

3.1GeometryFeature.py

class GeometryFeature:

    def __init__(self, img):
        self.img = img

    @staticmethod
    def trans_line_format(line):
        '''
        将原来由两点坐标确定的直线,转换为 y = ax + b 的格式
        '''
        x1 = line.x1()
        y1 = line.y1()
        x2 = line.x2()
        y2 = line.y2()

        if x1 == x2:
            # 避免完全垂直,x坐标相等的情况
            x1 += 0.1
        # 计算斜率 a
        a = (y2 - y1) / (x2 - x1)
        # 计算常数项 b
        # y = a*x + b -> b = y - a*x
        b = y1 - a * x1
        return a,b

    @staticmethod
    def calculate_angle(line1, line2):
        '''
        利用四边形的角公式, 计算出直线夹角
        '''
        angle  = (180 - abs(line1.theta() - line2.theta()))
        if angle > 90:
            angle = 180 - angle
        return angle

    @staticmethod
    def find_verticle_lines(lines, angle_threshold=(70, 90)):
        '''
        寻找相互垂直的两条线
        '''
        return GeometryFeature.find_interserct_lines(lines, angle_threshold=angle_threshold)

    @staticmethod
    def find_interserct_lines(lines, angle_threshold=(10,90), window_size=None):
        '''
        根据夹角阈值寻找两个相互交叉的直线, 且交点需要存在于画面中
        '''
        line_num = len(lines)
        for i in range(line_num -1):
            for j in range(i, line_num):
                # 判断两个直线之间的夹角是否为直角
                angle = GeometryFeature.calculate_angle(lines[i], lines[j])
                # 判断角度是否在阈值范围内
                if not(angle >= angle_threshold[0] and angle <=  angle_threshold[1]):
                    continue

                # 判断交点是否在画面内
                if window_size is not None:
                    # 获取串口的尺寸 宽度跟高度
                    win_width, win_height = window_size
                    # 获取直线交点
                    intersect_pt = GeometryFeature.calculate_intersection(lines[i], lines[j])
                    if intersect_pt is None:
                        # 没有交点
                        continue
                    x, y = intersect_pt
                    if not(x >= 0 and x < win_width and y >= 0 and y < win_height):
                        # 交点如果没有在画面中
                        continue
                return (lines[i], lines[j])
        return None

    @staticmethod
    def calculate_intersection(line1, line2):
        '''
        计算两条线的交点
        '''
        a1 = line1.y2() - line1.y1()
        b1 = line1.x1() - line1.x2()
        c1 = line1.x2()*line1.y1() - line1.x1()*line1.y2()

        a2 = line2.y2() - line2.y1()
        b2 = line2.x1() - line2.x2()
        c2 = line2.x2() * line2.y1() - line2.x1()*line2.y2()

        if (a1 * b2 - a2 * b1) != 0 and (a2 * b1 - a1 * b2) != 0:
            cross_x = int((b1*c2-b2*c1)/(a1*b2-a2*b1))
            cross_y = int((c1*a2-c2*a1)/(a1*b2-a2*b1))
            return (cross_x, cross_y)
        return None

3.2 main.py

'''
原理介绍
    算法的主要核心在于,讲整个画面分割出来5个ROI区域
    * 上方横向采样
    * 中间横向采样
    * 下方横向采样
    * 左侧垂直采样
    * 右侧垂直采样
    通过判断5个图片的组合关系给出路口类型的判断
'''
import sensor
import image
import time
import math
import pyb
from pyb import Pin, Timer,LED
from GeometryFeature import GeometryFeature
LED(4).on()
is_debug = True
DISTORTION_FACTOR = 1.5#畸变矫正因子
IMG_WIDTH  = 64#像素点宽度
IMG_HEIGHT = 64#像素点高度
def init_sensor():#初始化感光器
    sensor.reset()
    sensor.set_pixformat(sensor.GRAYSCALE)#设置为灰度图
    sensor.set_framesize(sensor.B64X64)  #设置像素大小
    sensor.skip_frames(time=2000)        #最大像素点个数
    sensor.set_auto_gain(False)          #颜色追踪关闭自动增益
    sensor.set_auto_whitebal(False)      #颜色追踪关闭自动白平衡
init_sensor()
INTERSERCT_ANGLE_THRESHOLD = (45,90)     #设置角度阈值
LINE_COLOR_THRESHOLD = [(0, 120)]        #设置巡线的颜色阈值
ROIS = {                                 #ROIS将镜头的画面分割为5个区域分别找寻色块
    'down': (0, 55, 64, 8),
    'middle': (0, 28, 64, 8),
    'up': (0, 0, 64, 8),
    'left': (0, 0, 8, 64),
    'right': (56, 0, 8, 64)
}
def find_blobs_in_rois(img):
    '''
    在ROIS中寻找色块,获取ROI中色块的中心区域与是否有色块的信息
    '''
    global ROIS
    global is_debug
    roi_blobs_result = {}
    for roi_direct in ROIS.keys():
        roi_blobs_result[roi_direct] = {
            'cx': -1,
            'cy': -1,
            'blob_flag': False
        }
    for roi_direct, roi in ROIS.items():
        blobs=img.find_blobs(LINE_COLOR_THRESHOLD, roi=roi, merge=True, pixels_area=10)
        if len(blobs) == 0:
            continue
        largest_blob = max(blobs, key=lambda b: b.pixels())
        x,y,width,height = largest_blob[:4]
        if not(width >=5 and width <= 15 and height >= 5 and height <= 15):
            continue
        roi_blobs_result[roi_direct]['cx'] = largest_blob.cx()
        roi_blobs_result[roi_direct]['cy'] = largest_blob.cy()
        roi_blobs_result[roi_direct]['blob_flag'] = True
        if is_debug:
            img.draw_rectangle((x,y,width, height), color=(255))
    return roi_blobs_result
def visualize_result(canvas, cx_mean, cx, cy, is_turn_left, is_turn_right, is_t, is_cross):
    '''
    可视化结果
    '''
    if not(is_turn_left or is_turn_right or is_t or is_cross):
        mid_x = int(canvas.width()/2)
        mid_y = int(canvas.height()/2)
        canvas.draw_circle(int(cx_mean), mid_y, 5, color=(255))
        canvas.draw_circle(mid_x, mid_y, 8, color=(0))
        canvas.draw_line((mid_x, mid_y, int(cx_mean), mid_y), color=(255))
    turn_type = 'N'   #判断为直线
    if is_t or is_cross:
        canvas.draw_cross(int(cx), int(cy), size=10, color=(255))
        canvas.draw_circle(int(cx), int(cy), 5, color=(255))
    if is_t:
        turn_type = 'T'  #判断为T字路口
    elif is_cross:
        turn_type = 'C'  #判断为十字路口
    elif is_turn_left:
        turn_type = 'L'  #判断为左转
    elif is_turn_right:
        turn_type = 'R'  #判断为右转
    canvas.draw_string(0, 0, turn_type, color=(0))
last_cx = 0
last_cy = 0
while True:
    img = sensor.snapshot()     #拍取一张图片
    img.lens_corr(DISTORTION_FACTOR)  #进行镜头畸形矫正,里面的参数是进行鱼眼矫正的程度
    lines = img.find_lines(threshold=1000, theta_margin = 50, rho_margin = 50)
    intersect_pt = GeometryFeature.find_interserct_lines(lines, angle_threshold=(45,90), window_size=(IMG_WIDTH, IMG_HEIGHT))
    if intersect_pt is None:
        intersect_x = 0
        intersect_y = 0
    else:
        intersect_x, intersect_y = intersect_pt
    reslut = find_blobs_in_rois(img)
    is_turn_left = False
    is_turn_right = False
    if (not reslut['up']['blob_flag'] ) and reslut['down']['blob_flag']:
        if reslut['left']['blob_flag']:
            is_turn_left = True
        if reslut['right']['blob_flag']:
            is_turn_right = True
    is_t = False
    is_cross = False
    cnt = 0
    for roi_direct in ['up', 'down', 'left', 'right']:
        if reslut[roi_direct]['blob_flag']:
            cnt += 1
    is_t = cnt == 3
    is_cross = cnt == 4
    cx_mean = 0
    for roi_direct in ['up', 'down', 'middle']:
        if reslut[roi_direct]['blob_flag']:
            cx_mean += reslut[roi_direct]['cx']
        else:
            cx_mean += IMG_WIDTH / 2
    cx_mean /= 3  #表示为直线时区域的中心x坐标
    cx = 0        #cx,cy表示当测到为T型或者十字型的时候计算的交叉点的坐标
    cy = 0
    if is_cross or is_t:
        cnt = 0
        for roi_direct in ['up', 'down']:
            if reslut[roi_direct]['blob_flag']:
                cnt += 1
                cx += reslut[roi_direct]['cx']
        if cnt == 0:
            cx = last_cx
        else:
            cx /= cnt
        cnt = 0
        for roi_direct in ['left', 'right']:
            if reslut[roi_direct]['blob_flag']:
                cnt += 1
                cy += reslut[roi_direct]['cy']
        if cnt == 0:
            cy = last_cy
        else:
            cy /= cnt
    last_cx = cx
    last_cy = cy
    if is_debug:
        visualize_result(img, cx_mean, cx, cy, is_turn_left, is_turn_right, is_t, is

4.几个块区域的数学运算,计算出轨迹偏离角度。

掌握具体知识后便可搭建循迹小车了。

然而并不是一帆风顺,肯定会漏洞百出…所以在搭建时候就细心耐心,一点一点解决问题。搭建时候的注意事项:

1、在用杜邦线连接小车各部分模块时候,首先确保杜邦线没有坏;

2、杜邦线也不能有松动或者没有插紧的情况,不然很容易造成接触不良,在调试陈旭时候也会被误认为程序问题而浪费时间;

3、接的电压要按照各器件额定值接入。

到现在小车基本上是搭建好了(除了摄像头部分),先启动小车,让它能够跑起来,还要能够跑直线,特别直!

同时还要时不时地检查一下接线是否有松动的情况!!

一切都OK了,那就装上openMV进行数据对接。

有几点软件部分需要注意的事项:

1、注意使用USART通信要明确自己所用的协议,否则会出现不是自己预想的数据;

2、在我使用openMV时候,左右的偏移量为0到45 和 -45到0,但是将该数据传输回STM32时候负数段数据不正常。解决方法为将左右偏移量的数据范围加上相应的数字,使其左右偏移量范围里没有负数数据。

搞好以后迫不及待的要跑一下了,但是刚按下开关,小车按照轨迹过弯道过到一半时候乱跑了,然后又是瞎跑。。。。做到这一步又有几点需要注意,可耐心读完:

1、 注意,这几点很重要!!!

2、小车能够简单寻迹,但又不能完全寻迹时候,可以把摄像头从小车上拿下来,固定在一个摄像头倾斜角和高度相同的简易支架上,连接到电脑上观看小车视野,,在OPENMV IDE里面查看画面,是否在过弯到一半时候找不到视野。若是这个问题则根据自己的需求配合程序进行相应的调整;

3、 小车向左转很顺利,向右转会“出轨”,或是左转到一半会瞎跑。首先要确保摄像头摆放位置是正的,接线是否有松动而影响数据传输,然后确保硬件没问题时候检查偏移量的数据。通过查看偏移时候openMV处理出来的数据,发现左边过弯很顺利是其偏移量正常,而右边偏移量不足,导致其过弯一半“出轨”。适当增加右转偏移量即可解决。

4、 有时会出现数据传输错乱,但是硬件又没有问题,这时候应该把数据传输有关的模块单独接电源或是原理接线群,其他的线路可能会有些许的影响(没有固然最好,但是值得一试)。

5、 测试时候-务-必-一-定-必-须-要在光线好的地方测试,很重要!!!!因为光线弱会影响到识别路径,从而导致乱跑、跑到旁边的路径上,此时又会误以为是程序出了问题,又去调试程序浪费时间。程序调试N多次,不如灯光照一次!!

# 机器人巡线例程
#
# 跟随机器人做一条线需要很多努力。 本示例脚本显示了如何执行跟随机器人的
# 机器视觉部分。 您可以使用此脚本的输出来驱动差分驱动机器人遵循一条线。
# 这个脚本只是产生一个转动的值,告诉你的机器人向左或向右。
#
# 为了使这个脚本正常工作,你应该把摄像机指向45度左右的一条线。请确保只有线在相机的视野内。
import sensor, image, time, math

# 跟踪一条黑线。使用[(128,255)]来跟踪白线。
GRAYSCALE_THRESHOLD = [(0, 64)]
#设置阈值,如果是黑线,GRAYSCALE_THRESHOLD = [(0, 64)];
#如果是白线,GRAYSCALE_THRESHOLD = [(128,255)]


# 每个roi为(x, y, w, h),线检测算法将尝试找到每个roi中最大的blob的质心。
# 然后用不同的权重对质心的x位置求平均值,其中最大的权重分配给靠近图像底部的roi,
# 较小的权重分配给下一个roi,以此类推。
ROIS = [ # [ROI, weight]
        (0, 100, 160, 20, 0.7), # 你需要为你的应用程序调整权重
        (0, 050, 160, 20, 0.3), # 取决于你的机器人是如何设置的。
        (0, 000, 160, 20, 0.1)
       ]
#roi代表三个取样区域,(x,y,w,h,weight),代表左上顶点(x,y)宽高分别为w和h的矩形,
#weight为当前矩形的权值。注意本例程采用的QQVGA图像大小为160x120,roi即把图像横分成三个矩形。
#三个矩形的阈值要根据实际情况进行调整,离机器人视野最近的矩形权值要最大,
#如上图的最下方的矩形,即(0, 100, 160, 20, 0.7)

# Compute the weight divisor (we're computing this so you don't have to make weights add to 1).
weight_sum = 0 #权值和初始化
for r in ROIS: weight_sum += r[4] # r[4] is the roi weight.
#计算权值和。遍历上面的三个矩形,r[4]即每个矩形的权值。

sensor.reset() # 初始化sensor

sensor.set_pixformat(sensor.GRAYSCALE) # use grayscale.
#设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种

sensor.set_framesize(sensor.QQVGA) # 使用QQVGA的速度。
#设置图像像素大小

sensor.skip_frames(30) # 让新的设置生效。
sensor.set_auto_gain(False) # 颜色跟踪必须关闭自动增益
sensor.set_auto_whitebal(False) # 颜色跟踪必须关闭白平衡
clock = time.clock() # 跟踪FPS帧率

while(True):
    clock.tick() # 追踪两个snapshots()之间经过的毫秒数.
    img = sensor.snapshot() # 拍一张照片并返回图像。

    centroid_sum = 0
    #利用颜色识别分别寻找三个矩形区域内的线段
    for r in ROIS:
        blobs = img.find_blobs(GRAYSCALE_THRESHOLD, roi=r[0:4], merge=True) 
        # r[0:4] is roi tuple.
        #找到视野中的线,merge=true,将找到的图像区域合并成一个

        #目标区域找到直线
        if blobs:
            # 查找像素最多的blob的索引。
            largest_blob = 0
            for i in range(len(blobs)):
            #目标区域找到的颜色块(线段块)可能不止一个,找到最大的一个,作为本区域内的目标直线
                if blobs[i].pixels() > most_pixels:
                    most_pixels = blobs[i].pixels()
                    #merged_blobs[i][4]是这个颜色块的像素总数,如果此颜色块像素总数大于                     
                    #most_pixels,则把本区域作为像素总数最大的颜色块。更新most_pixels和largest_blob
                    largest_blob = i

            # 在色块周围画一个矩形。
            img.draw_rectangle(blobs[largest_blob].rect())
            # 将此区域的像素数最大的颜色块画矩形和十字形标记出来
            img.draw_cross(blobs[largest_blob].cx(),
                           blobs[largest_blob].cy())

            centroid_sum += blobs[largest_blob].cx() * r[4] # r[4] is the roi weight.
            #计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值

    center_pos = (centroid_sum / weight_sum) # Determine center of line.
    #中间公式

    # 将center_pos转换为一个偏角。我们用的是非线性运算,所以越偏离直线,响应越强。
    # 非线性操作很适合用于这样的算法的输出,以引起响应“触发器”。
    deflection_angle = 0
    #机器人应该转的角度

    # 80是X的一半,60是Y的一半。
    # 下面的等式只是计算三角形的角度,其中三角形的另一边是中心位置与中心的偏差,相邻边是Y的一半。
    # 这样会将角度输出限制在-45至45度左右。(不完全是-45至45度)。

    deflection_angle = -math.atan((center_pos-80)/60)
    #角度计算.80 60 分别为图像宽和高的一半,图像大小为QQVGA 160x120.    
    #注意计算得到的是弧度值

    deflection_angle = math.degrees(deflection_angle)
    #将计算结果的弧度值转化为角度值

    # 现在你有一个角度来告诉你该如何转动机器人。
    # 通过该角度可以合并最靠近机器人的部分直线和远离机器人的部分直线,以实现更好的预测。

    print("Turn Angle: %f" % deflection_angle)
    #将结果打印在terminal中

        print(clock.fps())              
    # 注意: 当连接电脑后,OpenMV会变成一半的速度。当不连接电脑,帧率会增加。
    # 打印当前的帧率。

其他

畸变矫正

# Untitled - By: 黑尾鹿 - 周六 5月 21 2022




import sensor, image, time


sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
clock = time.clock()


while(True):
    clock.tick()


    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)


    print(clock.fps())

开启LED灯进行补光

from pyb import LED

LED(1).on()
LED(2).on()
LED(3).on()

表情识别

import sensor, image, time, math,ustruct    #模块导入
from pyb import UART


uart = UART(3, 9600)#串口配置
arr=[1,2,4,0]#存放颜色的代号


#元组里面的数值分别是L A B 的最大值和最小值。
thresholds = [(99, 0, 32, 127, -121, 127),     # 糖
              (3, 12, -74, 95, -107, 99),     # 锤子
             (44, 100, -128, 50, 35, 97)]     # 鄙视


sensor.reset()     #初始化感光远原件
#sensor.set_vflip(True)#打开摄像头垂直翻转模式
sensor.set_hmirror(True)#水平镜像模式
sensor.set_pixformat(sensor.RGB565)#设置图像的色彩
sensor.set_framesize(sensor.QVGA)#设置图像分辨率
sensor.skip_frames(time = 2000)#设置要跳过的帧率
sensor.set_auto_gain(False) # 颜色追踪时要关闭自动增益
sensor.set_auto_whitebal(False) # 颜色追踪时要关闭白平衡
clock = time.clock()#设置帧率检测




#**************************传输数据的函数********************************************************
'''
ustruct
打包和解压缩原始数据类型
支持的大小/字节顺序前缀: @, <, >, !.
支持的格式编码: b, B, h, H, i, I, l,L, q, Q, s, P, f, d 后两个取决于浮点支持)。
'''
def sending_data(code):
    global uart;
    #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];
    #data = bytearray(frame)
    data = ustruct.pack("<bbhb",      #格式为3个字符1个短整型(2字节)
                   0x2C,                      #帧头1
                   0x12,                      #帧头2
                   int(code), # up sample by 4   #数据1 分为低八位和高八位,先发低后发高
                   #int(cy), # up sample by 4    #数据2
                   0x5B)
    uart.write(data);   #必须要传入一字节的数组,这个函数似乎不能发送单个字节,必须得一次发送多个字节
#**********************************************************************************************


while(True):
    clock.tick()
    img = sensor.snapshot()#存储摄像头所拍摄的图像并保存在img变量中
    for blob in img.find_blobs(thresholds,roi=(130,80,70,70), pixels_threshold=250, area_threshold=250,merge=True):#find_blobs函数可以找到色块。
    #若一个色块的边界框区域小于 area_threshold ,则会被过滤掉。若一个色块的像素数小于 pixel_threshold ,则会被过滤掉。
        if blob.elongation() > 0.5:#调用该函数返回一个介于0和1之间的值,该值表示对象的长度(不是圆形)。一条线将是1。
            img.draw_edges(blob.min_corners(), color=(255,0,0))    #在blob.min_corners()返回的角列表之间绘制红线边
            #img.draw_line(blob.major_axis_line(), color=(0,255,0))
            #img.draw_line(blob.minor_axis_line(), color=(0,0,255))
        img.draw_rectangle(blob.rect())         #在blob.rect()返回的矩形元组(x, y, w, h) 图像中画一个矩形框
        img.draw_cross(blob.cx(), blob.cy())    #在blob.cx()返回的中心坐标 画一个十字
       # img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20)
    #print(clock.fps())
        a=blob[8]    #[8]索引得到code的代码
        if a==1:
            sending_data(str(arr[0]))    #存放到数组里面,在转换为str就变成1字节
            print('糖')
        elif a==2:
            sending_data(str(arr[1]))
            print('锤子')
        elif a==4:
            sending_data(str(arr[2]))
            print('鄙视')
        else :
            sending_data(str(arr[3]))
            print('正常')
    #print(a)
    #uart.write(str(arr[0]))

识别矩形角点和跟踪激光示例:

💡商业转载请联系作者获得授权,非商业转载请注明出处。
协议(License):署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)。
使用这些免费资源的时候应遵守版权意识,切勿非法利用,售卖,尊重原创内容知识产权。未经允许严禁转载。

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇