java实现图片比较

一 感知哈希

/**
 * 感知哈希算法
 * @author LCJA
 *1.缩小图片:32 * 32是一个较好的大小,这样方便DCT计算
 *
 *2.转化为灰度图:把缩放后的图片转化为256阶的灰度图。(具体算法见平均哈希算法步骤)
 *
 *3.计算DCT:DCT把图片分离成分率的集合
 *
 *4.缩小DCT:DCT是32*32,保留左上角的8*8,这些代表的图片的最低频率
 *
 *5.计算平均值:计算缩小DCT后的所有像素点的平均值。
 *
 *6.进一步减小DCT:大于平均值记录为1,反之记录为0.
 *
 *7.得到信息指纹:组合64个信息位,顺序随意保持一致性即可。
 *
 *8.对比指纹:计算两幅图片的指纹,计算汉明距离(从一个指纹到另一个指纹需要变几次),汉明距离越大则说明图片越不一致,
 *反之,汉明距离越小则说明图片越相似,当距离为0时,说明完全相同。(通常认为距离>10 就是两张完全不同的图片)
 *
 */

代码

public class PHash {

    /**
     * 缩小图片
     * @param image
     * @param width
     * @param height
     * @return
     */
    public static BufferedImage reduceSize(BufferedImage image, int width,int height) {
        BufferedImage new_image = null;
        double width_times = (double) width / image.getWidth();
        double height_times = (double) height / image.getHeight();
        if (image.getType() == BufferedImage.TYPE_CUSTOM) {
            ColorModel cm = image.getColorModel();
            WritableRaster raster = cm.createCompatibleWritableRaster(width,height);
            boolean alphaPremultiplied = cm.isAlphaPremultiplied();
            new_image = new BufferedImage(cm, raster, alphaPremultiplied, null);
        } else{
            new_image = new BufferedImage(width, height, image.getType());
        }
        Graphics2D g = new_image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
        g.drawRenderedImage(image, AffineTransform.getScaleInstance(width_times, height_times));
        g.dispose();
        return new_image;
    }

    /**
     * 得到灰度值
     * @param image
     * @return
     */
    public static double[][] getGrayValue(BufferedImage image) {
        int width = image.getWidth();  
        int height = image.getHeight();  
        double[][] pixels = new double[width][height];
        for(int i = 0; i < width; i++){
            for(int j = 0; j < height; j++){
                pixels[i][j] = computeGrayValue(image.getRGB(i, j)); 
            }  
        }  
        return pixels;
    }
    /**
     * 计算灰度值
     * @param pixels
     * @return
     */
    public static double computeGrayValue(int pixel) {
        int red = (pixel >> 16) & 0xFF;
        int green = (pixel >> 8) & 0xFF;
        int blue = (pixel) & 255;
        return  0.3 * red + 0.59 * green + 0.11 * blue;
    }

    public static int avgImage(int[][] smallImage){
        int avg=-1;
        int sum=0;
        int count=0;
        for(int i=0;i<smallImage.length;i++){
            for(int j=0;j<smallImage[i].length;j++){
                sum+=smallImage[i][j];
                count++;
            }
        }
        avg=sum/count;
        return avg;
    }
    public static String to64(int avg,int[][] smallImage){
        String result = "";
        for(int i=0;i<smallImage.length;i++){
            for(int j=0;j<smallImage[i].length;j++){
                if(smallImage[i][j]>avg){
                    result+="1";
                }else{
                    result+="0";
                }
            }
        }
        return result;
    }
    //越小越相似
    public static int compareFingerPrint(String orgin_fingerprint, String compared_fingerprint) {
        int count = 0;
        for(int i=0;i<orgin_fingerprint.length();i++){
            if(orgin_fingerprint.charAt(i)!=compared_fingerprint.charAt(i)){
                count++;
            }
        }
        return count;
    }


