2024年电赛E题-Openmv视觉部分(决策+角度计算)

一、需要视觉部分的小题

  前两问写死,第三位可以用角度传感器

二、解题思路

1.角度计算

  这里使用了两个相邻角点(corner[0]corner[1])来计算矩形的一条边的方向,并通过这条边的方向来推算整个矩形的旋转角度。

  1. 选取角点
    • 选取矩形的两个相邻角点,例如 corner[0] 和 corner[1]
  2. 计算向量
    • 使用这两个角点的坐标 (x1, y1) 和 (x2, y2) 来计算从第一个角点指向第二个角点的向量。
    • 向量的分量可以通过减法得到:dx = x2 - x1 和 dy = y2 - y1
  3. 计算角度
    • 使用 atan2(dy, dx) 函数来计算该向量与 x 轴正方向之间的角度(以弧度为单位)。atan2 函数可以处理所有四个象限的情况,并且返回的角度值范围是  到 π
    • 将得到的弧度值转换成度数,通常使用公式 angle_deg = radians * (180 / π)。在 Python 中,可以直接使用 math.degrees() 函数来完成这个转换。
  4. 显示角度
    • 最后,在图像上显示计算得到的角度值。

  这样的角度计算方法假设矩形的一个边是沿着 dxdy 所定义的方向。如果矩形是完全水平或垂直的,那么该边应该与 x 轴或 y 轴平行,此时计算出的角度应该是 0 度、90 度、180 度或 270 度(或者等价的负角度)。

# 检测图像中的矩形
def process_image(img):
    for r in img.find_rects(threshold=10000):
        # 判断矩形边长是否符合要求
        if r.w() > 30 and r.h() > 30 and r.w() < 70 and r.h() < 70:
            # 在图像中框出矩形
            img.draw_rectangle(r.rect(), color=(255, 0, 0), scale=4)
            # 获取矩形角点位置
            corner = r.corners()
            # 在图像中圈出矩形角点
            for point in corner:
                img.draw_circle(point[0], point[1], 5, color=(0, 0, 255), thickness=2, fill=False)
            # 计算矩形中心点
            center_x = r.x() + r.w() // 2
            center_y = r.y() + r.h() // 2
            # 在图像中标记矩形中心点
            img.draw_circle(center_x, center_y, 5, color=(0, 255, 0), thickness=2, fill=True)
            print(f"center: {center_x} {center_y}")
            # 打印角点坐标
            corner1_str = f"corner1 = ({corner[0][0]},{corner[0][1]})"
            corner2_str = f"corner2 = ({corner[1][0]},{corner[1][1]})"
            corner3_str = f"corner3 = ({corner[2][0]},{corner[2][1]})"
            corner4_str = f"corner4 = ({corner[3][0]},{corner[3][1]})"
            print(corner1_str + "\n" + corner2_str + "\n" + corner3_str + "\n" + corner4_str)
            # 计算角度
            x1, y1 = corner[0]
            x2, y2 = corner[1]
            dx = x2 - x1
            dy = y2 - y1
            angle_rad = atan2(dy, dx)
            angle_deg = degrees(angle_rad)
            print(f"Rotation angle: {angle_deg:.2f} degrees")
            # 在图像中显示角度信息
            img.draw_string(10, 10, f"Angle: {angle_deg:.2f} degrees", color=(255, 255, 255))

2.将棋盘上的棋子作为输入内容

  1. 图像输入:
    • 函数 img 是一个图像对象,这通常意味着它来自摄像头捕获的画面或者是文件加载的图像。
    • 这个图像应该包含一个棋盘以及上面的棋子。图像可以是彩色的也可以是灰度的,但在这个例子中,我们将会创建灰度图像用于进一步处理。
  2. 矩形区域列表 (ROIs):
    • rois 是一个列表,包含了多个矩形区域的坐标,这些矩形区域定义了棋盘上的每个格子。
    • 每个 ROI 的坐标以 (x, y, w, h) 的形式给出,其中 (x, y) 是左上角的坐标,而 (w, h) 是宽度和高度。
  3. 处理每个 ROI:
    • 对于每个 ROI,代码会复制该区域的图像,并将其转换为灰度图像。
    • 然后在灰度图像中寻找圆形物体(棋子),这通过 find_circles 方法完成。
  4. 检测圆并计算平均灰度值:
    • 对于每个检测到的圆形物体,计算其在原图中的绝对坐标。
    • 然后再次复制该圆形区域,并计算该区域内的平均灰度值。
  5. 更新棋盘状态:
    • 如果 ROI 对应的棋盘位置尚未被占用(即 board[row][col] == 0),则根据平均灰度值来决定放置哪种类型的棋子(黑色或白色)。
    • 黑色棋子(暗色)对应较低的灰度值,白色棋子(亮色)对应较高的灰度值。
  6. 记录最后的位置:
    • 如果成功放置了一个棋子,则记录下最后一次放置的位置和对应的 ROI 编号。
  7. 返回值:
    • 函数返回最后放置棋子的位置和对应的 ROI 编号。如果没有找到任何棋子,则返回 None。
