2021年电赛F题智能送药小车——openmv端开源分享(巡线、数字识别一体化实现)

1.前言

        智能送药小车是我在备战2023年电赛时做的训练题。通过研究,我发现网上大多数方案将巡线与数字识别分开实现。然而,我的想法是将这两者结合在一个OpenMV系统中,来模拟在比赛期间缺少了灰度传感器(因为巡线是循红线)的解决方案。(我是使用STC32作为小车的主控来控制其电机的运动,通信就是STC32和openmv的,实际上将串口接收和发送处理好所有单片机都可以使用这个方案)

                                                         (训练题目比较简陋“擦汗emoji”)

2.题目任务

设计并制作一款智能送药小车,模拟完成在医院药房与病房间药品的送取作业。以下是院区结构的示意图(图1),走廊两侧的墙体用黑色实线表示,走廊地面上有一条居中的红色实线,并且放置了标识病房号的黑色数字纸张。药房和近端病房号(1、2号)位置固定不变,而中部病房和远端病房号(3-8号)在测试时会随机设定。

工作过程

  1. 初始状态

    • 参赛者将小车手动摆放在药房处,使车头的投影位于门口区域内,并使车头面向病房。
  2. 药品装载

    • 手持数字标号纸张,由小车识别病房号。小车识别后,将约200克药品一次性装载到送药小车上。
  3. 自动送药

    • 小车在检测到药品装载完成后,自动开始运送。
    • 小车根据走廊上的标识信息自动识别病房号,并沿着标识路径寻径,将药品送到指定的病房(车头的投影位于门口区域内)。
  4. 卸药操作

    • 在病房处,点亮红色指示灯以指示药品送达。
    • 病房人员人工卸载药品后,小车会自动熄灭红色指示灯,并开始返回药房。
  5. 返回药房

    • 小车在返回药房时,确保车头的投影位于门口区域内并面向药房。
    • 返回到药房后,小车点亮绿色指示灯,表示药品送取任务完成

 3.实现原理

        手持数字标号纸张,由小车识别病房号。小车识别后,将约200克药品一次性装载到送药小车上。然后去找到这个指定的数字病房(就好像是一只小狗,我扔出去一个球,然后跑过去捡回来,哈哈哈!!!)。言归正传,我是通过根据不同的任务模式(Find_Task),执行相应的图像处理任务,例如目标模板的匹配、目标病房的寻找。

  • 图像处理与目标检测

    • 通过摄像头获取图像。
    • 识别和处理图像中的特定颜色块和模板。
    • 根据识别的结果计算偏转角度,控制系统的运动方向。
    • 检测十字路口并发送信号。
  • 串口通信

    • 通过串口与其他设备进行数据传输,例如发送转向角度或其他控制指令。
  • 任务模式处理

    • 根据不同的任务模式(Find_Task),执行相应的图像处理任务,例如目标模板的匹配。
  • 巡线功能

    • 实现对图像中预定义路径的跟踪。

主要功能模块

  1. UartReceiveDate() 函数

    • 处理串口数据接收。
    • 解析串口接收到的数据,并根据数据执行不同的操作(如检测十字路口)。
  2. FirstFindTemplate(template)FindTemplate(template) 函数

    • 这些函数用于查找特定的模板图像并返回匹配结果。
    • 模板匹配是图像识别中的一种常见技术,通过对比预定义的模板图像与当前图像中的区域来确定匹配程度。
  3. xunji() 函数

    • 执行巡线功能,检测图像中的颜色块、计算偏转角度,并发送控制信号以调整方向。
    • ROI(Region of Interest)区域的定义用于限制搜索范围,提升检测效率。
  4.  FirstFindedNum() 函数

    • 识别手持的数字标号纸张,上传病房号到小车主控。

  5.FindedNum()函数

              1.在小车运动的过程中,不断的寻找目标病房。

 以下是具体代码的实现:(模板图像需要自己进行拍摄数字卡片)

