Как устроен ИИ-сканер чеков: от фото до позиций, валюты и перевода
Технический разбор: что происходит с фотографией чека по дороге к списку позиций – предобработка, запрос к мультимодальной модели, извлечение валюты и языка, перевод и сверка с напечатанным итогом.
«Сфоткал чек – ИИ распознал позиции» звучит как одна кнопка. На деле между нажатием и готовым списком стоит с десяток шагов, и половина из них появилась не из красивой архитектуры, а из чеков, на которых предыдущая версия ломалась.
Это разбор нашего пайплайна: что происходит с фотографией по дороге, где именно работает нейросеть, как из ответа достаются позиции, валюта и язык – и почему самый важный шаг стоит в самом конце.
Шаг 1. Что вообще прилетает с телефона
Принимаем JPEG, PNG, WebP, HEIC и HEIF с айфонов и PDF – если чек пришёл из доставки или почтой. Ограничение – 10 МБ на файл и до десяти фото на один чек: длинную ленту из супермаркета проще снять частями, чем пытаться уместить в кадр целиком.
Первая же неожиданность – тип файла. Мобильные браузеры регулярно присылают его пустым или сообщают application/octet-stream, особенно при съёмке прямо с камеры. Поэтому тип определяется в три захода: заявленный, потом по расширению имени, а если и его нет – считаем, что это JPEG с камеры. Отказать в загрузке из-за кривого заголовка – худшее, что можно сделать с человеком, который стоит с телефоном над чеком.
Шаг 2. PDF превращается в картинку
Модель смотрит на изображение, поэтому PDF растеризуется: первые две страницы рендерятся при 150 dpi и склеиваются вертикально в один JPEG. Двух страниц хватает на любой реальный чек или инвойс.
Здесь же живёт ограничение, которое выглядит паранойей ровно до первого инцидента: сторона страницы обрезается по 4000 пикселям. Размер страницы объявляет сам PDF, и он может заявить что-нибудь вроде 200×200 дюймов – файла на несколько сотен байт достаточно, чтобы рендер занял несколько гигабайт и выключил сервис целиком. Оверсайз мы не отклоняем, а ужимаем с сохранением пропорций: настоящие чеки этого потолка не достают и рендерятся как были.
Шаг 3. Предобработка – меньше, чем кажется
Ожидаемого набора из повышения контраста, бинаризации и выравнивания перспективы здесь нет. Это наследие эпохи классического OCR: мультимодальной модели такие фильтры не помогают, а иногда мешают – контрастирование съедает бледную термопечать вернее, чем читает её.
Остаётся три вещи: развернуть снимок по EXIF, привести в RGB и перекодировать в JPEG под base64. Разворот – единственное, что реально важно: чек, снятый боком, боком и уедет.
На этом шаге мы, кстати, споткнулись. Штатная функция поворота в Pillow заодно пересобирает весь блок EXIF – и падает с делением на ноль, если в нём лежит битое рациональное число с нулевым знаменателем. У телефонных камер такое встречается регулярно. Пришлось написать свой разворот: он читает ровно один тег ориентации и не трогает остальное. Всё равно дальше мы кодируем в JPEG без EXIF, так что сохранять больше нечего.
Шаг 4. Запрос к модели
Дальше картинка уходит мультимодальной модели с температурой 0 и строгой JSON-схемой: ответ обязан быть валидным объектом с заранее описанными полями, а не текстом, который мы потом разбираем регулярками.
Моделей не одна, а цепочка: если первая недоступна или вернула мусор вместо JSON, запрос уходит следующей. Отдельно разделены ошибки, при которых перебор бессмыслен – кончились деньги на счёте, битый ключ, слишком большой файл, упёрлись в лимит запросов. В этих случаях цепочка обрывается сразу: следующая модель ответит тем же самым, а человек лишние секунды просто ждёт.
Пока модель думает, сжатая копия фото параллельно уезжает в хранилище – ждать по очереди незачем.
Шаг 5. Что именно мы просим достать
Промпт – это, по сути, свод правил чтения чеков, и почти каждая строка в нём выстрадана.
Позиции – сверху вниз, как напечатано. Если название перенеслось на две строки, склеить в одну. Строку без цены – продолжение или «2 × 150» – отнести к позиции выше. Итог, налоги, скидки и промежуточные суммы в позиции не попадают: это не то, что кто-то ел.
Заведение – бренд, а не юрлицо. «Пятёрочка», а не ООО «Феникс». В чеке обычно напечатано и то и другое, и без явного указания модель тянет юридическое название – которое человеку ничего не говорит.
Валюта – код ISO-4217, выведенный из символа, языка, страны, адреса или налоговой метки: НДС значит рубли, TVA и MwSt – евро, 消費税 – иены. Отдельно расписана неоднозначность символов: ¥ с иероглифом 円 – это иены, а с 元 – юани; $ бывает американским, канадским, австралийским, мексиканским; kr – шведские, норвежские и датские кроны. Если уверенности нет, модель обязана вернуть null, а не угадывать: на фронте пустая валюта подставится из прошлого чека компании, и это честнее, чем молча записать чужие деньги рублями.
Язык – двухбуквенный код позиций по ISO 639-1. Именно он потом включает кнопку перевода.
Шаг 6. Контрольная сумма – самое интересное
Помимо позиций мы просим у модели ещё две вещи, которые пользователю никогда не показываем: напечатанный итог и список надбавок – строки, явно подписанные как налог или сервисный сбор.
Нужны они как контрольная сумма. В странах, где налог не входит в цену, позиции честно складываются в сумму меньше напечатанного итога – разницу дают как раз налог и обслуживание. Мы сравниваем: если сумма надбавок объясняет разрыв, каждая добавляется отдельной строкой, которую можно поделить наравне с блюдами.
А вот если разрыв надбавками не объясняется – мы не добавляем ничего. Соблазн «дописать недостающее» здесь огромный и ошибочный: необъяснённый разрыв означает не сбор, а потерянную или неверно прочитанную позицию. Придумать строку на недостающую сумму значит спрятать ошибку распознавания и разделить между людьми выдуманные деньги.
Лучше показать расхождение с чеком, чем закрыть его цифрой, которой на чеке не было.
Порогов допуска два, и они разные. Для рублёвых чеков он нарочно грубее: в России налог уже в цене, разрыва быть не должно, и реагировать на копеечный дрейф округлений незачем. Для распознанных иностранных валют порог тоньше – там небольшой, но настоящий налог обязан сойтись.
Итог мы в конце пересчитываем по финальным позициям, а не берём напечатанный. Так сумма в приложении всегда равна сумме строк – а значит, сверив её с чеком, вы проверяете весь документ разом.
Шаг 7. Язык и перевод
Перевод – отдельный вызов, а не часть распознавания, и включается он по кнопке. Причина простая: чек на русском переводить незачем, а платить за лишний проход модели на каждом сканировании – тем более.
Распознанный код языка как раз и решает, показывать ли кнопку. Нажали – названия позиций и заведения уходят на перевод, каждая позиция со своим идентификатором, чтобы ответ лёг обратно строка в строку. Оригинальные названия при этом никуда не деваются: в чеке из Барселоны полезно видеть и «Pan con tomate», и то, что это хлеб с помидором.