# 更新棋盘状态
def update_board_with_circles(rois, board, img):
    global last_position
    global last_roi_index
    last_position = None
    last_roi_index = None
    for i, roi in enumerate(rois):
        x, y, w, h = roi
        img_roi = img.copy(roi=roi)
        img_gray = img_roi.to_grayscale()
        circles = img_gray.find_circles(min_radius=10, max_radius=30)
        for circle in circles:
            cx = circle.x() + x
            cy = circle.y() + y
            r = circle.r()
            img.draw_circle(cx, cy, r, color=(255, 0, 0))
            x1 = max(cx - r, 0)
            y1 = max(cy - r, 0)
            x2 = min(cx + r, img.width())
            y2 = min(cy + r, img.height())
            circle_roi = img.copy(roi=(x1, y1, x2 - x1, y2 - y1))
            circle_gray = circle_roi.to_grayscale()
            total_gray = 0
            pixel_count = 0
            for y in range(circle_gray.height()):
                for x in range(circle_gray.width()):
                    total_gray += circle_gray.get_pixel(x, y)
                    pixel_count += 1
            avg_gray = total_gray // pixel_count
            value = 2 if avg_gray < 100 else 1
            row = i // 3
            col = i % 3
            if board[row][col] == 0:
                board[row][col] = value
                last_position = (row, col)
                last_roi_index = i
                print(f"Detected piece at position: {last_position}")
    if last_position is not None:
        print("最后一次放置棋子的格子是:", last_position)
        print("对应的 ROI 编号是:", last_roi_index)
        return last_position, last_roi_index
    else:
        return None, None

3.决策部分

决策逻辑概述

  1. 检查胜利条件:
    • 首先,best_move 函数检查当前玩家是否可以通过占据某个空位立即获胜。
    • 如果可以获胜,函数就返回这个空位的位置。
  2. 阻止对手获胜:
    • 如果当前玩家不能立即获胜,函数接下来会模拟对手的可能行动,检查对手是否可以通过占据某个空位立即获胜。
    • 如果对手有这样的机会,函数就返回这个空位的位置,以阻止对手获胜。
  3. 占据中心位置:
    • 如果上述两种情况都不成立,函数尝试占据棋盘的中心位置 (1, 1),如果它是空闲的。
  4. 占据角落位置:
    • 如果中心位置已经被占据,函数会选择一个角落位置((0, 0)(0, 2)(2, 0)(2, 2))进行占据。
  5. 占据边线位置:
    • 最后,如果角落也被占据了,函数会选择一个边线位置((0, 1)(1, 0)(1, 2)(2, 1))进行占据。
  6. 没有可行的移动:
    • 如果所有上述位置都被占用了,函数返回 None 表示没有可行的移动
  7. 但是这个决策有一个bug:决策的思路是按照每个点的优先级顺序来执行。如果电脑执黑先行的话,白棋(错误决定)先下在哪一个格子忘记了,就会导致不是必赢局面。
def print_board(board):
    for row in board:
        print(row)
    print("//////////////")

# 检查是否有赢家
def is_winner(board, letter):
    return (any(board[r][0] == letter and board[r][1] == letter and board[r][2] == letter for r in range(3)) or
            any(board[0][c] == letter and board[1][c] == letter and board[2][c] == letter for c in range(3)) or
            (board[0][0] == letter and board[1][1] == letter and board[2][2] == letter) or
            (board[0][2] == letter and board[1][1] == letter and board[2][0] == letter))

# 获取空位
def get_empty_positions(board):
    return [(r, c) for r in range(3) for c in range(3) if board[r][c] == 0]

