Разработка AI-системы распознавания товаров на полке

Пустая полка или товар не на своём месте — ритейлер теряет до 5% выручки. По данным <cite>Nielsen</cite>, пустые полки обходятся ритейлерам в 4–6% оборота. Традиционные решения на основе штрих-кодов не работают при частых перестановках и смене упаковок. Автоматический контроль планограммы с помощью

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1302
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    714
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1006

Пустая полка или товар не на своём месте — ритейлер теряет до 5% выручки. По данным Nielsen, пустые полки обходятся ритейлерам в 4–6% оборота. Традиционные решения на основе штрих-кодов не работают при частых перестановках и смене упаковок. Автоматический контроль планограммы с помощью компьютерного зрения даёт измеримый результат, но при 10 000–50 000 уникальных SKU и регулярных сменах упаковок классические softmax-классификаторы перестают работать. Мы разработали решение на основе детекции YOLOv8 и metric learning с ArcFace, которое обновляется за секунды, а не дни. Система позволяет не только детектировать товары, но и идентифицировать каждый SKU через 512-мерные embedding'ы. В этой статье разберём, как устроена архитектура, какие проблемы мы решали и какие результаты получаем в продакшене.

Типичные проблемы при распознавании товаров

Частая смена дизайна упаковок требует быстрой адаптации. Бренды регулярно обновляют упаковки, и softmax-модель требует полного переобучения. Наш подход на embedding'ах позволяет обновить индекс за секунды — достаточно сфотографировать новую упаковку. Масштабирование на 50 000+ SKU также становится проблемой: традиционные классификаторы деградируют при большом количестве классов. Metric learning с ArcFace даёт Top-1 точность 87% на 50 000 SKU — промышленно приемлемый уровень. Необходимость быстрого обновления: если на полке появился новый товар, его нужно распознавать сразу. FAISS-индекс обновляется инкрементально, без переобучения модели.

Как работает детекция товаров?

Используем fine-tuning YOLOv8 на специально собранных полочных снимках. Размер входного изображения — 1280 пикселей: это критично для чтения мелких ценников и надписей на упаковках.

from ultralytics import YOLO import yaml from pathlib import Path def prepare_retail_dataset_config( data_dir: str, class_names: list[str] ) -> str: """ Конфиг датасета для YOLOv8. Для ритейл-полок рекомендуем imgsz=1280 — детали упаковок важны. """ config = { 'path': data_dir, 'train': 'images/train', 'val': 'images/val', 'test': 'images/test', 'nc': len(class_names), 'names': class_names } config_path = Path(data_dir) / 'dataset.yaml' with open(config_path, 'w') as f: yaml.dump(config, f, allow_unicode=True) return str(config_path) # Обучение детектора товаров model = YOLO('yolov8l.pt') model.train( data='retail_dataset.yaml', imgsz=1280, # важно: мелкие ценники и надписи требуют разрешения batch=8, # при 1280 батч меньше epochs=200, device='0', augment=True, mosaic=0.5, # снижаем mosaic — не хотим менять масштаб товаров copy_paste=0.3, # полезно для retail rect=False # прямоугольные батчи ухудшают детекцию мелких объектов ) 

На практике fine-tuning с подобными параметрами даёт mAP 50-95 ~0.85 на тестовой выборке из 2000 изображений. Если у вас есть специфические категории (например, блистеры или бутылки), мы адаптируем аугментации.

Почему embedding-подход лучше классификации?

При 10 000+ SKU softmax-классификатор требует переобучения при добавлении каждого нового товара. Embedding-подход на основе ArcFace решает проблему: обученная модель выдаёт 512-мерный вектор, а поиск по базе осуществляется через FAISS-индекс. Новый SKU — просто добавляем его embedding.

import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader import timm import faiss import numpy as np class SKUEmbeddingModel(nn.Module): """ ArcFace-like metric learning для идентификации товаров. Обучаем на кропах товаров → embedding 512-dim. """ def __init__(self, num_skus: int, embedding_dim: int = 512): super().__init__() self.backbone = timm.create_model( 'efficientnet_b4', pretrained=True, num_classes=0 ) self.embedding = nn.Sequential( nn.Linear(self.backbone.num_features, embedding_dim), nn.BatchNorm1d(embedding_dim) ) # ArcFace head для обучения self.arcface = ArcFaceHead(embedding_dim, num_skus) def forward(self, x: torch.Tensor, labels: torch.Tensor = None): feat = self.backbone(x) emb = F.normalize(self.embedding(feat), dim=1) if labels is not None: return self.arcface(emb, labels) return emb class ArcFaceHead(nn.Module): def __init__(self, dim: int, num_classes: int, margin: float = 0.3, scale: float = 32.0): super().__init__() self.weight = nn.Parameter(torch.randn(num_classes, dim)) self.margin = margin self.scale = scale def forward(self, emb: torch.Tensor, labels: torch.Tensor): import math W = F.normalize(self.weight, dim=1) cosine = F.linear(emb, W) # Применяем margin только к правильному классу one_hot = torch.zeros_like(cosine) one_hot.scatter_(1, labels.unsqueeze(1), 1) phi = cosine - self.margin output = (one_hot * phi + (1 - one_hot) * cosine) * self.scale return F.cross_entropy(output, labels) class SKUFAISSIndex: """FAISS-индекс для быстрого поиска похожих SKU""" def __init__(self, embedding_dim: int = 512): self.index = faiss.IndexFlatIP(embedding_dim) # inner product = cosine при нормализации self.sku_ids = [] def add_sku(self, sku_id: str, embedding: np.ndarray) -> None: emb_norm = embedding / (np.linalg.norm(embedding) + 1e-8) self.index.add(emb_norm.reshape(1, -1).astype(np.float32)) self.sku_ids.append(sku_id) def search( self, query_embedding: np.ndarray, top_k: int = 5 ) -> list[dict]: q = (query_embedding / (np.linalg.norm(query_embedding) + 1e-8) ).reshape(1, -1).astype(np.float32) scores, indices = self.index.search(q, top_k) return [ {'sku_id': self.sku_ids[idx], 'score': float(scores[0][i])} for i, idx in enumerate(indices[0]) if idx < len(self.sku_ids) ] 