import sensor, image, time, math , ustruct ,pyb  # 导入所需模块
from image import SEARCH_EX, SEARCH_DS  # 从image模块引入SEARCH_EX和SEARCH_DS,用于图像模板匹配
from pyb import UART, Pin, Timer, LED  # 从pyb模块导入UART、Pin、Timer和LED,控制串口、引脚、定时器和LED
import json  # 导入json模块,用于处理JSON数据

# 定义灰度阈值,用于图像处理
GRAYSCALE_THRESHOLD = [(36, 139)]
THRESHOLD = (17, 44, 12, 127, 0, 127)  # 定义颜色阈值

sensor.reset()  # 重置摄像头传感器

# 设置摄像头传感器的参数
sensor.set_contrast(1)  # 设置对比度为1
sensor.set_gainceiling(16)  # 设置增益上限为16
sensor.set_framesize(sensor.QQVGA)  # 设置图像分辨率为QQVGA(160x120)
sensor.set_pixformat(sensor.GRAYSCALE)  # 设置图像为灰度模式

sensor.set_windowing(0, 30, 300, 160)  # 设置观察窗口为(0, 30, 300, 160)

ROIS = [  # 定义ROI区域及其权重
    (0, 100, 160, 20, 0.7),  # ROI区域1,权重0.7
    (0, 50, 160, 20, 0.3),  # ROI区域2,权重0.3
    (0, 0, 160, 20, 0.1)    # ROI区域3,权重0.1
]

# 加载模板图像
template01 = image.Image("/1.pgm")
template02 = image.Image("/2.pgm")
template03 = image.Image("/3.pgm")
template04 = image.Image("/4.pgm")
template05 = image.Image("/5.pgm")
template06 = image.Image("/6.pgm")
template07 = image.Image("/7.pgm")
template08 = image.Image("/8.pgm")

# 加载不同方位的模板图像
template3L = image.Image("/3l.pgm")
template3LL = image.Image("/3ll.pgm")
template3R = image.Image("/3r.pgm")
template3RR = image.Image("/3rr.pgm")

template4L = image.Image("/4l.pgm")
template4LL = image.Image("/4ll.pgm")
template4R = image.Image("/4r.pgm")
template4RR = image.Image("/4rr.pgm")

template5L = image.Image("/5l.pgm")
template5LL = image.Image("/5ll.pgm")
template5R = image.Image("/5r.pgm")
template5RR = image.Image("/5rr.pgm")

template6L = image.Image("/6l.pgm")
template6LL = image.Image("/6ll.pgm")
template6R = image.Image("/6r.pgm")
template6RR = image.Image("/6rr.pgm")

template7L = image.Image("/7l.pgm")
template7LL = image.Image("/7ll.pgm")
template7R = image.Image("/7r.pgm")
template7RR = image.Image("/7rr.pgm")

template8L = image.Image("/8l.pgm")
template8LL = image.Image("/8ll.pgm")
template8R = image.Image("/8r.pgm")
template8RR = image.Image("/8rr.pgm")

templates = ["/tl1.pgm", "/tl2.pgm", "/tr1.pgm", "/tr2.pgm", "/tt1.pgm", "/tt2.pgm", "/tt3.pgm"]

uart = pyb.UART(3, 115200, timeout_char=1000)  # 定义串口3,波特率115200,字符超时1000毫秒
blue_led = LED(3)  # 初始化蓝色LED

# 通过接收STM32F103传来的数据来决定使用哪种模式
# FindTask:1. 轮询1~8,直至识别到;2. 根据F103给的值,单纯识别那个数
Find_Task = 1  # 初始化任务模式为1
Target_Num = 0  # 初始化目标数字为0
find_flag = 0  # 初始化查找标志
data = [0x00]*9  # 初始化数据缓存

# 串口接收数据函数处理
def UartReceiveDate():
    global Find_Task
    global Target_Num
    global x
    global data
    data[2] = uart.readchar()  # 读取串口数据
    time.sleep_ms(500)  # 延时500毫秒
    if data[2] >= 48 and data[2] <= 57:  # 检查数据是否为数字(ASCII范围48-57)
        Find_Task = 2  # 设置任务模式为2
        Target_Num = data[2] - 48  # 将ASCII字符转换为数字
        print("模式:", Find_Task, "目标病房;", Target_Num)  # 打印当前模式和目标病房号

