惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 司徒正美
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
J
Java Code Geeks
The Cloudflare Blog
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
H
Help Net Security
S
Security @ Cisco Blogs
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
O
OpenAI News
L
LINUX DO - 最新话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Help Net Security
Help Net Security
F
Full Disclosure
博客园 - 叶小钗
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
Scott Helme
Scott Helme

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как мы довели поиск товаров по изображению до 98% совпадений: FastAPI, DINOv2, Qdrant и поиск на фото полки
ksrepin (Fix · 2026-05-13 · via Все публикации подряд на Хабре

Время на прочтение6 мин

Охват и читатели867

Кейс

Поиск товара по изображению кажется простой задачей — ровно до момента, пока не сталкиваешься с реальным каталогом.

В теории все выглядит аккуратно: берем фото, считаем embedding, ищем ближайшие вектора, возвращаем совпадения. На практике начинаются нюансы: у товара несколько изображений, ракурсы отличаются, фон мешает, каталог обновляется постоянно, а бизнес ждет не исследовательский прототип, а сервис, который можно поставить в production.

С вами старший программист в Fix Price Константин Репин. И в этом материале разберу, как мы строили сервис визуального поиска товаров, какие инженерные решения реально повлияли на качество и почему текущий результат в 98% совпадений получился не из-за одной удачной модели, а из-за правильно собранного пайплайна.

Что нужно было построить

Задача была прикладная: по изображению найти соответствующий товар в каталоге.

Дополнительно хотелось закрыть еще один сценарий: определить, присутствует ли конкретный SKU на фотографии полки.

По сути, у нас было два связанных кейса:

1. "image-to-catalog search"

   Пользователь загружает изображение товара, сервис возвращает наиболее релевантные позиции каталога.

2. "SKU-on-shelf detection"

   Пользователь загружает фото полки и передает SKU, а сервис отвечает, есть ли этот товар на изображении и где именно он находится.

Оба сценария используют одно ядро: визуальные эмбеддинги и векторный поиск. Но требования к пайплайну у них разные, и это сильно повлияло на архитектуру.

Как устроено решение

Сервис построен на "FastAPI" и состоит из трех основных слоев:

- "PostgreSQL" хранит карточки товаров и метаданные.

- "Qdrant" хранит векторный индекс изображений.

- "DINOv2" строит embedding по изображению.

Схема работы для обычного поиска выглядит так:

1. API получает изображение файлом или по URL.

2. Энкодер преобразует его в embedding.

3. Qdrant ищет ближайшие вектора по cosine similarity.

4. Результаты группируются по товару.

5. Из PostgreSQL подтягиваются данные карточек.

6. Клиент получает готовую выдачу.

На этом уровне система уже рабочая. Но если остановиться здесь, качество быстро упирается в потолок.

Почему "один товар = один вектор" почти всегда проигрывает

Первая очевидная проблема в визуальном поиске товаров: один товар редко можно адекватно описать одной картинкой.

У карточки товара обычно есть:

- основное изображение;

- дополнительные фото;

- боковые и фронтальные ракурсы;

- фото упаковки под разным углом;

- иногда старые и новые версии визуала.

Если в индекс отправить только одну картинку, поиск будет хорошо работать только в тех случаях, когда пользовательский запрос визуально очень близок к главному изображению в каталоге. Любое отклонение по ракурсу, масштабу или композиции сразу снижает качество.

Поэтому ключевым решением стала индексация всех доступных изображений товара.

Это дало сразу несколько эффектов:

- вырос шанс корректного совпадения по неосновному ракурсу;

- уменьшилось количество "почти правильных" результатов;

- улучшился recall без изменения клиентского API.

По сути мы перешли от модели "товар представлен одной точкой" к модели "товар представлен облаком точек в векторном пространстве".

Почему пришлось делать дедупликацию выдачи

Как только у одного товара появляется несколько векторов, возникает следующая проблема: поиск начинает возвращать один и тот же товар несколько раз.

С точки зрения Qdrant это нормально: он ищет ближайшие вектора, а не бизнес-сущности.

С точки зрения пользователя это плохой UX: вместо списка товаров он видит список очень похожих картинок одного и того же SKU.

Поэтому после поиска мы добавили дедупликацию по "product_id":

- из всех найденных совпадений для товара выбирается лучший score;

- на выдачу идет одна карточка товара;

- при этом сохраняется ссылка на конкретное изображение, которое дало лучший матч.

Это небольшой, но важный слой логики. Он не меняет математику поиска, но сильно улучшает воспринимаемое качество результата.

Почему мы остановились на DINOv2

Изначально проект проектировался с поддержкой двух энкодеров: "CLIP" и "DINOv2". Это было логичное решение на старте.

CLIP хорошо подходит для мультимодальных сценариев, zero-shot задач и связки "текст + изображение". Для быстрого старта visual search это удобный и понятный выбор: экосистема зрелая, модель распространенная, интеграция относительно простая.

Но по мере работы стало понятно, что наш основной кейс другой. Мы решаем не текстово-визуальный поиск, а "image-to-image" матчинг внутри закрытого товарного каталога, где:

- много визуально похожих SKU;

- важны мелкие отличия упаковки;

- один и тот же товар может быть снят под разными углами;

- запросы часто далеки от идеальных студийных изображений;

- в shelf-сценарии приходится работать еще и с crop-ами после детекции.

В таком режиме CLIP оказался не лучшим компромиссом. Он хорошо понимает общую семантику изображения, но для тонкого различения похожих товарных позиций этого недостаточно. По внутренним прогонам стало видно, что для каталожного поиска нам важнее не универсальность мультимодальной модели, а устойчивое визуальное пространство признаков именно для image-to-image similarity.

Поэтому рабочий контур был переведен на "DINOv2", а текущая реализация использует "facebook/dinov2-large".(*Facebook запрещен в РФ, владелец — компания Meta, признанная экстремистской и запрещенная в России). 

Причины перехода были практическими:

- "DINOv2" дает более стабильные эмбеддинги для визуально близких товаров;

- модель лучше переносит различия в фоне, масштабе и композиции кадра;

- снижается количество ложных совпадений между похожими, но разными SKU;

- лучше работает сценарий с вырезанными регионами товара на фото полки;

- итоговое качество поиска на реальных данных оказалось выше, чем у CLIP.

Иными словами, CLIP был полезным этапом эволюции проекта, но для нашей прикладной задачи победил DINOv2.

Для задач визуального сходства товаров важны не только "узнаваемость объекта", но и устойчивость к:

- фону;

- масштабу;

- артефактам изображения;

- небольшим искажениям;

- различиям между студийным фото и реальным снимком.

DINOv2 показал себя как сильная база для такого сценария. Он дает более устойчивое пространство признаков, чем более простые варианты, и хорошо подходит для поиска похожих объектов по изображению.

Важно и то, что переход с CLIP на DINOv2 означал не просто замену одной модели на другую. При смене энкодера меняется размерность векторов и само пространство эмбеддингов, поэтому такой шаг требует полной переиндексации каталога и аккуратной синхронизации с векторной БД.

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

Почему производительность пришлось решать одновременно с качеством

Как только в пайплайн приходит более тяжелая модель, сразу встает вопрос latency.

Если сделать сервис точным, но медленным, он останется красивой демонстрацией, а не production-решением. Поэтому в проекте модели работают не как одиночные экземпляры, а через пулы.

Это касается и энкодера, и детектора:

- запросы можно обрабатывать параллельно;

- GPU используется эффективнее;

- сервис лучше держит нагрузку;

- рост качества не превращается в неприемлемое время ответа.

С инженерной точки зрения это был обязательный шаг.

Мы не просто улучшали метрику совпадений, а строили систему, которая должна жить под реальными запросами.

Как появился сценарий поиска товара на фото полки

Когда базовый поиск по изображению стал достаточно стабильным, логично было пойти дальше и закрыть еще один прикладной кейс: поиск конкретного SKU на фотографии полки.

Здесь пайплайн уже другой.

1. На вход приходит фото полки.

2. "YOLO-World" находит области, похожие на товары.

3. Каждая найденная область вырезается в crop.

4. Для каждого crop считается embedding.

5. Он сравнивается не со всем каталогом, а с эталонными векторами конкретного SKU.

6. Если similarity превышает порог, считаем, что товар найден.

Почему это важно:

- мы переходим от "поиска похожей картинки" к ответу на бизнес-вопрос;

- система начинает решать задачи shelf analytics;

- можно не просто найти товар, а локализовать его на изображении.

Здесь особенно хорошо проявилось преимущество индексации нескольких изображений товара.

Чем богаче reference-набор для SKU, тем выше шанс корректно распознать его на реальной полке.

Что в итоге дало основной прирост качества

Резюмируя, 98% совпадений — это результат не одной «волшебной» настройки, а комбинации решений. 

Наибольший вклад дали:

- переход на "DINOv2";

- индексация всех изображений товара, а не только основного;

- дедупликация выдачи по "product_id";

- инкрементальная синхронизация каталога;

- распараллеливание инференса через пулы моделей;

- отдельный detection-пайплайн для фото полки.

Именно комбинация этих решений превратила проект из базового image search в более зрелый сервис.

Что есть сейчас и что дальше

На текущий момент у нас работает платформа для двух сценариев:

- поиск товара по изображению;

- поиск конкретного SKU на фотографии полки.

Текущий результат по внутренней проверке на рабочей выборке составляет 98% совпадений при поиске товаров.

Следующий логичный шаг уже не в самой архитектуре, а в формализации качества:

- зафиксировать контрольную выборку;

- автоматизировать расчет метрик;

- добавить регулярный мониторинг качества поиска;

- отделить разовые удачные прогоны от стабильного benchmark-процесса.

Это позволит не просто сохранить текущий результат, а улучшать его управляемо.

Подробнее о нашей работе в этом направлении расскажу в следующих публикациях.