using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Masuit.Tools.Media;
public class ImageHasher
{
private readonly IImageTransformer _transformer;
private float[][] _dctMatrix;
private bool _isDctMatrixInitialized;
private readonly object _dctMatrixLockObject = new();
///
/// 默认使用ImageSharpTransformer初始化实例
///
public ImageHasher()
{
_transformer = new ImageSharpTransformer();
}
///
/// 使用给定的IImageTransformer初始化实例
///
/// 用于图像变换的IImageTransformer的实现类
public ImageHasher(IImageTransformer transformer)
{
_transformer = transformer;
}
///
/// 使用平均值算法计算图像的64位哈希
///
/// 图片的文件路径
/// 64位hash值
public ulong AverageHash64(string pathToImage)
{
using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
return AverageHash64(stream);
}
///
/// 使用平均值算法计算图像的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)
{
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)
{
using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
return MedianHash64(stream);
}
///
/// 使用中值算法计算给定图像的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)
{
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)
{
using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
return MedianHash256(stream);
}
///
/// 使用中值算法计算给定图像的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)
{
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)
{
using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
return DifferenceHash64(stream);
}
///
/// 使用差分哈希算法计算图像的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)
{
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)
{
using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
return DifferenceHash256(stream);
}
///
/// 使用差分哈希算法计算图像的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)
{
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(Stream sourceStream)
{
lock (_dctMatrixLockObject)
{
if (!_isDctMatrixInitialized)
{
_dctMatrix = GenerateDctMatrix(32);
_isDctMatrixInitialized = true;
}
}
var pixels = _transformer.TransformImage(sourceStream, 32, 32);
// 将像素转换成float类型数组
var fPixels = new float[1024];
for (var i = 0; i < 1024; i++)
{
fPixels[i] = pixels[i] / 255.0f;
}
// 计算 dct 矩阵
var dctPixels = ComputeDct(fPixels, _dctMatrix);
// 从矩阵中1,1到8,8获得8x8的区域,忽略最低频率以提高检测
var dctHashPixels = new float[64];
for (var x = 0; x < 8; x++)
{
for (var y = 0; y < 8; y++)
{
dctHashPixels[x + y * 8] = dctPixels[x + 1][y + 1];
}
}
// 计算中值
var pixelList = new List(dctHashPixels);
pixelList.Sort();
// 中间像素的平均值
var median = (pixelList[31] + pixelList[32]) / 2;
// 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
var hash = 0UL;
for (var i = 0; i < 64; i++)
{
if (dctHashPixels[i] > median)
{
hash |= 1UL << i;
}
}
return hash;
}
///
/// 使用DCT算法计算图像的64位哈希
///
///
/// 读取到的图片流
/// 64位hash值
public ulong DctHash(Image image)
{
lock (_dctMatrixLockObject)
{
if (!_isDctMatrixInitialized)
{
_dctMatrix = GenerateDctMatrix(32);
_isDctMatrixInitialized = true;
}
}
var pixels = _transformer.TransformImage(image, 32, 32);
// 将像素转换成float类型数组
var fPixels = new float[1024];
for (var i = 0; i < 1024; i++)
{
fPixels[i] = pixels[i] / 255.0f;
}
// 计算 dct 矩阵
var dctPixels = ComputeDct(fPixels, _dctMatrix);
// 从矩阵中1,1到8,8获得8x8的区域,忽略最低频率以提高检测
var dctHashPixels = new float[64];
for (var x = 0; x < 8; x++)
{
for (var y = 0; y < 8; y++)
{
dctHashPixels[x + y * 8] = dctPixels[x + 1][y + 1];
}
}
// 计算中值
var pixelList = new List(dctHashPixels);
pixelList.Sort();
// 中间像素的平均值
var median = (pixelList[31] + pixelList[32]) / 2;
// 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
var hash = 0UL;
for (var i = 0; i < 64; i++)
{
if (dctHashPixels[i] > median)
{
hash |= 1UL << i;
}
}
return hash;
}
///
/// 使用DCT算法计算图像的64位哈希
///
/// 图片的文件路径
/// 64位hash值
public ulong DctHash(string path)
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
return DctHash(stream);
}
///
/// 计算图像的DCT矩阵
///
/// 用于计算dct的图像
/// DCT系数矩阵
/// 图像的DCT矩阵
private static float[][] ComputeDct(float[] image, float[][] dctMatrix)
{
// dct矩阵的大小,图像的大小与DCT矩阵相同
var size = dctMatrix.GetLength(0);
// 降图像转换成矩阵
var imageMat = new float[size][];
for (var i = 0; i < size; i++)
{
imageMat[i] = new float[size];
}
for (var y = 0; y < size; y++)
{
for (var x = 0; x < size; x++)
{
imageMat[y][x] = image[x + y * size];
}
}
return Multiply(Multiply(dctMatrix, imageMat), Transpose(dctMatrix));
}
///
/// 生成DCT系数矩阵
///
/// 矩阵的大小
/// DCT系数矩阵
private static float[][] GenerateDctMatrix(int size)
{
var matrix = new float[size][];
for (int i = 0; i < size; i++)
{
matrix[i] = new float[size];
}
var c1 = Math.Sqrt(2.0f / size);
for (var j = 0; j < size; j++)
{
matrix[0][j] = (float)Math.Sqrt(1.0d / size);
}
for (var j = 0; j < size; j++)
{
for (var i = 1; i < size; i++)
{
matrix[i][j] = (float)(c1 * Math.Cos(((2 * j + 1) * i * Math.PI) / (2.0d * size)));
}
}
return matrix;
}
///
/// 矩阵的乘法运算
///
/// 矩阵a
/// 矩阵b
/// Result matrix.
private static float[][] Multiply(float[][] a, float[][] b)
{
var n = a[0].Length;
var c = new float[n][];
for (var i = 0; i < n; i++)
{
c[i] = new float[n];
}
for (var i = 0; i < n; i++)
for (var k = 0; k < n; k++)
for (var j = 0; j < n; j++)
c[i][j] += a[i][k] * b[k][j];
return c;
}
///
/// 矩阵转置
///
/// 待转换的矩阵
/// 转换后的矩阵
private static float[][] Transpose(float[][] mat)
{
var size = mat[0].Length;
var transpose = new float[size][];
for (var i = 0; i < size; i++)
{
transpose[i] = new float[size];
for (var j = 0; j < size; j++)
{
transpose[i][j] = mat[j][i];
}
}
return transpose;
}
///
/// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
///
/// 图像1的hash
/// 图像2的hash
/// 相似度范围:[0,1]
public static float Compare(ulong hash1, ulong hash2)
{
// hash异或运算
var hashDifference = hash1 ^ hash2;
// 计算汉明距离
var onesInHash = HammingWeight(hashDifference);
// 得到相似度
return 1.0f - onesInHash / 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...
}