The canonical way to get a perceptual hash is using the pHash library. In fact, we can get nearly the same performance in Haskell using hip and repa.

I ran these benchmarks on a six-core Linux machine. Here is the criterion output:

benchmarking fileHash/cat.png time 20.99 ms (20.88 ms .. 21.12 ms) 1.000 R² (1.000 R² .. 1.000 R²) mean 20.98 ms (20.92 ms .. 21.09 ms) std dev 183.1 μs (98.64 μs .. 280.9 μs)

benchmarking foreignHash/cat.png time 17.30 ms (17.25 ms .. 17.34 ms) 1.000 R² (1.000 R² .. 1.000 R²) mean 17.28 ms (17.25 ms .. 17.30 ms) std dev 53.08 μs (43.56 μs .. 74.98 μs)

foreignHash is a wrapper for ph_dct_imagehash from the pHash library; as we can see, fileHash nearly as fast.

You can find instructions for replicating the benchmark here.