Система комп'ютерного зору для розпізнавання товарів на полиці — ключ до скорочення втрат. Пуста полиця або товар не на своєму місці — ритейлер втрачає до 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.Процес роботи
- Аналітика та збір даних — визначаємо перелік SKU, збираємо поличні знімки (від 20 зображень на SKU для різних ракурсів).
- Розмітка та підготовка датасету — анотуємо bounding boxes та класи. Для пілоту достатньо 500–1000 розмічених зображень.
- Навчання моделі — fine-tuning YOLOv8 та ArcFace на зібраному датасеті. Ітеративний цикл з валідацією на тестовій вибірці. Використовуємо loss-функції Triplet Loss та ArcFace для кращої роздільності embedding'ів. Також застосовуємо контрастне навчання та триплетну втрату для покращення F1-міри.
- Інтеграція та тестування — розгортання REST API, підключення до камер, перевірка на реальних полицях. Налаштовуємо MLOps pipeline (CI/CD для моделей, моніторинг дрейфу, дашборди ритейл аналітики).
- Деплой та підтримка — встановлення на сервер, налаштування 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 тижнів. Гарантуємо конфіденційність даних.







