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

推荐订阅源

Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
罗磊的独立博客
S
SegmentFault 最新的问题
V
V2EX
V
Visual Studio Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
D
Docker
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
M
Microsoft Research Blog - Microsoft Research
Martin Fowler
Martin Fowler
S
Secure Thoughts
B
Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
T
True Tiger Recordings
GbyAI
GbyAI
P
Proofpoint News Feed
P
Privacy International News Feed
Jina AI
Jina AI
The Cloudflare Blog
I
Intezer
AWS News Blog
AWS News Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security Archives - TechRepublic
NISL@THU
NISL@THU
The Register - Security
The Register - Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
S
Schneier on Security
L
LINUX DO - 热门话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA

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

Как «спят» вкладки в браузере Приоритет задач определяется не только ощущением срочности [Перевод] Махинации с прибылью Anthropic Project Loom: Virtual Threads, Scoped Values и preview #7 Structured Concurrency Мнения математиков о том, как ИИ опроверг гипотезу Эрдёша Слабоумие и отвага: как я за выходные сделала прототип ИИ-помощника для UX-дизайнера ИИ учит нас писать лучше. Или хуже? Как проектировать ИИ-инструменты, которые делают пользователей лучше «Раньше хотел каждый, сейчас и бесплатно не надо»: гаджеты, про которые мы все забыли ИИ-агенты в бизнесе: почему 80% компаний увольняют людей, но не получают ROI Как я строил ИИ-стартап, или Новые архитектурные риски 2026 4 интересных парадокса, рождающих жаркие дискуссии Рабочее место не-вайбкодера: настраиваем harness Когнитивный инжиниринг Feature Based Clean Architecture. Часть 1: Эволюция NestJS-приложения в неподдерживаемое состояние Как мы перестали бояться «пустых охватов» и сделали инфлюенс-маркетинг управляемым каналом роста Подключили B2B email-платформу к голосовым ассистентам через MCP. Архитектура, код, где ломается [Перевод] Почему AI-агенты ломаются на длинных задачах — и как обвязка помогает им дописывать приложения Облачно, возможны нейросети: кризис датасетов и ахиллесова пята систем машинного зрения — DIY-чтение на выходные Спустя 5 лет и $5 миллионов: почему создание нового языка для веб-разработки оказалось ошибкой Безопасная песочница Облачная LLM на 16 ГБ VRAM — часть 2: LangGraph Server, LangSmith и SDK Современный SSH-клиент для MS-DOS Как продвигать агентство недвижимости: от вывески до прямых эфиров MCP для GitHub + GitLab: инженерный гайд 2026 Вы платите OpenAI $20 в месяц, а он зарабатывает на вас ещё $100 млн за полтора месяца. И это только начало ИИ забирает работу «белых воротничков»: чему учить детей, чтобы выжить в будущем Практический ИИ-агент Python: LangGraph + Qdrant Как я делал ping и traceroute на iOS без entitlements — и почему это оказалось проще, чем UMP-консент для AdMob 4 MVP за 4 месяца, 30 холодных DM, 1 регистрация: building in public по-русски VPS-бастион: доступ к домашнему серверу без белого IP Kampus AI — нейросеть для генерации учебных работ для студентов и школьников Игры, помогающие продавать — примеры интересных рекламных акций с видеоиграми €500 в Telegram Ads принесли сделку на 350 000 ₽. Разбор B2B-кампании Чтение на выходные: «Разработка игр и теория развлечений» Рафа Костера Личный архив: сбор, бэкап, таймлайн фотографий INFOSTART TECH EVENT или INFOSTART A&PM EVENT — как понять, куда вам нужнее? Peer testing на основе Закона Линуса Релиз GitLab 19.0: ИИ-оркестрация, которая наконец-то догнала темп написания кода Как бизнесу оценить готовность к аттестации по новому Приказу ФСТЭК № 117 Технический гайд по сторис – часть 4: как мы добавили видео формат Представительство в арбитражном процессе: правовые различия между внешним защитником и инхаусом «Где новые фичи?» — Как AI-миграция легаси вернет IT-бюджет бизнесу Что нужно знать работнику про увольнение Новые требования Москвы к ЦИМ для АГР: готовый инструмент для проектировщиков в nanoCAD BIM Строительство WireGuard: простота и надёжность современного VPN-туннеля или секретное рукопожатие в тёмной комнате Выйдет ли GTA 6 в 2026 году, и чего ждать от игры Как меня назвали «невовлечённым», а я нашёл офшоры на Кипре Как LLM научила рекомендательную модель видеть больше, чем историю взаимодействий От хаоса к экосистеме: Модель зрелости комьюнити в бизнесе Свет, тьма, VEML7700 и Python Сказ о том, как мы процессы разработки в GRI меняли. Часть 2 Майский «В тренде VM»: громкие уязвимости в Linux, ActiveMQ, SharePoint и Acrobat Reader Статический анализ, заряженный ИИ: как LLM ищут уязвимости в коде и где их границы Блок “Процессы” и почему мы называем его нашим мини-n8n Как поменялся рынок интернет-рекламы: сравнение первых кварталов 2025 и 2026 годов: исследование click.ru Мониторинг Kerio Connect через Zabbix 7: разбор шаблона без агентов и regex по DAT 671 Allow в Claude Code за день: как родился сетап Spec-build 3 известные интересные задачи на логику Как айтишнику позаботиться о менталке и не перерабатывать OpenAI vs Anthropic: битва экс-коллег за корпоративного клиента и $1 трлн на IPO SEO для интернет-магазина в 2026: что поменялось и как с этим работать Сможете ли вы спроектировать Maven‑монорепозиторий для 5 микросервисов? 6 неудобных вопросов про американское произношение, которые айтишники боятся задать Неожиданная встреча: теория графов вновь помогла решить проблему в анализе Фурье Иллюзия трансформации: почему компании платят за спектакль вместо изменений AMD представила Ryzen 9 PRO 9965X3D и еще 5 процессоров, которые пойдут далеко не всем История IDE в Google Первые отзывы на новинки о System Design Влияние параметра planner_upper_limit_estimation на планы выполнения и профиль нагрузки PostgreSQL при использовании 1C Границы 100% разработки с агентами Быстрый OCR на основе Paddle Дооснащение любительской электровакуумной мастерской. Вакуумметр, течеискатель, полярископ Mythos: модель, о которой Anthropic не говорит. Реверс по жертвам — от 27-летней дыры в OpenBSD до побега из песочницы Как использовать Qwen3.7-Max и Grok Build 0.1 для ИИ-агентов в России Suricata IPS NFQueue with nDPI. Часть VI Важные изменения в защите информации в России: что нового? В чем секрет достоверного замедления биологического старения? Вредное ускорение: Умный светофор на перегруженных перекрестках Как сисадмин написал свою библиотеку для Jira на Ruby: история Rujira Сломанный найм: почему рынок труда превратился в казино и что с этим делать Физики нашли свидетельства того, что Вселенная не идеально однородна, вопреки стандартной модели космологии Вопросы на собеседованиях, к которым лучше готовиться заранее Что детектировал детектор таксофонных карт? Как работают выделенные ядра в облачном сервере: от планировщика Linux до тестов производительности Математика кластеров: разбираемся в умной кластеризации данных на примере нашей системы поиска аномалий в логах. Часть 1 Ответы с «деврел‑супервизии», вопрос седьмой: выгорание, когда от вас ждут вечный драйв и креатив История одного // todo, который ждал своего часа пол года Если пропустили Claude последние 3 месяца: топ-5 фич с юзкейсами и история про $400K в Bitcoin Проектируем с нуля калькулятор на FPGA. Части 4 и 5: Фреймворк и оборудование Почему 10× от AI могут дать только лояльные сотрудники Speech-to-LaTeX: распознавание математических выражений и предложений в LaTeX Что внутри портфолио продуктовых и ux/ui-дизайнеров из Т-Банка, Додо, Figma, Альфы, Revolut? Чем заменить Excel в 2026 году: обзор российского ПО и других аналогов Как Rust обрабатывает repr и ABI на границе с C: что ломается и почему 5 промтов, чтобы подготовить презентацию в нейросетях через SpeShu.AI Каггл «200 ёлочек 2025»: призы уже раздали, но мы и за идею задачу укладки порешаем. Часть 1 Как ФНС стала data-driven за 5 лет: минус треть штата, плюс 20 новых цифровых сервисов Как настроить кастомную авторизацию в FESB и сохранить стандартный заголовок Как CISO защищаются от прошлого, игнорируя будущее
Как мы довели поиск товаров по изображению до 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-процесса.

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

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