Skip to content

Perceptual Hashing

polars-cv provides perceptual hashing for finding visually similar images. Unlike cryptographic hashes, perceptual hashes produce similar fingerprints for similar images.

Basic Usage

from polars_cv import Pipeline
import polars as pl

# Create hash pipeline
hash_pipe = Pipeline().source("image_bytes").perceptual_hash()

df = pl.DataFrame({"image": [image_bytes]})
result = df.with_columns(
    hash=pl.col("image").cv.pipe(hash_pipe).sink("list")
)

Algorithms

Algorithm Speed Robustness Best For
perceptual Medium High Most use cases (default)
average Fastest Lower Quick approximate matching
difference Fast Medium General purpose
blockhash Medium High Crop-resistant matching
Pipeline().source("image_bytes").perceptual_hash(algorithm="average")

perceptual is the standard pHash that imagehash.phash computes: grayscale, a Lanczos resize to 32×32, a 2-D DCT, and the 8×8 lowest frequencies compared with their median. The bits come row-major, most significant first, so int.from_bytes(bytes(h), "big") reads a 64-bit hash like the integer int(str(imagehash.phash(img)), 16). The two are close but not identical: the grayscale and the resize are polars-cv's own ops, which round differently from Pillow's, so 64-bit hashes typically differ from imagehash's in 0 to 2 bits. hash_size is the number of bits: hash_size=256 is the 16×16 block, as in imagehash.phash(img, hash_size=16).

Comparing Hashes

Use hamming_distance() and hash_similarity() for batch comparison:

from polars_cv import hamming_distance, hash_similarity

# Compare hashes from two pipelines
result = df.with_columns(
    similarity=hash_similarity(
        pl.col("image_a").cv.pipe(hash_pipe),
        pl.col("image_b").cv.pipe(hash_pipe)
    )
)

Next Steps