Блог

Распознавание товаров в ритейле: что может пойти не так? Часть 1

Зачем нужна эта статья?

Рассказывая в нескольких статьях про разные итерации нашего пайплайна распознавания товаров на полках в супермаркете, может показаться, что мы ко всем решениям приходили довольно быстро, что, конечно же, не так. Поэтому решили написать статью про то, что пробовали и что не получилось - может быть чтобы кто-то из читателей не наступал на те же грабли, или, наоборот, сделал то же самое, но намного лучше (и потом написал бы об этом комментарий).
Статья будет поделена на 2 части:
  • Неудачные эксперименты - это непосредственно то, что совсем не получилось;
  • Эксперименты, которые не вошли в пайплайн или не пригодились бизнесу - что-то интересное получилось реализовать, но в прод оно не пошло.

Неудачные эксперименты

Автоматическое очищение search space

Search space (эмбеддинги кропов + мета в формате ключ-значения) довольно быстро разросся до размера в несколько миллионов, и он был очень неоднородный. Для каких-то популярных товаров (условная кола) кропов было огромное количество, а других товаров (например, новогодняя коробка конфет) было всего 1-2 кропа, соответственно, в некоторых случаях количество товара имело большое значение. Кроме этого, в search space попадало довольно много ошибок разметки, некоторые были довольно однозначными (кола разметилась как фанта - и такое бывало), то есть непосредственно влияли на правильность предсказаний. С такими объемами заниматься очисткой вручную казалось нам неоптимальным, поэтому мы искали разные способы автоматической очистки.

Дедупликация

Сначала расскажем немного про дубликаты - их было огромное количество, и они не сильно помогали в нашем пайплайне (зачем нам 500 одинаковых кропов колы?), так что хотелось их найти и убрать, а в идеале делать это регулярно. Пробовали несколько способов:
  • Тривиальный - искать пары эмбеддингов, у которых cosine similarity больше порога (например, 0.99), и один из них удалять, а второй запоминать, чтобы не удалить в дальнейшем. Работало так себе, поскольку даже у кропов разного класса мог быть cosine similarity очень высокий. Если порог выставить так, чтобы удалить примерно 20% всех кропов, то метрика падала на 0.4% (существенно), а чтобы падение было меньше 0.1%, то мы могли бы удалить всего 5% кропов, хотя мы точно знаем, что дубликатов намного больше;
  • CleanVision - open-source решение, в котором есть алгоритма поиска near-duplicates на основании image hash (например phash - интересная статья про него с примерами). Картинка обычно сжимается, обесцвечивается и бинаризуется, а потом переводится в биты и получается хэш, то есть это преобразование устойчиво к изменениям размера, соотношения сторон, яркости и иногда даже изменениям цветов. Проблема только в том, что мелкие детали очень сильно затираются, поэтому качественный анализ на наших данных показал, что кропы одного класса слишком часто имеют разные хэши, а кропы разных классов имеют одинаковые хэши - такое нам не подходит;

Разные алгоритмы хэширования изображений. Source


  • Связные компоненты + сэмплирование самых далеких - сначала ищем связные компоненты похожих друг на друга кропов, чтобы cosine similarity был больше порога, но после этого не просто удаляем все, кроме одного, в этой связной компоненте, а итеративно ищем самые далекие эмбеддинги и оставляем только их. Например, в компоненте 10 эмбеддингов, мы берем первый из них, ищем самый далекий, потом ищем самый далекий для предыдущего, и так оставляем 5 эмбеддингов и 5 удаляем. Метрики стали лучше, чем в первом способе - с подбором параметров можно удалять около 30% search space с падением метрики 0.3%, но все равно эти цифры были далеки от идеала.

Ошибки разметки & устаревшие кропы