# 查找最佳移动位置
def best_move(board, player):
    for r, c in get_empty_positions(board):
        board[r][c] = player
        if is_winner(board, player):
            board[r][c] = 0
            return r, c
        board[r][c] = 0
    opponent = 2 if player == 1 else 1
    for r, c in get_empty_positions(board):
        board[r][c] = opponent
        if is_winner(board, opponent):
            board[r][c] = 0
            return r, c
        board[r][c] = 0
    empty_positions = get_empty_positions(board)
    if (1, 1) in empty_positions:
        return 1, 1
    corners = [(0, 0), (0, 2), (2, 0), (2, 2)]
    for corner in corners:
        if corner in empty_positions:
            return corner
    edges = [(0, 1), (1, 0), (1, 2), (2, 1)]
    for edge in edges:
        if edge in empty_positions:
            return edge
    return None

# 执行移动
def make_move(board, position, value):
    r, c = position
    if board[r][c] == 0:
        board[r][c] = value

# 检查是否和棋
def is_draw(board):
    return all(cell != 0 for row in board for cell in row) and not (is_winner(board, 1) or is_winner(board, 2))
4.完整的源代码

需要串口通信向openmv发送PLAYER_FIRST或者是COMPUTER_FIRST决定先后手,先手必执黑,制作棋盘的时候需要刚刚好贴合roi的9个边框效果更好,其中还采用了平均灰度值来判别黑色和白色的棋子,减少了光线对颜色阈值的影响

import sensor, image, time
from math import atan2, degrees
import pyb
from pyb import UART

# 初始化传感器
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time=2000)

# 初始化 UART 通信
uart = UART(3, 115200)
uart.init(115200, bits=8, parity=None, stop=1)
clock = time.clock()

# 检测图像中的矩形
def process_image(img):
    for r in img.find_rects(threshold=10000):
        # 判断矩形边长是否符合要求
        if r.w() > 30 and r.h() > 30 and r.w() < 70 and r.h() < 70:
            # 在图像中框出矩形
            img.draw_rectangle(r.rect(), color=(255, 0, 0), scale=4)
            # 获取矩形角点位置
            corner = r.corners()
            # 在图像中圈出矩形角点
            for point in corner:
                img.draw_circle(point[0], point[1], 5, color=(0, 0, 255), thickness=2, fill=False)
            # 计算矩形中心点
            center_x = r.x() + r.w() // 2
            center_y = r.y() + r.h() // 2
            # 在图像中标记矩形中心点
            img.draw_circle(center_x, center_y, 5, color=(0, 255, 0), thickness=2, fill=True)
            print(f"center: {center_x} {center_y}")
            # 打印角点坐标
            corner1_str = f"corner1 = ({corner[0][0]},{corner[0][1]})"
            corner2_str = f"corner2 = ({corner[1][0]},{corner[1][1]})"
            corner3_str = f"corner3 = ({corner[2][0]},{corner[2][1]})"
            corner4_str = f"corner4 = ({corner[3][0]},{corner[3][1]})"
            print(corner1_str + "\n" + corner2_str + "\n" + corner3_str + "\n" + corner4_str)
            # 计算角度
            x1, y1 = corner[0]
            x2, y2 = corner[1]
            dx = x2 - x1
            dy = y2 - y1
            angle_rad = atan2(dy, dx)
            angle_deg = degrees(angle_rad)
            print(f"Rotation angle: {angle_deg:.2f} degrees")
            # 在图像中显示角度信息
            img.draw_string(10, 10, f"Angle: {angle_deg:.2f} degrees", color=(255, 255, 255))

