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


def scan_frame(cell_board, color_database, templates_gray, templates_masks, ALL_PIECES, variance_threshold=14.0):
    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)
            center_gray = gray_cell[7:43, 7:43]

            # 1. Проверка на пустую клетку
            if np.std(center_gray) < variance_threshold:
                row_pieces.append('.')
                continue

            # 2. Определение цвета фигуры (белая / черная)
            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, max_score = '.', -999.0

            # Для чёрных фигур готовим маску
            cell_mask = get_piece_mask(center_gray) if not is_white_piece else None

            for piece_symbol in candidate_pieces:
                if piece_symbol not in templates_gray or piece_symbol not in color_database:
                    continue
                
                tpl_gray = templates_gray[piece_symbol]
                
                if is_white_piece:
                    # Для белых фигур — чистый Grayscale
                    res = cv2.matchTemplate(center_gray, tpl_gray, cv2.TM_CCOEFF_NORMED)
                    _, shape_score, _, _ = cv2.minMaxLoc(res)
                else:
                    # Для чёрных фигур — комбинируем Grayscale и Маску 50/50
                    tpl_mask = templates_masks[piece_symbol]
                    
                    res_gray = cv2.matchTemplate(center_gray, tpl_gray, cv2.TM_CCOEFF_NORMED)
                    _, gray_score, _, _ = cv2.minMaxLoc(res_gray)
                    
                    res_mask = cv2.matchTemplate(cell_mask, tpl_mask, cv2.TM_CCOEFF_NORMED)
                    _, mask_score, _, _ = cv2.minMaxLoc(res_mask)
                    
                    # Итоговый балл формы объединяет силуэт и внутренние детали (крест/линии)
                    shape_score = (gray_score * 0.5) + (mask_score * 0.5)

                # Штраф за цвет
                tpl_color = np.array(color_database[piece_symbol])
                color_dist = np.linalg.norm(cell_color_vec - tpl_color)
                
                score = shape_score - (color_dist / 200.0)
                
                if score > max_score:
                    max_score = score
                    best_match = 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"