Система работает и разбирает документы. Претензия у меня к ней одна, зато неустранимая изнутри: я не могу доказать ни одно из полученных значений.
Ниже — опись. По каждому полю: как оно читается, чем проверяется и чего эта проверка стоит. Последняя строка описи самая интересная, потому что напротив неё не стоит ничего.
Что за задача
Люди присылают фотографии своих паспортов прямо в мессенджер оператору. Снято телефоном: паспорт полураскрыт, придержан пальцами, блик от лампы поперёк страницы, кадр сверху под углом. Иногда попадается скан, но развёрнутый непонятной стороной. Оператор смотрит на это пятнадцать секунд и вбивает четыре поля: серию, номер, дату рождения, фамилию с именем. Снимков поток, и я взялся заменить эти пятнадцать секунд машиной.
Ограничение, которое определило всё остальное: паспорта наружу не уходят. Чужие персональные данные прислали не для того, чтобы я раздавал их по чужим серверам. Поэтому никаких облачных распознавалок — всё считается локально, Ollama и gemma3:12b на машине в локальной сети, температура ноль, ответ строгим JSON.
Перед распознаванием снимок готовится обычным зрением: обрезается фон по плотности границ Кэнни, определяется ориентация, вытягивается контраст. Ориентацию, кстати, я нахожу по лицу владельца — кручу изображение на четыре угла, в каждом положении ищу лицо каскадом Хаара, выбираю лучшее по площади со штрафом за непортретную пропорцию и доворачиваю так, чтобы фотография оказалась в левом нижнем углу.
for k in range(4): rotated = np.rot90(in_img, k=k) faces = faceCascade.detectMultiScale(cv2.cvtColor(rotated, cv2.COLOR_BGR2GRAY), scaleFactor=1.1, minNeighbors=5, minSize=(40, 40)) for (x, y, w, h) in faces: aspect = h / float(w) portraitPenalty = 1.0 if aspect >= 1.0 else 0.3 score = w * h * portraitPenalty
Сразу скажу, чего этот конвейер не делает: перспективу он не выпрямляет. Снятый под углом паспорт таким и остаётся.
Поле первое: серия и номер
Серия и номер напечатаны вертикально вдоль правого поля страницы. Вырезаю правую полосу в двадцать процентов ширины, поворачиваю на девяносто градусов, автоконтраст, нерезкая маска, увеличение в пять раз, PNG — чтобы не добавлять к своим бедам ещё и артефакты JPEG. Полученную картинку показываю модели.
Чем проверяю результат: формат. Четыре цифры серии, шесть цифр номера, иначе поле обнуляется. Плюс отбраковка одного конкретного значения:
if f'{series}{number}' == '0101123456': series, number = None, None
Это не шутка и не отладочный мусор. В промпте я показал модели пример — как выглядит серия с номером, — и она начала возвращать мне этот пример вместо того, что напечатано на снимке. Достаточно часто, чтобы я вписал константу прямо в проверку.
Здесь уместно возражение, которое я слышу первым: почему не Tesseract, зачем вообще языковая модель ради десяти цифр. Начинал я именно с него, и вот всё, до чего дошёл, — узкая задача на подготовленной полоске, режим одной строки, белый список из одних цифр:
DIGITS_ONLY_CONFIG = r"--psm 7 -c tessedit_char_whitelist=0123456789"
На телефонных снимках он выдавал кашу. Функция до сих пор лежит в проекте и не вызывается ниоткуда. Скажу за него слово: Tesseract ждёт скан — выпрямленный, бинаризованный, нормального разрешения, — а ему давали фотографию изогнутой страницы под углом и с бликом. Каша была честной правдой о качестве входа. Подозреваю, у него был шанс, если бы я сначала выпрямил геометрию, а не только покрутил контраст.
Итог по полю: проверка есть, но ловит она только грубое. Формат «четыре плюс шесть» пропустит любую неверную цифру, если их всё равно десять.
Поле второе: дата рождения
Читается вместе с фамилией и именем, одним запросом по всему развороту. Требование к формату записано в промпте: строго «ДД.ММ.ГГГГ», иначе поле пустое. На выходе сервиса стоит валидатор, который это же и проверяет.
Проверяется, таким образом, форма записи. Значение не проверяется ничем.
А значение модель искажает предсказуемым образом: путает шестёрку с восьмёркой, восьмёрку с девяткой. Это обычная беда распознавания, тут у меня претензий нет. Претензия в том, что «06.08.1970» пройдёт мой валидатор ровно так же гладко, как «08.06.1970», и обе даты выглядят одинаково правдоподобно. Человек, глядя на результат, тоже ничего не заподозрит.
Поле третье: фамилия и имя
Проверить нечем. Совсем.
Нет ни формата, ни диапазона, ни справочника, с которым имело бы смысл сверяться. Что модель написала, то и уходит дальше.
И ровно здесь она ошибается самым неприятным способом. Редкую фамилию она правит на распространённую, потому что распространённая вероятнее. С точки зрения языковой модели это не сбой, а ровно то, чему её учили: продолжать текст наиболее правдоподобным образом. С точки зрения паспорта это подлог.
Разница между двумя видами вранья тут и вылезает. Когда распознавание выдаёт кашу, это видно сразу — каша не похожа на фамилию. Когда модель заменяет одну фамилию другой, результат чистый: читается, склоняется, выглядит как надо. Он просто не тот.
Возражение, которое я слышу вторым: возьми модель побольше. Побольше — это ещё больше видеокарты ради нескольких строк текста, и, главное, реже врать не значит доказуемо не врать. Мне нужно не «скорее всего верно», мне нужно «верно, и вот проверка».
Строка четвёртая: сам документ
Теперь то, из-за чего я и считаю всю работу незаконченной.
Прежде чем извлекать поля, надо убедиться, что перед нами паспорт. У меня это отдельный запрос — к той же самой модели: «это разворот российского паспорта, да или нет».
Сторож сделан из того же материала, что и подозреваемый.
Появился он не от хорошей жизни. Среди снимков попался акт приёма-передачи — обычная бумага с подписями, никакого паспорта в кадре. Модель посмотрела на неё и уверенно вернула серию и номер. Четыре цифры, шесть цифр, всё как положено. Их там не было: я дал ей задание найти серию и номер, а она такая программа, которая задание выполняет. Всегда. Из чего угодно.
Отсюда третье возражение — про коммерческие движки, специально написанные под российские паспорта. Знаю, что они есть. Вопрос в цене и в том, что я не видел ни одного честного отчёта от человека, который поставил такой движок на поток телефонных снимков из мессенджера. И вопрос доказательства это всё равно не снимает: чужой закрытый движок отвечает «это паспорт» с тем же основанием, что и моя модель, — со своим словом.
Итог по строке: проверки нет. Есть мнение программы.
Восемьдесят процентов, которые ничего не значат
Со всеми описанными подпорками система разбирает верно примерно восемь документов из десяти.
Звучит прилично. На деле это ноль.
Потому что из описи выше видно: я не знаю, какие именно восемь. Оператор всё равно открывает все сто снимков и сверяет каждое поле глазами. Экономии нет — есть добавленная работа: раньше он просто читал документ, теперь читает документ и сверяет с тем, что нафантазировала машина.
А что было бы полезно? Шестьдесят процентов. Но с пометкой: вот эти шестьдесят прочитаны верно, гарантированно, можно не смотреть; остальные сорок открой сам. Это шестьдесят процентов сэкономленного времени вместо нуля.
Не хватает не точности. Не хватает доказуемости.
Чем на самом деле обеспечены мои гарантии
Вот как выглядит место, где в этом проекте живёт надёжность. Кусок рабочего промпта, не сокращал, только перевёл:
Твоя задача — прочитать ВСЕ цифры в этой строке ровно так, как они напечатаны. Серия из четырёх цифр — это просто ПЕРВЫЕ ЧЕТЫРЕ цифры по порядку чтения. НЕ ПРИМЕНЯЙ никаких закономерностей вроде повторения первых двух цифр. Например, когда ты видишь «27 08 123456»: - ВЕРНО: "Series": "2708" - НЕВЕРНО: "Series": "2727" - НЕВЕРНО: "Series": "2808" НИКОГДА не выдумывай и не исправляй данные, опираясь на частотные русские имена или типичные даты. Если что-то неразборчиво — считай это нечитаемым. Гораздо ЛУЧШЕ вернуть None, если ты не уверен, чем вернуть неправильное значение.
Разбор с «ВЕРНО» и «НЕВЕРНО» появился после того, как модель устойчиво выдавала серию «2727» там, где напечатано «27 08»: видела первую пару и дорисовывала вторую по образцу. Запрет исправлять по частотным именам — после подмены фамилий. Последняя строка повторена в промпте дважды, разными словами.
Прочитайте это подряд. Запреты капслоком, разбор на пальцах, просьба промолчать. Так пишут не инструкцию.
Теперь сравните со второй половиной проекта — той, где нет ни одной фразы человеческими словами. Каскад Хаара либо находит лицо, либо не находит, но не сочиняет его, чтобы меня не расстраивать. Обрезка по границам не выдумывает документ на пустом столе. Эти куски ошибаются, и чаще, чем мне бы хотелось, но ошибаются они честно: не справился — видно, что не справился.
Уговаривать в этом проекте приходится ровно одну вещь. Она же единственная, чей ответ я не могу проверить.
Что можно подпереть счётом
Одну строку описи закрыть всё-таки можно, и я до этого дошёл поздно.
С первого июля 2011 года в российском паспорте есть машиночитаемая запись — две строки по сорок четыре знака внизу страницы с фотографией. Введена постановлением правительства № 424, правила формирования сейчас живут в приложении 12 к приказу МВД № 186.
Ценно там не то, что запись машиночитаемая. Ценно, что в нижней строке пять контрольных цифр: позиции 10, 20, 28, 43 и 44, алгоритм «модуль 10» с повторяющейся весовой функцией 7-3-1.
Дальше арифметика. Веса 7, 3 и 1 взаимно просты с десяткой — значит подмена любой одной цифры на любую другую всегда меняет контрольную сумму. Не «с высокой вероятностью», а всегда. Та самая путаница шестёрки с восьмёркой, ради которой написаны уговоры в промпте, ловится гарантированно.
Пределы у этого пути честные. Фамилии и имени кириллицей в записи нет — там своя перекодировка, причём не такая, как в загранпаспорте: во внутреннем паспорте кириллица кодируется особой таблицей, где, например, буква «Е» превращается в цифру 2, поэтому готовые библиотеки под международный стандарт разбирают имя неверно. Перестановка двух соседних цифр контрольной суммой ловится не всегда, в отличие от одиночной ошибки. Паспорта старше 2011 года записи не имеют вовсе. И полоса должна попасть в кадр.
И то, на чём я застрял
Казалось бы, вопрос про документ закрыт: если в кадре нашлись две строки нужной длины и контрольные цифры сошлись — перед нами паспорт, и он прочитан верно. Акт приёма-передачи сходящейся суммы не даст никогда.
Проблема в обратном случае.
Контрольная сумма различает «сошлось» и «не сошлось». Она не различает «это не паспорт» и «это паспорт, снятый криво». А для оператора это два разных исхода: в первом документ отклоняют, во втором просят человека переснять. Одна и та же несошедшаяся сумма означает и то и другое.
И спросить не у кого. Модель на вопрос «паспорт ли это» отвечает всегда и всегда уверенно — я показал выше, чего стоит её уверенность. Арифметика отвечает только там, где данные уже прочитаны.
Чем в такой ситуации доказывают, что перед тобой документ нужного типа, если прочитать его не удалось?
Коллеги, поделитесь опытом, кто уже решал такую задачу
Я уже думаю вернуться к геометрическим преобразованиям, чтобы опять прибегнуть к Tesseract, но это уже от бесысходности, что ли)))
Комментарии (14)

