2024电赛——OPENMV识别三子棋棋盘与黑白棋识别思路(包含获胜判断,AI下棋,串口通信)_电赛三子棋棋盘
OpenMV是国外一家公司生产的,目前国内的代理商是星瞳科技,在星瞳科技的OpenMV官网上提供了许多中文资料,包括一个帮助你快速上手的中文手册,一个可以查询函数库的中文网站,下载IDE的网站,论坛,官方录制的视频教程(根据上手手册讲的),还有售卖的MV的购买界面和详细参数。

1.识别与ai下棋思路
首先识别棋盘,然后进行识别黑白棋子。然后创建一个二维数组,通过二维数组进行记录棋盘当前状态。
board = [3][3]
# 初始化棋盘状态,0表示空格,1表示黑子,2表示白子
# 将棋盘位置编号转换为行列坐标def num_to_position(num):row = 2 - (num - 1) // 3 # 反转行顺序col = (num - 1) % 3return row, col# 将行列坐标转换为棋盘位置编号def position_to_num(row, col):return (2 - row) * 3 + (col + 1)
2.识别棋盘并且防止后期光线干扰思路
将棋盘第一次被openmv识别到就进行读取棋盘角点坐标,然后只读取这个棋盘坐标区域内的棋子颜色,不在识别棋盘是否为黑色边框矩形。
-
初始化棋盘坐标:在第一次识别到棋盘后,保存棋盘的角点坐标。
-
绘制棋盘区域:使用保存的角点坐标来绘制棋盘区域,并在该区域内读取棋子颜色。
避免重新识别棋盘:在后续帧中,只关注已经确定的棋盘区域,避免重新识别棋盘边框。
# 变量初始化corners = None # 用于存储棋盘角点坐标black_threshold = (6, 100, -39, 18, -31, 43) # 黑色棋子的阈值white_threshold = (30, 47, 76, 47, 23, 56) # 白色棋子的阈值 红色原点board = [[0 for _ in range(3)] for _ in range(3)] # 棋盘状态初始化为3x3的空格子# 插值函数,用于计算角点间的位置def interpolate(p1, p2, factor):return (p1[0] + (p2[0] - p1[0]) * factor, p1[1] + (p2[1] - p1[1]) * factor)寻找符合棋盘条件的矩形并且记录棋盘四个角点坐标
-
if first_detection:# 查找矩形rects = img.find_rects(threshold=10000)max_area = 0max_rect = Nonefor rect in rects:area = rect.w() * rect.h()if area > max_area:max_area = areamax_rect = rectif max_rect:# 确保识别到的矩形符合棋盘的预期尺寸if 50 < max_rect.w() < 100 and 50 < max_rect.h() < 100:corners = max_rect.corners() # 获取矩形的角点for corner in corners:img.draw_circle(corner[0], corner[1], 5, color=(255, 0, 0), thickness=2, fill=False) # 绘制角点for i in range(len(corners)):start_point = corners[i]end_point = corners[(i + 1) % 4]img.draw_line(start_point[0], start_point[1], end_point[0], end_point[1], color=(255, 0, 0)) # 绘制边线first_detection = False # 标记为已检测到棋盘
3.识别棋盘内的黑白棋并且判断棋盘内是否有获胜
黑白棋子的判断:通过阈值的检测与形状的检测;
1.黑白阈值
black_threshold = (6, 100, -39, 18, -31, 43) # 黑色棋子的阈值white_threshold = (30, 47, 76, 47, 23, 56) # 白色棋子的阈值
2.圆形棋子
black_blobs = img.find_blobs([(0, 10)], roi=(x_min, y_min, roi_width, roi_height), merge=True) # 查找黑色棋子white_blobs = img.find_blobs([white_threshold], roi=(x_min, y_min, roi_width, roi_height), merge=True) # 查找白色棋子valid_black_blobs = [blob for blob in black_blobs if blob.pixels() > ```python20 and blob.area() > 50 and blob.roundness() > 0.8] # 筛选有效的黑子valid_white_blobs = [blob for blob in white_blobs if blob.roundness() > 0.4] # 筛选有效的白子
检查是否有三个连续的棋子(在水平、垂直或对角线方向上),并在图像上绘制一条绿色的连线表示获胜
定义方向:directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
定义四个检查方向:
-
(0, 1):水平右 -
(1, 0):垂直下 -
(1, 1):对角线右下 -
(1, -1):对角线左下然后通过遍历方向和起始点对每个方向,检查每个位置是否可以作为起始点形成连续的三子连线。
从每个起始点出发,按照当前方向检查下一个位置是否有棋子。
如果有,增加
consecutive_count。如果consecutive_count达到 3,表示形成连续的三子连线。
# 检查是否有连续的三子连线def check_continuous_line(img, positions):directions = [(0, 1), (1, 0), (1, 1), (1, -1)] # 定义四个方向for direction in directions:for i in range(len(positions)):consecutive_count = 1for j in range(1, len(positions)):if (positions[i][0] + j * direction[0], positions[i][1] + j * direction[1]) in positions:consecutive_count += 1if consecutive_count == 3:p1 = (positions[i][0], positions[i][1])p2 = (positions[i][0] + 2 * direction[0], positions[i][1] + 2 * direction[1])img.draw_line(p1[0], p1[1], p2[0], p2[1], color=(0, 255, 0), thickness=2) # 绘制连线return Trueelse:breakreturn False