    public static String toPhash(BufferedImage image){
        //缩小图片
        BufferedImage newImage= reduceSize(image, 32, 32);
        //转换为256位灰度
        double[][] pixels = getGrayValue(image);
        //计算DCT
        DCT dct =new DCT(25);
        int[][] tempDCT = dct.forwardDCT(pixels);
        //缩小DCT
        int[][] smallImage = dct.dequantitizeImage(tempDCT,false);
        //计算平均值
        int avg = avgImage(smallImage);
        //进一步减小DCT,得到信息指纹
        String result =to64(avg, smallImage);
        return result;
    }

    public static void main(String[] args) throws IOException {
        String phash1 = toPhash(ImageIO.read(new File("C:/Users/dell/Desktop/11.png")));
        String phash2 = toPhash(ImageIO.read(new File("C:/Users/dell/Desktop/12.png")));
        System.out.println(phash1);
        System.out.println(phash2);
        int r1 =compareFingerPrint(phash1,phash2);
        String ahash1 =SimilarImageSearch.produceFingerPrint(ImageIO.read(new File("C:/Users/dell/Desktop/11.png")));
        String ahash2 =SimilarImageSearch.produceFingerPrint(ImageIO.read(new File("C:/Users/dell/Desktop/12.png")));
        System.out.println(ahash1);
        System.out.println(ahash2);
        int r2 =compareFingerPrint(ahash1,ahash2);
        System.out.println("r1="+r1);
        System.out.println("r2="+r2);

    }

DCT转换,这个是找的现成的,具体算法不太了解


/***
 * 
 * 
 */

/**
 * <h3><b>DCT - A Java implementation of the Discreet Cosine Transform</b></h3><br><br>
 * <hr>
 * The discreet cosine transform converts spatial information to "frequency" or
 * spectral information, with the X and Y axes representing frequencies of the
 * signal in different dimensions. This allows for "lossy" compression of image
 * data by determining which information can be thrown away without compromising
 * the image.<br><br>
 * The DCT is used in many compression and transmission codecs, such as JPEG, MPEG
 * and others. The pixels when transformed are arraged from the most signifigant pixel
 * to the least signifigant pixel. The DCT functions themselves are lossless.
 * Pixel loss occurs when the least signifigant pixels are quantitized to 0.
 * <br><br>
 * This is NOT a JPEG or JFIF compliant implementation however it could
 * be with very little extra work. (i.e. A huffman encoding stage needs
 * to be added.) I am making this source availible in the hopes that
 * someone will add this functionality to the class, if you do, please
 * email me! As always, if you have any problems feel free to contact
 * me. (Or comments, or praise, etc..)
 * <br><br>
 * <b>Keep in mind</b> that when compressing color images with this, you will
 * need to break the image up into it's R G B components and preform
 * the calculations three times!!
 * <br><br>
 * <b>A general algorithim for DCT compression with this class:</b> <br><br>
 * 1) Create a DCT Object.<br>
 * 2) Set up your program to read pixel information in 8*8 blocks. See example.<br>
 * 3) Run the forwardDCT() on all blocks.<br>
 * 4) Run the quantitizeImage() on all blocks.<br>
 * 5) If you want, send the information to the imageCompressor().<br><br>
 * <b>A general algorithim for DCT decompression with this class:</b> <br><br>
 * 1) Create a DCT Object. <br>
 * 2) Set up the program to convert compressed data in 8*8 blocks. (if compressed)<br>
 * 3) Run the data through dequantitizeImage(). <br>
 * 4) Run the data through inverseDCT().<br>
 * <br><br>
 * A complete implementation of an image compressor which compares
 * the quality of the two images is also availible. See the
 * JEncode/Decode <a href="JEncode.java">source code.</a> The
 * <a href="DCT.java">DCT.java source code</a> is also availible.
 * The implementation is also handy for seeing how to break the image
 * down, read in 8x8 blocks, and reconstruct it. The <a href="steve.jpg">
 * sample graphic</a> is handy to have too. (Bad pic of me)
 * The best way to get ahold of me is through my
 * <a href="http://eagle.uccb.ns.ca/steve/home.html">homepage</a>. There's
 * lots of goodies there too.
 * @version 1.0.1 August 22nd 1996
 * @author <a href="http://eagle.uccb.ns.ca/steve/home.html">Stephen Manley</a> - smanley@eagle.uccb.ns.ca
 */

// <pre> 
public class DCT
{
    /**
     * DCT Block Size - default 8
     */
    public int N        = 8;

