前言
最近在逛淘寶時發現了淘寶的圖片搜尋功能,可能是我太Low了這個技術點已經實現很長時間了。想想自己能不能實現這個功能,起初我是這麼想的,對兩張圖片從左上角的第一個畫素點一直比較到右下角的最後一個畫素點,並在比較時記錄它們的相似度,可能是我太天真了(主要還是知識限制了想象),這樣做有很多問題,比如說兩張圖片大小不一致、核心要素點的位置不同等...最終只得藉助網路了,找到了一種叫做均值雜湊的演算法(Average hash algorithm),接下來具體闡述它的基本思路以及適用場景。
均值雜湊的基本思路
1、縮小尺寸:
去除圖片的高頻和細節的最快方法是縮小圖片,將圖片縮小到8x8的尺寸,總共64個畫素。不要保持縱橫比,只需將其變成8乘8的正方形。這樣就可以比較任意大小的圖片,摒棄不同尺寸、比例帶來的圖片差異。
2、簡化色彩:
將8乘8的小圖片轉換成灰度影像。
3、計算平均值:
計算所有64個畫素的灰度平均值。
4、比較畫素的灰度:
將每個畫素的灰度,與平均值進行比較。大於或等於平均值,記為1;小於平均值,記為0。
5、計算hash值:
將上一步的比較結果,組合在一起,就構成了一個64位的整數,這就是這張圖片的指紋。組合的次序並不重要,只要保證所有圖片都採用同樣次序就行了。
如果圖片放大或縮小,或改變縱橫比,結果值也不會改變。增加或減少亮度或對比度,或改變顏色,對hash值都不會太大的影響。最大的優點:計算速度快!
那麼完成了以上步驟,一張圖片就相當於有了自己的"指紋"了,然後就是計算不同位的個數,也就是漢明距離(例如1010001與1011101的漢明舉例就是2,也就是不同的個數)。
如果漢明距離小於5,則表示有些不同,但比較相近,如果漢明距離大於10則表明完全不同的圖片。
以上就是均值雜湊的基本實現思路,總體來說是比較簡單的。
C#實現
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…
效果演示:
1、原圖查詢
2、完全馬賽克查詢
原始碼下載:
最後
均值雜湊適合縮圖查詢原圖,人相匹配等並不適用。
參考文獻: