/// <summary> /// 感知哈希算法 /// </summary> public class ImageComparer { /// <summary> /// 获取图片的Hashcode /// </summary> /// <param name="imageName"></param> /// <returns></returns> public static string GetImageHashCode(string imageName) { int width = 8; int height = 8; // 第一步 // 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节, // 只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。 Bitmap bmp = new Bitmap(Thumb(imageName)); int[] pixels = new int[width * height]; // 第二步 // 将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。 for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { Color color = bmp.GetPixel(i, j); pixels[i * height + j] = RGBToGray(color.ToArgb()); } } // 第三步 // 计算所有64个像素的灰度平均值。 int avgPixel = Average(pixels); // 第四步 // 将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。 int[] comps = new int[width * height]; for (int i = 0; i < comps.Length; i++) { if (pixels[i] >= avgPixel) { comps[i] = 1; } else { comps[i] = 0; } } // 第五步 // 将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。 StringBuilder hashCode = new StringBuilder(); for (int i = 0; i < comps.Length; i += 4) { int result = comps[i] * (int)Math.Pow(2, 3) + comps[i + 1] * (int)Math.Pow(2, 2) + comps[i + 2] * (int)Math.Pow(2, 1) + comps[i + 2]; hashCode.Append(BinaryToHex(result)); } bmp.Dispose(); return hashCode.ToString(); } /// <summary> /// 计算"汉明距离"(Hamming distance)。 /// 如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。 /// </summary> /// <param name="sourceHashCode"></param> /// <param name="hashCode"></param> /// <returns></returns> public static int HammingDistance(String sourceHashCode, String hashCode) { int difference = 0; int len = sourceHashCode.Length; for (int i = 0; i < len; i++) { if (sourceHashCode[i] != hashCode[i]) { difference++; } } return difference; } /// <summary> /// 缩放图片 /// </summary> /// <param name="imageName"></param> /// <returns></returns> private static Image Thumb(string imageName) { return Image.FromFile(imageName).GetThumbnailImage(8, 8, () => { return false; }, IntPtr.Zero); } /// <summary> /// 转为64级灰度 /// </summary> /// <param name="pixels"></param> /// <returns></returns> private static int RGBToGray(int pixels) { int _red = (pixels >> 16) & 0xFF; int _green = (pixels >> 8) & 0xFF; int _blue = (pixels) & 0xFF; return (int)(0.3 * _red + 0.59 * _green + 0.11 * _blue); } /// <summary> /// 计算平均值 /// </summary> /// <param name="pixels"></param> /// <returns></returns> private static int Average(int[] pixels) { float m = 0; for (int i = 0; i < pixels.Length; ++i) { m += pixels[i]; } m = m / pixels.Length; return (int)m; } private static char BinaryToHex(int binary) { char ch = ' '; switch (binary) { case 0: ch = '0'; break; case 1: ch = '1'; break; case 2: ch = '2'; break; case 3: ch = '3'; break; case 4: ch = '4'; break; case 5: ch = '5'; break; case 6: ch = '6'; break; case 7: ch = '7'; break; case 8: ch = '8'; break; case 9: ch = '9'; break; case 10: ch = 'a'; break; case 11: ch = 'b'; break; case 12: ch = 'c'; break; case 13: ch = 'd'; break; case 14: ch = 'e'; break; case 15: ch = 'f'; break; default: ch = ' '; break; } return ch; } }