    /**
     * Image Quality (0-25) - default 25 (worst image / best compression)
     */
    public int QUALITY  = 25;

    /**
     * Image width - must correspond to imageArray bounds - default 320
     */
    public int ROWS     = 320;

    /**
     * Image height - must correspond to imageArray bounds - default 200
     */
    public int COLS     = 240;

    /**
     * The ZigZag matrix.
     */
    public int zigZag[][] = new int[64][2];

    /**
     * Cosine matrix. N * N.
     */
    public double c[][]        = new double[N][N];

    /**
     * Transformed cosine matrix, N*N.
     */
    public double cT[][]       = new double[N][N];

    /**
     * Quantitization Matrix.
     */
    public int quantum[][]     = new int[N][N];

    /**
     * DCT Result Matrix
     */
    public int resultDCT[][] = new int[ROWS][COLS];

    /**
     * Constructs a new DCT object. Initializes the cosine transform matrix
     * these are used when computing the DCT and it's inverse. This also
     * initializes the run length counters and the ZigZag sequence. Note that
     * the image quality can be worse than 25 however the image will be
     * extemely pixelated, usually to a block size of N.
     *
     * @param QUALITY The quality of the image (0 best - 25 worst)
     *
     */
    public DCT(int QUALITY)
    {
        initZigZag();
        initMatrix(QUALITY);
    }


    /**
     * This method sets up the quantization matrix using the Quality parameter
     * and then sets up the Cosine Transform Matrix and the Transposed CT.
     * These are used by the forward and inverse DCT. The RLE encoding
     * variables are set up to track the number of consecutive zero values
     * that have output or will be input.
     * @param quality The quality scaling factor
     */
    private void initMatrix(int quality)
    {
        int i;
        int j;

        for (i = 0; i < N; i++)
        {
            for (j = 0; j < N; j++)
            {
                quantum[i][j] = (1 + ((1 + i + j) * quality));
            }
        }

        for (j = 0; j < N; j++)
        {
            double nn = (double)(N);
            c[0][j]  = 1.0 / Math.sqrt(nn);
            cT[j][0] = c[0][j];
        }

        for (i = 1; i < 8; i++)
        {
            for (j = 0; j < 8; j++)
            {
                double jj = (double)j;
                double ii = (double)i;
                c[i][j]  = Math.sqrt(2.0/8.0) * Math.cos(((2.0 * jj + 1.0) * ii * Math.PI) / (2.0 * 8.0));
                cT[j][i] = c[i][j];
            }
        }
    }