InsiderCrush
01.09.2026 05:22Я бы пробовал сначала задать модели контекст задачи жёстче. Ты распознаёшь паспорт гражданина РФ. Ошибка даже в одной букве или цифре недопустима. Ничего не додумывай и не исправляй по вероятности — если символ не читается, верни null. И я бы вообще убрал из промпта конкретные примеры серии/номера. Похожеу Вас модель начала подставлять пример вместо фактических цифр.

akakoychenko
01.09.2026 05:22И не будет работать с таким стеком. Проверено на более простой задаче. Сколько гемму не промпть, она неспособна прочесть текст с картинки без галлюцинаций. Tesseract - вообще несерьезно. Это устаревший задолго до LLM бума проект, который неспособен распознать ничего кроме скана черно-белого печатного текста. Кодинг агенты рекомендуют его, ведь в их обучающих выборках много упоминаний.
Возьмите OCR нового поколения, и будет вам счастье. Как пример, GLM-OCR, Paddle-paddle.
Да и вообще, парсить документы со строгим шаблоном при помощи ллм странно. Промышленное качество можно получить только на шаблонах и на механизме, который создаст проекцию каждого пикселя фото на точку шаблона.

Timur555 Автор
01.09.2026 05:22За идеи по поводу новых ocr спасибо, обязательно попробую. Просто конкретно мой случай это к сожалению не пиксель в пиксель. Это кривые, косые фоточки сделанные телефоном на бегу