Автоматически искать ошибки разметки было еще сложнее, чем искать дубликаты, потому что это уже превращается в задачу поиска выбросов или умную кластеризацию. Собственно, примерно эти способы мы и использовали:
  • DBSCAN в пределах одного продукта - для каждого продукта ищем все его эмбеддинги, и на всех эмбеддингах запускаем кластеризацию. Поскольку DBSCAN умеет искать выбросы, он здесь в целом справлялся неплохо, но все равно выдавал много FP, то есть много хороших кропов мы удаляли и получали просадку метрики в 0.3-0.4%;
  • Cleanlab - похожая идея, но вместо DBSCAN используется KNN, а outlier score - это метрика, обратно пропорциональная среднему расстоянию до ближайших соседей, но FP получалось еще больше, чем у DBSCAN. Также пытались еще брать пересечение и объединение двух способов, но в каждом из них мы или слишком мало удаляли, или слишком роняли метрику, в итоге ни один из этих способов в прод для очистки не поехал;
  • Очистка по времени - самый простой и очевидный способ оказался самым эффективным. На нескольких датасетах мы проверили, что оставлять только самые последние разметки для каждого товара (30-40 разметок - это может быть 30-40 кропов, а может быть 300-400, если на полке по 10 одинаковых товаров), то это не просто в несколько раз сокращает search space и поддерживает его примерно одинаковый размер, но еще и повышает метрику, поскольку внешний вид товаров со временем часто меняется (меняются полки, где товары стоят, освещение и так далее).
Выводы: если камеры не снимают продукты в 4K под студийным светом в анфас - автоматическая вычистка дубликатов и ошибок будет работать так себе. Гораздо лучше просто поддерживать свежее состояние search space + иногда чистить его вручную (например, отправлять подозрительные классы в FiftyOne и сажать руководителя разметчиков искать в них выбросы).

Super resolution

После экспериментов с автофокусом, экспозиционным фьюзингом и классическими CV-фильтрами (ссылка на статью) мы перешли к следующему очевидному направлению. Мы попробовали AI модели для улучшения изображений и super-resolution. На первый взгляд такие модели выглядят многообещающе. Они делают картинку более четкой и визуально приятной. Однако для нас ключевым критерием была не визуальная эстетика, а практическая польза для OCR.

Как мы оценивали AI фильтры

Оценка была максимально прикладной и простой.
Мы прогоняли изображения через AI фильтр, затем запускали стандартный пайплайн:
  • детекция ценников,
  • OCR распознавание текста.
Дальше считали:
  • количество найденных кропов,
  • количество распознанных символов,
  • средний confidence OCR.
Параллельно проводили визуальный анализ. Если ценник глазами читается лучше, это потенциально полезно. Если картинка выглядит лучше, но текст размывается, это минус.

AuraSR v2

Первым кандидатом был AuraSR v2.
Наблюдения оказались следующими:
  • надписи на товарах становились чуть более четкими,
  • на ценниках существенного улучшения не появлялось,
  • итоговый размер изображения превышал 100 МБ, что делает модель практически неприменимой в продакшене.

Пример работы детектора AuraSR v2 на снимке из магазина

Итоговые метрики для полного кадра

Фактически количество распознанных символов даже немного снизилось, а средний OCR score остался на том же уровне.
Если передавать в модель только кроп ценника, визуально качество выглядит лучше, а инференс потенциально можно уложить в менее чем одну секунду. Однако стабильного прироста метрик мы все равно не увидели.

Пример работы детектора AuraSR v2 на кропе ценника из той же сцены

Другие попытки

Мы также попробовали несколько других решений.
Flux ControlNet Upscaler

Результат работы Flux ControlNet Upscaler на снимке из магазина

В ряде случаев модель дорисовывала неполные или искаженные элементы. Для текстовых областей это критично, форма символов начинала ломаться.

Результат работы Flux ControlNet Upscaler на кропе ценника с той же фотографии

