Розробка 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 для детекції товарів, FAISS та metric learning для ідентифікації SKU, що дозволяє швидко адаптуватися до зміни упаковок і контролювати планограму, а також інтегрувати MLOps та ритейл аналітику. Ми розробили рішення на основі детекції 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% на тій самій базі). ArcFace + FAISS у 3 рази точніший за CLIP zero-shot на базі з 1000 SKU. Крім того, наша система в 5 разів швидша за традиційні рішення завдяки інкрементальному оновленню індексу.

Як обробляти зміну упаковки?

Головний біль ритейлу — 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 місяців: вартість пілоту — $15 000 USD, економія від скорочення out-of-stock склала $200 000 USD на рік для мережі з 200 магазинів. Оптимізація викладки дала додатковий дохід.

Деталі налаштування 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 на зібраному датасеті. Ітеративний цикл з валідацією на тестовій вибірці. Використовуємо loss-функції Triplet Loss та ArcFace для кращої роздільності embedding'ів. Також застосовуємо контрастне навчання та триплетну втрату для покращення F1-міри.
  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 місяців після запуску

Наша команда має 5+ років досвіду в комп'ютерному зорі для ритейлу, реалізовано понад 30 проектів. За нашими оцінками, автоматизація скорочує час мерчандайзингу на 30% і знижує out-of-stock на 15%.

Початок роботи з системою

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