1. 程式人生 > >谷歌百度以圖搜圖 "感知雜湊演算法" C#簡單實現

谷歌百度以圖搜圖 "感知雜湊演算法" C#簡單實現

/// <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;
		}
	}