001 Notes

Semantic Image Search From Embeddings To Ranked Results

Semantic image search means finding images by meaning, not only by filename, folder, timestamp, or exact pixels. In a product like ClusterLens, semantic search lets a user ask for visually or conceptually related images even when files have names like IMG_2048.jpg.

The key mechanism is an embedding. An embedding model turns an image, and sometimes text, into a vector. Search becomes nearest-neighbor lookup in that vector space.

1. What Semantic Search Is Actually Doing

A normal search asks direct metadata questions:

does filename contain "receipt"?
does folder name contain "invoice"?
was this image modified last week?

Semantic search asks a different question:

which images are visually or conceptually close to the query?

The query can be another image:

find images like this one

Or it can be text:

find images of whiteboard notes
find screenshots with error messages
find outdoor photos with cars

This only works if the system can convert images and queries into comparable vectors.

image -> embedding vector
text query -> embedding vector
compare vectors -> ranked results

The goal is not that the model knows the correct label for every image. The goal is to return a useful candidate list faster than manual browsing.

That distinction matters. Semantic search is retrieval, not truth.

2. Why This Exists

Image folders often have useless filenames:

IMG_2031.jpg
Screenshot_2026-07-10.png
download-4.jpeg

Filename search fails because the useful meaning is inside the image. Manual tagging works, but it is expensive. Users usually do not tag thousands of existing files before they need search.

Semantic search gives the product a way to retrieve likely matches without requiring perfect manual metadata. The output is a ranked review list, not a certified classification.

3. Visual Pipeline

flowchart LR
  A[Folder images] --> B[Image encoder]
  B --> C[Image vectors]
  C --> D[(Vector index)]
  E[Query image or text] --> F[Query encoder]
  F --> G[Query vector]
  G --> H[Similarity search]
  D --> H
  H --> I[Ranked candidates]
  I --> J[Metadata filters and rerank]
  J --> K[Explainable results]

The model defines the meaning of “near”. A CLIP-style model can put text and images in a shared space. A visual-only model may be strong for image-to-image similarity but not support text queries directly.

4. What Is An Embedding?

An embedding is a way to turn an object into a vector of numbers so that distance in vector space roughly matches similarity in meaning.

The object can be:

image
text
audio
video frame
email
PDF page
product listing
source code

The embedding is usually a vector:

image: beach_photo.jpg
vector: [0.12, -0.44, 0.03, 0.81, ...]

That vector is not meant to be read one dimension at a time. Dimension 17 does not necessarily mean “dog”, and dimension 44 does not necessarily mean “receipt”. The full vector is a location in a learned space.

The important idea:

similar things should land near each other
different things should land farther apart

If the model has learned visual structure well, these should be close:

photo of a dog in grass
photo of another dog in a park

And these should be farther apart:

photo of a dog
screenshot of a terminal error

5. Embeddings As Coordinates Of Meaning

Think of a map. A physical map gives coordinates for places:

Delhi -> latitude, longitude
Mumbai -> latitude, longitude

Nearby coordinates usually mean nearby places.

An embedding model gives coordinates for meaning:

beach image -> vector
forest image -> vector
invoice image -> vector
receipt image -> vector

In a useful embedding space:

beach images are near ocean photos
receipts are near invoices and document photos
screenshots are near other screenshots
whiteboard notes are near diagrams and handwriting

The vector space is learned from training data, so it is not perfect. But it gives the search system a geometry that filenames do not provide.

That geometry is what makes semantic search possible.

6. A Toy Embedding Space

Toy example with only two dimensions:

Imagedimension 1: outdoordimension 2: document-like
beach0.950.05
forest0.880.04
receipt0.050.94
invoice0.040.90
dog park0.820.12

This toy space suggests:

beach is near forest and dog park
receipt is near invoice
beach is far from receipt

Real spaces are higher-dimensional and less interpretable, but the search behavior is similar.

