Загрузка данных


import cv2
import numpy as np
import os
import json

TEMPLATES_DIR = "templates"
COLORS_FILE = "templates_colors.json"

PIECES_CONFIG = {
    'r': ('black_r.png', (0, 0)),
    'n': ('black_n.png', (0, 1)),
    'b': ('black_b.png', (0, 2)),
    'q': ('black_q.png', (0, 3)),
    'k': ('black_k.png', (0, 4)),
    'p': ('black_p.png', (1, 0)),
    'R': ('white_R.png', (7, 0)),
    'N': ('white_N.png', (7, 1)),
    'B': ('white_B.png', (7, 2)),
    'Q': ('white_Q.png', (7, 3)),
    'K': ('white_K.png', (7, 4)),
    'P': ('white_P.png', (6, 0))
}

ALL_PIECES = list(PIECES_CONFIG.keys())


# =====================================================================
#  [НАЧАЛО БЛОКА: ВИЗУАЛЬНЫЙ ВЫВОД ДОСКИ 8x8]
# =====================================================================
def print_visual_board(board_matrix):
    print("\n" + "="*43)
    print("        ВИЗУАЛЬНАЯ ДОСКА (СЕТКА 8x8)")
    print("="*43)
    print("    a   b   c   d   e   f   g   h")
    print("  +---------------------------------+")
    for idx, row in enumerate(board_matrix):
        rank = 8 - idx
        row_cells = [f"[{p}]" if p != '.' else " . " for p in row]
        row_str = " ".join(row_cells)
        print(f"{rank} | {row_str} | {rank}")
    print("  +---------------------------------+")
    print("    a   b   c   d   e   f   g   h")
    print("="*43)
# =====================================================================
#  [КОНЕЦ БЛОКА: ВИЗУАЛЬНЫЙ ВЫВОД ДОСКИ 8x8]
# =====================================================================


def crop_board(image_path):
    img = cv2.imread(image_path)
    if img is None:
        raise FileNotFoundError(f"Не удалось открыть файл: {image_path}")

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blur, 50, 150)

    contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    board_cnt = None
    max_area = 0

    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area > 10000:
            peri = cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            if len(approx) == 4 and area > max_area:
                board_cnt = approx
                max_area = area

    if board_cnt is not None:
        pts = board_cnt.reshape(4, 2)
        rect = np.zeros((4, 2), dtype="float32")
        s = pts.sum(axis=1)
        rect[0] = pts[np.argmin(s)]
        rect[2] = pts[np.argmax(s)]
        diff = np.diff(pts, axis=1)
        rect[1] = pts[np.argmin(diff)]
        rect[3] = pts[np.argmax(diff)]
        dst = np.array([[0, 0], [399, 0], [399, 399], [0, 399]], dtype="float32")
        M = cv2.getPerspectiveTransform(rect, dst)
        return cv2.warpPerspective(img, M, (400, 400))
    else:
        return cv2.resize(img, (400, 400))


def extract_dominant_piece_color(cell_bgr):
    """Определяет преобладающий RGB-цвет самой фигуры, исключая фон клетки"""
    cell_rgb = cv2.cvtColor(cell_bgr, cv2.COLOR_BGR2RGB)
    h, w, _ = cell_rgb.shape

    # Берём фон из углов
    corners = np.concatenate([
        cell_rgb[0:5, 0:5], cell_rgb[0:5, w-5:w],
        cell_rgb[h-5:h, 0:5], cell_rgb[h-5:h, w-5:w]
    ])
    bg_color = np.median(corners.reshape(-1, 3), axis=0)

    # Пиксели внутри клетки
    inner_pixels = cell_rgb[int(h*0.15):int(h*0.85), int(w*0.15):int(w*0.85)].reshape(-1, 3)

    # Фильтруем пиксели, отличающиеся от цвета фона клетки
    distances = np.linalg.norm(inner_pixels - bg_color, axis=1)
    piece_pixels = inner_pixels[distances > 25]

    if len(piece_pixels) == 0:
        return [128, 128, 128]

    # Наиболее часто встречаемый цвет (медиана тела фигуры)
    dominant_color = np.median(piece_pixels, axis=0)
    return [int(c) for c in dominant_color]