Шаг 8. Из ответа модели – в чек
Дальше начинается уже продуктовая часть. Валюта пишется в чек – а если фото было несколько и валюты в них разные, чек разделяется на несколько: один чек – одна валюта, иначе баланс компании перестанет сходиться.
После этого позиции раскладываются по людям, и считается, кто кому сколько должен. Как читать результат распознавания и что проверять в первую очередь – в статье о разделении чека по фото. Как это выглядит на поездке с полусотней чеков в разных валютах – в сравнении с другими сервисами.
Что оказалось важнее всего
Если коротко: качество ИИ-сканера чеков решает не выбор модели. Модели меняются каждые полгода, и подставить новую – это одна строка в цепочке.
Решает то, что вокруг: не отвалиться на кривом заголовке с телефона, не лечь от стобайтового PDF, не потерять поворот из-за битого EXIF, не угадать валюту наугад и – главное – не дописать в чек сумму, которой там не было.
Частые вопросы
- Как ИИ распознаёт чек по фото?
- Снимок разворачивают по EXIF, приводят к JPEG и отдают мультимодальной модели вместе с инструкцией и строгой JSON-схемой ответа. Модель читает чек целиком и возвращает структуру: позиции с ценами, валюту в коде ISO-4217 и язык. Классический OCR такого не делает – он возвращает только текст, который потом ещё надо разобрать на поля.
- Как определяется валюта чека?
- По символу валюты, языку чека, стране, адресу и налоговой метке: НДС означает рубли, TVA и MwSt – евро, 消費税 – иены. Неоднозначные символы разводятся по контексту: ¥ рядом с 円 – это иены, рядом с 元 – юани. Если уверенности нет, валюта остаётся пустой и подставляется из прошлого чека компании – угадывать нельзя.
- Как проверяется, что чек распознан правильно?
- Напечатанный на чеке итог и явно подписанные надбавки (налог, сервисный сбор) используются как контрольная сумма. Если сумма позиций расходится с итогом ровно на величину надбавок, они добавляются отдельными строками. Если расхождение надбавками не объясняется, не добавляется ничего: это признак потерянной позиции, а не сбора, и дописывать недостающую сумму нельзя.
- Переводятся ли названия позиций с иностранного чека?
- Да, отдельным шагом по кнопке. Модель возвращает код языка позиций, и если чек не на русском, появляется кнопка перевода. Названия заведения и позиций переводятся на русский, при этом оригинальные названия остаются видны.
Читайте также
Как разделить чек по фото с помощью ИИ
Как из фотографии чека получить список позиций и разложить их по людям: что нейросеть распознаёт сама, где ошибается и как проверить результат за полминуты.
Splitwise в России: что не работает и чем заменить в 2026
Splitwise из России открывается, но Pro не оплатить, а бесплатный тариф урезали до нескольких трат в день. Сравниваем Splitwise, Tricount, ЧекСплит и Пополаму по нашему опыту поездки на шестерых.
Как разделить счёт в ресторане на компанию
Четыре способа разделить счёт в ресторане на большую компанию: поровну, по позициям, с учётом обслуживания и алкоголя. Личный опыт компании, которая перепробовала все четыре.