Онлайн image enhancer сервисы
Сервисы вроде Airbrush и Unblurimage делали картинку более приятной визуально. Повышался контраст и уменьшался шум. Однако текст на ценниках часто становился более размытым, и OCR начинал ошибаться чаще.
Результат работы Airbrush на фотографии из магазина
Результат работы Airbrush на кропе ценника с той же фотографии
Даже в сценарии, когда в модель передавался только кроп ценника и она отрабатывала без галлюцинаций , проблема сохранялась. Визуально лучше не означало полезнее для распознавания.
Примеры работы открытых моделей с Hugging Face
Примеры работы открытых моделей с Hugging Face
Даже при работе только с кропами товаров в отдельности эффект оставался нестабильным. Визуальное улучшение не приводило к улучшению распознавания, а иногда были разные галлюцинации.
Примеры работы открытых моделей с Hugging Face
Примеры работы открытых моделей с Hugging Face

Общий вывод по AI фильтрам

Эксперименты с AI super-resolution дали достаточно однозначный результат.
Такие модели улучшают перцептивное качество изображения, но для задач с текстом они часто:
  • сглаживают мелкие штрихи,
  • дорисовывают несуществующие детали,
  • искажают форму символов.
Без доменного обучения выигрыш по OCR и детекции либо отсутствует, либо становится отрицательным. Для продакшена это критично, так как галлюцинации недопустимы.

Создание “лучшего кадра”

Контекст
Основной источник данных у нас фото с камер. Но периодически этого качества не хватает: где‑то не читается товар, где‑то смаз/шумы/пересвет, и для разметки «не за что зацепиться». В такие моменты подключается человеческий процесс: сотрудник идет и фотографирует полку на телефон, потому что телефон обычно дает заметно более высокое качество.
Отсюда родилась идея: раз уж у нас за день накапливается много снимков одной и той же полки, можно ли автоматически собрать “лучшую фотку за день” композит, в котором каждый товар взят с того кадра, где он получился резче всего и передать его в помощь разметчику.
Идея подхода (коротко)
Мы брали историю телефонных фото за день и строили итоговый кадр так:
  • Детектим товары на опорном кадре (обычно последнем).
  • Для каждого товара ищем соответствия на остальных кадрах (по классу + IoU боксов).
  • Для каждого соответствия считаем скор качества кропа (резкость через Лапласиан с штрафом за пересвет).
  • Выбираем лучший кроп и подменяем им область на опорном кадре.
В итоге получалась “как бы хайрез‑картинка” не из одного кадра, а из набора лучших кропов по всей дневной серии.
Что получилось на практике (и почему трек закрыли)
На бумаге идея выглядела перспективно, но в реальности телефонные фото всё равно часто были не того качества, которое нужно разметчикам: смаз из‑за движения, блики от света, непопадание фокуса, неидеальный угол, перекрытия и т.д. То есть мы “вытаскивали лучшее из имеющегося”, но само “имеющееся” оказалось слабым.
Чтобы проверить это честно, мы сделали пилот:
  • собрали около 60 таких “улучшенных” изображений,
  • отправили их разметчикам, которые как раз просили хайрезы с телефона.
Фидбек был прямой: реально помог только один кадр. Во всех остальных случаях итоговый композит либо не давал заметного выигрыша, либо качество оставалось недостаточным для уверенной разметки.
Из‑за низкой практической ценности и отсутствия масштабируемого эффекта мы закрыли этот трек как нецелесообразный.

Бизнес применение системы OCR и метчинга ценников

Краткий обзор пайплайна: Наша система использует компьютерное зрение и OCR для автоматического контроля ценников на полках. Пайплайн состоит из нескольких этапов: сначала модель детектирует товары на изображении полки и отдельные ценники, затем выполняется OCR распознавание текста на каждом ценнике (например, цены), и наконец классификатор сопоставляет найденный ценник с соответствующим товаром. Проще говоря, система распознаёт ценники в изображении и определяет, соответствует ли каждый ценник своему товару – в частности, совпадает ли считанная цена с ценой данного продукта в базе данных.
Применение для проверки полок: Цель такого пайплайна – проверить, у каждого ли товара на витрине есть правильный ценник. В ходе эксперимента мы пропустили фотографии торговых полок через эту систему, чтобы автоматически выявлять случаи отсутствия ценника или несоответствия цены. Для удобства анализа каждую полку мы разделили на три категории в зависимости от статусов товаров на ней:
  • Полки “all_approved” – все продукты на полке получили статус approved, то есть для каждого найден соответствующий ценник. Иными словами, ни у одного товара не обнаружено проблем с ценником.
  • Полки “mixed” – ситуация смешанная: на полке есть и товары со своим ценником, и товары без ценника. Часть продуктов получила approved, а часть – статус not_found (не найден ценник) или аналогичный отрицательный статус. Это означает, что не для всех товаров нашлись корректные ценники.
  • Полки “all_not_approved” – ни один из товаров не получил ценник (not_found для всех позиций), то есть система не нашла ни одного ценника, соответствующего товарам на этой полке.