    /**
     * Initializes the ZigZag matrix.
     */
    private void initZigZag()
    {
        zigZag[0][0] = 0; // 0,0
        zigZag[0][1] = 0;
        zigZag[1][0] = 0; // 0,1
        zigZag[1][1] = 1;
        zigZag[2][0] = 1; // 1,0
        zigZag[2][1] = 0;
        zigZag[3][0] = 2; // 2,0
        zigZag[3][1] = 0;
        zigZag[4][0] = 1; // 1,1
        zigZag[4][1] = 1;
        zigZag[5][0] = 0; // 0,2
        zigZag[5][1] = 2;
        zigZag[6][0] = 0; // 0,3
        zigZag[6][1] = 3;
        zigZag[7][0] = 1; // 1,2
        zigZag[7][1] = 2;
        zigZag[8][0] = 2; // 2,1
        zigZag[8][1] = 1;
        zigZag[9][0] = 3; // 3,0
        zigZag[9][1] = 0;
        zigZag[10][0] = 4; // 4,0
        zigZag[10][1] = 0;
        zigZag[11][0] = 3; // 3,1
        zigZag[11][1] = 1;
        zigZag[12][0] = 2; // 2,2
        zigZag[12][1] = 2;
        zigZag[13][0] = 1; // 1,3
        zigZag[13][1] = 3;
        zigZag[14][0] = 0; // 0,4
        zigZag[14][1] = 4;
        zigZag[15][0] = 0; // 0,5
        zigZag[15][1] = 5;
        zigZag[16][0] = 1; // 1,4
        zigZag[16][1] = 4;
        zigZag[17][0] = 2; // 2,3
        zigZag[17][1] = 3;
        zigZag[18][0] = 3; // 3,2
        zigZag[18][1] = 2;
        zigZag[19][0] = 4; // 4,1
        zigZag[19][1] = 1;
        zigZag[20][0] = 5; // 5,0
        zigZag[20][1] = 0;
        zigZag[21][0] = 6; // 6,0
        zigZag[21][1] = 0;
        zigZag[22][0] = 5; // 5,1
        zigZag[22][1] = 1;
        zigZag[23][0] = 4; // 4,2
        zigZag[23][1] = 2;
        zigZag[24][0] = 3; // 3,3
        zigZag[24][1] = 3;
        zigZag[25][0] = 2; // 2,4
        zigZag[25][1] = 4;
        zigZag[26][0] = 1; // 1,5
        zigZag[26][1] = 5;
        zigZag[27][0] = 0; // 0,6
        zigZag[27][1] = 6;
        zigZag[28][0] = 0; // 0,7
        zigZag[28][1] = 7;
        zigZag[29][0] = 1; // 1,6
        zigZag[29][1] = 6;
        zigZag[30][0] = 2; // 2,5
        zigZag[30][1] = 5;
        zigZag[31][0] = 3; // 3,4
        zigZag[31][1] = 4;
        zigZag[32][0] = 4; // 4,3
        zigZag[32][1] = 3;
        zigZag[33][0] = 5; // 5,2
        zigZag[33][1] = 2;
        zigZag[34][0] = 6; // 6,1
        zigZag[34][1] = 1;
        zigZag[35][0] = 7; // 7,0
        zigZag[35][1] = 0;
        zigZag[36][0] = 7; // 7,1
        zigZag[36][1] = 1;
        zigZag[37][0] = 6; // 6,2
        zigZag[37][1] = 2;
        zigZag[38][0] = 5; // 5,3
        zigZag[38][1] = 3;
        zigZag[39][0] = 4; // 4,4
        zigZag[39][1] = 4;
        zigZag[40][0] = 3; // 3,5
        zigZag[40][1] = 5;
        zigZag[41][0] = 2; // 2,6
        zigZag[41][1] = 6;
        zigZag[42][0] = 1; // 1,7
        zigZag[42][1] = 7;
        zigZag[43][0] = 2; // 2,7
        zigZag[43][1] = 7;
        zigZag[44][0] = 3; // 3,6
        zigZag[44][1] = 6;
        zigZag[45][0] = 4; // 4,5
        zigZag[45][1] = 5;
        zigZag[46][0] = 5; // 5,4
        zigZag[46][1] = 4;
        zigZag[47][0] = 6; // 6,3
        zigZag[47][1] = 3;
        zigZag[48][0] = 7; // 7,2
        zigZag[48][1] = 2;
        zigZag[49][0] = 7; // 7,3
        zigZag[49][1] = 3;
        zigZag[50][0] = 6; // 6,4
        zigZag[50][1] = 4;
        zigZag[51][0] = 5; // 5,5
        zigZag[51][1] = 5;
        zigZag[52][0] = 4; // 4,6
        zigZag[52][1] = 6;
        zigZag[53][0] = 3; // 3,7
        zigZag[53][1] = 7;
        zigZag[54][0] = 4; // 4,7
        zigZag[54][1] = 7;
        zigZag[55][0] = 5; // 5,6
        zigZag[55][1] = 6;
        zigZag[56][0] = 6; // 6,5
        zigZag[56][1] = 5;
        zigZag[57][0] = 7; // 7,4
        zigZag[57][1] = 4;
        zigZag[58][0] = 7; // 7,5
        zigZag[58][1] = 5;
        zigZag[59][0] = 6; // 6,6
        zigZag[59][1] = 6;
        zigZag[60][0] = 5; // 5,7
        zigZag[60][1] = 7;
        zigZag[61][0] = 6; // 6,7
        zigZag[61][1] = 7;
        zigZag[62][0] = 7; // 7,6
        zigZag[62][1] = 6;
        zigZag[63][0] = 7; // 7,7
        zigZag[63][1] = 7;
    }

