一、需要视觉部分的小题
前两问写死,第三位可以用角度传感器
二、解题思路
1.角度计算
这里使用了两个相邻角点(corner[0]
和 corner[1]
)来计算矩形的一条边的方向,并通过这条边的方向来推算整个矩形的旋转角度。
-
选取角点:
- 选取矩形的两个相邻角点,例如
corner[0]
和corner[1]
。
- 选取矩形的两个相邻角点,例如
-
计算向量:
- 使用这两个角点的坐标
(x1, y1)
和(x2, y2)
来计算从第一个角点指向第二个角点的向量。 - 向量的分量可以通过减法得到:
dx = x2 - x1
和dy = y2 - y1
。
- 使用这两个角点的坐标
-
计算角度:
- 使用
atan2(dy, dx)
函数来计算该向量与 x 轴正方向之间的角度(以弧度为单位)。atan2
函数可以处理所有四个象限的情况,并且返回的角度值范围是-π
到π
。 - 将得到的弧度值转换成度数,通常使用公式
angle_deg = radians * (180 / π)
。在 Python 中,可以直接使用math.degrees()
函数来完成这个转换。
- 使用
-
显示角度:
- 最后,在图像上显示计算得到的角度值。
这样的角度计算方法假设矩形的一个边是沿着 dx
和 dy
所定义的方向。如果矩形是完全水平或垂直的,那么该边应该与 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.将棋盘上的棋子作为输入内容
-
图像输入:
- 函数
img
是一个图像对象,这通常意味着它来自摄像头捕获的画面或者是文件加载的图像。 - 这个图像应该包含一个棋盘以及上面的棋子。图像可以是彩色的也可以是灰度的,但在这个例子中,我们将会创建灰度图像用于进一步处理。
- 函数
-
矩形区域列表 (ROIs):
rois
是一个列表,包含了多个矩形区域的坐标,这些矩形区域定义了棋盘上的每个格子。- 每个 ROI 的坐标以
(x, y, w, h)
的形式给出,其中(x, y)
是左上角的坐标,而(w, h)
是宽度和高度。
-
处理每个 ROI:
- 对于每个 ROI,代码会复制该区域的图像,并将其转换为灰度图像。
- 然后在灰度图像中寻找圆形物体(棋子),这通过
find_circles
方法完成。
-
检测圆并计算平均灰度值:
- 对于每个检测到的圆形物体,计算其在原图中的绝对坐标。
- 然后再次复制该圆形区域,并计算该区域内的平均灰度值。
-
更新棋盘状态:
- 如果 ROI 对应的棋盘位置尚未被占用(即
board[row][col] == 0
),则根据平均灰度值来决定放置哪种类型的棋子(黑色或白色)。 - 黑色棋子(暗色)对应较低的灰度值,白色棋子(亮色)对应较高的灰度值。
- 如果 ROI 对应的棋盘位置尚未被占用(即
-
记录最后的位置:
- 如果成功放置了一个棋子,则记录下最后一次放置的位置和对应的 ROI 编号。
-
返回值:
- 函数返回最后放置棋子的位置和对应的 ROI 编号。如果没有找到任何棋子,则返回
None。
- 函数返回最后放置棋子的位置和对应的 ROI 编号。如果没有找到任何棋子,则返回
# 更新棋盘状态
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.决策部分
决策逻辑概述
-
检查胜利条件:
- 首先,
best_move
函数检查当前玩家是否可以通过占据某个空位立即获胜。 - 如果可以获胜,函数就返回这个空位的位置。
- 首先,
-
阻止对手获胜:
- 如果当前玩家不能立即获胜,函数接下来会模拟对手的可能行动,检查对手是否可以通过占据某个空位立即获胜。
- 如果对手有这样的机会,函数就返回这个空位的位置,以阻止对手获胜。
-
占据中心位置:
- 如果上述两种情况都不成立,函数尝试占据棋盘的中心位置
(1, 1)
,如果它是空闲的。
- 如果上述两种情况都不成立,函数尝试占据棋盘的中心位置
-
占据角落位置:
- 如果中心位置已经被占据,函数会选择一个角落位置(
(0, 0)
,(0, 2)
,(2, 0)
,(2, 2)
)进行占据。
- 如果中心位置已经被占据,函数会选择一个角落位置(
-
占据边线位置:
- 最后,如果角落也被占据了,函数会选择一个边线位置(
(0, 1)
,(1, 0)
,(1, 2)
,(2, 1)
)进行占据。
- 最后,如果角落也被占据了,函数会选择一个边线位置(
-
没有可行的移动:
- 如果所有上述位置都被占用了,函数返回
None
表示没有可行的移动
- 如果所有上述位置都被占用了,函数返回
-
但是这个决策有一个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