akakoychenko
01.09.2026 05:22это к сожалению не пиксель в пиксель. Это кривые, косые фоточки сделанные телефоном на бегу
Вот именно для этого проекция пиксель в пиксель и нужна. Никто не говорил, что она будет линейной, и что это будет просто. Но без соответствия «точка А на фото соответствует точке В на шаблоне» промышленного качества, увы, не достичь

fire64
01.09.2026 05:22А почему кстати реально не правите перспективу кадра с помощью OpenCV?
Ведь реально проще будет. Потом обрезаете по контурам, фильтруете шумы и на выходе у вас получается пусть и не скан, но некое его подобие.
Опять же размер фотографии у всех одинаковый, на его примере определяете масштабирование кадра и подгоняете все снимки под один размер, далее делаете координатную сетку и скармливаете конкретные зоны адекватной OCR
п.с.
Вот чего бы я точно не стал делать - это использовать модель общего назначения, да ещё и с небольшим количеством параметров, без файнтюнинга и доп. претрейна в надежде получить точные данные. Даже Frontier модели которые не заточены конкретно под это не дадут идеальный результат, а уж такие небольшие локальные модели тем более...

Timur555 Автор
01.09.2026 05:22Там достаточно хитрый выбор искажений получается:
бабочка (когда края центрального разворота, как бы наверх смотрят)
Трапеция (самое простое)
Бабочку не смог победить, а она самая частая

