三子棋装置(电赛24E题)K230/STM32全开源,后续有具体代码参数讲解,帮助大家移植
k230代码
import time, os, sysfrom media.sensor import * from media.display import * from media.media import *from machine import UART from machine import FPIOA from machine import TimerUartFlag=False#串口数据接收标志# 配置引脚 fpioa = FPIOA() fpioa.set_function(11, FPIOA.UART2_TXD) fpioa.set_function(12, FPIOA.UART2_RXD)# 初始化UART2,波特率115200,8位数据位,无校验,1位停止位 uart = UART(UART.UART2, baudrate=115200, bits=UART.EIGHTBITS, parity=UART.PARITY_NONE, stop=UART.STOPBITS_ONE)sensor_id = 2 sensor = None# 构造一个具有默认配置的摄像头对象 sensor = Sensor(id=sensor_id,width=1920, height=1080) # 重置摄像头sensor sensor.reset() # 无需进行镜像和翻转 # 设置不要水平镜像 sensor.set_hmirror(False) # 设置不要垂直翻转 sensor.set_vflip(False) sensor.set_framesize(width=800, height=480, chn=CAM_CHN_ID_0) # 设置通道0的输出像素格式为RGB565,要注意有些案例只支持GRAYSCALE格式 sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0)Display.init(Display.ST7701, width=800, height=480, to_ide=True)# 初始化媒体管理器 MediaManager.init() # 启动传感器 sensor.run() # 丢掉前面50帧数据,防止摄像头还不稳定 for i in range(50):sensor.snapshot()fps = time.clock()correction_points = [[268, 72], [635, 64],[708, 469], [226, 476]] taruge_rect= [[57, 412],[411, 411],[411, 29],[55,29]] # 棋盘数组 # 黑子:X # 白子:O # 没有棋子:空字符串 board = [[" "," "," "],[" "," "," "],[" "," "," "], ]#标准灰度值 std_grayscale_values = [[0,0,0],[0,0,0],[0,0,0], ]''' #实际坐标系九宫格中心位置 中心点顺序为从左到右、从上到下: 第一行:中心点1(左)、中心点2(中)、中心点3(右) 第二行:中心点4(左)、中心点5(中)、中心点6(右) 第三行:中心点7(左)、中心点8(中)、中心点9(右) ''' centers_real=[] #白色棋子位置 white_positions=[[25,0],[50,0],[75,0],[100,0],[125,0]] white_cnt=0 #黑色棋子位置 black_positions=[[25,155],[50,155],[75,155],[100,155],[125,155]] black_cnt=0 ''' 获得九宫格灰度 ''' def get_grid_region_grayscale():# 1. 对四个顶点排序(tl, tr, br, bl) = [[0, 0],[480, 0],[480, 480],[0,480]]# 2. 计算大正方形的宽度和高度width = max(abs(tr[0]-tl[0]), abs(br[0]-bl[0]))height = max(abs(bl[1]-tl[1]), abs(br[1]-tr[1]))# 3. 计算每个小格子的宽度和高度cell_width = width / 3cell_height = height / 3# 4. 计算中心区域的大小(按比例)region_w = int(cell_width * 0.3)region_h = int(cell_height * 0.3)# 5. 存储9个格子的灰度值grayscale_values = [[0,0,0],[0,0,0],[0,0,0]]for i in range(3): # 行 (y方向)for j in range(3): # 列 (x方向)# 计算当前格子的左上角坐标cell_x = tl[0] + j * cell_widthcell_y = tl[1] + i * cell_height# 计算中心区域的坐标center_x = cell_x + cell_width / 2center_y = cell_y + cell_height / 2# 提取中心区域 (region_w × region_h)x1 = int(center_x - region_w / 2)y1 = int(center_y - region_h / 2)x2 = int(center_x + region_w / 2)y2 = int(center_y + region_h / 2)# 防止越界x1 = max(0, x1)y1 = max(0, y1)x2 = min(img.width()-1, x2)y2 = min(img.height()-1, y2)# 计算该区域的平均灰度img.draw_rectangle(x1, y1, x2-x1, y2-y1,color=(255,255,255))region = img.get_statistics(roi=(x1, y1, x2-x1, y2-y1))grayscale_values[i][j]=region.l_mean()return grayscale_values''' 更新棋盘信息 ''' def update_board():global boardimg = sensor.snapshot() # Take a picture and return the image.img.gamma_corr(0.8)img.rotation_corr(corners = (correction_points[:4])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑#for n in range(4):#img.draw_cross(int(taruge_rect[n][0]),int(taruge_rect[n][1]))img.rotation_corr(corners = (taruge_rect[3],taruge_rect[2],taruge_rect[1],taruge_rect[0])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑grayscale_values=get_grid_region_grayscale()for i in range(3): # 行 (y方向)for j in range(3): # 列 (x方向)if grayscale_values[i][j]-std_grayscale_values[i][j]>10:board[i][j]='O'elif grayscale_values[i][j]-std_grayscale_values[i][j]<-10:board[i][j]='X'else:board[i][j]=" "print(board[i])#print(grayscale_values[i])print('......')''' 初始化标准灰度 ''' def init_std_grayscale_values():global std_grayscale_valuesimg = sensor.snapshot() # Take a picture and return the image.img.gamma_corr(0.8)img.rotation_corr(corners = (correction_points[:4])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑#for n in range(4):#img.draw_cross(int(taruge_rect[n][0]),int(taruge_rect[n][1]))img.rotation_corr(corners = (taruge_rect[3],taruge_rect[2],taruge_rect[1],taruge_rect[0])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑std_grayscale_values=get_grid_region_grayscale()""" 计算四边形内 9 宫格的 9 个中心点坐标,同步更新真实坐标系下的坐标 参数:corners: 四边形的 4 个角点,顺序为 [左下, 右下,右上 , 左上] 返回: 9 个中心点的列表,顺序为从左到右、从上到下中心点顺序为从左到右、从上到下:第一行:中心点1(左)、中心点2(中)、中心点3(右)第二行:中心点4(左)、中心点5(中)、中心点6(右)第三行:中心点7(左)、中心点8(中)、中心点9(右) """ def get_nine_grid_centers(corners):global centers_real# 提取四个角点lb = corners[0] # 左下 (u=0, v=0)rb = corners[1] # 右下 (u=1, v=0)rt = corners[2] # 右上 (u=1, v=1)lt = corners[3] # 左上 (u=0, v=1)# 双线性插值函数def bilinear_interp(u, v):x = (1-u)*(1-v)*lb[0] + u*(1-v)*rb[0] + u*v*rt[0] + (1-u)*v*lt[0]y = (1-u)*(1-v)*lb[1] + u*(1-v)*rb[1] + u*v*rt[1] + (1-u)*v*lt[1]return (x, y)# 计算9个中心点(从左到右,从上到下)tmp=[]centers = []for v in [5/6, 3/6, 1/6]: # 从上到下(v=1是顶部,v=0是底部)for u in [1/6, 3/6, 5/6]: # 从左到右(u=0是左侧,u=1是右侧)center = bilinear_interp(u, v)centers.append(center)center_real =(center[1]*13/48+11, center[0]*13/48+11)#x,y反转tmp.append(center_real)centers_real=tmpreturn centers""" 输入当前棋盘和执棋颜色,返回最佳下棋位置 (row, col)参数:board: 3x3 的二维列表,表示当前棋盘,例如:[["X", " ", "O"],[" ", "X", " "],["O", " ", " "]]player_color: 0 表示白棋 (O),1 表示黑棋 (X)返回:(row, col): 最佳下棋位置,行列范围 0-2 """ def get_best_move(board, player_color):# 预计算所有可能的赢法(8 种:3行 + 3列 + 2对角线)win_patterns = [[(0, 0), (0, 1), (0, 2)], # 第一行[(1, 0), (1, 1), (1, 2)], # 第二行[(2, 0), (2, 1), (2, 2)], # 第三行[(0, 0), (1, 0), (2, 0)], # 第一列[(0, 1), (1, 1), (2, 1)], # 第二列[(0, 2), (1, 2), (2, 2)], # 第三列[(0, 0), (1, 1), (2, 2)], # 主对角线[(0, 2), (1, 1), (2, 0)] # 副对角线]def evaluate(b):"""快速评估当前棋盘是否有玩家获胜"""for pattern in win_patterns:cells = [b[i][j] for (i, j) in pattern]if cells[0] == cells[1] == cells[2] != " ":return 1 if cells[0] == "X" else -1return 0 # 无胜负或平局def minimax(b, depth, alpha, beta, is_maximizing):"""带 Alpha-Beta 剪枝的极小化极大算法"""score = evaluate(b)if score != 0: # 有玩家获胜return scoreif all(cell != " " for row in b for cell in row): # 平局return 0if is_maximizing:max_score = -float("inf")for i in range(3):for j in range(3):if b[i][j] == " ":b[i][j] = "X"current_score = minimax(b, depth + 1, alpha, beta, False)b[i][j] = " "max_score = max(max_score, current_score)alpha = max(alpha, current_score)if beta <= alpha: # Alpha-Beta 剪枝breakreturn max_scoreelse:min_score = float("inf")for i in range(3):for j in range(3):if b[i][j] == " ":b[i][j] = "O"current_score = minimax(b, depth + 1, alpha, beta, True)b[i][j] = " "min_score = min(min_score, current_score)beta = min(beta, current_score)if beta <= alpha: # Alpha-Beta 剪枝breakreturn min_scoreplayer = "X" if player_color == 1 else "O"best_score = -float("inf") if player == "X" else float("inf")best_move = (-1, -1)alpha = -float("inf")beta = float("inf")for i in range(3):for j in range(3):if board[i][j] == " ":board[i][j] = playerscore = minimax(board, 0, alpha, beta, player == "O")board[i][j] = " "if (player == "X" and score > best_score) or (player == "O" and score < best_score):best_score = scorebest_move = (i, j)# 更新 Alpha/Betaif player == "X":alpha = max(alpha, best_score)else:beta = min(beta, best_score)return best_move''' 找矩形外框 ''' loop=True#False# last_taruge_rect = [[0,0],[0,0],[0,0],[0,0]] #记录上一次识别到的定点数据,用来判断数据是否稳定 matching_counts = 0#记录识别稳定不变的次数,达到一定数量则判断识别成功 while(loop):img = sensor.snapshot() # Take a picture and return the image.#img.gamma_corr(0.8)img.rotation_corr(corners = (correction_points[:4])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(0,0,0),fill=True) #右侧空白处涂黑img.midpoint(2, bias=0.9, threshold=True, offset=10, invert=True) #凸显黑线rr = img.find_rects(threshold=500000) #找矩形if rr: #如果有目标for r in rr:img.draw_rectangle(r.rect(), color = (255, 0, 0)) #在屏幕绘制标识框taruge_rect = r.corners() #存储方框顶点坐标for n in range(4):#对比方框定点坐标数据的变动情况,判断是否获取成功for n2 in range(2):#注意,此处判断阈值为3,如果画面不稳定,可能程序无法向后进行。可以修改阈值。另外还可以增加低通滤波。if abs(taruge_rect[n][n2] - last_taruge_rect[n][n2]) < 30:matching_counts += 1print(matching_counts)else:matching_counts = 0print('识别失败')last_taruge_rect = taruge_rectif matching_counts > 10:loop = Falseprint('识别成功')#print(taruge_rect)#img.draw_string_advanced(50,50,80,"fps:{}".format(fps.fps()*10000),color=(255,0,0))Display.show_image(img) init_std_grayscale_values() centers=get_nine_grid_centers(taruge_rect) UartFlag=True#开始接收串口数据 #print(centers) #print(centers_real) ''' while(True):img = sensor.snapshot() # Take a picture and return the image.img.rotation_corr(corners = (correction_points[:4])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑for i in range(9):img.draw_cross(int(centers[i][0]),int(centers[i][1]))Display.show_image(img) ''' def TimCallBack(timer):global white_positions,black_positions,white_cnt,black_cnt,UartFlag,boardif UartFlag:data = uart.read()if data:#通过CanMV IDE K230中的串行终端控制台打印出来hex_str = data.hex() # 转为16进制字符串print('收到数字:',hex_str)# 提取第一个字节的高低4位high_nibble = int(hex_str[0], 16)low_nibble = int(hex_str[1], 16)print(high_nibble,low_nibble)if high_nibble==0:uart.write(f"X{white_positions[white_cnt][0]:.2f}Y{white_positions[white_cnt][1]:.2f}P")white_cnt+=1time.sleep_ms(4000)uart.write(f"X{centers_real[low_nibble-1][0]:.2f}Y{centers_real[low_nibble-1][1]:.2f}L")#print('.....')#print(f"X{white_positions[white_cnt][0]:.2f}Y{white_positions[white_cnt][1]:.2f}P")#print(f"X{centers_real[low_nibble][0]:.2f}Y{centers_real[low_nibble][1]:.2f}L")elif high_nibble==1:uart.write(f"X{black_positions[black_cnt][0]:.2f}Y{black_positions[black_cnt][1]:.2f}P")black_cnt+=1time.sleep_ms(4000)uart.write(f"X{centers_real[low_nibble-1][0]:.2f}Y{centers_real[low_nibble-1][1]:.2f}L")elif high_nibble==5:#执白棋update_board()x,y=get_best_move(board,0)uart.write(f"X{white_positions[white_cnt][0]:.2f}Y{white_positions[white_cnt][1]:.2f}P")white_cnt+=1time.sleep_ms(4000)uart.write(f"X{centers_real[3*x+y][0]:.2f}Y{centers_real[3*x+y][1]:.2f}L")print('.....')print(x,y)#print(f"X{centers_real[3*x+y][0]:.2f}Y{centers_real[3*x+y][1]:.2f}L")elif high_nibble==6:#执黑棋update_board()x,y=get_best_move(board,1)uart.write(f"X{black_positions[black_cnt][0]:.2f}Y{black_positions[black_cnt][1]:.2f}P")black_cnt+=1time.sleep_ms(4000)uart.write(f"X{centers_real[3*x+y][0]:.2f}Y{centers_real[3*x+y][1]:.2f}L")print('.....')print(x,y)#print(f"X{centers_real[3*x+y][0]:.2f}Y{centers_real[3*x+y][1]:.2f}L") # 创建一个软件定时器实例,-1 表示使用软件定时器 tim = Timer(-1)# 配置定时器,定时2000毫秒 tim.init(period=2000, mode=Timer.PERIODIC, callback=TimCallBack)while(True):img = sensor.snapshot() # Take a picture and return the image.img.gamma_corr(0.8)img.rotation_corr(corners = (correction_points[:4])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑img.rotation_corr(corners = (taruge_rect[3],taruge_rect[2],taruge_rect[1],taruge_rect[0])) #画面梯形校正img.draw_image(img,0,0,x_size=480,y_size=480) #缩放画面至正常比例img.draw_rectangle(480,0,320,480,color=(255,255,255),fill=True) #右侧空白处涂黑'''update_board()(y,x)=get_best_move(board,1)img.draw_cross(80+160*x,80+160*y)'''Display.show_image(img)time.sleep_ms(1000)