OCR

OCR находит текст на изображении и читает его. В LibreYOLO это задача ocr: она возвращает по одному четырёхточечному полигону и одной распознанной строке на каждую текстовую область, в порядке чтения.

Определение

Задача ocr делает за один вызов две вещи: находит на изображении все текстовые области и распознаёт их текст. Области возвращаются четырёхточечными полигонами, а не рамками по осям, потому что текст в сцене часто повёрнут, — и в порядке чтения: сверху вниз, затем слева направо.

Предсказание заполняет result.ocr — структуру OCRRegions. В .data лежит float-массив (N, 4, 2) с полигонами в пикселях исходного изображения, углы идут слева сверху, справа сверху, справа снизу, слева снизу; .texts — список из N распознанных строк; .conf — оценка уверенности распознавания по каждой области, а .det_conf — оценка детекции; .xyxy даёт прямоугольник по осям, описанный вокруг каждого полигона. Четырёхугольники — настоящие полигоны, поэтому result.boxes они не заполняют. Срез OCRRegions переносит распознанный текст и оба массива оценок вместе с геометрией.

Модели

За ocr отвечают два семейства.

PP-OCRv5 — специализированный пайплайн: детектор с дифференцируемой бинаризацией находит четырёхугольники с текстом, а распознаватель SVTR/CTC их читает, причём обе стадии упакованы в один файл .pt вместе с набором символов распознавателя. Он поставляется в двух уровнях — более лёгком для CPU и серверном для более высокой точности, — а один словарь покрывает упрощённый и традиционный китайский, английский, японский и пиньинь.

SenseNova-Vision решает задачу OCR иначе: слова генерируются как размеченный тегами текст тем же чекпойнтом на 7B, который обслуживает остальные шесть его задач; загружается он вызовом LibreVLM("sensenova-vision", task="ocr"). Ему нужен extra sensenova, а его веса разрешены только для некоммерческого использования; лицензия указана на странице модели.

Предсказание

Веса скачиваются с Hugging Face при первом запуске и кэшируются локально.

Чтение текста на изображении
from libreyolo import LibreYOLO, SAMPLE_IMAGE # Уровень t — более лёгкий из двух, рассчитан на CPU. SAMPLE_IMAGE# делает пример запускаемым; подставьте своё изображение с текстом.model = LibreYOLO("LibrePPOCRt-ocr.pt")result = model(SAMPLE_IMAGE) regions = result.ocrprint(len(regions), "regions")for text, score in zip(regions.texts, regions.conf):    print(repr(text), float(score))
Чтение четырёхугольников
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibrePPOCRt-ocr.pt")result = model(SAMPLE_IMAGE) regions = result.ocrprint(regions.data.shape)   # полигоны (N, 4, 2), TL TR BR BLprint(regions.xyxy)         # прямоугольники по осям вокруг этих полигоновprint(regions.det_conf)     # оценка детекции, отдельно от .conf
Фильтрация по уверенности распознавания
import numpy as npfrom libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibrePPOCRt-ocr.pt")result = model(SAMPLE_IMAGE) # Индексируйте позициями, а не булевой маской: срез переносит# распознанный текст и оба массива оценок вместе с геометрией.regions = result.ocr.numpy()keep = regions[np.flatnonzero(regions.conf >= 0.9)]print(keep.texts)

PP-OCRv5 запускает детекцию с фиксированным ограничением по длинной стороне, а затем распознаёт вырезанные области батчами; rec_batch задаёт, сколько фрагментов проходит через распознаватель за один прямой проход. Источники из нескольких изображений обрабатываются последовательно: двухстадийный пайплайн не собирает батчи из разных изображений. Про источники, стриминг и обработку результатов — в разделе предсказание.

Формат датасета

Разметка OCR — это один файл JSONL на сплит, по одному JSON-объекту на изображение, рядом с самими изображениями.

my-ocr-dataset/
  images/
    val/receipt.jpg
  labels/
    val.jsonl

Каждая строка называет изображение и перечисляет его области:

json
{"image": "receipt.jpg", "regions": [{"polygon": [[10, 12], [118, 14], [117, 40], [9, 38]], "text": "TOTAL 12.50"}]}

polygon — четырёхугольник, заданный четырьмя точками в абсолютных пиксельных координатах: слева сверху, справа сверху, справа снизу, слева снизу. Область, текст которой прочитать нельзя, помечается как "text": "###" — это соглашение don't-care из ICDAR: она исключается из оценки распознавания, а предсказание, которое с ней пересекается, игнорируется, а не засчитывается как ложноположительное.

Достаточно передать корневой каталог как data=. Альтернатива — YAML датасета: path плюс необязательные имена каталогов images и labels, а nc: 1 и names: {0: text} служат заглушками схемы, поскольку OCR-модель возвращает Results.ocr, а не детекции. Полный контракт — в разделе форматы датасетов.

Обучение

Ни у одного из OCR-семейств нет реализации обучения: train() в обоих случаях выбрасывает NotImplementedError, а поддержка OCR покрывает только предсказание и валидацию. На странице PP-OCRv5 указаны оригинальный код обучения под Apache-2.0 и скрипт конвертации, который возвращает дообученный чекпойнт обратно в LibreYOLO.

Валидация

val() оценивает весь пайплайн целиком, детекцию и распознавание вместе, сопоставляя предсказанные полигоны с эталонными (ground truth) один к одному при IoU выше 0.5.

Валидация и чтение ключей метрик
from libreyolo import LibreYOLO model = LibreYOLO("LibrePPOCRt-ocr.pt")metrics = model.val(data="my-ocr-dataset") print(metrics["metrics/det_precision"], metrics["metrics/det_recall"])print(metrics["metrics/det_hmean"])print(metrics["metrics/e2e_f1"])       # fitnessprint(metrics["metrics/rec_1-NED"])

metrics/det_precision, metrics/det_recall и metrics/det_hmean оценивают только локализацию: для совпадения достаточно пересечения полигонов, что бы ни было в распознанном тексте. metrics/e2e_precision, metrics/e2e_recall и metrics/e2e_f1 добавляют чтение: для совпадения нужно то же пересечение полигонов и в точности тот же текст после нормализации NFKC и удаления пробелов, причём сравнение учитывает регистр. metrics/e2e_f1 — это ещё и fitness, число, по которому выбирается лучший чекпойнт.

metrics/rec_1-NED оценивает распознаватель отдельно, по тем парам, которые уже сопоставила детекция: единица минус нормализованное расстояние редактирования, поэтому строка с ошибкой в один символ получает почти 1 там, где сквозная F1 даёт ей 0.

Экспорт

Для этой задачи нет ни одного доступного формата экспорта. PP-OCRv5 — две сети, работающие вместе, а не один трассируемый граф, и export() выбрасывает исключение для любого формата в обоих семействах. Чтобы развернуть модель вне LibreYOLO, дообучите её в оригинальном проекте и используйте его же путь развёртывания.

Проверено с LibreYOLO v1.5.0.