# 当定时器回调时,将返回定时器对象
def tick(timer):
    blue_led.toggle()  # 切换LED状态

tim = Timer(2, freq=5)  # 创建定时器2,频率5Hz
tim.callback(tick)  # 设置定时器回调函数为tick

# FindTask == 0 时使用的模板匹配函数
def FirstFindTemplate(template):
    sensor.set_pixformat(sensor.GRAYSCALE)  # 设置图像为灰度模式
    img = sensor.snapshot()  # 拍摄一张图片
    R = img.find_template(template, 0.8, step=1, roi=(55, 0, 50, 90), search=SEARCH_EX)  # 在指定ROI区域内进行模板匹配
    return R  # 返回匹配结果

def FirstFindedNum(R, Finded_Num):
    global Find_Task
    global find_flag
    img.draw_rectangle(R, color=(225, 0, 0))  # 绘制匹配到的模板的矩形框
    time.sleep_ms(100)  # 延时100毫秒
    find_flag = 1  # 设置查找标志为1
    Num = Finded_Num  # 记录找到的数字
    uart.write(Finded_Num)  # 通过串口发送找到的数字
    print("目标病房号:", Num)  # 打印目标病房号

def FindTemplate(template):
    sensor.set_pixformat(sensor.GRAYSCALE)  # 设置图像为灰度模式
    img = sensor.snapshot()  # 拍摄一张图片
    R = img.find_template(template, 0.8, step=1, search=SEARCH_EX)  # 在全图范围内进行模板匹配
    return R  # 返回匹配结果

def FindedNum(R, Finded_Num):
    global Find_Task
    global find_flag
    img.draw_rectangle(R, color=(225, 0, 0))  # 绘制匹配到的模板的矩形框
    # 根据模板的x坐标判断其位置
    if R[0] >= 65:
        LoR = "r"  # 如果x坐标大于等于65,表示在右边
    elif 0 < R[0] <= 65:
        LoR = "l"  # 如果x坐标在0到65之间,表示在左边
    uart.write(LoR)  # 通过串口发送位置标识
    find_flag = 1  # 设置查找标志为1
    Num = Finded_Num  # 记录找到的数字
    print("识别到的数字是:", Num, "此数字所在方位:", LoR)  # 打印识别到的数字及其位置

def xunji():
    sensor.set_pixformat(sensor.RGB565)  # 设置图像为RGB565模式
    img = sensor.snapshot().binary([THRESHOLD])  # 拍摄一张图片并进行二值化处理
    line = img.get_regression([(100, 100)], robust=True)  # 进行直线回归分析
    centroid_sum = 0  # 初始化质心和
    for r in ROIS:  # 遍历每个ROI区域
        blobs = img.find_blobs(GRAYSCALE_THRESHOLD, merge=True)  # 查找图像中的颜色块
        if blobs:
            most_pixels = 0  # 初始化最大像素数
            largest_blob = 0  # 初始化最大颜色块索引
            for i in range(len(blobs)):  # 遍历所有找到的颜色块
                if blobs[i].pixels() > most_pixels:  # 找到最大的颜色块
                    most_pixels = blobs[i].pixels()
                    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]  # 计算质心和
    weight_sum = 1.1  # 权重和
    center_pos = (centroid_sum / weight_sum)  # 计算中心位置
    deflection_angle = -math.atan((center_pos - 160) / 120)  # 计算偏转角度
    deflection_angle = math.degrees(deflection_angle)  # 转换为角度
    ROI = [(10, 0, 40, 50), (110, 0, 50, 50)]  # 定义两个ROI区域
    blobs1 = img.find_blobs(GRAYSCALE_THRESHOLD, roi=ROI[0], area_threshold=150, merge=True)  # 查找第一个ROI区域的颜色块
    if blobs1:
        blobs2 = img.find_blobs(GRAYSCALE_THRESHOLD, roi=ROI[1], area_threshold=150, merge=True)  # 查找第二个ROI区域的颜色块
        if blobs2:
            print("十字路口")  # 打印检测到十字路口
            uart.write("+")  # 通过串口发送十字路口信号
            time.sleep(1)  # 延时1秒
    A = deflection_angle  # 将偏转角度赋值给A
    uart_buf = int(A) + 55  # 将角度转换为整数并加上55
    uart_buf1 = uart_buf - 45  # 计算调整后的角度
    print("Turn Angle: %d" % uart_buf)  # 打印转向角度
    print('you send:', uart_buf1)  # 打印发送的数据
    uart.write(str(uart_buf1))  # 通过串口发送数据
    sensor.set_pixformat(sensor.GRAYSCALE)  # 设置图像为灰度模式
    img = sensor.snapshot()  # 拍摄一张图片