Для каждой категории полок мы дополнительно анализировали соотношение количества ценников и числа уникальных товаров. Это позволило выделить случаи, когда ценников ровно столько же, сколько видов товаров, а также аномалии – ситуации с недостатком ценников (ценников меньше, чем товаров) или с их избытком (ценников больше, чем товаров на полке). Такой подход помог понять характер проблем: действительно отсутствующие ценники vs. ошибки детекции/распознавания или особенности выкладки.
Метрики и результаты: По итогам обработки ~4.7 тыс. изображений полок система классифицировала порядка 20–25% полок как полностью корректные (all_approved), более половины – как смешанные случаи, и оставшиеся ~25% – как проблемные полки без единого правильного ценника. Для оценки точности мы провели ручную проверку выборочных случаев. Автоматически система сравнивает цену с ценника с эталонной ценой из базы данных и таким образом помечает товар как approved или not_found. Ручная проверка подтвердила, что многие сработки отражают реальные проблемы, но также выявила ряд ложных срабатываний. Например, среди случаев, когда на полке не найдено 4 и более ценников (значительный недостаток ценников), приблизительно в 40% действительно имели место отсутствующие ценники, а в остальных ~60% причина оказалась технической: смещённый ракурс/viewport камеры (≈30%), дублирование одного ценника в детекции (≈20%), либо пропуск ценника системой (≈10%). С другой стороны, встречались и полки с “избытком” ценников (ценников больше, чем товаров) – в 85% таких ситуаций при проверке оказывалось, что на полке были пустующие места от убранных товаров, за которыми остались ценники (т.е. лишние ценники не сняли вовремя). Остальные случаи избытка – это в основном артефакты алгоритма, например, фантомные срабатывания детектора ценников.
Вот например разбор ошибок для класс `approved`
Отсутствующие товары (дыры)
Дублирование ценника
Лишнее детекции ценников
Фантомные ценники
Ограничения и выводы: В целом наш OCR-пайплайн показал потенциал как компонент системы контроля ценников. Он действительно умеет находить значимую часть проблемных кейсов и может стать основой для автоматизации проверок. Однако в реальном применении выяснилось, что качество end-to-end результата здесь почти полностью определяется не самим OCR, а качество входных детекторов и контекста, который они дают.
Во-первых, пайплайн критически зависит от точности детекции товаров, ценников и “структуры полки”(включая детекторы дырок/пустот, границ полок, корректного выделения групп). Любая систематическая ошибка на ранней стадии превращается в ошибку метчинга: ценник не найден, товар не классифицирован, перепутана геометрия, а дальше даже идеальный OCR уже не спасает. Это означает, что для устойчивой бизнес-метрики нужен очень высокий уровень качества всей цепочки CV-моделей и их согласованная интеграция, а не “сильный OCR в вакууме”.
Во-вторых, в “сыром” виде, если смотреть только на чистую OCR-часть без усиления детекции и без дополнительных контекстных сигналов, точности было недостаточно для массового продового сценария. Мы получали слишком много кейсов, которые выглядят как отсутствие ценника или несоответствие, но на деле объясняются ошибками детекции или качеством кадра. Это резко снижает ценность результата: бизнесу важен не отчёт о потенциальных проблемах, а поток задач с приемлемой долей истинных нарушений.
В-третьих, мы сознательно не покрывали часть самых сложных условий. В частности, мы не опирались на камеры и ракурсы, которые хорошо “видят” неупакованные товары и сложные полки (россыпь, нестандартные ценники, плотные выкладки). А именно там бизнес-эффект от проверки ценников часто максимальный. В результате пилот больше отражал “относительно чистый” сценарий, и даже в нём стало понятно, что основной узкий горлышко не OCR, а качество детекции и полнота контекста.
Поэтому итоговый вывод был прагматичным: пайплайн как идея рабочий, но чтобы он стал бизнес-инструментом, нужна серьёзная доработка и интеграция с другими CV-треками (качество детекции товаров/ценников, детекция пустот, структурирование полок, фильтры по качеству кадра, учёт промо-цен). На текущем этапе целесообразность и польза для бизнеса больше сместились в сторону улучшения качества детекции и контекстных моделей, а OCR-метчинг мы рассматривали как следующий слой, который имеет смысл “включать на полную”, когда фундамент стабилен.