    /**
     * This method preforms a matrix multiplication of the input pixel data matrix
     * by the transposed cosine matrix and store the result in a temporary
     * N * N matrix. This N * N matrix is then multiplied by the cosine matrix
     * and the result is stored in the output matrix.
     *
     * @param input The Input Pixel Matrix
     * @returns output The DCT Result Matrix
     */
    public int[][] forwardDCT(double input[][])
    {
        int output[][] = new int[N][N];
        double temp[][] = new double[N][N];
        double temp1;
        int i;
        int j;
        int k;

        for (i = 0; i < N; i++)
        {
            for (j = 0; j < N; j++)
            {
                temp[i][j] = 0.0;
                for (k = 0; k < N; k++)
                {
                    temp[i][j] += (((double)(input[i][k]) - 128) * cT[k][j]);
                }
            }
        }

        for (i = 0; i < N; i++)
        {
            for (j = 0; j < N; j++)
            {
                temp1 = 0.0;

                for (k = 0; k < N; k++)
                {
                    temp1 += (c[i][k] * temp[k][j]);
                }

                output[i][j] = (int)Math.round(temp1);
            }
        }

        return output;
    }

    /**
     * This method reads in DCT codes  dequanitizes them
     * and places them in the correct location. The codes are stored in the
     * zigzag format so they need to be redirected to a N * N block through
     * simple table lookup. After dequantitization the data needs to be
     * run through an inverse DCT.
     *
     * @param inputData 8x8 Array of quantitized image data
     * @param zigzag Boolean switch to enable/disable zigzag path.
     * @returns outputData A N * N array of de-quantitized data
     *
     */
    public int[][] dequantitizeImage(int[][] inputData, boolean zigzag)
    {
        int i = 0;
        int j = 0;
        int a = 0;
        int b = 0;
        int row;
        int col;
        int outputData[][] = new int[N][N];

        double result;

        if (zigzag)
        {
            for (i=0; i<(N*N); i++)
            {
                row = zigZag[i][0];
                col = zigZag[i][1];

                result = inputData[row][col] * quantum[row][col];
                outputData[row][col] = (int)(Math.round(result));
            }
        }

        else
        {
            for (i=0; i<8; i++)
            {
                for (j=0; j<8; j++)
                {
                    result = inputData[i][j] * quantum[i][j];
                    outputData[i][j] = (int)(Math.round(result));
                }
            }
        }

        return outputData;
    }

