Freedom Sale
Independence Day Special — Unlock the AI Path 70% off our most popular AI course · Limited time offer
--Days
--Hrs
--Min
--Sec
Claim Your Discount
✦ Intermediate ⏱ 40 min

🔍 Build a Local Image Search with CLIP

🎯 What You'll Build

A semantic image search engine that indexes photos using CLIP embeddings, then returns the best matching images for any natural language query — no labels needed.

📋 What You'll Need

1

Understand CLIP embeddings

CLIP (Contrastive Language-Image Pretraining) maps images and text into the same vector space. Similar concepts are close together.

from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import torch

# Download CLIP (~600 MB on first run)
model     = CLIPModel.from_pretrained('openai/clip-vit-base-patch32')
processor = CLIPProcessor.from_pretrained('openai/clip-vit-base-patch32')

# Compare an image to text descriptions
image = Image.open('dog.jpg').convert('RGB')
texts = ['a dog', 'a cat', 'a car', 'a mountain']

inputs = processor(text=texts, images=image, return_tensors='pt', padding=True)
with torch.no_grad():
    outputs = model(**inputs)

# Higher score = better match
probs = outputs.logits_per_image.softmax(dim=1)
for text, prob in zip(texts, probs[0]):
    print(f"{text:15} {prob.item():.2%}")
a dog           86.3%
a cat            8.1%
a car            3.2%
a mountain       2.4%
2

Index a folder of images

Pre-compute embeddings for all images and save them — then search at interactive speed.

import os, json
import numpy as np
from pathlib import Path

IMAGE_DIR  = Path('my_photos')
INDEX_FILE = 'image_index.npy'
PATHS_FILE = 'image_paths.json'

def index_images(image_dir):
    paths = list(Path(image_dir).rglob('*.jpg')) + \
            list(Path(image_dir).rglob('*.png')) + \
            list(Path(image_dir).rglob('*.jpeg'))

    print(f"Indexing {len(paths)} images...")
    embeddings = []

    for i, path in enumerate(paths):
        try:
            img    = Image.open(path).convert('RGB')
            inputs = processor(images=img, return_tensors='pt', padding=True)
            with torch.no_grad():
                feat = model.get_image_features(**inputs)
            feat = feat / feat.norm(dim=-1, keepdim=True)  # normalise
            embeddings.append(feat[0].numpy())
            if (i+1) % 10 == 0:
                print(f"  {i+1}/{len(paths)} done")
        except Exception as e:
            print(f"  Skipped {path}: {e}")

    np.save(INDEX_FILE,  np.array(embeddings))
    with open(PATHS_FILE,'w') as f:
        json.dump([str(p) for p in paths], f)

    print(f"Index saved! {len(embeddings)} images indexed.")

index_images(IMAGE_DIR)
3

Search by natural language query

Embed the search query with CLIP and find the closest image embeddings using cosine similarity.

import json
import numpy as np

def search_images(query, top_k=5):
    # Load index
    embeddings = np.load(INDEX_FILE)
    with open(PATHS_FILE) as f:
        paths = json.load(f)

    # Embed the text query
    inputs = processor(text=[query], return_tensors='pt', padding=True)
    with torch.no_grad():
        text_feat = model.get_text_features(**inputs)
    text_feat = text_feat / text_feat.norm(dim=-1, keepdim=True)
    text_vec  = text_feat[0].numpy()

    # Cosine similarity
    scores = embeddings @ text_vec
    top_idx = scores.argsort()[::-1][:top_k]

    print(f"\nTop {top_k} results for: '{query}'\n")
    for rank, idx in enumerate(top_idx, 1):
        print(f"  {rank}. {paths[idx]}  (score: {scores[idx]:.4f})")

    return [paths[i] for i in top_idx]

search_images("a sunset over the ocean")
search_images("people eating food at a table")
search_images("dog playing in the grass")
💡 Tip: CLIP was trained on 400 million image-text pairs from the internet. It understands visual concepts without any labelling on your part — it genuinely "reads" an image. The normalise step (dividing by the L2 norm) converts embeddings to unit vectors so that dot product equals cosine similarity.

🎉 You Did It!

You built semantic image search that works on your own private photo library with no cloud uploads and no labels. This is the same technology behind Google Photos "search by what you remember" and Pinterest visual search.

Found something wrong?

Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.