Compare two images' similarity from perceptual hashes
Two 16-bit perceptual hashes that differ in 2 positions have a Hamming distance of 2 and are 87.5 % similar. Paste two fingerprints made of 0s and 1s and see how close the images are without sending either file. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Hash a
- 1011001110001111
- Hash b
- 1011001010101111
- Hamming distance
- 2
- Similarity percentage
- 87.5
- Bits
- 16
Compare two images without uploading either file when you already have their perceptual hashes. This tool accepts two equal-length binary hashes, checks every bit position, and reports both the Hamming distance and a similarity percentage. A distance of zero means the hashes are identical, while larger distances indicate more perceptual difference. The calculation is deterministic, fast, private, and useful for duplicate detection, media review, migration checks, and automated image pipelines.
Prepare comparable perceptual hashes
A perceptual hash is a compact fingerprint designed to preserve visual relationships rather than exact file bytes. Before using this comparison, generate both hashes with the same hashing method, dimensions, bit length, preprocessing rules, and output convention. For example, do not compare an average hash from one image with a difference hash from another, even if both happen to contain the same number of bits.
This capability expects each value as a non-empty binary string made only of 0 and 1. It deliberately rejects hexadecimal, separators, prefixes, and whitespace so that every character represents exactly one measured bit and the result cannot depend on an implicit decoding guess. Both strings must also be the same length.
That equality is essential because Hamming distance compares corresponding positions; a shorter hash has no meaningful bit to compare with the extra positions in a longer one. If your source stores hashes in hexadecimal, convert both values to equally padded binary strings before submitting them. Preserve leading zeroes during conversion, since dropping them changes the declared length and position alignment even though a numeric parser might treat the values as equivalent.
Understand distance and percentage
The Hamming distance is the number of positions where the two hashes contain different bits. If two 64-bit hashes differ at five positions, their distance is five. The similarity percentage expresses the complementary share of matching positions: the number of equal bits divided by the total bit length, multiplied by one hundred.
An identical pair therefore returns distance zero and similarity one hundred percent. A pair that differs at every position returns a distance equal to its length and zero percent similarity. Treat the percentage as a normalized hash comparison, not as a universal probability that two files depict the same subject.
Thresholds depend on the perceptual-hash algorithm, hash size, image domain, and the cost of false matches. A duplicate-photo workflow may accept only very small distances, while a visual-clustering workflow may intentionally use a broader threshold. Calibrate that decision with representative known matches and non-matches from your own collection rather than copying a threshold designed for another hash family.
Use the result safely in a pipeline
A practical duplicate-detection pipeline usually generates one consistent perceptual hash per asset, compares candidate pairs, and then applies a policy to the returned distance. Store the hash method and bit length beside each fingerprint so incompatible records can be rejected before comparison. Use a coarse index or another candidate-generation step for large collections; comparing every image with every other image grows quadratically and is rarely necessary.
When a pair falls near your chosen threshold, keep the original images available for human review or a more detailed visual check. Perceptual hashing is intentionally tolerant of some transformations, so it can find resized or recompressed variants that cryptographic checksums miss, but it can also place visually simple images close together. This capability does not inspect pixels, identify content, or prove ownership.
It only compares the two bit strings supplied to it. The computation uses no network, randomness, clock, or hidden state, so the same valid pair always produces the same result.
What you can do with it
Detect likely duplicate photos
Compare consistently generated hashes and send pairs below your calibrated distance threshold to review.
Check a media migration
Confirm that recompressed or resized destination images remain perceptually close to their source assets.
Group visually related assets
Use normalized similarity scores as one signal when organizing a catalogue into candidate visual clusters.
FAQ
What input format is accepted?
Both must contain the same number of bits.
What does a similarity of 100 percent mean?
It means every supplied bit matches. It does not prove that the original image files are byte-for-byte identical.
Can I compare hashes made by different algorithms?
You should not. Equal length alone does not make average hash, difference hash, and perceptual hash outputs semantically comparable.
Does the tool upload or inspect my images?
No. It receives only the two hash strings and performs a deterministic local bit comparison.
Why must both hashes be the same length?
Because the distance compares bits position by position. A shorter hash has nothing to set against the extra positions of a longer one, so both must come from the same method, size and settings before you compare them.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/image/compare-similarity \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"hash_a":"1011001110001111","hash_b":"1011001010101111"}'const res = await fetch("https://api.kit.forhosting.com/image/compare-similarity", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"hash_a": "1011001110001111",
"hash_b": "1011001010101111"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/image/compare-similarity",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"hash_a": "1011001110001111",
"hash_b": "1011001010101111"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/image/compare-similarity", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"hash_a":"1011001110001111","hash_b":"1011001010101111"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"hash_a":"1011001110001111","hash_b":"1011001010101111"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/image/compare-similarity", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"hash_a": "1011001110001111",
"hash_b": "1011001010101111"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "image.compare_similarity",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_chars | 4096 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Use the result safely in a pipeline
Browser use is free, while automated API calls use the published $0.002 request price.
Technical questions
Why must the hashes have equal lengths?
Hamming distance compares matching positions. Unequal strings leave some positions without a counterpart, so the request is rejected.
What does an API request cost?
The API price is $0.002 per request. The browser version runs locally for free.