7. How Images Become Embeddings

For an image, the conversion path is:

flowchart LR
  A[Image file] --> B[Decode image]
  B --> C[Resize/crop/normalize pixels]
  C --> D[Vision model]
  D --> E[Feature vector]
  E --> F[Normalize vector]
  F --> G[Store embedding]

The code shape is:

for image in images:
    pixels = decode(image)
    input_tensor = preprocess(pixels)
    vector = model(input_tensor)
    vector = normalize(vector)
    store(image_id, vector, model_name, dimension)

The preprocessing step matters. Most embedding models expect a specific input shape and pixel normalization policy:

resize to expected size
center crop or pad
convert to RGB
scale pixel values
normalize by model-specific mean/std

If preprocessing is wrong, the embedding can be bad even when the model is good. A model trained on normalized RGB inputs may produce poor vectors if it receives BGR pixels, unscaled values, or stretched aspect ratios.

8. How Text Becomes An Embedding

Text follows a different path:

flowchart LR
  A[Text query] --> B[Tokenize]
  B --> C[Text model]
  C --> D[Feature vector]
  D --> E[Normalize vector]
  E --> F[Use as query embedding]

For text-to-image search, the text vector and image vectors must live in a compatible space.

That is why a CLIP-style model has two encoders:

image encoder: image -> vector
text encoder: text -> vector

The system can compare them only because the model was trained to align matching images and captions.

If the app indexed images with a visual-only model, natural-language text search may be invalid. The UI should disable it, route it to a compatible model, or clearly explain that the current index supports image-to-image search only.

9. Why Embeddings Work

Embeddings work because the model was trained to place related things near each other.

Different model families learn that geometry in different ways.

Classification Models

A classic vision model may be trained to classify images:

image -> model -> label probability

During training, the model learns internal features that help distinguish objects:

edges
textures
shapes
parts
object-level patterns
scene-level patterns

If you take the vector from a layer before the final classifier, that vector can be used as an embedding.

Why it works:

images that need similar features for classification often have similar internal representations

Good for:

visual similarity
object/category grouping
basic image organization

Weakness:

not naturally aligned with text queries
may focus on classification categories more than general meaning

Contrastive Image-Text Models

A CLIP-style model is trained on image/text pairs.

Training idea:

matching image and caption should have high similarity
non-matching pairs should have lower similarity

Example:

image: dog running on grass
text: "a dog running on grass"

The image encoder and text encoder learn to place those two items near each other.

Why it works:

image meaning and language meaning are trained into a shared space

Good for:

text-to-image search
image-to-image search
natural-language visual queries
zero-shot categories

Weakness:

depends heavily on training data
can miss fine-grained visual details
can reflect caption biases

Self-Supervised Vision Models

A DINO-style model learns visual representations without requiring explicit human labels for every image.

The training objective may force the model to produce stable representations across different crops, augmentations, or views of the same image.

Why it works:

the model learns visual structure that remains stable under transformations

Good for:

visual grouping
near-scene similarity
image clustering
fine visual structure

Weakness:

does not automatically support text queries unless paired with a text model or alignment step

10. What To Use For Embeddings

The right embedding model depends on the product.

Use CaseGood Embedding ChoiceWhy
text-to-image searchCLIP/SigLIP-style image-text modeltext and image share a space
image clustering onlyDINO/visual encoder/CLIP image encoderstrong visual representation
duplicate-ish visual groupingperceptual hash + embeddinghash catches cheap near-duplicates, embedding ranks
mobile/local appMobileCLIP/small ONNX modelfaster and easier to ship
server/GPU batch processinglarger CLIP/DINO-style modelbetter quality if latency allows
documents/emailsentence-transformer/text embedding modeloptimized for text chunks
code searchcode embedding modeltrained on code/text relationships

Practical rule:

If the user searches with text, use a model trained for text-image alignment.
If the user searches with example images only, a strong visual embedding may be enough.
If the user needs duplicates, do not rely only on semantic embeddings.

