

Сервис детектирования и распознавания товаров и цен на изображениях
Возможности
Решение от Napoleon IT позволяет ритейлерам определять цены и классифицировать товары на изображениях. Автоматизируется процесс ручного сбора данных мониторинга цен и товаров конкурентов.
Описание сервиса: Сервис состоит из нескольких моделей, которые позволяют последовательно распознать товары и ценники по изображениям. Product analyzer обучен на изображениях из Российских ритейл сетей.
Пайплайн моделей:
- Находит координаты всех товаров и ценников на изображении
- На ценниках находит область цены
- OCR модель распознает цены на ценниках
- Извлекает из изображений товаров вектора, отличимые в векторном пространстве. Эти векторы сравниваются с эталонными и если значение меньше порогового, то это означает принадлежность к классу.
Под каждую задачу используются следующие модели:
Детекция:
- EfficientDet5
Feature Extraction:
- ResNet обученный с применением ArcFace Loss
Детектирование цены в области ценника:
- Unet
OCR:
- AttentionOCR, обученная на миллионах примеров вырезанных цен
Преимущества
Бизнес преимущества: автоматизация процесса ручного сбора данных мониторинга цен и товаров конкурентов, а также автоматизированный контроль раскладки товаров.
Технические: Сервис уже собран в общий пайплайн распознавать и позволяет находить все товары и ценники, считывать текст цены и классифицировать товар без дополнительного обучения моделей, с помощью сравнения векторов признаков с эталонными.
Сценарии использования
- Мониторинг цен
Изначально формирование цены на товарные позиции в крупном магазине складывается из нескольких десятков переменных: затраты на закупку, логистику, выплату налогов, а также маржа, эластичность спроса, стоимость аналогов в других торговых сетях и пр. Чтобы их отслеживать, можно использовать ценовой мониторинг. Это система, которая по фотографии распознает товары на полках, их объем и цены, а затем агрегирует их стоимость у разных ритейлеров. Фото стеллажей в магазинах-конкурентах делают специальные сотрудники — мониторщики.
Как использовать? Ваш сотрудник отправляется на конкурентную разведку в ближайший магазин и присылает фотографии полок с товарами. Далее вы хотите распознавать товары на этих изображениях. Вы отправляете их на сервис, в ответ получаете все координаты товаров и их цен, а также вектора товаров, извлеченные моделью, которые можно сравнить с эталонными векторами полученными из фотографий в базе товаров.
- Контроль выкладки товаров
Ритейлер может легко потерять покупателей, если полки его магазинов окажутся пустыми: никто не захочет заходить в несколько супермаркетов, чтобы приобрести все необходимое. Данное решение позволяет избежать подобной ситуации, поможет отследить количество продуктов на стеллажах и сформировать заявки на закупки.
Как использовать? Для проверки уровня заполненности полки в магазине необходимо ее сфотографировать. Далее отправить на сервис, в ответ получить все координаты товаров, на основе которых определяется сколько пространства заполнено товарами.
Инструкции по использованию
import base64
import requests
# считать изображение в байтах
with open("test_image.jpg", "rb") as fp:
encoded_string = base64.b64encode(fp.read()).decode("utf-8")
headers = {
"content-type": "application/json",
"x-api-key": "your-api-key",
"x-workspace-id": "your-workspace-id"
}
# отправить запрос на распознавание
results = requests.post(
server_url, json={"image": encoded_string}, headers=headers
)
image_recognition_results = results.json()["raw_shelf_results"]
product_contours = image_recognition_results["raw_product_contours"]
pricetag_contours = image_recognition_results["raw_pricetag_contours"]
price_contours = image_recognition_results["raw_price_contours"]
Ответ от сервиса приходит в формате json:
"raw_shelf_results":
{
"raw_product_contours": Список задетектированных товаров на изображении и их эмбеддинги
[
{
"coords": [x, y, width, height] - Координаты товаров на изображении
"embedding": [] Вектор 512 значений, извлеченный из задетектированного товара
}
]
"raw_pricetag_contours": Список задетектированных ценников на изображении
[
{
"coords": [x, y, width, height] - Координаты ценников на изображении
}
]
"raw_price_contours": Список распознанных цен на изображении, координаты и вероятности распознаваний
[
{
"coords": [x, y, width, height] - Координаты цен на изображении
"price": str Распознанная на изображении цена (рубли и копейки)
"proba": float Вероятность распознанной цены
}
]
}
Отрисовать результаты распознавания на изображении можно следующим образом:
import cv2
import matplotlib.pyplot as plt
def plot(src, cmap=None, title=None, size=(10, 10)):
%matplotlib inline
plt.rcParams["figure.figsize"] = size
plt.imshow(src, cmap)
plt.title(title)
def plot_boxes(img, contours: list, color=None, labels=None, line_thickness=None):
if not labels:
labels = [None for i in range(len(contours))]
# Plots one bounding box on image img
for contour, label in zip(contours, labels):
tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness
c1, c2 = (contour[0], contour[1]), (contour[2]+contour[0], contour[3]+contour[1])
img = cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
if label:
tf = max(tl - 1, 1) # font thickness
t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [0, 0, 0], thickness=tf, lineType=cv2.LINE_AA)
return img
image = cv2.cvtColor(cv2.imread("test_image.jpg"), cv2.COLOR_BGR2RGB)
plot(img, title=image_name)
products_img = plot_boxes(
image,
[i["coords"]
for i in product_contours],
color=(0, 255, 0),
labels=["product" for i in range(len(product_contours))]
)
plot(products_img)
pricetags_img = plot_boxes(
image,
[i["coords"] for i in pricetag_contours],
color=(255, 0, 0),
labels=["pricetag" for i in range(len(pricetag_contours))]
)
prices_img = plot_boxes(
pricetags_img,
[i["coords"] for i in price_contours],
color=(125, 125, 125),
labels=[i["price"] for i in price_contours]
)
plot(prices_img)
Классификация товаров
В данном случае классификация товаров происходит путем расчета Евклидова расстояния между извлеченными векторами, товар считается классифицированным по достижению порогового значения расстояния. Модель извлечения вектора из изображения обучена таким образом, что расстояние между векторами похожих изображений минимизируется а между векторами разных изображений максимизируется.
База с товарами, которые мы будем классифицировать в таком случае будет выглядеть как изображения товаров и извлеченные из них вектора той же длины и той же моделью.
Добавить свой товар в базу для распознавания можно путем добавления фотографий этого товара с разных ракурсов(чем больше вариативность тем лучше) и извлечения векторов из этих изображений. Таким образом у нас реализован подход Zero-shot learning, для добавления нового класса нам не нужно переобучать модель.
Отправим изображение для извлечения вектора и создадим базу товаров :
import base64
with open("adrenalin.jpg", "rb") as fp:
encoded_string = base64.b64encode(fp.read()).decode("utf-8")
results = requests.post(server_url, json={"image": encoded_string}, headers=headers)
image_recognition_results = results.json()["raw_shelf_results"]
product_contours = image_recognition_results["raw_product_contours"]
pricetag_contours = image_recognition_results["raw_pricetag_contours"]
price_contours = image_recognition_results["raw_price_contours"]
# проверим, что товар корректно задетектился
img = cv2.cvtColor(
cv2.imread(f"adrenalin.jpg"), cv2.COLOR_BGR2RGB)
products_img = plot_boxes(
img.copy(),
[i["coords"]
for i in product_contours],
color=(0, 255, 0),
labels=["product" for i in range(len(product_contours))]
)
plot(products_img)
from scipy.spatial import distance
def find_closest(desc_1, base):
"функция классификации по векторному расстоянию"
dists = []
for d in base:
desc_2 = d["desc"]
dist = distance.euclidean(desc_1, desc_2)
dists.append(dist)
ind = int(np.argmin(dists))
sku = base[ind]["sku"]
return sku, dists[ind]
# добавим в базу извлеченный вектор
base = [{"sku": "adrenaline", "desc": product_contours[0]["embedding"]}]
# фильтруем вектора найденных контуров по расстоянию до вектора из base
matched_contours = []
distance_threshold = 0.7
for product_contour in product_contours:
product_embedding = np.array(product_contour["embedding"])
closest_product, dist = find_closest(product_embedding, base)
if dist <= distance_threshold:
matched_contours.append((product_contour["coords"], closest_product))
img_copy = img.copy()
products_img = plot_boxes(
img.copy(),
[i[0] for i in matched_contours],
color=(0, 255, 0),
labels=[i[1] for i in matched_contours]
)
plot(products_img)
Ccылки
https://arxiv.org/abs/1801.07698
https://arxiv.org/abs/1911.09070