На практике ArcFace даёт Top-1 точность ~95% для 1 000 SKU и ~87% для 50 000 SKU. Это значительно выше, чем CLIP zero-shot (78% на той же базе).

Как обрабатывать смену упаковки?

Главная головная боль ритейла — packaging refresh. Раз в год бренды меняют дизайн, и модель начинает ошибаться на новых упаковках. Наш подход: онлайн-обновление индекса. Достаточно сфотографировать новую упаковку и добавить её embedding в FAISS. Старый embedding можно удалить или оставить как вариант.

def update_sku_appearance( sku_index: SKUFAISSIndex, model: SKUEmbeddingModel, sku_id: str, new_product_images: list, keep_old: bool = False # False = заменяем, True = добавляем вариант ) -> None: model.eval() embeddings = [] with torch.no_grad(): for img in new_product_images: emb = model(img.unsqueeze(0).cuda()).cpu().numpy() embeddings.append(emb.squeeze()) # Усредняем по нескольким ракурсам mean_emb = np.mean(embeddings, axis=0) if not keep_old: # Удаляем старые записи (FAISS IDMap для удаления) pass # требует IndexIDMap sku_index.add_sku(sku_id, mean_emb) print(f'Updated SKU {sku_id} with {len(new_product_images)} images') 

Эта операция занимает секунды. Переобучение модели не требуется — только обновление индекса. В production мы используем FAISS с поддержкой удаления через IndexIDMap.

Сравнение точности методов

SKU база Метод Top-1 Accuracy Top-5 Accuracy Время обновления
1 000 SKU Softmax 91.4% 98.2% Переобучение (дни)
1 000 SKU CLIP zero-shot 78.3% 91.7% Мгновенно
1 000 SKU ArcFace + FAISS 95.8% 99.1% Секунды
10 000 SKU ArcFace + FAISS 92.3% 97.8% Секунды
50 000 SKU ArcFace + FAISS 87.1% 95.4% Секунды

Отметим: как видно из таблицы, ArcFace + FAISS значительно превосходит CLIP zero-shot по точности и softmax — по скорости обновления. Для 50 000 SKU точность падает, но остаётся промышленно приемлемой.

Кейс из нашей практики: внедрение для сети гипермаркетов

Для одного из наших клиентов, сети гипермаркетов с 200+ магазинами, пилот на 500 SKU занял 5 недель. После разметки 8000 кропов товаров обучен детектор и embedding-модель. На тесте точность Top-1 составила 96%. После развёртки на 10 000 SKU точность снизилась до 92%, но система стабильно работает, обрабатывая до 1000 кадров в минуту на одном GPU. Внедрение окупилось за 7 месяцев: экономия от сокращения out-of-stock составила существенную сумму, а оптимизация выкладки дала дополнительный доход.

Детали настройки FAISS для production Для удаления векторов используем IndexIDMap с IVFFlat: инкрементальное обновление без перестроения. Ключевые параметры: nlist=100, nprobe=10. Это даёт скорость поиска <1 мс на 50K векторов при точности 99% от brute-force.

Процесс работы

  1. Аналитика и сбор данных — определяем перечень SKU, собираем полочные снимки (от 20 изображений на SKU для разных ракурсов).
  2. Разметка и подготовка датасета — аннотируем bounding boxes и классы. Для пилота достаточно 500–1000 размеченных изображений.
  3. Обучение модели — fine-tuning YOLOv8 и ArcFace на собранном датасете. Итеративный цикл с валидацией на тестовой выборке.
  4. Интеграция и тестирование — развёртывание REST API, подключение к камерам, проверка на реальных полках. Настраиваем MLOps pipeline (CI/CD для моделей, мониторинг дрейфа, дашборды ритейл аналитики).
  5. Деплой и поддержка — установка на сервер, настройка CI/CD для обновления индекса, мониторинг метрик.

Типичные ошибки, которые мы встречали:

  • Недостаточное разнообразие фона. Если обучать только на фото с идеально ровными полками, на реальных снимках с тенью и бликами модель теряет точность.
  • Перекос классов. Некоторые SKU встречаются редко — для них нужно применять аугментацию или собирать больше данных.
  • Неправильная нормализация освещения. Мы используем адаптивную гистограммную коррекцию перед подачей в модель.

Сроки и что входит в работу

Задача Срок
Детектор + идентификатор для пилота (500 SKU) 4–6 недель
Промышленная система (10 000+ SKU) 8–14 недель
Интеграция с SAP/1С + мобильное приложение 12–20 недель

В стоимость работ входят следующие этапы:

  • Сбор и разметка тренировочного датасета (до 10 000 изображений на пилот)
  • Обучение и валидация модели с отчётом по метрикам
  • REST API для интеграции
  • Документация и инструкция по эксплуатации
  • Обучение сотрудников (2 часа онлайн)
  • Поддержка в течение 3 месяцев после запуска

По нашим оценкам, автоматизация сокращает время мерчандайзинга на 30% и снижает out-of-stock на 15%.

Начало работы с системой

Оценим ваш проект бесплатно. Свяжитесь с нами, и мы подготовим коммерческое предложение с точными сроками и стоимостью под ваш масштаб. Закажите пилот — и вы получите рабочую систему для 500 SKU за 4–6 недель. Гарантируем конфиденциальность данных.