    /**
     * This method orders the DCT result matrix into a zigzag pattern and then
     * quantitizes the data. The quantitized value is rounded to the nearest integer.
     * Pixels which round or divide to zero are the loss associated with
     * quantitizing the image. These pixels do not display in the AWT. (null)
     * Long runs of zeros and the small ints produced through this technique
     * are responsible for the small image sizes. For block sizes < or > 8,
     * disable the zigzag optimization. If zigzag is disabled on encode it
     * must be disabled on decode as well.
     *
     * @param inputData 8x8 array of DCT image data.
     * @param zigzag Boolean switch to enable/disable zigzag path.
     * @returns outputData The quantitized output data
     */
    public int[][] quantitizeImage(int inputData[][], boolean zigzag)
    {
        int outputData[][] = new int[N][N];
        int i = 0;
        int j = 0;
        int a = 0;
        int b = 0;
        int row;
        int col;

        double result;

        if (zigzag)
        {
            for (i = 0; i < (N*N); i++)
            {
                row = zigZag[i][0];
                col = zigZag[i][1];
                result = (inputData[row][col] / quantum[row][col]);
                outputData[row][col] = (int)(Math.round(result));
            }

        }

        else
        {
            for (i=0; i<N; i++)
            {
                for (j=0; j<N; j++)
                {
                    result = inputData[i][j] / quantum[i][j];
                    outputData[i][j] = (int)(Math.round(result));
                }
            }
        }

        return outputData;
    }

    /**
     * <br><br>
     * This does not huffman code,
     * it uses a minimal run-length encoding scheme. Huffman, Adaptive Huffman
     * or arithmetic encoding will give much better preformance. The information
     * accepted is quantitized DCT data. The output array should be
     * scanned to determine where the end is.
     *
     * @param image Quantitized image data.
     * @returns The string representation of the image. (Compressed)
     */
    public int[] compressImage(int[] QDCT, boolean log)
    {
        int i = 0;
        int j = 0;
        int k = 0;
        int temp = 0;
        int curPos = 0;
        int runCounter = 0;
        int imageLength = ROWS*COLS;

        int pixel[] = new int[ROWS*COLS];

        while((i<imageLength))
        {
            temp = QDCT[i];

            while((i < imageLength) && (temp == QDCT[i]))
            {
                runCounter++;
                i++;
            }

            if (runCounter > 4)
            {
                pixel[j] = 255;
                j++;
                pixel[j] = temp;
                j++;
                pixel[j] = runCounter;
                j++;
            }
            else
            {
                for (k=0; k<runCounter; k++)
                {
                    pixel[j] = temp;
                    j++;
                }
            }

            if (log)
            {
                System.out.print("." + "\r");
            }

            runCounter = 0;
            //i++;
        }

        return pixel;
    }

    /**
     * This method determines the runs in the input data, decodes it
     * and then returnss the corrected matrix. It is used to decode the data
     * from the compressImage method. Huffman encoding, Adaptive Huffman or
     * Arithmetic will give much better compression.
     *
     * @param DCT Compressed DCT int array (Expands to whole image).
     * @returns The decompressed one dimensional array.
     */
    public int[] decompressImage(int[] DCT, boolean log)
    {
        int i = 0;
        int j = 0;
        int k = 0;
        int temp = 0;
        int imageLength = ROWS*COLS;
        int pixel[] = new int[ROWS*COLS];

        while (i < imageLength)
        {
            temp = DCT[i];

            if (k < imageLength)
            {
                if (temp == 255)
                {
                    i++;
                    int value = DCT[i];
                    i++;
                    int length = DCT[i];

                    for(j=0; j<length; j++)
                    {
                        pixel[k] = value;
                        k++;
                    }
                }

                else
                {
                    pixel[k] = temp;
                    k++;
                }
            }
            if (log)
            {
                System.out.print(".." + "\r");
            }

            i++;
        }

        for (int a = 0; a < 80; a++)
        {
            System.out.print(pixel[a] + " ");
        }
        System.out.println();
        for (int a = 0; a < 80; a++)
        {
            System.out.print(DCT[a] + " ");
        }

        return pixel;
    }