11. Model Choice Changes What Similar Means

This is the part many products hide.

Two models can produce different nearest neighbors for the same image.

A CLIP-style model may group by caption-like concept:

dog
receipt
screenshot
whiteboard

A self-supervised visual model may group by visual structure:

texture
layout
shape
scene composition

A perceptual hash may group by low-level visual fingerprint:

same image resized
same screenshot compressed
same photo lightly edited

So the product should not say:

these are objectively similar

It should say:

these are similar under this model and this scoring method

That is why the index should store:

model name
model version
embedding dimension
preprocessing config
normalization policy
created time
source file fingerprint

12. Embedding Dimension, Storage, And Index Cost

Embedding dimension is the vector length.

Examples:

384 dimensions
512 dimensions
768 dimensions
1024 dimensions

Higher dimension is not automatically better.

Higher DimensionLower Dimension
may preserve more informationless storage
more expensive to searchfaster search
larger indexeasier local deployment
may improve qualitymay be enough for simple tasks

Storage estimate:

N images * D dimensions * bytes_per_value

For 100,000 images with 512-dimensional float32 embeddings:

100,000 * 512 * 4 = 204,800,000 bytes
about 195 MB

If stored as float16:

about 97 MB

Embeddings are generated data. A product should show index size and allow deletion/rebuild.

13. Normalization

Many retrieval systems normalize embeddings before search:

v_normalized = v / ||v||

Why?

Because then dot product becomes cosine similarity:

dot(normalize(a), normalize(b)) = cosine(a, b)

Without normalization:

longer vectors can score higher because of magnitude

With normalization:

direction matters more than length

For semantic search, direction is usually the intended signal.

14. Cosine Similarity

Semantic search often uses cosine similarity because the direction of the vector matters more than raw magnitude.

score(query, image) = dot(normalize(query), normalize(image))

Worked example:

VectorValues
query “receipt”[0.0, 1.0]
receipt image[0.1, 0.99]
beach image[0.99, 0.1]

Scores:

CandidateDot scoreRank
receipt image0.991
beach image0.102

If vectors are not normalized, vector length can distort the score. This is why a robust pipeline normalizes embeddings before cosine scoring.

15. Embedding Quality Checks

Before trusting search results, test the embedding model with small probes.

For image search:

query: one receipt image
expected: other receipts/invoices near top

query: one screenshot
expected: other screenshots near top

query: one outdoor photo
expected: outdoor photos near top

For text-to-image search:

query: "whiteboard notes"
expected: whiteboards, diagrams, handwritten boards

query: "terminal error"
expected: terminal screenshots or error dialogs

Quality checks should include bad cases:

similar colors but different meaning
same object but different context
documents with similar layout but different content
screenshots from different apps

The point is not to prove the model is perfect. The point is to learn what kind of similarity it provides.

16. Why Embeddings Are Not Enough

Embeddings do not replace indexing, filtering, or product logic.

A good embedding can still return bad results if:

the wrong folder is searched
the index is stale
the query model differs from the stored model
metadata filters are hidden
top_k is too small
the user wanted duplicates, not semantic similarity

So embedding search should be combined with:

metadata filters
file freshness checks
model compatibility checks
hashes for duplicates
reranking
result explanations

The embedding is one signal, not the whole product.

Flat search compares the query vector against every indexed image vector.

best = []
for image in indexed_images:
  score = dot(query_vector, image.vector)
  if score >= min_score:
    best.add(image, score)
return top_k(best)

Visual model:

query vector
     |
     v
compare with image 1
compare with image 2
compare with image 3
...
compare with image N
sort by score

Flat search is exact and easy to debug. It becomes expensive when N grows large.

Flat search has complexity:

O(N * D)

Where:

N = number of images
D = embedding dimensions

For 10,000 images and 512-dimensional embeddings:

