| import cv2
|
| import numpy as np
|
| import mss
|
|
|
| class VisionHandler:
|
| def __init__(self):
|
| self.templates = {}
|
| self.is_calibrated = False
|
|
|
| def capture_screen(self, region):
|
| """
|
| Capture a specific region of the screen.
|
| region: {'top': int, 'left': int, 'width': int, 'height': int}
|
| """
|
| with mss.mss() as sct:
|
|
|
| monitor = {
|
| "top": int(region["top"]),
|
| "left": int(region["left"]),
|
| "width": int(region["width"]),
|
| "height": int(region["height"])
|
| }
|
| screenshot = sct.grab(monitor)
|
| img = np.array(screenshot)
|
| return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
|
|
|
| def split_board(self, board_image):
|
| """
|
| Split board image into 64 squares.
|
| Assumption: board_image is cropped exactly to the board edges.
|
| """
|
| h, w, _ = board_image.shape
|
| sq_h, sq_w = h // 8, w // 8
|
| squares = []
|
|
|
|
|
| for r in range(8):
|
| row_squares = []
|
| for c in range(8):
|
|
|
| margin_h = int(sq_h * 0.1)
|
| margin_w = int(sq_w * 0.1)
|
|
|
| y1 = r * sq_h + margin_h
|
| y2 = (r + 1) * sq_h - margin_h
|
| x1 = c * sq_w + margin_w
|
| x2 = (c + 1) * sq_w - margin_w
|
|
|
| square = board_image[y1:y2, x1:x2]
|
| row_squares.append(square)
|
| squares.append(row_squares)
|
| return squares
|
|
|
| def calibrate(self, board_image):
|
| """
|
| Calibrate by learning piece appearance from a starting position board image.
|
| Standard Starting Position:
|
| r n b q k b n r
|
| p p p p p p p p
|
| . . . . . . . .
|
| . . . . . . . .
|
| . . . . . . . .
|
| . . . . . . . .
|
| P P P P P P P P
|
| R N B Q K B N R
|
| """
|
| squares = self.split_board(board_image)
|
| self.templates = {}
|
|
|
|
|
|
|
|
|
|
|
| piece_map = {
|
| 'r': [(0, 0), (0, 7)],
|
| 'n': [(0, 1), (0, 6)],
|
| 'b': [(0, 2), (0, 5)],
|
| 'q': [(0, 3)],
|
| 'k': [(0, 4)],
|
| 'p': [(1, i) for i in range(8)],
|
| 'R': [(7, 0), (7, 7)],
|
| 'N': [(7, 1), (7, 6)],
|
| 'B': [(7, 2), (7, 5)],
|
| 'Q': [(7, 3)],
|
| 'K': [(7, 4)],
|
| 'P': [(6, i) for i in range(8)],
|
| '.': []
|
| }
|
|
|
|
|
| for r in range(2, 6):
|
| for c in range(8):
|
| piece_map['.'].append((r, c))
|
|
|
|
|
|
|
|
|
| for p_char, coords in piece_map.items():
|
| if not coords:
|
| continue
|
|
|
|
|
| r, c = coords[0]
|
| template = squares[r][c]
|
|
|
|
|
| gray_template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
|
|
|
|
|
| self.templates[p_char] = gray_template
|
|
|
| self.is_calibrated = True
|
| print("Calibration complete.")
|
|
|
| def match_square(self, square_img):
|
| if not self.templates:
|
| return '.'
|
|
|
| gray_sq = cv2.cvtColor(square_img, cv2.COLOR_BGR2GRAY)
|
|
|
| best_score = float('inf')
|
| best_piece = '.'
|
|
|
|
|
|
|
|
|
| target_h, target_w = gray_sq.shape
|
|
|
| for p_char, template in self.templates.items():
|
|
|
| if template.shape != gray_sq.shape:
|
| template = cv2.resize(template, (target_w, target_h))
|
|
|
|
|
| err = np.sum((gray_sq.astype("float") - template.astype("float")) ** 2)
|
| err /= float(gray_sq.shape[0] * gray_sq.shape[1])
|
|
|
| if err < best_score:
|
| best_score = err
|
| best_piece = p_char
|
|
|
| return best_piece
|
|
|
| def get_fen_from_image(self, board_image):
|
| if not self.is_calibrated:
|
|
|
| return None
|
|
|
| squares = self.split_board(board_image)
|
| fen_rows = []
|
|
|
| for r in range(8):
|
| empty_count = 0
|
| row_str = ""
|
| for c in range(8):
|
| piece = self.match_square(squares[r][c])
|
|
|
| if piece == '.':
|
| empty_count += 1
|
| else:
|
| if empty_count > 0:
|
| row_str += str(empty_count)
|
| empty_count = 0
|
| row_str += piece
|
|
|
| if empty_count > 0:
|
| row_str += str(empty_count)
|
|
|
| fen_rows.append(row_str)
|
|
|
|
|
| fen_board = "/".join(fen_rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| return f"{fen_board} w KQkq - 0 1"
|
|
|