Library / SDK
aetilius/pHash avatar
aetilius/pHash

pHash: perceptual hashing across images, video, audio and text

pHash - the open source perceptual hash library

660 stars83 forksC++GPL-3.0

At a glance

What is it?
pHash is a C++ library for perceptual hashes, where a re-encoded or resized file produces a hash close to the original in Hamming distance rather than a completely different value. It covers six hash families with different tolerance properties, and the GPL-3.0 licence plus a C ABI and language bindings are the two things to check before you embed it in a service.
Who is it for?
pHash fits a team that needs to detect near-duplicate media across re-encodes, resizes and mild noise, and that is prepared to tune a distance threshold against its own data rather than trust a default. It does not fit a product that needs a permissive licence, because pHash is GPL-3.0 and a service that links it inherits copyleft obligations, and it does not fit a case where cryptographic integrity is what you need, since a perceptual hash is deliberately collision-prone.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 136 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A hash that changes gradually, and what that buys you

The library's central idea is the contrast with a cryptographic hash. The README says that unlike cryptographic hashes, which change completely with a single bit flip, perceptual hashes change gradually as the input changes, so two files that look or sound the same to a human produce hash values that are close to each other in Hamming distance, even after lossy re-encoding, resizing, mild noise, or other content-preserving transformations. That property is what makes similarity search possible at all. A cryptographic hash of a re-encoded file is unrelated to the original, so it can answer only whether two files are byte identical. A perceptual hash answers a different question, whether two files carry the same content, and the cost of that flexibility is that the function is no longer injective by design. The listed use cases follow directly: near-duplicate image, video and audio detection, copy detection and content-ID systems, reverse-image search backends, indexing media libraries against perturbation, and plagiarism detection for text.

Six hash families, and the perturbation each one survives

The README's table of hash families is the part of the documentation that actually guides an implementation decision, because each family survives a different set of transformations. The DCT image hash produces a 64-bit ulong64 and is unaffected by resize, compression and mild brightness or contrast changes. The Radon-based family, called Radish in the table, produces a 40-byte digest compared by cross-correlation and is unaffected by rotation and resize. The Marr-Hildreth wavelet hash produces a 72-byte, 576-bit value and survives resize, slight crop and JPEG noise. The video hash combines keyframe DCT hashes with a longest common subsequence step and is unaffected by re-encoding and frame-rate change. The audio hash uses Bark-scale spectral analysis in a Haitsma-Kalker style and yields an array of 32-bit frames, unaffected by re-encoding and mild equalisation. The text hash uses k-gram winnowing and returns an array of hash and offset pairs, unaffected by insertion, deletion and edits. The output size alone tells you a lot: 64 bits for the DCT hash is a compact index key, while 576 bits for the Marr-Hildreth variant is a different storage and comparison budget.

Distance functions are chosen per family, not universally

There is no single distance function across the library, and using the wrong one is the most common mistake. The README lists the companion functions as ph_hamming_distance, ph_hammingdistance2, ph_crosscorr, ph_dct_videohash_dist, and ph_audio_distance_ber, each for the appropriate hash family. Hamming distance applies to the fixed-width integer hashes, cross-correlation applies to the Radish digests, the video hash has its own distance, and the audio family uses a Bark-scale distance that returns a confidence value rather than a count. The full API is in src/pHash.h and src/audiophash.h, and the README points at those headers rather than duplicating the reference. The audio example shows the shape of the return value, a double that the example then reduces to a maximum over the computed blocks, and the printed interpretation is a confidence where 1.0 means identical and 0.5 means unrelated. That is a different reporting convention from a Hamming count, and it is worth noticing that the thresholds are not interchangeable either.

Building the library and the demo binaries

The quick start is a standard clone, configure, compile, install sequence, with the CMake options for the components you want enabled up front.

sh
git clone https://github.com/aetilius/pHash.git
cd pHash
mkdir build && cd build
cmake -DPHASH_EXAMPLES=ON -DWITH_AUDIO_HASH=ON -DWITH_VIDEO_HASH=ON ..
make -j
sudo make install

That produces libpHash.so, or the .dylib equivalent on macOS, along with a set of demonstration binaries in build/Release/.