# 更新棋盘状态
def update_board_with_circles(rois, board, img):
    global last_position
    global last_roi_index
    last_position = None
    last_roi_index = None
    for i, roi in enumerate(rois):
        x, y, w, h = roi
        img_roi = img.copy(roi=roi)
        img_gray = img_roi.to_grayscale()
        circles = img_gray.find_circles(min_radius=10, max_radius=30)
        for circle in circles:
            cx = circle.x() + x
            cy = circle.y() + y
            r = circle.r()
            img.draw_circle(cx, cy, r, color=(255, 0, 0))
            x1 = max(cx - r, 0)
            y1 = max(cy - r, 0)
            x2 = min(cx + r, img.width())
            y2 = min(cy + r, img.height())
            circle_roi = img.copy(roi=(x1, y1, x2 - x1, y2 - y1))
            circle_gray = circle_roi.to_grayscale()
            total_gray = 0
            pixel_count = 0
            for y in range(circle_gray.height()):
                for x in range(circle_gray.width()):
                    total_gray += circle_gray.get_pixel(x, y)
                    pixel_count += 1
            avg_gray = total_gray // pixel_count
            value = 2 if avg_gray < 100 else 1
            row = i // 3
            col = i % 3
            if board[row][col] == 0:
                board[row][col] = value
                last_position = (row, col)
                last_roi_index = i
                print(f"Detected piece at position: {last_position}")
    if last_position is not None:
        print("最后一次放置棋子的格子是:", last_position)
        print("对应的 ROI 编号是:", last_roi_index)
        return last_position, last_roi_index
    else:
        return None, None

# 打印棋盘状态
def print_board(board):
    for row in board:
        print(row)
    print("//////////////")

# 检查是否有赢家
def is_winner(board, letter):
    return (any(board[r][0] == letter and board[r][1] == letter and board[r][2] == letter for r in range(3)) or
            any(board[0][c] == letter and board[1][c] == letter and board[2][c] == letter for c in range(3)) or
            (board[0][0] == letter and board[1][1] == letter and board[2][2] == letter) or
            (board[0][2] == letter and board[1][1] == letter and board[2][0] == letter))

# 获取空位
def get_empty_positions(board):
    return [(r, c) for r in range(3) for c in range(3) if board[r][c] == 0]

# 查找最佳移动位置
def best_move(board, player):
    for r, c in get_empty_positions(board):
        board[r][c] = player
        if is_winner(board, player):
            board[r][c] = 0
            return r, c
        board[r][c] = 0
    opponent = 2 if player == 1 else 1
    for r, c in get_empty_positions(board):
        board[r][c] = opponent
        if is_winner(board, opponent):
            board[r][c] = 0
            return r, c
        board[r][c] = 0
    empty_positions = get_empty_positions(board)
    if (1, 1) in empty_positions:
        return 1, 1
    corners = [(0, 0), (0, 2), (2, 0), (2, 2)]
    for corner in corners:
        if corner in empty_positions:
            return corner
    edges = [(0, 1), (1, 0), (1, 2), (2, 1)]
    for edge in edges:
        if edge in empty_positions:
            return edge
    return None

# 执行移动
def make_move(board, position, value):
    r, c = position
    if board[r][c] == 0:
        board[r][c] = value

# 检查是否和棋
def is_draw(board):
    return all(cell != 0 for row in board for cell in row) and not (is_winner(board, 1) or is_winner(board, 2))


# 从串口读取数据并确定先手方
def determine_first_player_from_uart():
    '''
    - player_mark: 玩家标记
    - computer_mark: 电脑标记
    - player_turn: 如果玩家先手,则为 True;否则为 False
    '''
    while True:
        if uart.any():
            data = uart.read().decode('utf-8').strip()
            if data == "PLAYER_FIRST":
                player_mark = 2
                computer_mark = 1
                player_turn = True
                break
            elif data == "COMPUTER_FIRST":
                player_mark = 1
                computer_mark = 2
                player_turn = False
                break
            else:
                print("Invalid data received from UART. Waiting for valid input...")
    return player_mark, computer_mark, player_turn

# 设置棋盘的感兴趣区域 (ROIs)
rois = [
    (130, 65, 35, 35), (165, 65, 35, 35), (200, 65, 35, 35),
    (130, 100, 35, 35), (165, 100, 35, 35), (200, 100, 35, 35),
    (130, 135, 35, 35), (165, 135, 35, 35), (200, 135, 35, 35)
]
rows = 3
cols = 3
board = [[0 for _ in range(cols)] for _ in range(rows)]  # 初始化棋盘为空
last_position = None
last_roi_index = None

# 决定谁是先手
player_mark, computer_mark, player_turn = determine_first_player_from_uart()

# 玩家回合时间窗口
player_move_time = 5000  # 玩家下棋后的时间窗口(5秒)
player_move_start_time = time.ticks_ms()  # 记录玩家开始下棋的时间