clock = time.clock()  # 创建一个时钟对象

while True:
    clock.tick()  # 更新时钟
    img = sensor.snapshot()  # 拍摄一张图片
    UartReceiveDate()  # 处理串口数据
    if Find_Task == 1:  # 如果任务模式为1
        r01 = FirstFindTemplate(template01)  # 查找模板01
        r02 = FirstFindTemplate(template02)  # 查找模板02
        r03 = FirstFindTemplate(template03)  # 查找模板03
        r04 = FirstFindTemplate(template04)  # 查找模板04
        r05 = FirstFindTemplate(template05)  # 查找模板05
        r06 = FirstFindTemplate(template06)  # 查找模板06
        r07 = FirstFindTemplate(template07)  # 查找模板07
        r08 = FirstFindTemplate(template08)  # 查找模板08
        if r01:
            FirstFindedNum(r01, 'A')  # 如果找到模板01,处理结果
        elif r02:
            FirstFindedNum(r02, 'B')  # 如果找到模板02,处理结果
        elif r03:
            FirstFindedNum(r03, 'C')  # 如果找到模板03,处理结果
        elif r04:
            FirstFindedNum(r04, 'D')  # 如果找到模板04,处理结果
        elif r05:
            FirstFindedNum(r05, 'E')  # 如果找到模板05,处理结果
        elif r06:
            FirstFindedNum(r06, 'F')  # 如果找到模板06,处理结果
        elif r07:
            FirstFindedNum(r07, 'G')  # 如果找到模板07,处理结果
        elif r08:
            FirstFindedNum(r08, 'I')  # 如果找到模板08,处理结果
        xunji()  # 执行巡线功能
    elif Find_Task == 2:  # 如果任务模式为2
        if Target_Num == 3:  # 如果目标数字为3
            # 进行数字3的模板匹配
            r3L = FindTemplate(template3L)
            r3LL = FindTemplate(template3LL)
            r3R = FindTemplate(template3R)
            r3RR = FindTemplate(template3RR)
            if r3L:
                FindedNum(r3L, 3)  # 如果找到模板3L,处理结果
            elif r3LL:
                FindedNum(r3LL, 3)  # 如果找到模板3LL,处理结果
            elif r3R:
                FindedNum(r3R, 3)  # 如果找到模板3R,处理结果
            elif r3RR:
                FindedNum(r3RR, 3)  # 如果找到模板3RR,处理结果
            else:
                FH = bytearray([0x2C, 0x12, 0x00, 0x00, find_flag, Find_Task, 0x5B])  # 创建错误消息
                uart.write(FH)  # 通过串口发送错误消息

        elif Target_Num == 4:  # 如果目标数字为4
            # 进行数字4的模板匹配
            r4L = FindTemplate(template4L)
            r4LL = FindTemplate(template4LL)
            r4R = FindTemplate(template4R)
            r4RR = FindTemplate(template4RR)
            if r4L:
                FindedNum(r4L, 4)  # 如果找到模板4L,处理结果
            elif r4LL:
                FindedNum(r4LL, 4)  # 如果找到模板4LL,处理结果
            elif r4R:
                FindedNum(r4R, 4)  # 如果找到模板4R,处理结果
            elif r4RR:
                FindedNum(r4RR, 4)  # 如果找到模板4RR,处理结果
            else:
                FH = bytearray([0x2C, 0x12, 0x00, 0x00, find_flag, Find_Task, 0x5B])  # 创建错误消息
                uart.write(FH)  # 通过串口发送错误消息

        elif Target_Num == 5:  # 如果目标数字为5
            # 进行数字5的模板匹配
            r5L = FindTemplate(template5L)
            r5LL = FindTemplate(template5LL)
            r5R = FindTemplate(template5R)
            r5RR = FindTemplate(template5RR)
            if r5L:
                FindedNum(r5L, 5)  # 如果找到模板5L,处理结果
            elif r5LL:
                FindedNum(r5LL, 5)  # 如果找到模板5LL,处理结果
            elif r5R:
                FindedNum(r5R, 5)  # 如果找到模板5R,处理结果
            elif r5RR:
                FindedNum(r5RR, 5)  # 如果找到模板5RR,处理结果
            else:
                FH = bytearray([0x2C, 0x12, 0x00, 0x00, find_flag, Find_Task, 0x5B])  # 创建错误消息
                uart.write(FH)  # 通过串口发送错误消息
                xunji()  # 执行巡线功能

        elif Target_Num == 6:  # 如果目标数字为6
            # 进行数字6的模板匹配
            r6L = FindTemplate(template6L)
            r6LL = FindTemplate(template6LL)
            r6R = FindTemplate(template6R)
            r6RR = FindTemplate(template6RR)
            if r6L:
                FindedNum(r6L, 6)  # 如果找到模板6L,处理结果
            elif r6LL:
                FindedNum(r6LL, 6)  # 如果找到模板6LL,处理结果
            elif r6R:
                FindedNum(r6R, 6)  # 如果找到模板6R,处理结果
            elif r6RR:
                FindedNum(r6RR, 6)  # 如果找到模板6RR,处理结果
            else:
                FH = bytearray([0x2C, 0x12, 0x00, 0x00, find_flag, Find_Task, 0x5B])  # 创建错误消息
                uart.write(FH)  # 通过串口发送错误消息
                xunji()  # 执行巡线功能

        elif Target_Num == 7:  # 如果目标数字为7
            # 进行数字7的模板匹配
            r7L = FindTemplate(template7L)
            r7LL = FindTemplate(template7LL)
            r7R = FindTemplate(template7R)
            r7RR = FindTemplate(template7RR)
            if r7L:
                FindedNum(r7L, 7)  # 如果找到模板7L,处理结果
            elif r7LL:
                FindedNum(r7LL, 7)  # 如果找到模板7LL,处理结果
            elif r7R:
                FindedNum(r7R, 7)  # 如果找到模板7R,处理结果
            elif r7RR:
                FindedNum(r7RR, 7)  # 如果找到模板7RR,处理结果
            else:
                FH = bytearray([0x2C, 0x12, 0x00, 0x00, find_flag, Find_Task, 0x5B])  # 创建错误消息
                uart.write(FH)  # 通过串口发送错误消息
                xunji()  # 执行巡线功能

        elif Target_Num == 8:  # 如果目标数字为8
            # 进行数字8的模板匹配
            r8L = FindTemplate(template8L)
            r8LL = FindTemplate(template8LL)
            r8R = FindTemplate(template8R)
            r8RR = FindTemplate(template8RR)
            if r8L:
                FindedNum(r8L, 8)  # 如果找到模板8L,处理结果
            elif r8LL:
                FindedNum(r8LL, 8)  # 如果找到模板8LL,处理结果
            elif r8R:
                FindedNum(r8R, 8)  # 如果找到模板8R,处理结果
            elif r8RR:
                FindedNum(r8RR, 8)  # 如果找到模板8RR,处理结果
            else:
                FH = bytearray([0x2C, 0x12, 0x00, 0x00, find_flag, Find_Task, 0x5B])  # 创建错误消息
                uart.write(FH)  # 通过串口发送错误消息
                xunji()  # 执行巡线功能
    else:
        time.sleep_ms(100)  # 如果没有找到目标,延时100毫秒

总结

        核心功能是图像识别与处理,通过识别目标物体、计算运动方向、以及处理不同的任务模式来控制系统。结合串口通信,代码实现了图像处理与外部设备的交互,同时具备了处理错误情况的能力。这种系统通常用于机器人、自动驾驶等应用场景中,能够根据环境信息进行实时的决策和控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值