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}