while True:
    clock.tick()
    img = sensor.snapshot()  # 获取图像

    # 在感兴趣区域 (ROI) 绘制矩形
    for roi in rois:
        img.draw_rectangle(roi, color=(0, 255, 0), thickness=2)

    #process_image(img)计算角度
    if player_turn:
        # 玩家回合:检测玩家放置的棋子
        if time.ticks_diff(time.ticks_ms(), player_move_start_time) > player_move_time:
            # 超过时间窗口后更新棋盘状态
            update_board_with_circles(rois, board, img)
            if last_position is not None:
                # 检查是否玩家获胜
                if is_winner(board, player_mark):
                    print("玩家获胜!")
                    break
                # 切换到电脑回合
                player_turn = False
                print("玩家的回合已结束,电脑准备下棋...")
                time.sleep(2)  # 玩家放置棋子后等待一秒
                player_move_start_time = time.ticks_ms()  # 记录电脑回合开始时间
        else:
            print("玩家下棋时间窗口未结束")

    else:
        # 电脑回合
        move = best_move(board, computer_mark)
        if move:
            make_move(board, move, computer_mark)
            print(f"电脑选择的位置: {move}")
            print_board(board)
            if is_winner(board, computer_mark):
                print("电脑获胜!")
                break
        else:
            print("没有可用的移动位置。")
        # 切换到玩家回合
        player_turn = True
        print(clock.fps())
        print("电脑的回合已结束,玩家准备下棋...")
        time.sleep(2)  # 电脑放置棋子后等待一秒
        player_move_start_time = time.ticks_ms()  # 记录玩家回合开始时间

    # 检查是否和棋
    if is_draw(board):
        print("和棋!")
        break

2024子设计竞E中,OpenMV视觉模块的应用主要涉及图像采集、颜色识别、目标追踪等任务。该模块以其轻量级和高效的图像处理能力,成为嵌入式视觉应用的理想选择。 ### OpenMV基础设置 OpenMV摄像头可以通过MicroPython进行编程,支持多种图像处理功能。初次使用时,需要连接到脑并安装相应的固件。通过串口调试工具(如Tera Term或Arduino IDE的串口监视器),可以发送指令与OpenMV交互。 初始化摄像头的基本代码如下: ```python import sensor, image, time # 初始化摄像头传感器 sensor.reset() sensor.set_pixformat(sensor.RGB565) # 设置像素格式为RGB565 sensor.set_framesize(sensor.QVGA) # 设置分辨率 sensor.skip_frames(time = 2000) # 等待摄像头稳定 clock = time.clock() # 创建时钟对象以跟踪帧率 ``` ### 颜色识别 颜色识别是中常见的需求之一,例如检测特定颜色的物体位置。以下是一个基于阈值的颜色识别示例: ```python # 定义颜色阈值(L A B) red_threshold = (30, 100, 15, 127, 15, 127) while(True): clock.tick() # 开始计时 img = sensor.snapshot() # 拍摄一张照片 blobs = img.find_blobs([red_threshold]) if blobs: for b in blobs: img.draw_rectangle(b.rect()) # 绘制矩形框选目标 print("找到红色物体") ``` 此代码段展示了如何检测红色物体,并在图像中绘制矩形框标记其位置[^1]。 ### 目标追踪 OpenMV还支持简单的运动目标追踪功能,常用于机器人跟随或导航场景。结合PID控制算法,可实现更精确的跟踪效果。 ```python # 假设已经定义了find_target函数来获取目标的位置信息 def track_target(): while True: target_info = find_target() # 获取目标位置 if target_info: x, y, w, h = target_info # 计算偏差 error = x + w//2 - img.width()//2 # 使用PID控制器调整机速度 adjust_motors(error) ``` ### 图像传输 为了将OpenMV捕捉到的数据传输出去,通常采用UART或Wi-Fi模块。例如,使用UART协议向主控板发送坐标数据: ```python import pyb uart = pyb.UART(3, 9600) # 初始化UART端口 while True: data = "X:%d,Y:%d\n" % (x_center, y_center) uart.write(data) # 发送坐标信息 ``` ### 注意事项 - **源管理**:确保供稳定,避免因压波动导致图像质量下降。 - **镜头校准**:根据实际应用场景调整焦距和曝光参数。 - **环境光影响**:室内与室外光照条件差异较大,建议动态调整增益设置。 以上内容提供了关于OpenMV视觉模块的基础知识及其在2024E中的具体应用方法。希望这些资料能够帮助参者快速上手并优化他们的项目实现方案。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值