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控制类目要求解析 #### 控制类目的背景与重要性 全国大学生子设计竞中的控制类目旨在考察参者对于控制系统的设计能力以及实际应用技能。这类目通常涉及复杂的硬件路搭建、软件编程实现,尤其是嵌入式系统的开发调试。 #### H:自动行驶小车的具体要求 针对2024的具体比情况,在H中提出了基于STM32微控制器平台构建自动驾驶车辆的任务[^2]。此项目不仅考验选手们对传感器数据处理的理解程度,还检验其能否有效利用视觉识别技术完成路径规划等功能模块的集成工作。 - **目标检测**:能够准确识别人行横道线并作出相应反应;同时具备障碍物感知功能以保障行车安全。 - **轨迹跟踪算法**:采用PID或其他先进调节机制来保持稳定直线行驶状态,并能灵活调整方向角偏差量至最小化水平。 - **无线通信接口**:支持远程监控命令下发操作,允许裁判人员实时查看当前运行状况或发送特定指令改变行为模式。 ```c++ // PID Control Example Code Snippet float Kp = 1.0, Ki = 0.5, Kd = 0.1; void pidControl(float error){ static float preError = 0; static float integral = 0; integral += error * dt; // 积分项累加误差值乘时间间隔dt float derivative = (error - preError)/dt; // 微分计算相邻两次采样间变化率 float output = Kp*error + Ki*integral + Kd*derivative; preError = error; } ``` #### 技术难点分析 为了成功解决上述挑战,团队成员需掌握丰富的专业知识技术手段: - 对于图像处理部分而言,OpenMV相机提供了强大的机器学习框架用于特征提取与分类判断任务; - 在运动学建模方面,则需要深入理解机器人动力学原理以便更好地预测未来位置趋势从而优化决策过程; - 此外还需考虑如何提高整个系统的鲁棒性适应不同环境条件下的性能表现等问
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值