text
TestDCT          # ph_dct_imagehash on two directories of images
TestRadish       # ph_image_digest (radon-based) on one image
TestMH           # ph_mh_imagehash distance between two images
TestAudio        # ph_audiohash + ph_audio_distance_ber on two audio files
TestVideoHash    # ph_dct_videohash on two video files

The demos are a quick way to see the library work on your own files, which is more useful than any default threshold, since the README is explicit that the thresholds should be tuned against your own data. The build options table shows the defaults, and most of them are off. PHASH_DYNAMIC is on and builds the shared library, PHASH_STATIC is off, and WITH_AUDIO_HASH, WITH_VIDEO_HASH, WITH_HEIF and PHASH_EXAMPLES are all off by default, with PHASH_BINDINGS off as well. A minimal image-only build is a plain cmake .. with no options, and enabling everything takes several flags at once.

Your first image hash in twenty lines of C++

The image path is the simplest to start with, and the example shows the shape of the whole API: hash two files, compute the distance, print it.

cpp
#include <cstdio>
#include "pHash.h"

int main(int argc, char *argv[]) {
    if (argc < 3) return 1;

    ulong64 hashA = 0, hashB = 0;
    if (ph_dct_imagehash(argv[1], hashA) < 0) return 1;
    if (ph_dct_imagehash(argv[2], hashB) < 0) return 1;

    int distance = ph_hamming_distance(hashA, hashB);
    std::printf("%016llx vs %016llx -> Hamming distance %d / 64\n",
                (unsigned long long)hashA,
                (unsigned long long)hashB,
                distance);
    return 0;
}

Two conventions are visible. The hash is passed by reference into the call and the return value is an error check, so a negative return means the file could not be hashed rather than that the hash is negative. And the hash is printed as a 16 digit hexadecimal value, which is the form you would store in an index. The README gives the rule of thumb for this family: a Hamming distance of 0 means the images are essentially identical, 10 or less means very similar, and 20 or more usually means unrelated. The final instruction is the one to take seriously, because those numbers are a starting point and the thresholds need tuning against your own data.

Linking against the installed library pulls in the image codecs it uses.

sh
c++ -std=c++14 -O2 example.cpp -lpHash -lpng -ljpeg -ltiff -lz -o example

Audio hashing returns a confidence, not a distance

The audio example differs enough to be worth reading separately, because the API is a different shape. Audio is read into float buffers with ph_readaudio at a sample rate of 8000 and one channel, then ph_audiohash turns each buffer into an array of 32-bit frame hashes, and ph_audio_distance_ber compares the two sets.

cpp
    int sr = 8000, channels = 1;
    int Na = 0, Nb = 0;
    float *a = ph_readaudio(argv[1], sr, channels, NULL, Na);
    float *b = ph_readaudio(argv[2], sr, channels, NULL, Nb);
    if (!a || !b) return 1;

    int frames_a = 0, frames_b = 0;
    uint32_t *ha = ph_audiohash(a, Na, sr, frames_a);
    uint32_t *hb = ph_audiohash(b, Nb, sr, frames_b);

    int Nc = 0;
    double *C = ph_audio_distance_ber(ha, frames_a, hb, frames_b,
                                       /*threshold=*/0.30f,
                                       /*block_size=*/256, Nc);

The comparison function takes a threshold and a block size, and it returns a per-block array whose maximum the example reduces to a single confidence value, printed with the reading that 1.0 means identical and 0.5 means unrelated. So the audio path is a similarity score with tunable internal parameters, and a fixed number of 32-bit hashes per frame. The buffer handling is manual, with explicit frees at the end, which is a reminder that this is a C library with a C-style interface rather than a managed one.

Codec dependencies, bundled CImg and the FFmpeg versions

The dependency table is explicit about which library is needed for which feature, and that matters for the build machine and for the container image. The image hash is always required, since HAVE_IMAGE_HASH is on by default, and it needs libpng-dev, libjpeg-dev and libtiff-dev on Debian or Ubuntu, with the matching Homebrew packages on macOS. The video hash needs the FFmpeg development packages and is enabled with -DWITH_VIDEO_HASH=ON, and the audio hash needs libsndfile1-dev, libsamplerate0-dev and libmpg123-dev, enabled with -DWITH_AUDIO_HASH=ON. HEIF, HEIC and AVIF decoding is a separate -DWITH_HEIF=ON switch that pulls in libheif-dev, and the table attributes it to issue 18 in the tracker. Threads come from pthreads in the standard library on Linux and are built in on macOS. Two conveniences are worth noting: pHash bundles CImg under third-party/CImg so there is no separate install for it, and FFmpeg 5, 6, 7 and 8 are all supported.