def generate_templates_and_colors(start_image_path="start_board.png"):
    """Создает графические шаблоны и файл templates_colors.json"""
    os.makedirs(TEMPLATES_DIR, exist_ok=True)
    board = crop_board(start_image_path)
    cell_size = 50

    color_database = {}

    for piece_symbol, (filename, (row, col)) in PIECES_CONFIG.items():
        y1, y2 = row * cell_size, (row + 1) * cell_size
        x1, x2 = col * cell_size, (col + 1) * cell_size
        cell_img = board[y1:y2, x1:x2]
        
        # Сохранение картинки-шаблона
        save_path = os.path.join(TEMPLATES_DIR, filename)
        cv2.imwrite(save_path, cell_img)

        # Вычисление и сохранение преобладающего цвета
        dom_color = extract_dominant_piece_color(cell_img)
        color_database[piece_symbol] = dom_color

    # Сохраняем в JSON
    with open(COLORS_FILE, 'w', encoding='utf-8') as f:
        json.dump(color_database, f, indent=4)

    print("✓ Папка шаблонов и файл 'templates_colors.json' созданы!")


def scan_board_with_templates(target_image_path="board.png"):
    # Пересоздаем базы при отсутствии хотя бы одного файла
    if not os.path.exists(TEMPLATES_DIR) or not os.path.exists(COLORS_FILE):
        generate_templates_and_colors("start_board.png")

    # Загружаем базу цветов фигур из JSON
    with open(COLORS_FILE, 'r', encoding='utf-8') as f:
        color_database = json.load(f)

    # Подготавливаем контурные шаблоны
    templates_edges = {}
    for piece_symbol, (filename, _) in PIECES_CONFIG.items():
        t_path = os.path.join(TEMPLATES_DIR, filename)
        t_img = cv2.imread(t_path)
        gray_t = cv2.cvtColor(t_img, cv2.COLOR_BGR2GRAY)
        h, w = gray_t.shape
        inner_t = gray_t[int(h*0.15):int(h*0.85), int(w*0.15):int(w*0.85)]
        templates_edges[piece_symbol] = cv2.Canny(inner_t, 50, 150)

    board = crop_board(target_image_path)
    cell_size = 50
    board_matrix = []

    for row in range(8):
        row_pieces = []
        for col in range(8):
            y1, y2 = row * cell_size, (row + 1) * cell_size
            x1, x2 = col * cell_size, (col + 1) * cell_size
            cell = board[y1:y2, x1:x2]

            # 1. Проверка на пустую клетку по наличию контуров
            gray_cell = cv2.cvtColor(cell, cv2.COLOR_BGR2GRAY)
            h, w = gray_cell.shape
            inner_cell = gray_cell[int(h*0.15):int(h*0.85), int(w*0.15):int(w*0.85)]
            cell_edge = cv2.Canny(inner_cell, 50, 150)

            if np.count_nonzero(cell_edge) < 25:
                row_pieces.append('.')
                continue

            # 2. Извлекаем преобладающий цвет фигуры на текущей клетке
            cell_color = np.array(extract_dominant_piece_color(cell))

            best_match = '.'
            max_combined_score = -999.0

            # 3. Комплексная проверка: ФОРМА + ЦВЕТ
            for piece_symbol in ALL_PIECES:
                # Оценка формы (Canny match)
                t_edge = templates_edges[piece_symbol]
                res = cv2.matchTemplate(cell_edge, t_edge, cv2.TM_CCOEFF_NORMED)
                _, shape_score, _, _ = cv2.minMaxLoc(res)

                # Оценка цвета (дистанция до цвета из шаблона)
                ref_color = np.array(color_database[piece_symbol])
                color_dist = np.linalg.norm(cell_color - ref_color)

                # Итоговый балл: совпадение формы со штрафом за разницу в цвете
                combined_score = shape_score - (color_dist / 100.0)

                if combined_score > max_combined_score:
                    max_combined_score = combined_score
                    best_match = piece_symbol

            row_pieces.append(best_match)

        board_matrix.append(row_pieces)

    # 1. Вывод сетки 8x8
    print_visual_board(board_matrix)

    # 2. Генерация FEN
    fen_rows = []
    for r in board_matrix:
        empty_count = 0
        row_str = ""
        for p in r:
            if p == '.':
                empty_count += 1
            else:
                if empty_count > 0:
                    row_str += str(empty_count)
                    empty_count = 0
                row_str += p
        if empty_count > 0:
            row_str += str(empty_count)
        fen_rows.append(row_str)

    final_fen = "/".join(fen_rows) + " w - - 0 1"
    
    print("FEN-строка:")
    print(final_fen)


if __name__ == "__main__":
    try:
        scan_board_with_templates("board.png")
    except Exception as e:
        print("Ошибка:", e)
    
    input("\nНажми Enter, чтобы закрыть...")