using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Masuit.Tools.Media;
///
/// 图像hash机算器
///
public class ImageHasher
{
private readonly IImageTransformer _transformer;
///
/// 默认使用ImageSharpTransformer初始化实例
///
public ImageHasher()
{
_transformer = new ImageSharpTransformer();
}
///
/// 使用给定的IImageTransformer初始化实例
///
/// 用于图像变换的IImageTransformer的实现类
public ImageHasher(IImageTransformer transformer)
{
_transformer = transformer;
}
///
/// 使用平均值算法计算图像的64位哈希
///
/// 图片的文件路径
/// 64位hash值
public ulong AverageHash64(string pathToImage)
{
#if NET6_0_OR_GREATER
var decoderOptions = new DecoderOptions
{
TargetSize = new Size(144),
SkipMetadata = true
};
using var image = Image.Load(decoderOptions, pathToImage);
#else
using var image = Image.Load(pathToImage);
#endif
return AverageHash64(image);
}
///
/// 使用平均值算法计算图像的64位哈希
///
/// 读取到的图片流
/// 64位hash值
public ulong AverageHash64(Stream sourceStream)
{
var pixels = _transformer.TransformImage(sourceStream, 8, 8);
var average = pixels.Sum(b => b) / 64;
// 遍历所有像素,如果超过平均值,则将其设置为1,如果低于平均值,则将其设置为0。
var hash = 0UL;
for (var i = 0; i < 64; i++)
{
if (pixels[i] > average)
{
hash |= 1UL << i;
}
}
return hash;
}
///
/// 使用平均值算法计算图像的64位哈希
///
/// 读取到的图片流
/// 64位hash值
public ulong AverageHash64(Image image)
{
using var source = image.CloneAs();
return AverageHash64(source);
}
///
/// 使用平均值算法计算图像的64位哈希
///
/// 读取到的图片流
/// 64位hash值
public ulong AverageHash64(Image image)
{
var pixels = _transformer.TransformImage(image, 8, 8);
var average = pixels.Sum(b => b) / 64;
// 遍历所有像素,如果超过平均值,则将其设置为1,如果低于平均值,则将其设置为0。
var hash = 0UL;
for (var i = 0; i < 64; i++)
{
if (pixels[i] > average)
{
hash |= 1UL << i;
}
}
return hash;
}
///
/// 使用中值算法计算给定图像的64位哈希
/// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 图片的文件路径
/// 64位hash值
public ulong MedianHash64(string pathToImage)
{
#if NET6_0_OR_GREATER
var decoderOptions = new DecoderOptions
{
TargetSize = new Size(144),
SkipMetadata = true
};
using var image = Image.Load(decoderOptions, pathToImage);
#else
using var image = Image.Load(pathToImage);
#endif
return MedianHash64(image);
}
///
/// 使用中值算法计算给定图像的64位哈希
/// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 读取到的图片流
/// 64位hash值
public ulong MedianHash64(Stream sourceStream)
{
var pixels = _transformer.TransformImage(sourceStream, 8, 8);
// 计算中值
var pixelList = new List(pixels);
pixelList.Sort();
// 中间像素中值
var median = (byte)((pixelList[31] + pixelList[32]) / 2);
// 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
var hash = 0UL;
for (var i = 0; i < 64; i++)
{
if (pixels[i] > median)
{
hash |= 1UL << i;
}
}
return hash;
}
///
/// 使用中值算法计算给定图像的64位哈希
/// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 读取到的图片流
/// 64位hash值
public ulong MedianHash64(Image image)
{
using var source = image.CloneAs();
return MedianHash64(source);
}
///
/// 使用中值算法计算给定图像的64位哈希
/// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 读取到的图片流
/// 64位hash值
public ulong MedianHash64(Image image)
{
var pixels = _transformer.TransformImage(image, 8, 8);
// 计算中值
var pixelList = new List(pixels);
pixelList.Sort();
// 中间像素中值
var median = (byte)((pixelList[31] + pixelList[32]) / 2);
// 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
var hash = 0UL;
for (var i = 0; i < 64; i++)
{
if (pixels[i] > median)
{
hash |= 1UL << i;
}
}
return hash;
}
///
/// 使用中值算法计算给定图像的256位哈希
/// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 图片的文件路径
/// 256位hash值,生成一个4长度的数组返回
public ulong[] MedianHash256(string pathToImage)
{
#if NET6_0_OR_GREATER
var decoderOptions = new DecoderOptions
{
TargetSize = new Size(144),
SkipMetadata = true
};
using var image = Image.Load(decoderOptions, pathToImage);
#else
using var image = Image.Load(pathToImage);
#endif
return MedianHash256(image);
}
///
/// 使用中值算法计算给定图像的256位哈希
/// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 读取到的图片流
/// 256位hash值,生成一个4长度的数组返回
public ulong[] MedianHash256(Stream sourceStream)
{
var pixels = _transformer.TransformImage(sourceStream, 16, 16);
// 计算中值
var pixelList = new List(pixels);
pixelList.Sort();
// 中间像素中值
var median = (byte)((pixelList[127] + pixelList[128]) / 2);
// 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
var hash64 = 0UL;
var hash = new ulong[4];
for (var i = 0; i < 4; i++)
{
for (var j = 0; j < 64; j++)
{
if (pixels[64 * i + j] > median)
{
hash64 |= 1UL << j;
}
}
hash[i] = hash64;
hash64 = 0UL;
}
return hash;
}
///
/// 使用中值算法计算给定图像的256位哈希
/// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 读取到的图片流
/// 256位hash值,生成一个4长度的数组返回
public ulong[] MedianHash256(Image image)
{
using var source = image.CloneAs();
return MedianHash256(source);
}
///
/// 使用中值算法计算给定图像的256位哈希
/// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
///
/// 读取到的图片流
/// 256位hash值,生成一个4长度的数组返回
public ulong[] MedianHash256(Image image)
{
var pixels = _transformer.TransformImage(image, 16, 16);
// 计算中值
var pixelList = new List(pixels);
pixelList.Sort();
// 中间像素中值
var median = (byte)((pixelList[127] + pixelList[128]) / 2);
// 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
var hash64 = 0UL;
var hash = new ulong[4];
for (var i = 0; i < 4; i++)
{
for (var j = 0; j < 64; j++)
{
if (pixels[64 * i + j] > median)
{
hash64 |= 1UL << j;
}
}
hash[i] = hash64;
hash64 = 0UL;
}
return hash;
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 图片的文件路径
/// 64位hash值
public ulong DifferenceHash64(string pathToImage)
{
#if NET6_0_OR_GREATER
var decoderOptions = new DecoderOptions
{
TargetSize = new Size(144),
SkipMetadata = true
};
using var image = Image.Load(decoderOptions, pathToImage);
#else
using var image = Image.Load(pathToImage);
#endif
return DifferenceHash64(image);
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 读取到的图片流
/// 64位hash值
public ulong DifferenceHash64(Stream sourceStream)
{
var pixels = _transformer.TransformImage(sourceStream, 9, 8);
// 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
var hash = 0UL;
var hashPos = 0;
for (var i = 0; i < 8; i++)
{
var rowStart = i * 9;
for (var j = 0; j < 8; j++)
{
if (pixels[rowStart + j] > pixels[rowStart + j + 1])
{
hash |= 1UL << hashPos;
}
hashPos++;
}
}
return hash;
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 读取到的图片流
/// 64位hash值
public ulong DifferenceHash64(Image image)
{
using var source = image.CloneAs();
return DifferenceHash64(source);
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 读取到的图片流
/// 64位hash值
public ulong DifferenceHash64(Image image)
{
var pixels = _transformer.TransformImage(image, 9, 8);
// 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
var hash = 0UL;
var hashPos = 0;
for (var i = 0; i < 8; i++)
{
var rowStart = i * 9;
for (var j = 0; j < 8; j++)
{
if (pixels[rowStart + j] > pixels[rowStart + j + 1])
{
hash |= 1UL << hashPos;
}
hashPos++;
}
}
return hash;
}
///
/// 使用差分哈希算法计算图像的256位哈希。
///
///
/// 图片的文件路径
/// 256位hash值
public ulong[] DifferenceHash256(string pathToImage)
{
#if NET6_0_OR_GREATER
var decoderOptions = new DecoderOptions
{
TargetSize = new Size(144),
SkipMetadata = true,
};
using var image = Image.Load(decoderOptions, pathToImage);
#else
using var image = Image.Load(pathToImage);
#endif
return DifferenceHash256(image);
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 读取到的图片流
/// 256位hash值
public ulong[] DifferenceHash256(Stream sourceStream)
{
var pixels = _transformer.TransformImage(sourceStream, 17, 16);
// 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
var hash = new ulong[4];
var hashPos = 0;
var hashPart = 0;
for (var i = 0; i < 16; i++)
{
var rowStart = i * 17;
for (var j = 0; j < 16; j++)
{
if (pixels[rowStart + j] > pixels[rowStart + j + 1])
{
hash[hashPart] |= 1UL << hashPos;
}
if (hashPos == 63)
{
hashPos = 0;
hashPart++;
}
else
{
hashPos++;
}
}
}
return hash;
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 读取到的图片流
/// 256位hash值
public ulong[] DifferenceHash256(Image image)
{
using var source = image.CloneAs();
return DifferenceHash256(source);
}
///
/// 使用差分哈希算法计算图像的64位哈希。
///
///
/// 读取到的图片流
/// 256位hash值
public ulong[] DifferenceHash256(Image image)
{
var pixels = _transformer.TransformImage(image, 17, 16);
// 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
var hash = new ulong[4];
var hashPos = 0;
var hashPart = 0;
for (var i = 0; i < 16; i++)
{
var rowStart = i * 17;
for (var j = 0; j < 16; j++)
{
if (pixels[rowStart + j] > pixels[rowStart + j + 1])
{
hash[hashPart] |= 1UL << hashPos;
}
if (hashPos == 63)
{
hashPos = 0;
hashPart++;
}
else
{
hashPos++;
}
}
}
return hash;
}
///
/// 使用DCT算法计算图像的64位哈希
///
///
/// 图片路径
/// 64位hash值
public ulong DctHash(string path)
{
#if NET6_0_OR_GREATER
var decoderOptions = new DecoderOptions
{
TargetSize = new Size(160),
SkipMetadata = true
};
using var image = Image.Load(decoderOptions, path);
#else
using var image = Image.Load(path);
#endif
return DctHash(image);
}
///
/// 使用DCT算法计算图像的64位哈希
///
///
/// 读取到的图片
/// 64位hash值
public ulong DctHash(Image image)
{
using var clone = image.CloneAs();
return DctHash(clone);
}
///
/// 使用DCT算法计算图像的64位哈希
///
///
/// 读取到的图片
/// 64位hash值
public ulong DctHash(Image image)
{
var grayscalePixels = _transformer.GetPixelData(image, 32, 32);
var dctMatrix = ComputeDct(grayscalePixels, 32);
var topLeftBlock = ExtractTopLeftBlock(dctMatrix, 8);
var median = CalculateMedian(topLeftBlock);
var hash = GenerateHash(topLeftBlock, median);
return hash;
}
///
/// 计算图像的DCT矩阵
///
///
///
///
private double[,] ComputeDct(byte[,] input, int size)
{
var output = new double[size, size];
var rowDCT = new double[size, size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
rowDCT[y, x] = input[y, x];
}
}
for (int y = 0; y < size; y++)
{
var row = new double[size];
for (int x = 0; x < size; x++)
{
row[x] = rowDCT[y, x];
}
var dctRow = DCT1D(row);
for (int x = 0; x < size; x++)
{
rowDCT[y, x] = dctRow[x];
}
}
for (int x = 0; x < size; x++)
{
var col = new double[size];
for (int y = 0; y < size; y++)
{
col[y] = rowDCT[y, x];
}
var dctCol = DCT1D(col);
for (int y = 0; y < size; y++)
{
output[y, x] = dctCol[y];
}
}
return output;
}
private double[] DCT1D(double[] input)
{
int n = input.Length;
var output = new double[n];
for (int u = 0; u < n; u++)
{
double sum = 0.0;
double cu = u == 0 ? 1.0 / Math.Sqrt(2.0) : 1.0;
for (int x = 0; x < n; x++)
{
sum += input[x] * Math.Cos((Math.PI / n) * (x + 0.5) * u);
}
output[u] = cu * sum * Math.Sqrt(2.0 / n);
}
return output;
}
private double[,] ExtractTopLeftBlock(double[,] matrix, int blockSize)
{
var block = new double[blockSize, blockSize];
for (int y = 0; y < blockSize; y++)
{
for (int x = 0; x < blockSize; x++)
{
block[y, x] = matrix[y, x];
}
}
return block;
}
private double CalculateMedian(double[,] matrix)
{
int height = matrix.GetLength(0);
int width = matrix.GetLength(1);
var flatArray = new double[height * width];
int index = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
flatArray[index++] = matrix[y, x];
}
}
Array.Sort(flatArray);
if (flatArray.Length % 2 == 0)
{
return (flatArray[flatArray.Length / 2 - 1] + flatArray[flatArray.Length / 2]) / 2.0;
}
return flatArray[flatArray.Length / 2];
}
private ulong GenerateHash(double[,] block, double median)
{
ulong hash = 0UL;
int bitPosition = 0;
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
if (block[y, x] >= median)
{
hash |= (1UL << bitPosition);
}
bitPosition++;
}
}
return hash;
}
///
/// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
///
/// 图像1的hash
/// 图像2的hash
/// 相似度范围:[0,1]
public static float Compare(ulong hash1, ulong hash2)
{
// hash异或运算
var hashDifference = hash1 ^ hash2;
// 计算汉明距离
var hamming = HammingWeight(hashDifference);
// 得到相似度
return 1.0f - hamming / 64.0f;
}
///
/// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
///
/// 图像1的hash
/// 图像2的hash
/// 相似度范围:[0,1]
public static float Compare(ulong[] hash1, ulong[] hash2)
{
// 检查两个图像的hash长度是否一致
if (hash1.Length != hash2.Length)
{
throw new ArgumentException("hash1 与 hash2长度不匹配");
}
var hashSize = hash1.Length;
ulong onesInHash = 0;
// hash异或运算
var hashDifference = new ulong[hashSize];
for (var i = 0; i < hashSize; i++)
{
hashDifference[i] = hash1[i] ^ hash2[i];
}
// 逐个计算汉明距离
for (var i = 0; i < hashSize; i++)
{
onesInHash += HammingWeight(hashDifference[i]);
}
// 得到相似度
return 1.0f - onesInHash / (hashSize * 64.0f);
}
///
/// 计算hash的汉明权重.
///
///
///
///
private static ulong HammingWeight(ulong hash)
{
hash -= (hash >> 1) & M1;
hash = (hash & M2) + ((hash >> 2) & M2);
hash = (hash + (hash >> 4)) & M4;
var onesInHash = (hash * H01) >> 56;
return onesInHash;
}
// 汉明距离常量. http://en.wikipedia.org/wiki/Hamming_weight
private const ulong M1 = 0x5555555555555555; //binary: 0101...
private const ulong M2 = 0x3333333333333333; //binary: 00110011..
private const ulong M4 = 0x0f0f0f0f0f0f0f0f; //binary: 4 个0, 4 个1 ...
private const ulong H01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3...
}