LLM/VLM

Дисклеймер: мы старались использовать LLM/VLM не просто из-за того, что все и везде их используют, а конкретно потому что в некоторых случаях хотелось иметь некое подобие human-in-the-loop и/или уточнение предсказаний, и казалось, что большие языковые модели уже достигли того уровня эволюции, чтобы помочь нам в этих задачах.
Естественно, оказалось все не так просто, поскольку почти везде нам нужны были именно VLM с возможность подавать картинки на вход, а они сразу более жирные/дорогие, для локального инференса в наших ограничениях никак не подходили, так что мы использовали только проприетарные модели по API. Что вообще пробовали делать с помощью них:
  • Доставать фичи из изображений. Гипотеза была в том, что мы недоиспользуем визуальную информацию из кропа, а берем только ближайшие кропы из search space, поэтому мы можем доставать какие-то фичи с помощью VLM, чтобы потом подать эти фичи в классификатор второго уровня. Какие именно - бренд, тип упаковки, категория, вес/объем и так далее. Реальность оказалась такова, что для хорошо видимых кропов мы и без этих фичей делаем правильное предсказание, а для шакальных кропов эти фичи не достаются нормально, поэтому ни на что и не влияют. Также здесь был эксперимент с тем, чтобы сравнивать кроп товара с хайрезами кандидатов (хайрез - стоковая фотка из базы данных), но проблема была примерно такая же, что для кропов плохого качества сравнение с хайрезами не давало никакой пользы;
  • Проверять разметку и находить ошибки. Здесь было несколько вариантов: первый - просто сравнивать кроп товара и хайрез размеченного продукта; второй - сравнивать кроп товара и N кропов из search space (говорить модели, мол, вот точно правильные разметки этого товара, а вот возможно неправильная). Тут возвращаемся к предыдущему пункту - проблема в основном была в качестве кропов, где и человек то почти ничего не увидит, а VLM тем более;
  • Точечно исправлять ошибки размеров/объемов. Одной из самых больших проблем среди легких категорий были ошибки объемов, когда на полке стоит несколько бутылок 0.5л, а предсказываются они как 1.5л. Ошибка закономерная, потому что кропы буквально выглядят идентично для разных товаров, поэтому для определения правильного объема нужно много контекста - например, какие бутылки стоят слева/справа/сверху/снизу. Пробовали подавать фото всей полки, выделять сомнительные бутылки (у которых предсказаны классы, которые часто друг с другом путаются) красным, а другие зеленым, и писать в промпте объемы всех зеленых бутылок. Но, к нашему удивлению, разные модели работали одинаково нестабильно, даже при фиксировании температуры и seed выдавали недетерминированные результаты, многие из которых были неправильными (логичное объяснение этой проблемы). В итоге отчасти из-за нестабильности и прекратили попытки исправить эти ошибки, а еще из-за того, что это довольно специфический кейс, который не масштабируется на все данные, а работает только на напитках и некоторых упаковках.
Компьютерное зрение ИИ