2025, Dec 30 15:03

Практический OCR для игровых панелей: Tesseract и OpenCV

Надёжный OCR для шумных скриншотов интерфейса: маска по цветканалу, горизонтальная дилатация, выделение строк и psm 6. Практика Tesseract + OpenCV. Быстрее.

OCR на шумных скриншотах интерфейса может быть обманчиво сложным. Даже когда текст кажется читаемым невооружённым глазом, универсальная бинаризация оставляет Tesseract без нужной структуры. Ниже — компактный разбор, который превращает малоконтрастную цветную игровую панель в чистый построчный текст с помощью Tesseract и простого, легко повторяемого конвейера предобработки в OpenCV.

Что идёт не так при наивной пороговой обработке

Первая реакция — перевести изображение в градации серого, инвертировать для контраста и перед запуском Tesseract применить адаптивный порог. Ход разумный, но в данном случае он усложняет процесс, не усиливая полезный сигнал. Фоновая текстура и цветовые градиенты переживают пороговую обработку, а символы распадаются и теряют непрерывность — от этого OCR спотыкается.

Постановка задачи: исходный подход

Следующий минимальный фрагмент иллюстрирует общий шаблон, который здесь не даёт сильного результата. Программа переводит в оттенки серого, инвертирует и применяет адаптивный порог, после чего передаёт изображение в Tesseract.

import cv2
import pytesseract
# читаем кадр с диска или из захвата экрана
frame = cv2.imread('input.png')
# переводим в оттенки серого и инвертируем
img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
img_inv = cv2.bitwise_not(img_gray)
# необязательный фиксированный порог для экспериментов
# _, t_bin = cv2.threshold(img_inv, 95, 255, cv2.THRESH_BINARY)
# адаптивная пороговая обработка
bin_auto = cv2.adaptiveThreshold(
    img_inv,
    120,
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
    cv2.THRESH_BINARY,
    11,
    2
)
# OCR с переключением языка
text_fr = pytesseract.image_to_string(bin_auto, lang='fra')
text_en = pytesseract.image_to_string(bin_auto, lang='eng')
print(text_fr)
print(text_en)

Выход остаётся шумным и непоследовательным. Часть текста человеку различима, но бинаризованное изображение заполнено разрывами и артефактами, мешающими анализу макета в Tesseract.

Почему это здесь не работает

Этап пороговой обработки не выделяет устойчивые области переднего плана. Перевод в градации серого уничтожает цветовые подсказки, которые на самом деле полезны, а адаптивный порог подчёркивает локальный контраст, но одновременно усиливает фоновые текстуры и края. В итоге строки теряют целостность, контуры фрагментируются, и движок OCR получает картинку, слабо похожую на чистый тёмный текст на светлом фоне.

Прицельный конвейер, который работает

Исправление несложное: получить аккуратную маску из одного цветового канала, горизонтально «соединить» текст узкой морфологической дилатацией, обнаружить каждую строку как отдельную ROI и распознавать их по отдельности. Небольшая рамка и масштабирование в 2 раза стабилизируют формы букв, а режим сегментации страницы Tesseract для текстового блока завершает работу.

import cv2
import numpy as np
import matplotlib.pyplot as plt
import pytesseract as tess
# путь к изображению
img_path = 'input.png'
# читаем исходник
src = cv2.imread(img_path)
# выбираем стабильный канал (зелёный)
chan_g = src[:, :, 1]
# простая маска вместо адаптивного порога
mask = np.zeros_like(chan_g)
mask[chan_g > 80] = 255
# горизонтально соединяем символы
kernel = np.array([[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]], dtype=np.uint8)
mask_dilated = cv2.dilate(mask, kernel, iterations=10)
# находим внешние контуры — кандидаты на строки
contours, _ = cv2.findContours(mask_dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# сортируем по высоте ограничивающего прямоугольника и берём верхние строки
contours = sorted(
    contours,
    key=lambda c: cv2.boundingRect(c)[-1],
    reverse=True
)[:7]
# визуализируем и запускаем OCR построчно
fig = plt.figure()
fig.subplots_adjust(hspace=0.1)
rows_by_y = {}
for i, c in enumerate(contours, 1):
    x, y, w, h = cv2.boundingRect(c)
    # изолируем строку по зелёному каналу и инвертируем
    row = cv2.bitwise_not(chan_g[y:y+h, x:x+w])
    # добавляем отступы для стабильного OCR
    row_pad = cv2.copyMakeBorder(row, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=int(row[0, 0]))
    # увеличиваем масштаб для лучшего распознавания
    row_up = cv2.resize(row_pad, None, fx=2.0, fy=2.0)
    # распознаём строку как блок текста
    txt = tess.image_to_string(row_up, lang='fra', config='--psm 6 --oem 3').strip()
    rows_by_y[y] = txt
    # быстрый визуальный контроль
    ax = fig.add_subplot(1, len(contours), i)
    ax.imshow(row_up, cmap='gray')
    ax.axis('off')
# упорядочиваем строки по вертикали и печатаем
ordered_lines = dict(sorted(rows_by_y.items())).values()
print(*ordered_lines, sep='\n')

Этот конвейер выдаёт стабильные строки вроде:

345 Vitalité

75 Intelligence

35 Sagesse

1 Portée

15 Dommages Feu

13 Tacle

7 Retrait PM

Общий замысел прост. Маска по отдельному каналу в этой задаче надёжнее отделяет видимый текст от фонового мусора, чем адаптивный порог. Узкая горизонтальная дилатация закрывает разрывы между символами, превращая строку в единую область. Извлечение ROI по контурам превращает загруженную панель в несколько плотных кропов, что заметно упрощает OCR. Небольшая рамка и увеличение в 2 раза предотвращают срез по краям и дают больше пикселей на глиф. Наконец, режим сегментации страницы 6 говорит Tesseract ожидать один текстовый блок — это совпадает с обрезанной строкой.

Почему это важно

Качество OCR часто зависит не от более «тяжёлых» порогов или сильного размытия, а от продуманной структуры входных данных: чистого бинарного контраста, связного переднего плана и семантически осмысленных ROI. Именно это делает модель символов устойчивой. Тот же принцип масштабируется, когда позже вы добавляете языковые модели или постпроверку с NLP, чтобы убедиться, что извлечённые слова соответствуют допустимому словарю.

Практические заметки и итог

Конкретный канал, порог, форма ядра и число ожидаемых строк — параметры, которые, возможно, придётся подстроить под тему вашего экрана. Когда всё отлажено, подход получается быстрым и надёжным. Его также просто встроить в цикл для экранного агента, который читает панель, разбирает несколько известных токенов и чисел и действует. Если далее нужна проверка корректности, добавьте лёгкую текстовую проверку с NLP или словарь допустимых терминов. Так редкие ошибки OCR можно обработать без хрупких покомпонентных сравнений пикселей для каждого возможного числа.

Главный вывод — сначала позаботьтесь о структуре, затем о распознавании. Предпочитайте минимальную маску избыточным порогам, объединяйте текст в осмысленные области, работайте построчно и давайте Tesseract задачу, под которую он создан.