三子棋裝置(電賽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)