    /**
     * This method is preformed using the reverse of the operations preformed in
     * the DCT. This restores a N * N input block to the corresponding output
     * block with values scaled to 0 to 255 and then stored in the input block
     * of pixels.
     *
     * @param input N * N input block
     * @returns output The pixel array output
     */
    public int[][] inverseDCT(int input[][])
    {
        int output[][] = new int[N][N];
        double temp[][] = new double[N][N];
        double temp1;
        int i;
        int j;
        int k;

        for (i=0; i<N; i++)
        {
            for (j=0; j<N; j++)
            {
                temp[i][j] = 0.0;

                for (k=0; k<N; k++)
                {
                    temp[i][j] += input[i][k] * c[k][j];
                }
            }
        }

        for (i=0; i<N; i++)
        {
            for (j=0; j<N; j++)
            {
                temp1 = 0.0;

                for (k=0; k<N; k++)
                {
                    temp1 += cT[i][k] * temp[k][j];
                }

                temp1 += 128.0;

                if (temp1 < 0)
                {
                    output[i][j] = 0;
                }
                else if (temp1 > 255)
                {
                    output[i][j] = 255;
                }
                else
                {
                     output[i][j] = (int)Math.round(temp1);
                }
            }
        }

        return output;
    }

    /**
     * This method uses bit shifting to convert an array of two bytes
     * to a integer (16 bits max). Byte 0 is the most signifigant byte
     * and Byte 1 is the least signifigant byte.
     * @param bitSet Two bytes to convert
     * @returns The constructed integer
     */
    private int bytetoInt(byte bitSet[])
    {
        int returnInt = 0;

        byte MSB = bitSet[0];
        byte LSB = bitSet[1];

        returnInt = ((MSB+128) << 8) | (LSB+128);

        return returnInt;
    }

    /**
     * This method uses bit shifting to convert an integer to an array
     * of two bytes, byte 0, the most signifigant and byte 1, the least
     * signifigant.
     * @param count The integer to convert. (16 bit max)
     * @returns The array of two bytes.
     */
    private byte[] inttoByte(int count)
    {
        int LSB = 0;
        int MSB = 0;
        byte bitSet[] = new byte[2];

        if (!(count > 65535))
        {
            LSB = ((count & 0x000000ff));
            MSB = ((count & 0x0000ff00) >> 8);
        }
        else
        {
            System.out.println("Integer > than 16 bit. Exiting..");
            System.exit(count);
        }

        bitSet[0] = (byte)(MSB-128);
        bitSet[1] = (byte)(LSB-128);

        return bitSet;
    }
}
// </pre>

二 平均哈希

// 第一步,缩小尺寸。
// 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
// 第二步,简化色彩。
// 将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
// 第三步,计算平均值。
// 计算所有64个像素的灰度平均值。
// 第四步,比较像素的灰度。
// 将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
// 第五步,计算哈希值。
// 将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
// 得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。
public class SimilarImageSearch {
    private static Logger logger = LoggerFactory.getLogger(SimilarImageSearch.class);

    public static List<String> hashCodes;
    public static List<String> hashCodes1;
    /**
     * 计算"汉明距离"(Hamming distance)。
     * 如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。
     * @param sourceHashCode 源hashCode
     * @param hashCode 与之比较的hashCode
     */
    public static int hammingDistance(String sourceHashCode, String hashCode) {
        int difference = 0;
        int len = sourceHashCode.length();

        for (int i = 0; i < len; i++) {
            if (sourceHashCode.charAt(i) != hashCode.charAt(i)) {
                difference ++;
            } 
        }

        return difference;
    }

    public static String produceFingerPrint(BufferedImage source) {

        int width = 12;
        int height = 12;

        // 第一步,缩小尺寸。
        // 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
        BufferedImage thumb = ImageUtil.thumb(source, width, height, false);

        // 第二步,简化色彩。
        // 将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
        int[] pixels = new int[width * height];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                pixels[i * height + j] = ImageUtil.rgbToGray(thumb.getRGB(i, j));
            }
        }

        // 第三步,计算平均值。
        // 计算所有64个像素的灰度平均值。
        int avgPixel = ImageUtil.average(pixels);

        // 第四步,比较像素的灰度。
        // 将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
        int[] comps = new int[width * height];
        for (int i = 0; i < comps.length; i++) {
            if (pixels[i] >= avgPixel) {
                comps[i] = 1;
            } else {
                comps[i] = 0;
            }
        }