4.识别棋盘后对棋盘进行棋格划分与棋格编号标识
# 计算小格子的边界x_min = max(0, min(int(top_left[0]), int(top_right[0]), int(bottom_left[0]), int(bottom_right[0])))y_min = max(0, min(int(top_left[1]), int(top_right[1]), int(bottom_left[1]), int(bottom_right[1])))x_max = min(img.width(), max(int(top_left[0]), int(top_right[0]), int(bottom_left[0]), int(bottom_right[0])))y_max = min(img.height(), max(int(top_left[1]), int(top_right[1]), int(bottom_left[1]), int(bottom_right[1])))roi_width = x_max - x_minroi_height = y_max - y_min
if len(valid_black_blobs) == 0 and len(valid_white_blobs) == 0:ir_led.on() # 开启红外LEDcenter_x = (x_min + x_max) // 2center_y = (y_min + y_max) // 2img.draw_string(center_x - 10, center_y - 10, str(num), color=(255, 0, 0), scale=2) # 在格子中央绘制编号
5.下棋思路,我是通过openmv进行三维数组存储棋盘状态,并且通过算法进行输出ai所下棋格,然后通过串口回传到单片机控制机械臂进行夹取棋子(但该算法带有bug)
以上是一些编写代码的思路,下面附上整个opnmv代码:
import sensor, image, time, pybfrom pyb import UART, LED# 初始化UART通信,波特率9600uart = UART(3, 9600, timeout_char=3000)# 初始化LEDir_led = LED(1)win_led = LED(2)# 初始化相机sensor.reset()sensor.set_pixformat(sensor.GRAYSCALE) # 使用灰度图像格式sensor.set_framesize(sensor.QQVGA) # 设置图像大小为QQVGAsensor.skip_frames(time=4000) # 跳过几帧,等待相机稳定clock = time.clock() # 初始化时钟# 变量初始化corners = None # 用于存储棋盘角点坐标black_threshold = (6, 100, -39, 18, -31, 43) # 黑色棋子的阈值white_threshold = (30, 47, 76, 47, 23, 56) # 白色棋子的阈值 红色原点board = [[0 for _ in range(3)] for _ in range(3)] # 棋盘状态初始化为3x3的空格子# 插值函数,用于计算角点间的位置def interpolate(p1, p2, factor):return (p1[0] + (p2[0] - p1[0]) * factor, p1[1] + (p2[1] - p1[1]) * factor)# 检查是否有连续的三子连线def check_continuous_line(img, positions):directions = [(0, 1), (1, 0), (1, 1), (1, -1)] # 定义四个方向for direction in directions:for i in range(len(positions)):consecutive_count = 1for j in range(1, len(positions)):if (positions[i][0] + j * direction[0], positions[i][1] + j * direction[1]) in positions:consecutive_count += 1if consecutive_count == 3:p1 = (positions[i][0], positions[i][1])p2 = (positions[i][0] + 2 * direction[0], positions[i][1] + 2 * direction[1])img.draw_line(p1[0], p1[1], p2[0], p2[1], color=(0, 255, 0), thickness=2) # 绘制连线return Trueelse:breakreturn False# 将棋盘位置编号转换为行列坐标def num_to_position(num):row = 2 - (num - 1) // 3 # 反转行顺序col = (num - 1) % 3return row, col# 将行列坐标转换为棋盘位置编号def position_to_num(row, col):return (2 - row) * 3 + (col + 1)# 检查是否有玩家获胜def check_winner(player):for row in board:if all(s == player for s in row): # 检查行return Truefor col in range(3):if all(board[row][col] == player for row in range(3)): # 检查列return Trueif all(board[i][i] == player for i in range(3)) or all(board[i][2 - i] == player for i in range(3)): # 检查对角线return Truereturn False# 检查棋盘是否已满def is_full():return all(cell != 0 for row in board for cell in row)# AI的移动def ai_move():for row in range(3):for col in range(3):if board[row][col] == 0:board[row][col] = 2 # AI 放置白子# 在图像上绘制 AI 的移动(叉子)cell_size = img.width() // 3center_x = col * cell_size + cell_size // 2center_y = row * cell_size + cell_size // 2img.draw_cross(center_x, center_y, 10, color=(0, 255, 0)) # 绘制绿色叉子# 输出 AI 选择的棋格xianum = position_to_num(row, col)print(f\"AI 下棋到格子: {xianum}\")# 通过UART发送AI选择的棋格FH = bytearray([0x2C, 0x12, xianum, xianum, 0x5B])uart.write(FH)returnplayer_turn = True # 游戏开始时由玩家先手first_detection = True # 标志位,表示是否为第一次检测棋盘while True:clock.tick() # 记录每次循环的开始时间img = sensor.snapshot().lens_corr(strength=1.0, zoom=1.0) # 拍摄图像并进行镜头畸变校正ir_led.off() # 关闭红外LEDif first_detection:# 查找矩形rects = img.find_rects(threshold=10000)max_area = 0max_rect = Nonefor rect in rects:area = rect.w() * rect.h()if area > max_area:max_area = areamax_rect = rectif max_rect:# 确保识别到的矩形符合棋盘的预期尺寸if 50 < max_rect.w() < 100 and 50 < max_rect.h() < 100:corners = max_rect.corners() # 获取矩形的角点for corner in corners:img.draw_circle(corner[0], corner[1], 5, color=(255, 0, 0), thickness=2, fill=False) # 绘制角点for i in range(len(corners)):start_point = corners[i]end_point = corners[(i + 1) % 4]img.draw_line(start_point[0], start_point[1], end_point[0], end_point[1], color=(255, 0, 0)) # 绘制边线first_detection = False # 标记为已检测到棋盘if corners:num = 9black_positions = []white_positions = []for row in range(3):for col in range(3):# 计算每个小格子的角点top_left = interpolate(interpolate(corners[0], corners[3], row / 3.0), interpolate(corners[1], corners[2], row / 3.0), 1 - col / 3.0)top_right = interpolate(interpolate(corners[0], corners[3], row / 3.0), interpolate(corners[1], corners[2], row / 3.0), 1 - (col + 1) / 3.0)bottom_left = interpolate(interpolate(corners[0], corners[3], (row + 1) / 3.0), interpolate(corners[1], corners[2], (row + 1) / 3.0), 1 - col / 3.0)bottom_right = interpolate(interpolate(corners[0], corners[3], (row + 1) / 3.0), interpolate(corners[1], corners[2], (row + 1) / 3.0), 1 - (col + 1) / 3.0)# 计算小格子的边界x_min = max(0, min(int(top_left[0]), int(top_right[0]), int(bottom_left[0]), int(bottom_right[0])))y_min = max(0, min(int(top_left[1]), int(top_right[1]), int(bottom_left[1]), int(bottom_right[1])))x_max = min(img.width(), max(int(top_left[0]), int(top_right[0]), int(bottom_left[0]), int(bottom_right[0])))y_max = min(img.height(), max(int(top_left[1]), int(top_right[1]), int(bottom_left[1]), int(bottom_right[1])))roi_width = x_max - x_minroi_height = y_max - y_minif roi_width > 0 and roi_height > 0:ir_led.on() # 开启红外LEDcell_stats = img.get_statistics(roi=(x_min, y_min, roi_width, roi_height)) # 获取ROI区域的统计信息black_blobs = img.find_blobs([(0, 10)], roi=(x_min, y_min, roi_width, roi_height), merge=True) # 查找黑色棋子white_blobs = img.find_blobs([white_threshold], roi=(x_min, y_min, roi_width, roi_height), merge=True) # 查找白色棋子