leveler
01.09.2026 05:22бабочка (когда края центрального разворота, как бы наверх смотрят)У самого такой паспорт от ношения в кармане. Ещё и ламинат пленка начала немного отслаиваться, теперь из-за этого нормальную фотку уже не сделать, только если через именно сканер его пропускать, иначе блики ужасные

fire64
01.09.2026 05:22По поводу бликов, это вообще ужас. У самого паспорт в таком состоянии, что его уже нормально не сфоткать. Во-первых он выцвел сильно, стал бледным, а во вторых ламинат даёт ужасные блики, но ему уже больше 15 лет и была ситуация, когда после дождей он отслоится. Попались хорошие девчонки в МФЦ, смогли его прогреть и склеить, но блики просто жесть.

Timur555 Автор
01.09.2026 05:22Все поправимо). В 45 лет получите новый). Правда блики от ламината никуда не денутся

akakoychenko
01.09.2026 05:22Ну, к слову, дообучение - опасная штука. Завтра изменится шаблон документа, и будет крайне неприятная ситуация - кейсов под обучение под новый шаблон не существует в природе, а задачи на их распознавание заходят в прод уже сегодня
leveler
А не проще забирать паспортные данные через ЕСИА? Там, конечно, подключение очень бюрократизировано, но зато сразу нормальные данные на входе. Или под ваши задачи принципиально нужен скан?
Timur555 Автор
Клиенты кидают фотки документов, чтобы оператор оформлял их дальше