最近在逛淘宝时发现了淘宝的图片搜索功能,多是我太Low了这个技术点已经实现很长时间了。想一想本身能不能实现这个功能,起初我是这么想的,对两张图片从左上角的第一个像素点一直比较到右下角的最后一个像素点,并在比较时记录它们的类似度,多是我太天真了(主要仍是知识限制了想象),这样作有不少问题,好比说两张图片大小不一致、核心要素点的位置不一样等...最终只得借助网络了,找到了一种叫作均值哈希的算法(Average hash algorithm),接下来具体阐述它的基本思路以及适用场景。php
一、缩小尺寸:html
去除图片的高频和细节的最快方法是缩小图片,将图片缩小到8x8的尺寸,总共64个像素。不要保持纵横比,只需将其变成8乘8的正方形。这样就能够比较任意大小的图片,摒弃不一样尺寸、比例带来的图片差别。算法
二、简化色彩:网络
将8乘8的小图片转换成灰度图像。函数
三、计算平均值:spa
计算全部64个像素的灰度平均值。code
四、比较像素的灰度:cdn
将每一个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。htm
五、计算hash值:blog
将上一步的比较结果,组合在一块儿,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证全部图片都采用一样次序就好了。
若是图片放大或缩小,或改变纵横比,结果值也不会改变。增长或减小亮度或对比度,或改变颜色,对hash值都不会太大的影响。最大的优势:计算速度快!
那么完成了以上步骤,一张图片就至关于有了本身的"指纹"了,而后就是计算不一样位的个数,也就是汉明距离(例如1010001与1011101的汉明举例就是2,也就是不一样的个数)。
若是汉明距离小于5,则表示有些不一样,但比较相近,若是汉明距离大于10则代表彻底不一样的图片。
以上就是均值哈希的基本实现思路,整体来讲是比较简单的。
public class ImageHashHelper
{
/// <summary>
/// 获取缩略图
/// </summary>
/// <returns></returns>
private static Bitmap GetThumbImage(Image image, int w, int h) {
Bitmap bitmap = new Bitmap(w, h);
Graphics g = Graphics.FromImage(bitmap);
g.DrawImage(image,
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
return bitmap;
}
/// <summary>
/// 将图片转换为灰度图像
/// </summary>
/// <returns></returns>
private static Bitmap ToGray(Bitmap bmp) {
for (int i = 0; i < bmp.Width; i++)
{
for (int j = 0; j < bmp.Height; j++)
{
//获取该点的像素的RGB的颜色
Color color = bmp.GetPixel(i, j);
//利用公式计算灰度值
int gray = (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11);//计算方式1
//int gray1 = (int)((color.R + color.G + color.B) / 3.0M);//计算方式2
Color newColor = Color.FromArgb(gray, gray, gray);
bmp.SetPixel(i, j, newColor);
}
}
return bmp;
}
/// <summary>
/// 获取图片的均值哈希
/// </summary>
/// <returns></returns>
public static int[] GetAvgHash(Bitmap bitmap) {
Bitmap newBitmap = ToGray(GetThumbImage(bitmap, 8, 8));
int[] code = new int[64];
//计算全部64个像素的灰度平均值。
List<int> allGray = new List<int>();
for (int row = 0; row < bitmap.Width; row++)
{
for (int col = 0; col < bitmap.Height; col++)
{
allGray.Add(newBitmap.GetPixel(row, col).R);
}
}
double avg = allGray.Average(a => a);//拿到平均值
//比较像素的灰度
for (int i = 0; i < allGray.Count; i++)
{
code[i] = allGray[i] >= avg ? 1 : 0;//将比较结果进行组合
}
//返回结果
return code;
}
/// <summary>
/// 对两个AvgHash进行比较
/// </summary>
/// <returns></returns>
public static int Compare(int[] code1, int[] code2) {
int v = 0;
for (int i = 0; i < 64; i++)
{
if (code1[i] == code2[i])
{
v++;
}
}
return v;
}
}
复制代码
这里咱们在GetAvgHash函数中获取64个像素的灰度值时直接经过了R来获取,由于RGB都是同样的,因此哪个均可以。
灰度值换算:baike.baidu.com/item/灰度值/10…
效果演示:
一、原图查找
二、彻底马赛克查找
源码下载:
均值哈希适合缩略图查找原图,人相匹配等并不适用。
参考文献: