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


import os
import cv2
import numpy as np

# --- 1. АВТОМАТИЧЕСКАЯ ВЫРЕЗКА ДОСКИ ---

def crop_chess_board(input_img):
    """
    Находит шахматную доску на кадре, обрезает всё лишнее 
    и приводит к ровному размеру 400x400.
    """
    img_h, img_w = input_img.shape[:2]
    total_area = img_h * img_w

    gray = cv2.cvtColor(input_img, cv2.COLOR_BGR2GRAY)

    thresh = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
        cv2.THRESH_BINARY_INV, 11, 2
    )

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)

    contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        print("[ПРЕДУПРЕЖДЕНИЕ] Контуры не найдены. Используем ресайз кадра целиком.")
        return cv2.resize(input_img, (400, 400))

    contours = sorted(contours, key=cv2.contourArea, reverse=True)
    best_box = None

    for c in contours:
        x, y, w, h = cv2.boundingRect(c)
        area = w * h

        if area < (total_area * 0.03) or area > (total_area * 0.95):
            continue

        aspect_ratio = float(w) / h
        if 0.85 <= aspect_ratio <= 1.15:
            best_box = (x, y, w, h)
            break

    # Fallback по Canny
    if best_box is None:
        edges = cv2.Canny(gray, 50, 150)
        contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
        contours = sorted(contours, key=cv2.contourArea, reverse=True)

        for c in contours:
            x, y, w, h = cv2.boundingRect(c)
            area = w * h
            if (total_area * 0.05) < area < (total_area * 0.90) and (0.85 <= float(w) / h <= 1.15):
                best_box = (x, y, w, h)
                break

    if best_box is None:
        print("[ПРЕДУПРЕЖДЕНИЕ] Доска не обнаружена. Используем стандартный ресайз.")
        return cv2.resize(input_img, (400, 400))

    x, y, w, h = best_box
    cropped_img = input_img[y:y+h, x:x+w]
    print(f"[ОК] Доска успешно найдена и вырезана (координаты: x={x}, y={y}, w={w}, h={h})!")
    
    return cv2.resize(cropped_img, (400, 400))


# --- 2. HOG-ДЕКРИПТОРЫ И ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---

hog = cv2.HOGDescriptor(
    _winSize=(36, 36),
    _blockSize=(18, 18),
    _blockStride=(9, 9),
    _cellSize=(9, 9),
    _nbins=9
)

def extract_dominant_piece_color(cell_bgr):
    h, w = cell_bgr.shape[:2]
    center = cell_bgr[int(h * 0.25):int(h * 0.75), int(w * 0.25):int(w * 0.75)]
    return np.mean(center, axis=(0, 1)).astype(float).tolist()


def get_hog_features(crop_bgr):
    gray = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2GRAY)
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4, 4))
    gray_norm = clahe.apply(gray)
    
    descriptor = hog.compute(gray_norm)
    if descriptor is None:
        return np.zeros((324,), dtype=np.float32)
    return descriptor.flatten()


def print_board(board_matrix, fen):
    divider = "  +" + "---+" * 8
    print("\n    a   b   c   d   e   f   g   h")
    print(divider)
    for idx, row in enumerate(board_matrix):
        rank = 8 - idx
        row_str = " | ".join([f"{p}" if p != '.' else " " for p in row])
        print(f"{rank} | {row_str} | {rank}")
        print(divider)
    print("    a   b   c   d   e   f   g   h")
    print(f"\nFEN: {fen}\n")


# --- 3. СКАНИРОВАНИЕ ДОСКИ ---

def scan_frame(cell_board, color_database, templates_hog, ALL_PIECES, variance_threshold=12.0):
    # 1. Автоматически обрезаем доску по вашему алгоритму
    board = crop_chess_board(cell_board)
    
    # Сохраняем вырезанную доску для проверки
    cv2.imwrite("debug_cropped_board.png", board)

    board_matrix = []
    
    for row in range(8):
        row_pieces = []
        for col in range(8):
            cell = board[row * 50:(row + 1) * 50, col * 50:(col + 1) * 50]
            gray_cell = cv2.cvtColor(cell, cv2.COLOR_BGR2GRAY)
            center_crop = cell[7:43, 7:43]
            center_gray = gray_cell[7:43, 7:43]

            if np.std(center_gray) < variance_threshold:
                row_pieces.append('.')
                continue

            cell_hog = get_hog_features(center_crop)

            cell_color_vec = np.array(extract_dominant_piece_color(cell))
            brightness = np.mean(cell_color_vec)
            
            is_white_piece = brightness > 110
            candidate_pieces = [p for p in ALL_PIECES if p.isupper() == is_white_piece]

            best_match, min_distance = '.', float('inf')

            for piece_symbol in candidate_pieces:
                if piece_symbol not in templates_hog or piece_symbol not in color_database:
                    continue
                
                tpl_hog = templates_hog[piece_symbol]
                hog_dist = np.linalg.norm(cell_hog - tpl_hog)

                tpl_color = np.array(color_database[piece_symbol])
                color_dist = np.linalg.norm(cell_color_vec - tpl_color)

                total_dist = hog_dist + (color_dist / 100.0)

                if total_dist < min_distance:
                    min_distance = total_dist
                    best_match = piece_symbol
                    
            row_pieces.append(best_match)
        board_matrix.append(row_pieces)

    fen_rows = []
    for r in board_matrix:
        empty, row_str = 0, ""
        for p in r:
            if p == '.': empty += 1
            else:
                if empty > 0: row_str += str(empty); empty = 0
                row_str += p
        if empty > 0: row_str += str(empty)
        fen_rows.append(row_str)
        
    return board_matrix, "/".join(fen_rows) + " w - - 0 1"


# --- 4. ТОЧКА ВХОДА ---

def main():
    TEMPLATES_DIR = "extracted_pieces"
    BOARD_IMAGE_PATH = "board.png"
    
    ALL_PIECES = ['K', 'Q', 'R', 'B', 'N', 'P', 'k', 'q', 'r', 'b', 'n', 'p']
    
    PIECES_CONFIG = {
        "K": "white_K.png", "Q": "white_Q.png", "R": "white_R.png",
        "B": "white_B.png", "N": "white_N.png", "P": "white_P.png",
        "k": "black_k.png", "q": "black_q.png", "r": "black_r.png",
        "b": "black_b.png", "n": "black_n.png", "p": "black_p.png"
    }

    if not os.path.exists(TEMPLATES_DIR):
        print(f"[ОШИБКА] Папка '{TEMPLATES_DIR}' не найдена!")
        return

    templates_hog = {}
    color_database = {}

    for sym, filename in PIECES_CONFIG.items():
        path = os.path.join(TEMPLATES_DIR, filename)
        if not os.path.exists(path):
            continue

        img = cv2.imread(path)
        if img is None:
            continue

        img_50 = cv2.resize(img, (50, 50))
        crop_center = img_50[7:43, 7:43]
        
        templates_hog[sym] = get_hog_features(crop_center)
        color_database[sym] = extract_dominant_piece_color(img_50)

    if not os.path.exists(BOARD_IMAGE_PATH):
        print(f"[ОШИБКА] Файл '{BOARD_IMAGE_PATH}' не найден!")
        return

    board_img = cv2.imread(BOARD_IMAGE_PATH)
    if board_img is None:
        print("[ОШИБКА] Не удалось прочитать картинку!")
        return

    matrix, fen = scan_frame(board_img, color_database, templates_hog, ALL_PIECES)
    print_board(matrix, fen)


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"\n[КРИТИЧЕСКАЯ ОШИБКА]: {e}")
        import traceback
        traceback.print_exc()
        
    input("\nНажмите Enter, чтобы закрыть окно...")