這陣子發現個人圖像數據庫中有很多內容同樣的圖像須要剔除,這些內容同樣的圖像可能尺寸不同,通道數也可能不同(灰度/彩色),以下三張圖內容徹底同樣,只是亮度或色彩通道數不一樣,
因而想到了用google或baidu的識圖功能所用到的「感知哈希算法」來搜索數據庫內容同樣的圖像。
經過這篇文章搞清楚了「感知哈希算法」的基本原理,
《三種基於感知哈希算法的類似圖像檢索技術》,發現原理很簡單,很適合我等粗人,呵呵,因而在java下實現了這個算法的代碼 :java
package net.gdface.image; import java.awt.Graphics; import java.awt.Image; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import java.util.Arrays; /** * 均值哈希實現圖像指紋比較 * @author guyadong * */ public final class FingerPrint { /** * 圖像指紋的尺寸,將圖像resize到指定的尺寸,來計算哈希數組 */ private static final int HASH_SIZE=16; /** * 保存圖像指紋的二值化矩陣 */ private final byte[] binaryzationMatrix; public FingerPrint(byte[] hashValue) { if(hashValue.length!=HASH_SIZE*HASH_SIZE) throw new IllegalArgumentException(String.format("length of hashValue must be %d",HASH_SIZE*HASH_SIZE )); this.binaryzationMatrix=hashValue; } public FingerPrint(String hashValue) { this(toBytes(hashValue)); } public FingerPrint (BufferedImage src){ this(hashValue(src)); } private static byte[] hashValue(BufferedImage src){ BufferedImage hashImage = resize(src,HASH_SIZE,HASH_SIZE); byte[] matrixGray = (byte[]) toGray(hashImage).getData().getDataElements(0, 0, HASH_SIZE, HASH_SIZE, null); return binaryzation(matrixGray); } /** * 從壓縮格式指紋建立{@link FingerPrint}對象 * @param compactValue * @return */ public static FingerPrint createFromCompact(byte[] compactValue){ return new FingerPrint(uncompact(compactValue)); } public static boolean validHashValue(byte[] hashValue){ if(hashValue.length!=HASH_SIZE) return false; for(byte b:hashValue){ if(0!=b&&1!=b)return false; } return true; } public static boolean validHashValue(String hashValue){ if(hashValue.length()!=HASH_SIZE) return false; for(int i=0;i<hashValue.length();++i){ if('0'!=hashValue.charAt(i)&&'1'!=hashValue.charAt(i))return false; } return true; } public byte[] compact(){ return compact(binaryzationMatrix); } /** * 指紋數據按位壓縮 * @param hashValue * @return */ private static byte[] compact(byte[] hashValue){ byte[] result=new byte[(hashValue.length+7)>>3]; byte b=0; for(int i=0;i<hashValue.length;++i){ if(0==(i&7)){ b=0; } if(1==hashValue[i]){ b|=1<<(i&7); }else if(hashValue[i]!=0) throw new IllegalArgumentException("invalid hashValue,every element must be 0 or 1"); if(7==(i&7)||i==hashValue.length-1){ result[i>>3]=b; } } return result; } /** * 壓縮格式的指紋解壓縮 * @param compactValue * @return */ private static byte[] uncompact(byte[] compactValue){ byte[] result=new byte[compactValue.length<<3]; for(int i=0;i<result.length;++i){ if((compactValue[i>>3]&(1<<(i&7)))==0) result[i]=0; else result[i]=1; } return result; } /** * 字符串類型的指紋數據轉爲字節數組 * @param hashValue * @return */ private static byte[] toBytes(String hashValue){ hashValue=hashValue.replaceAll("\\s", ""); byte[] result=new byte[hashValue.length()]; for(int i=0;i<result.length;++i){ char c = hashValue.charAt(i); if('0'==c) result[i]=0; else if('1'==c) result[i]=1; else throw new IllegalArgumentException("invalid hashValue String"); } return result; } /** * 縮放圖像到指定尺寸 * @param src * @param width * @param height * @return */ private static BufferedImage resize(Image src,int width,int height){ BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); Graphics g = result.getGraphics(); try{ g.drawImage(src.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); }finally{ g.dispose(); } return result; } /** * 計算均值 * @param src * @return */ private static int mean(byte[] src){ long sum=0; // 將數組元素轉爲無符號整數 for(byte b:src)sum+=(long)b&0xff; return (int) (Math.round((float)sum/src.length)); } /** * 二值化處理 * @param src * @return */ private static byte[] binaryzation(byte[]src){ byte[] dst = src.clone(); int mean=mean(src); for(int i=0;i<dst.length;++i){ // 將數組元素轉爲無符號整數再比較 dst[i]=(byte) (((int)dst[i]&0xff)>=mean?1:0); } return dst; } /** * 轉灰度圖像 * @param src * @return */ private static BufferedImage toGray(BufferedImage src){ if(src.getType()==BufferedImage.TYPE_BYTE_GRAY){ return src; }else{