Загрузка данных
import os
import cv2
import numpy as np
import json
import time
import keyboard
from mss import mss
# --- НАСТРОЙКИ И ПУТИ ---
TEMPLATES_DIR = "extracted_pieces"
COLORS_FILE = "colors.json"
CONFIG_FILE = "config.json"
ALL_PIECES = ['K', 'Q', 'R', 'B', 'N', 'P', 'k', 'q', 'r', 'b', 'n', 'p']
PIECES_CONFIG = {
"K": ("white_K.png", "white"), "Q": ("white_Q.png", "white"),
"R": ("white_R.png", "white"), "B": ("white_B.png", "white"),
"N": ("white_N.png", "white"), "P": ("white_P.png", "white"),
"k": ("black_k.png", "black"), "q": ("black_q.png", "black"),
"r": ("black_r.png", "black"), "b": ("black_b.png", "black"),
"n": ("black_n.png", "black"), "p": ("black_p.png", "black")
}
is_active = False
board_roi = None
need_selection = False
def clear_console():
os.system('cls' if os.name == 'nt' else 'clear')
def toggle_scanner():
global is_active
is_active = not is_active
print(f"\n[СТАТУС] Сканирование: {'ВКЛ' if is_active else 'ПАУЗА'}")
def request_selection():
global need_selection
need_selection = True
def extract_dominant_piece_color(cell_bgr):
"""Извлекает медианный цвет центральной части фигуры."""
h, w, _ = cell_bgr.shape
center = cell_bgr[int(h*0.25):int(h*0.75), int(w*0.25):int(w*0.75)]
median_color = np.median(center, axis=(0, 1))
return median_color.tolist()
def generate_colors_database():
"""Автоматически генерирует файл colors.json на основе шаблонов."""
print("[ИНФО] Файл colors.json не найден. Создаём базу цветов по шаблонам...")
colors_db = {}
for sym, (filename, _) in PIECES_CONFIG.items():
img_path = os.path.join(TEMPLATES_DIR, filename)
if os.path.exists(img_path):
img = cv2.imread(img_path)
colors_db[sym] = extract_dominant_piece_color(img)
else:
# Дефолтные значения, если файла шаблона не оказалось
colors_db[sym] = [240, 240, 240] if sym.isupper() else [30, 30, 30]
with open(COLORS_FILE, 'w', encoding='utf-8') as f:
json.dump(colors_db, f, indent=4)
print("[УСПЕХ] Файл colors.json успешно создан!\n")
def select_board_region(sct_instance):
"""Выбор области доски мышкой."""
global board_roi, is_active
was_active = is_active
is_active = False
print("\n[ВЫБОР] Выделите доску мышкой и нажмите ENTER (ESC — отмена)")
monitor = sct_instance.monitors[1]
frame = np.array(sct_instance.grab(monitor))[:, :, :3]
win_name = "Выделите доску (ENTER - ок, ESC - отмена)"
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
roi = cv2.selectROI(win_name, frame, fromCenter=False, showCrosshair=True)
cv2.waitKey(1)
cv2.destroyWindow(win_name)
cv2.waitKey(1)
x, y, w, h = roi
if w > 50 and h > 50:
board_roi = {"top": int(y), "left": int(x), "width": int(w), "height": int(h)}
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(board_roi, f, indent=4)
print(f"[УСПЕХ] Область доски успешно сохранена в config.json!")
else:
print("[ОТМЕНА] Выделение отменено.")
time.sleep(0.5)
is_active = was_active
def print_visual_board(board_matrix, fen):
clear_console()
status = "[ВКЛ]" if is_active else "[ПАУЗА]"
print("=" * 60)
print(f" {status} | F7: Старт/Пауза | F8: Выделить доску мышкой")
print("=" * 60)
print(" a b c d e f g h")
print(" +---------------------------------+")
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(" +---------------------------------+")
print(" a b c d e f g h")
print("=" * 60)
print("FEN:", fen)
def scan_frame(cell_board, color_database, templates_edges):
"""Главная функция сканирования доски (Canny + Color Penalty)."""
board = cv2.resize(cell_board, (400, 400))
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)
# Контуры Canny только по центру (7:43)
cell_edge = cv2.Canny(gray_cell[7:43, 7:43], 50, 150)
# 1. Проверка на пустую клетку
if np.count_nonzero(cell_edge) < 25:
row_pieces.append('.')
continue
# 2. Вычисление цвета и поиск максимального совпадения
cell_color = np.array(extract_dominant_piece_color(cell))
best_match, max_score = '.', -999.0
for piece_symbol in ALL_PIECES:
if piece_symbol not in templates_edges:
continue
# Сравнение контуров
res = cv2.matchTemplate(cell_edge, templates_edges[piece_symbol], cv2.TM_CCOEFF_NORMED)
_, shape_score, _, _ = cv2.minMaxLoc(res)
# Штраф за цвет
color_dist = np.linalg.norm(cell_color - np.array(color_database[piece_symbol]))
score = shape_score - (color_dist / 100.0)
if score > max_score:
max_score, best_match = score, piece_symbol
row_pieces.append(best_match)
board_matrix.append(row_pieces)
# Генерация FEN
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"
def main():
global is_active, board_roi, need_selection
# Проверка наличия папки с шаблонами
if not os.path.exists(TEMPLATES_DIR):
print(f"[!] ОШИБКА: Папка '{TEMPLATES_DIR}' с изображениями фигур не найдена!")
print("Создайте папку 'extracted_pieces' и положите туда шаблоны фигур.")
return
# Авто-генерация базы цветов
if not os.path.exists(COLORS_FILE):
generate_colors_database()
# Загрузка ROI если есть
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
board_roi = json.load(f)
except:
board_roi = None
# Загружаем созданный/существующий colors.json
with open(COLORS_FILE, 'r', encoding='utf-8') as f:
color_database = json.load(f)
# Подготовка Canny шаблонов
templates_edges = {}
for sym, (filename, _) in PIECES_CONFIG.items():
t_path = os.path.join(TEMPLATES_DIR, filename)
if os.path.exists(t_path):
t_img = cv2.imread(t_path)
t_img = cv2.resize(t_img, (50, 50))
gray_t = cv2.cvtColor(t_img, cv2.COLOR_BGR2GRAY)
templates_edges[sym] = cv2.Canny(gray_t[7:43, 7:43], 50, 150)
sct = mss()
keyboard.add_hotkey('f7', toggle_scanner)
keyboard.add_hotkey('f8', request_selection)
last_matrix, last_fen = [['.']*8 for _ in range(8)], "---"
print("Программа успешно запущена!")
print("Нажмите [F8] для выбора области доски мышкой.")
while True:
start_time = time.time()
if need_selection:
need_selection = False
select_board_region(sct)
if is_active:
if board_roi is None:
clear_console()
print("⚠️ Область доски не задана! Нажмите [F8] для выбора мышкой.")
time.sleep(1)
continue
try:
frame = np.array(sct.grab(board_roi))[:, :, :3]
last_matrix, last_fen = scan_frame(frame, color_database, templates_edges)
except Exception as e:
print(f"Ошибка захвата экрана: {e}")
board_roi = None
print_visual_board(last_matrix, last_fen)
else:
print_visual_board(last_matrix, last_fen)
time.sleep(max(0.01, 0.4 - (time.time() - start_time)))
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"\n[КРИТИЧЕСКАЯ ОШИБКА]: {e}")
input("\nНажмите Enter, чтобы закрыть программу...")