Bindings, licence and the state of the repository

The bindings directory ships three wrappers, and they are gated behind the PHASH_BINDINGS CMake option, which is off by default. Java has JNI bindings under bindings/java with JDK requirements described in that directory's README, C# has a P/Invoke wrapper for the C ABI under bindings/c_sharp, and PHP has a wrapper built against the PHP extension API. The C ABI is the stable integration point for anyone not using one of those three, and it is the same interface the C# binding targets.

The licence is GPL-3.0, and that is the first thing to settle if you intend to ship this inside a product. A copyleft library linked into a distributed application obliges you to publish the corresponding source, which is a different conversation from the permissive libraries most services assemble. pHash is the open source perceptual hash library and the project site is phash.org. The repository has no GitHub releases at all, so there is no tagged artefact to pin against, and the default branch is master. The last push was on 2026-06-09, and the top level carries the usual distribution furniture, with CMakeLists.txt, a libpHash.spec for RPM packaging, pHash.pc.in and pHash-config.h.in for pkg-config, COPYING, ChangeLog, NEWS, INSTALL, a format.sh with a .clang-format, and a tests directory. The examples directory carries a test program per hash family, including separate files for the DCT video hash, the Marr-Hildreth hash with and without multithreading, the Radon digest, the audio hash and two text hash variants.

Editorial conclusion

pHash fits a team that needs to detect near-duplicate media across re-encodes, resizes and mild noise, and that is prepared to tune a distance threshold against its own data rather than trust a default. It does not fit a product that needs a permissive licence, because pHash is GPL-3.0 and a service that links it inherits copyleft obligations, and it does not fit a case where cryptographic integrity is what you need, since a perceptual hash is deliberately collision-prone. Before you adopt it, pick the hash family that matches your perturbation, build with the options you need, and measure the distance distribution on your own corpus, because the README's thresholds are a rule of thumb for the DCT image hash only.

Frequently asked questions

What is pHash and how is it different from a cryptographic hash?

pHash is a C++ library for computing perceptual hashes of images, video, audio and text. The README contrasts it with cryptographic hashes: those change completely with a single bit flip, while perceptual hashes change gradually, so two files that look or sound the same to a human stay close in Hamming distance even after lossy re-encoding, resizing or mild noise.

How do I build pHash from source?

Clone the repository, create a build directory, run cmake with the options for the components you need, then make and install. The README's quick start uses cmake -DPHASH_EXAMPLES=ON -DWITH_AUDIO_HASH=ON -DWITH_VIDEO_HASH=ON .. followed by make -j and sudo make install, which produces libpHash.so or the macOS .dylib.

What Hamming distance should I treat as a match?

The README gives a rule of thumb for the DCT image hash: a distance of 0 means the images are essentially identical, 10 or less means very similar, and 20 or more usually means unrelated. It also says to tune the threshold against your own data rather than rely on those numbers.

Which hash family should I use for images that have been rotated?

The README's table lists the Radon transform plus DCT family, ph_image_digest and ph_compare_images, as unaffected by rotation and resize, and it returns a 40-byte digest compared with cross-correlation rather than a Hamming distance. The DCT image hash is the one unaffected by resize, compression and mild brightness or contrast changes, and the Marr-Hildreth wavelet hash survives resize, slight crop and JPEG noise.

Does pHash have bindings for other languages?

Yes. The bindings directory ships Java JNI bindings, a C# P/Invoke wrapper for the C ABI and a PHP wrapper built against the PHP extension API. They are compiled only when you configure the build with -DPHASH_BINDINGS=ON, which is off by default.

Official sources

  1. aetilius/pHash on GitHub
  2. Issues
  3. License: GPL-3.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aetilius-phash.svg)](https://hysenlabs.com/projects/aetilius-phash)

Was this useful?