/**
* 感知哈希算法
* @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;ifor(int j=0;jreturn avg;
}
public static String to64(int avg,int[][] smallImage){
String result = "";
for(int i=0;ifor(int j=0;jif(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;iif(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转换,这个是找的现成的,具体算法不太了解
/***
*
*
*/
/**
* DCT - A Java implementation of the Discreet Cosine Transform
*
* 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.
* 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.
*
* 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..)
*
* Keep in mind 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!!
*
* A general algorithim for DCT compression with this class:
* 1) Create a DCT Object.
* 2) Set up your program to read pixel information in 8*8 blocks. See example.
* 3) Run the forwardDCT() on all blocks.
* 4) Run the quantitizeImage() on all blocks.
* 5) If you want, send the information to the imageCompressor().
* A general algorithim for DCT decompression with this class:
* 1) Create a DCT Object.
* 2) Set up the program to convert compressed data in 8*8 blocks. (if compressed)
* 3) Run the data through dequantitizeImage().
* 4) Run the data through inverseDCT().
*
* A complete implementation of an image compressor which compares
* the quality of the two images is also availible. See the
* JEncode/Decode source code. The
* DCT.java source code is also availible.
* The implementation is also handy for seeing how to break the image
* down, read in 8x8 blocks, and reconstruct it. The
* sample graphic is handy to have too. (Bad pic of me)
* The best way to get ahold of me is through my
* homepage. There's
* lots of goodies there too.
* @version 1.0.1 August 22nd 1996
* @author Stephen Manley - [email protected]
*/
//
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; ifor (j=0; jint)(Math.round(result));
}
}
}
return outputData;
}
/**
*
* 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((iwhile((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; kif (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; jelse
{
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; ifor (j=0; j0.0;
for (k=0; kfor (i=0; ifor (j=0; j0.0;
for (k=0; k128.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;
}
}
//
// 第一步,缩小尺寸。
// 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
// 第二步,简化色彩。
// 将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
// 第三步,计算平均值。
// 计算所有64个像素的灰度平均值。
// 第四步,比较像素的灰度。
// 将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
// 第五步,计算哈希值。
// 将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
// 得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。
public class SimilarImageSearch {
private static Logger logger = LoggerFactory.getLogger(SimilarImageSearch.class);
public static List hashCodes;
public static List 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 getHashCodes() {
return hashCodes;
}
public static void setHashCodes(List hashCodes) {
SimilarImageSearch.hashCodes = hashCodes;
}
public static List getHashCodes1() {
return hashCodes1;
}
public static void setHashCodes1(List hashCodes1) {
SimilarImageSearch.hashCodes1 = hashCodes1;
}
}
相比较下 平均哈希比较的更准确,感知哈希可以找到更多相似