10,000 * 512 = 5,120,000 multiply-add operations

That is often fine on a desktop for moderate folders. Approximate search is an optimization after this exact baseline exists.

A product should not rely on vectors alone. Cheap filters narrow the candidate set and improve explanation.

Examples:

FilterWhy it helps
foldersearch only selected project or album
file extensionskip unsupported formats
date rangeavoid old irrelevant results
dimensionsfocus on screenshots, wallpapers, or photos
camera/deviceseparate capture sources
duplicate modeuse hashes before embeddings

Pipeline:

flowchart LR
  A[All indexed images] --> B[Folder/date/type filters]
  B --> C[Candidate IDs]
  C --> D[Vector scoring]
  D --> E[Top results]

This is both faster and easier to explain than scoring the whole world every time.

Image query search starts with an example image.

query image -> query embedding -> nearest image embeddings

Good uses:

  • find visually similar shots;
  • find near duplicates after editing or resizing;
  • find images from the same scene;
  • find related screenshots or documents.

Failure modes:

  • the query model differs from the indexed model;
  • query embedding dimension does not match the stored dimension;
  • the example is visually ambiguous;
  • metadata filters exclude the right answer;
  • the top score is low but still returned because top_k forced output.

A good product checks embedding dimension and model identity. If the folder was indexed with one embedding model and the query uses another, scores are not meaningful.

Text query search works when text and images can be embedded into a compatible shared space.

flowchart LR
  A[Text: red car at night] --> B[Text encoder]
  B --> C[Text vector]
  C --> D[Compare against image vectors]
  D --> E[Images that match the text meaning]

Important caveat:

text search is model-dependent

If the selected model does not support shared text/image embeddings, text search should be disabled or clearly routed to a compatible path. The UI should not pretend every visual model supports natural-language search.

Approximate nearest neighbor search avoids comparing against every vector.

It trades exactness for speed. The product question is:

Can we return nearly the same top results faster?

Not:

Can we change what similarity means?

The similarity definition stays the same. The index just narrows candidates faster.

HNSW

HNSW builds a navigable graph. Search starts from an entry point and walks toward neighbors that look closer to the query.

query -> enter graph -> move to closer neighbor -> move again -> collect candidates

Visual model:

far node --- mid node --- close node --- nearest node
                \             \
                 \             other candidate
                  skip many unrelated points

HNSW is fast and often accurate, but approximate. It can miss a true nearest neighbor if parameters are too small.

IVF/PQ

IVF partitions vectors into coarse buckets. PQ compresses vectors inside those buckets.

flowchart TD
  A[All vectors] --> B[Train coarse centroids]
  B --> C[Assign vectors to buckets]
  D[Query] --> E[Find closest buckets]
  E --> F[Search only probed buckets]
  F --> G[Return approximate candidates]

Tradeoff knobs:

KnobEffect
number of bucketsmemory/search partitioning
probesmore probes improve recall but cost time
PQ code sizesmaller memory, more approximation

The product should label ANN as candidate narrowing, not as a change in the definition of similarity.

22. Reranking

Reranking means:

use a cheap or approximate method to get candidates
use a stronger or more specific method to reorder them

ClusterLens-style examples:

flowchart LR
  A[All images] --> B[pHash/dHash/wHash prefilter]
  B --> C[Candidate duplicates]
  C --> D[Embedding score]
  D --> E[Optional ORB feature rerank]
  E --> F[Final review order]

Signals:

SignalGood atNot good at
perceptual hashnear duplicate narrowingbroad semantic meaning
embedding cosinevisual/semantic similarityexact local structure
ORBlocal feature overlapabstract meaning
metadatanarrowing and explanationvisual understanding

23. Stale Indexes

Every embedding index can become stale.

A file can be:

modified
deleted
moved
renamed
replaced

A model can change:

old embeddings: model A, 512 dimensions
new query: model B, 768 dimensions

