FImageSearch: Multimodal Search for an AI Stylist Agent

Case Study

A client was building a personal AI stylist that lives in messengers: you send it a street-style photo, it finds every piece of the look in a shop catalog. The stylist agent needs a search core it can call — and that core is what this project is: a shop-the-look engine that detects every garment on a photo and matches each one against 105,100 catalog items in a couple hundred milliseconds. Photo, text, photo + text refinement, manual crop — all four query modes land in the same vector space. The first working demo went live in one evening.

First demo in one evening105,100 items indexedFull look in ~100–200 ms< 3 GB VRAM

Recorded on a phone against the live demo: full-look analysis, match percentages, manual crop and text refinement over the real 105k catalog.

Open the live demo

The demo runs on an on-demand GPU box, so it sleeps between showings — if the link is down, get in touch and I will wake it up for you.

FImageSearch home screen and a street-style look analyzed with 7 items found
Search results grouped by garment with match percentages
Another look auto-detected and bag results with match percentages
Manual crop mode with a drag box around a garment
Menswear look detected and matching tank tops from the men’s section
1 / 5
Snap it or describe it: upload a photo (or pick a preset look) — auto-detect finds every garment. This look: 7 items, analyzed in 208 ms.

The numbers

105,100
Catalog indexed
full public H&M dataset (Kaggle, 2022)
74–225 ms
Full look analysis
detect + embed + search, up to 7 slots
16–26 ms
Simple search
text or single image query
768-d
Embeddings
~271 MB for the whole catalog
< 3 GB
GPU memory
all three models resident in VRAM
~$64/mo
Infra cost
rented GPU box + Cloudflare Tunnel

How a photo becomes ranked results

One HTTP request runs the whole pipeline. The trick is batching: every garment crop plus the person crop are embedded in a single GPU forward pass, and all slots go to the vector DB as one batched query.

photo ──▶ person detection            RT-DETRv2, ~15 ms
      ──▶ garments in person crop     YOLOS-Fashionpedia, 46 classes
      ──▶ 9 slots, head to toe        headwear → … → shoes, max 7 per look
      ──▶ ONE GPU batch               all crops + person, fashionSigLIP, 768-d
      ──▶ ONE batched Qdrant query    hard filters: group / category / gender
      ──▶ CIELAB color re-rank ──▶ ranked results with % match

Vector similarity alone is not enough — the search is hybrid. Hard metadata filters ride on top of every query: a shoe crop only searches Shoes, a men’s look only searches the men’s sections, and jacket families are excluded from top slots so a layered outfit doesn’t drown in puffer jackets. Inside those fences, the vector ranks by similarity.

What the models couldn’t do out of the box

The interesting engineering is in the gap between “embeddings work” and “results look right”. Everything below was found on real photos and fixed with measurements, not vibes:

SigLIP is nearly color-blind on basics

Black vs beige basic tees differ by Δcos ≈ 0.01 — even on a clean fabric crop. Fix: pre-computed dominant CIELAB color for all 105k items and a color re-rank (weight 0.12, lightness at half weight, since user photos live in shadows and catalogs in studio light).

The catalog skews the gender vote

First gender-detection attempt — majority vote over search results — deadlocked: the catalog is 3:1 womenswear, so male crops legitimately pull female items. Fix: a zero-shot probe comparing the person crop against “a man” / “a woman” text anchors in the same batch. ~2 ms, 7/7 on reference photos.

Phones read as sunglasses

In mirror selfies the fashion detector sees the phone at face level as glasses (it returned phone cases for a glasses slot). Fix: cell-phone boxes from the person detector’s same forward pass — zero extra cost — suppress glasses/headwear that overlap them.

Padding pulls the jacket into the shirt

A top worn under an open jacket embeds badly with context padding — the crop drags the blazer in and returns jackets. Verified by sweep: layered tops are embedded with no padding at all, at a lowered acceptance floor.

A pair of shoes is mostly asphalt

The union box around both shoes mid-stride is dominated by road surface, which poisons the embedding. The UI highlights both shoes, but only the larger, less occluded one is embedded.

Raw cosine scores make no visual sense

Text and image cosines live in different ranges; a linear clamp showed every result as 100%. Fix: per-modality logistic calibration, so the “97% match” badge actually ranks results and never quite reaches 100.

What I built

Two-stage detection

RT-DETRv2 finds the people, picks the main person; YOLOS-Fashionpedia (46 garment classes) runs inside that crop — 2–4× effective resolution on small items like glasses and shoes.

Nine-slot look taxonomy

Detections resolve into head-to-toe slots (headwear → shoes) with rules for dress-vs-top+bottom conflicts, layered outfits and slot-specific crop padding, calibrated by sweeps on reference photos.

One shared vector space

marqo-fashionSigLIP embeds images and text into the same 768-d cosine space. Photo + “but in red” is a weighted vector sum (0.6 image + 0.4 text) — and multilingual queries work for free.

Hybrid search over Qdrant

Batched queries with hard payload filters per slot (group, category, gender section) plus keyword indexes — vector similarity ranks only within the right shelf of the shop.

Resumable indexing that survived a dead server

Embeddings persist as incremental shards; the index restores without re-embedding. The original GPU box was physically destroyed mid-project, then the whole thing migrated Germany → Poland → California — no GPU re-run needed.

Honest-UX frontend

Next.js 14 mobile-first UI: auto-analysis on upload, tap-synced dots and result rows, WB-style manual cropper, % on every card and a “no exact match — closest options” banner instead of pretending.

Stack

Python · FastAPI · PyTorch fp16 · open_clip · marqo-fashionSigLIP · RT-DETRv2 · YOLOS-Fashionpedia · Qdrant · Next.js 14 · Tailwind + shadcn/ui · Vast.ai GPU · Cloudflare Tunnel + R2

Want visual search over your catalog?

The core is domain-agnostic: detector → slots → embeddings → filtered vector search → re-rank. Fashion today; furniture, interiors or any visual catalog with a week of calibration. If your product needs “find me this” — let’s talk.

Get in touch

Solo project, July 2026. Demo over the public H&M Kaggle dataset (2022); prices in the demo are illustrative. Client and commercial details are under NDA.

Читати цей кейс українською