        // 第五步,计算哈希值。
        // 将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
        StringBuffer hashCode = new StringBuffer();
        for (int i = 0; i < comps.length; i+= 4) {
            int result = comps[i] * (int) Math.pow(2, 3) + comps[i + 1] * (int) Math.pow(2, 2) + comps[i + 2] * (int) Math.pow(2, 1) + comps[i + 2];
            hashCode.append(binaryToHex(result));
        }

        // 得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。
        return hashCode.toString();
    }

    /**
     * 生成图片指纹
     * @param filename 文件名
     * @return 图片指纹
     */
    public static String produceFingerPrint(CommonsMultipartFile file) {
        String filename = file.getOriginalFilename();
        System.out.println(filename);
        BufferedImage source = null;
        try {
            if(filename.endsWith("png")){
                source = ImageUtil.readPNGImage(file.getInputStream());
            }else if(filename.endsWith("jpg")){
                source = ImageUtil.readJPEGImage(file.getInputStream());
            }
            return produceFingerPrint(source);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String produceFingerPrint(File file) {
        BufferedImage source = ImageUtil.readPNGImage(file);// 读取文件

        int width = 12;
        int height = 12;

        // 第一步,缩小尺寸。
        // 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
        BufferedImage thumb = ImageUtil.thumb(source, width, height, false);

        // 第二步,简化色彩。
        // 将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
        int[] pixels = new int[width * height];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                pixels[i * height + j] = ImageUtil.rgbToGray(thumb.getRGB(i, j));
            }
        }

        // 第三步,计算平均值。
        // 计算所有64个像素的灰度平均值。
        int avgPixel = ImageUtil.average(pixels);

        // 第四步,比较像素的灰度。
        // 将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
        int[] comps = new int[width * height];
        for (int i = 0; i < comps.length; i++) {
            if (pixels[i] >= avgPixel) {
                comps[i] = 1;
            } else {
                comps[i] = 0;
            }
        }

        // 第五步,计算哈希值。
        // 将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
        StringBuffer hashCode = new StringBuffer();
        for (int i = 0; i < comps.length; i+= 4) {
            int result = comps[i] * (int) Math.pow(2, 3) + comps[i + 1] * (int) Math.pow(2, 2) + comps[i + 2] * (int) Math.pow(2, 1) + comps[i + 2];
            hashCode.append(binaryToHex(result));
        }

        System.out.println(hashCode.toString());
        return hashCode.toString();
    }
    /**
     * 十进制转为十六进制
     * @param int binary
     * @return char hex
     */
    private static char binaryToHex(int binary) {
        char ch = ' ';
        switch (binary)
        {
        case 0:
            ch = '0';
            break;
        case 1:
            ch = '1';
            break;
        case 2:
            ch = '2';
            break;
        case 3:
            ch = '3';
            break;
        case 4:
            ch = '4';
            break;
        case 5:
            ch = '5';
            break;
        case 6:
            ch = '6';
            break;
        case 7:
            ch = '7';
            break;
        case 8:
            ch = '8';
            break;
        case 9:
            ch = '9';
            break;
        case 10:
            ch = 'a';
            break;
        case 11:
            ch = 'b';
            break;
        case 12:
            ch = 'c';
            break;
        case 13:
            ch = 'd';
            break;
        case 14:
            ch = 'e';
            break;
        case 15:
            ch = 'f';
            break;
        default:
            ch = ' ';
        }
        return ch;
    }

    public static List<String> getHashCodes() {
        return hashCodes;
    }

    public static void setHashCodes(List<String> hashCodes) {
        SimilarImageSearch.hashCodes = hashCodes;
    }

    public static List<String> getHashCodes1() {
        return hashCodes1;
    }

    public static void setHashCodes1(List<String> hashCodes1) {
        SimilarImageSearch.hashCodes1 = hashCodes1;
    }

}

相比较下 平均哈希比较的更准确,感知哈希可以找到更多相似

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值