| | 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"
|
| |
|