Мы делаем сервис разбора счетов. Начинали, как все: берём PDF, гоним через распознавание, получаем поля. Точность была около 90%, и мы долго искали, где потерять оставшиеся 10%.
Ответ оказался обидным: эти 10% вообще не нужно было распознавать. В большинстве счетов данные уже лежат в машиночитаемом виде, их надо не «узнавать», а прочитать. Разница в подходе даёт разницу в результате: 100% вместо 90% и ноль затрат на модель.
PDF, не картинка, а контейнер объектов. Текст в нём, это последовательность инструкций виртуальной машине: «выбери шрифт F1, поставь курсор в точку (x, y), покажи строку». Сами строки могут храниться двумя способами:
Способ 1, обычная кодировка. Внутри лежит текст вида Rechnungsnummer: R0005532486,
и его можно просто извлечь. Так делают старые генераторы и LaTeX.
Способ 2,, Identity-H (CID). Здесь это не текст, а индексы глифов. В файле стоит нечто
вроде <00370038001F0021>, и чтобы понять, что это «R0005532486», нужно найти таблицу ToUnicode
и сопоставить индексы с символами. Именно так печатает большинство современных программ —
и именно на этом спотыкается наивное извлечение текста.
Вот реальный пример из счёта, который мы разбирали: текст в файле есть, но любой стандартный экстрактор выдаёт пустую строку или мусор. Не потому что данных нет, а потому что нужна таблица соответствия, которую никто не потрудился прочитать.
Логика нашего разбора в таких случаях такая: находим объекты шрифтов, вытаскиваем их ToUnicode,
строим карту «индекс глифа → символ», а если ToUnicode нет, читаем /Encoding и /Differences,
то есть стандартную кодировку. Первая версия нашего разбора подняла покрытие с 70% до 97% именно
на этом: мы перестали спотыкаться на CID-шрифтах.
Прежде чем запускать распознавание картинки, стоит проверить три вещи:
Вшитый XML. С 2025 года в Германии, а скоро и по всей Европе, счета приходят в форматах
ZUGFeRD, Factur-X, XRechnung, UBL/Peppol. Технически это PDF, внутри которого лежит XML,
или просто XML. Все значения там есть в явном виде: InvoiceNumber, DueDate, TaxTotal,
PayableAmount. Это не «распознавание с точностью 99%», это чтение поля. Ошибки возможны только
если поле пустое в исходнике.
QR-код. Швейцарские счета (QR-Rechnung) и европейские SEPA (GiroCode) несут в QR-коде IBAN, сумму, валюту, получателя и назначение платежа. Читается декодером за миллисекунды, точность 100%.
Текстовый слой PDF. Даже без XML в PDF обычно есть извлекаемый текст. Мы стараемся сохранить структуру,, строки, таблицы, потому что «слепить» из потока инструкций плоский текст, а потом искать в нём поля регулярками это разные задачи по качеству.
Только если всё это не дало результата, включается распознавание изображения. У нас это происходит примерно в трети случаев, и именно поэтому средняя стоимость разбора у нас около 14 рублей за 1000 страниц: треть тратит деньги, две трети, нет.
Мы измеряли, поэтому говорим не «примерно так», а конкретно. Когда модель смотрит на картинку счёта, ошибки концентрируются в трёх местах:
985-73-8194 и честно возвращает значение, а
валидатор, заточенный под немецкий DE123456789, отбрасывает его как «не похоже». Зрение тут
ни при чём, это ошибка проверки, а не распознавания.Последний пункт важный. Мы сознательно не «достраиваем» данные: если нетто плюс налог не дают итог, поле помечается, а не исправляется моделью. Человек должен видеть неуверенность системы, а не получать красивый результат с подставленной цифрой.
Мы делаем упор на воспроизводимость: один и тот же файл должен давать один и тот же результат. Распознавание изображений этому не помогает (температура модели, нестабильность), поэтому структурные слои у нас всегда в приоритете, а результат зрительного разбора проходит детерминированные проверки: арифметика сумм, длина и контрольная сумма IBAN, формат налогового номера по стране.
Практическая разница: когда клиент говорит «в прошлый раз было другое значение», нам есть что ответить. Обычно это означает, что файл реально другой или его первая версия прошла другим путём.
Мы делаем DueBuddy, сервис разбора счетов и работы с ними. Если тема близка, посмотрите нашу документацию API и обзор возможностей.