That comparison is invalid.

Each indexed record should store:

file id or path
file size
modified time
embedding model id
embedding dimension
embedding vector
index version

Before search:

if file changed:
    recompute embedding

if model id mismatch:
    require reindex

if dimension mismatch:
    reject query

This is not optional. A stale semantic index creates confusing failures because the UI looks correct while the data is wrong.

24. Result Explanation

A search result should not only show thumbnails. It should say why the image appeared:

score: 0.83 cosine similarity
backend: flat or FAISS HNSW
filters: folder=Screenshots, type=png
rerank: ORB score added
hash distance: not used for this query
model: selected embedding model
index freshness: current

This turns semantic search from magic into an inspectable retrieval system.

25. Failure Modes

FailureSymptomProduct response
stale indexmissing or outdated imagesdetect mtime/size changes and reindex
wrong modelweird scores or dimension mismatchrequire reindex
low-quality embeddingpoor relevanceexpose model choice
approximate misstrue neighbor missingallow flat search or higher recall settings
overly broad querygeneric resultssuggest filters
hidden metadata filtergood result excludedshow active filters

26. What Was Done And Why

Semantic search was implemented as a retrieval system, not as a magic search box. The important decision is to store embeddings with metadata and freshness information, then combine vector scoring with ordinary filters. The vector score says “similar in the embedding space”. The metadata says whether that result is in the right folder, date range, file type, and index version.

Flat search is the correctness baseline. It compares the query vector against every candidate vector. It is slower at large scale, but it is exact and debuggable. Approximate indexes such as HNSW or IVF/PQ only make sense after the exact path exists, because the exact path gives you a recall baseline.

Reranking exists because no single signal answers every visual question. A hash can find near duplicates cheaply. An embedding can rank broader visual meaning. ORB can verify local feature overlap. Metadata can narrow the candidate set before expensive scoring. The final product should combine these signals openly rather than hiding one score behind a generic “AI match” label.

27. Implementation Blueprint

Minimal version:

for image in folder:
    embedding = embed_image(image)
    embedding = normalize(embedding)
    save(image_id, embedding, model_id, dimension, mtime, size)

query = embed_query(query_input)
query = normalize(query)

results = []
for record in records:
    if record.model_id != query.model_id:
        skip_or_reindex()
    if metadata_filter_fails(record):
        continue
    score = dot(query, record.embedding)
    results.append((record, score))

return top_k(results)

Production version:

add batch embedding
add cache invalidation
add query-time filters
add result explanations
add ANN index for scale
add reranking
add debug view
add rebuild controls

28. Implementation Checklist

A practical semantic image search implementation should have these gates:

index gate: every embedding has model id, dimension, file fingerprint, and timestamp
query gate: query vector dimension matches index dimension
filter gate: folder/date/type filters run before expensive scoring when possible
score gate: score threshold and top_k are visible in diagnostics
rerank gate: candidate order explains which signals were used
freshness gate: changed or deleted files invalidate stale records

Recommended build order:

StepWhy this order
flat cosine searcheasiest to validate
metadata filtersreduces bad results and work
stale-index detectionprevents confusing misses
result explanationsmakes search trustable
ANN indexperformance upgrade after correctness
rerankingimproves ordering after candidate quality exists

The main research question is not “which embedding model is best” in isolation. It is “which model, index, filter policy, and explanation surface produce useful review results for the data size and latency budget”.

What Was Used, Removed, Or Deferred

Used: image embeddings, normalized cosine scoring, SQLite-backed records, metadata filters, image-query search, text-query support where model-compatible, optional FAISS candidate narrowing, duplicate hash prefilters, and optional ORB rerank.

Removed or avoided: treating embeddings as exact labels, hiding model/dimension mismatches, and presenting approximate search as exact unless the path is flat.

Deferred: automatic model selection, universal text search for every embedding backend, and claims about semantic quality without a public benchmark.

Comments