001package net.gdface.image; 002 003import java.awt.Graphics; 004import java.awt.Image; 005import java.awt.color.ColorSpace; 006import java.awt.image.BufferedImage; 007import java.awt.image.ColorConvertOp; 008import java.util.Arrays; 009 010import net.gdface.utils.Assert; 011import net.gdface.utils.FaceUtilits; 012import net.gdface.utils.Judge; 013 014/** 015 * å‡å€¼å“ˆå¸Œå®žçŽ°å›¾åƒæŒ‡çº¹æ¯”较,用于比较两个图åƒçš„内容的相似度 016 * @author guyadong 017 * 018 */ 019public final class Fingerprint { 020 /** 021 * å›¾åƒæŒ‡çº¹çš„尺寸,将图åƒresize到指定的尺寸,æ¥è®¡ç®—哈希数组 022 */ 023 private static final int HASH_SIZE=16; 024 /** 025 * ä¿å˜å›¾åƒæŒ‡çº¹çš„二值化矩阵 026 */ 027 private final byte[] binaryzationMatrix; 028 public Fingerprint(byte[] hashValue) { 029 Assert.notEmpty(hashValue, "hashValue"); 030 if(hashValue.length!=HASH_SIZE*HASH_SIZE) 031 throw new IllegalArgumentException(String.format("length of hashValue must be %d",HASH_SIZE*HASH_SIZE )); 032 this.binaryzationMatrix=hashValue; 033 } 034 public Fingerprint(String hashValue) { 035 this(toBytes(hashValue)); 036 } 037 public Fingerprint (Image src){ 038 this(hashValue(src)); 039 } 040 private static byte[] hashValue(Image src){ 041 Assert.notNull(src, "src"); 042 BufferedImage hashImage = resize(src,HASH_SIZE,HASH_SIZE); 043 byte[] matrixGray = (byte[]) toGray(hashImage).getData().getDataElements(0, 0, HASH_SIZE, HASH_SIZE, null); 044 return binaryzation(matrixGray); 045 } 046 public static <T> Fingerprint createFromImage(T src) throws UnsupportedFormatException, NotImageException{ 047 return null==src?null:new Fingerprint(LazyImage.create(src).read(null, null)); 048 } 049 public static <T> Fingerprint createFromImageNoThrow(T src) { 050 try { 051 return createFromImage(src); 052 } catch (UnsupportedFormatException e) { 053 throw new RuntimeException(e); 054 } catch (NotImageException e) { 055 throw new RuntimeException(e); 056 } 057 } 058 /** 059 * ä»ŽåŽ‹ç¼©æ ¼å¼æŒ‡çº¹åˆ›å»º{@link Fingerprint}对象 060 * @param compactValue 061 * @return 062 */ 063 public static Fingerprint createFromCompact(byte[] compactValue){ 064 return Judge.isEmpty(compactValue)?null:new Fingerprint(uncompact(compactValue)); 065 } 066 /** 067 * @param compactValueHex 068 * @return 069 * @see #createFromCompact(byte[]) 070 */ 071 public static Fingerprint createFromCompact(String compactValueHex){ 072 return Judge.isEmpty(compactValueHex)?null:new Fingerprint(uncompact(FaceUtilits.hex2Bytes(compactValueHex))); 073 } 074 /** 075 * éªŒè¯æ˜¯å¦ä¸ºæœ‰æ•ˆçš„æŒ‡çº¹æ•°æ® 076 * @param hashValue 077 * @return 078 */ 079 public static boolean validHashValue(byte[] hashValue){ 080 Assert.notEmpty(hashValue, "hashValue"); 081 if(hashValue.length!=HASH_SIZE) 082 return false; 083 for(byte b:hashValue){ 084 if(0!=b&&1!=b)return false; 085 } 086 return true; 087 } 088 /** 089 * @param hashValue 090 * @return 091 * @see #validHashValue(byte[]) 092 */ 093 public static boolean validHashValue(String hashValue){ 094 Assert.notEmpty(hashValue, "hashValue"); 095 if(hashValue.length()!=HASH_SIZE) 096 return false; 097 for(int i=0;i<hashValue.length();++i){ 098 if('0'!=hashValue.charAt(i)&&'1'!=hashValue.charAt(i))return false; 099 } 100 return true; 101 } 102 /** 103 * 返回压缩å˜å‚¨çš„æŒ‡çº¹æ•°æ® 104 * @return 105 */ 106 public byte[] compact(){ 107 return compact(binaryzationMatrix); 108 } 109 /** 110 * 返回压缩å˜å‚¨çš„æŒ‡çº¹æ•°æ®(HEXå—符串) 111 * @return 112 */ 113 public String compactHEX(){ 114 return FaceUtilits.toHex(compact()); 115 } 116 /** 117 * æŒ‡çº¹æ•°æ®æŒ‰ä½åŽ‹ç¼© 118 * @param hashValue 119 * @return 120 */ 121 private static byte[] compact(byte[] hashValue){ 122 Assert.notEmpty(hashValue, "hashValue"); 123 byte[] result=new byte[(hashValue.length+7)>>3]; 124 byte b=0; 125 for(int i=0;i<hashValue.length;++i){ 126 if(0==(i&7)){ 127 b=0; 128 } 129 if(1==hashValue[i]){ 130 b|=1<<(i&7); 131 }else if(hashValue[i]!=0) 132 throw new IllegalArgumentException("invalid hashValue,every element must be 0 or 1"); 133 if(7==(i&7)||i==hashValue.length-1){ 134 result[i>>3]=b; 135 } 136 } 137 return result; 138 } 139 140 /** 141 * åŽ‹ç¼©æ ¼å¼çš„æŒ‡çº¹è§£åŽ‹ç¼© 142 * @param compactValue 143 * @return 144 */ 145 private static byte[] uncompact(byte[] compactValue){ 146 Assert.notEmpty(compactValue, "compactValue"); 147 byte[] result=new byte[compactValue.length<<3]; 148 for(int i=0;i<result.length;++i){ 149 if((compactValue[i>>3]&(1<<(i&7)))==0) 150 result[i]=0; 151 else 152 result[i]=1; 153 } 154 return result; 155 } 156 /** 157 * å—符串类型的指纹数æ®è½¬ä¸ºå—节数组 158 * @param hashValue 159 * @return 160 */ 161 private static byte[] toBytes(String hashValue){ 162 Assert.notEmpty(hashValue, "hashValue"); 163 hashValue=hashValue.replaceAll("\\s", ""); 164 byte[] result=new byte[hashValue.length()]; 165 for(int i=0;i<result.length;++i){ 166 char c = hashValue.charAt(i); 167 if('0'==c) 168 result[i]=0; 169 else if('1'==c) 170 result[i]=1; 171 else 172 throw new IllegalArgumentException("invalid hashValue String"); 173 } 174 return result; 175 } 176 /** 177 * 缩放图åƒåˆ°æŒ‡å®šå°ºå¯¸ 178 * @param src 179 * @param width 180 * @param height 181 * @return 182 */ 183 private static BufferedImage resize(Image src,int width,int height){ 184 Assert.notNull(src, "src"); 185 BufferedImage result = new BufferedImage(width, height, 186 BufferedImage.TYPE_3BYTE_BGR); 187 Graphics g = result.getGraphics(); 188 try{ 189 g.drawImage(src.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); 190 }finally{ 191 g.dispose(); 192 } 193 return result; 194 } 195 /** 196 * 计算å‡å€¼ 197 * @param src 198 * @return 199 */ 200 private static int mean(byte[] src){ 201 Assert.notEmpty(src, "src"); 202 long sum=0; 203 // å°†æ•°ç»„å…ƒç´ è½¬ä¸ºæ— ç¬¦å·æ•´æ•° 204 for(byte b:src)sum+=(long)b&0xff; 205 return (int) (Math.round((float)sum/src.length)); 206 } 207 /** 208 * äºŒå€¼åŒ–å¤„ç† 209 * @param src 210 * @return 211 */ 212 private static byte[] binaryzation(byte[]src){ 213 Assert.notEmpty(src, "src"); 214 byte[] dst = src.clone(); 215 int mean=mean(src); 216 for(int i=0;i<dst.length;++i){ 217 // å°†æ•°ç»„å…ƒç´ è½¬ä¸ºæ— ç¬¦å·æ•´æ•°å†æ¯”较 218 dst[i]=(byte) (((int)dst[i]&0xff)>=mean?1:0); 219 } 220 return dst; 221 222 } 223 /** 224 * 转ç°åº¦å›¾åƒ 225 * @param src 226 * @return 227 */ 228 private static BufferedImage toGray(BufferedImage src){ 229 Assert.notNull(src, "src"); 230 if(src.getType()==BufferedImage.TYPE_BYTE_GRAY){ 231 return src; 232 }else{ 233 // 图åƒè½¬ç° 234 BufferedImage grayImage = new BufferedImage(src.getWidth(), src.getHeight(), 235 BufferedImage.TYPE_BYTE_GRAY); 236 new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(src, grayImage); 237 return grayImage; 238 } 239 } 240 241 @Override 242 public String toString() { 243 return toString(true); 244 } 245 /** 246 * @param multiLine 是å¦åˆ†è¡Œ 247 * @return 248 */ 249 public String toString(boolean multiLine) { 250 StringBuffer buffer=new StringBuffer(); 251 int count=0; 252 for(byte b:this.binaryzationMatrix){ 253 buffer.append(0==b?'0':'1'); 254 if(multiLine&&++count%HASH_SIZE==0) 255 buffer.append('\n'); 256 } 257 return buffer.toString(); 258 } 259 @Override 260 public boolean equals(Object obj) { 261 if(super.equals(obj)) 262 return true; 263 if(obj instanceof Fingerprint) 264 return Arrays.equals(this.binaryzationMatrix,((Fingerprint)obj).binaryzationMatrix); 265 else 266 return false; 267 } 268 269 /** 270 * ä¸ŽæŒ‡å®šçš„åŽ‹ç¼©æ ¼å¼æŒ‡çº¹æ¯”较相似度 271 * @param compactValue 272 * @return 273 * @see #compare(Fingerprint) 274 */ 275 public float compareCompact(byte[] compactValue){ 276 return compare(createFromCompact(compactValue)); 277 } 278 /** 279 * @param hashValue 280 * @return 281 * @see #compare(Fingerprint) 282 */ 283 public float compare(String hashValue){ 284 return compare(new Fingerprint(hashValue)); 285 } 286 /** 287 * 与指定的指纹比较相似度 288 * @param hashValue 289 * @return 290 * @see #compare(Fingerprint) 291 */ 292 public float compare(byte[] hashValue){ 293 return compare(new Fingerprint(hashValue)); 294 } 295 /** 296 * ä¸ŽæŒ‡å®šå›¾åƒæ¯”较相似度 297 * @param image2 298 * @return 299 * @see #compare(Fingerprint) 300 */ 301 public float compare(Image image2){ 302 return compare(new Fingerprint(image2)); 303 } 304 /** 305 * 比较指纹相似度 306 * @param src 307 * @return 308 * @see #compare(byte[], byte[]) 309 */ 310 public float compare(Fingerprint src){ 311 Assert.notNull(src, "src"); 312 if(src.binaryzationMatrix.length!=this.binaryzationMatrix.length) 313 throw new IllegalArgumentException("length of hashValue is mismatch"); 314 return compare(binaryzationMatrix,src.binaryzationMatrix); 315 } 316 /** 317 * 判æ–两个数组相似度,数组长度必须一致å¦åˆ™æŠ›å‡ºå¼‚常 318 * @param f1 319 * @param f2 320 * @return 返回相似度(0.0~1.0) 321 */ 322 private static float compare(byte[] f1,byte[] f2){ 323 Assert.notEmpty(f1, "f1"); 324 Assert.notEmpty(f2, "f2"); 325 if(f1.length!=f2.length) 326 throw new IllegalArgumentException("mismatch FingerPrint length"); 327 int sameCount=0; 328 for(int i=0;i<f1.length;++i){ 329 if(f1[i]==f2[i])++sameCount; 330 } 331 return (float)sameCount/f1.length; 332 } 333 /** 334 * åŽ‹ç¼©æ ¼å¼æŒ‡çº¹æ¯”较 335 * @param f1 336 * @param f2 337 * @return 338 */ 339 public static float compareCompact(byte[] f1,byte[] f2){ 340 return compare(uncompact(f1),uncompact(f2)); 341 } 342 /** 343 * 比较两个图åƒå†…容的相似度 344 * @param image1 345 * @param image2 346 * @return 347 */ 348 public static float compare(Image image1,Image image2){ 349 Assert.notNull(image1, "image1"); 350 Assert.notNull(image2, "image2"); 351 return new Fingerprint(image1).compare(new Fingerprint(image2)); 352 } 353}