项目中有一个功能需要通过两张图片之间相似率来确定图盘是否一样,图片原图和一样原图上加上水印他们的相似率都不是百分之百的相似,如何实现,接来下就一起看一下代码!
看代码前先看一下图片(注意两张图片唯一的区别就是图片下面的那个水印)
先看工具类代码:
package com.tfjybj.english.provider.until;
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 class ContrastImgUntil {
/**
* 图像指纹的尺寸,将图像resize到指定的尺寸,来计算哈希数组
*/
private static final int HASH_SIZE = 16;
/**
* 保存图像指纹的二值化矩阵
*/
private final byte[] binaryzationMatrix;
public ContrastImgUntil(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 ContrastImgUntil(String hashValue) {
this(toBytes(hashValue));
}
/**
* 本次调用的方法,
* @param src
*/
public ContrastImgUntil(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 ContrastImgUntil}对象
*
* @param compactValue
* @return
*/
public static ContrastImgUntil createFromCompact(byte[] compactValue) {
return new ContrastImgUntil(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;
}
/**
* 缩放图像到指定尺寸1
*
* @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));
}
/**
* 二值化处理1
*
* @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;
}
/**
* 转灰度图像1
*
* @param src
* @return
*/
private static BufferedImage toGray(BufferedImage src) {
if (src.getType() == BufferedImage.TYPE_BYTE_GRAY) {
return src;
} else {
// 图像转灰
BufferedImage grayImage = new BufferedImage(src.getWidth(), src.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(src, grayImage);
return grayImage;
}
}
@Override
public String toString() {
return toString(true);
}
/**
* @param multiLine 是否分行
* @return
*/
public String toString(boolean multiLine) {
StringBuffer buffer = new StringBuffer();
int count = 0;
for (byte b : this.binaryzationMatrix) {
buffer.append(0 == b ? '0' : '1');
if (multiLine && ++count % HASH_SIZE == 0)
buffer.append('\n');
}
return buffer.toString();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ContrastImgUntil) {
return Arrays.equals(this.binaryzationMatrix, ((ContrastImgUntil) obj).binaryzationMatrix);
} else
return super.equals(obj);
}
/**
* 与指定的压缩格式指纹比较相似度
*
* @param compactValue
* @return
* @see #compare(ContrastImgUntil)
*/
public float compareCompact(byte[] compactValue) {
return compare(createFromCompact(compactValue));
}
/**
* @param hashValue
* @return
* @see #compare(ContrastImgUntil)
*/
public float compare(String hashValue) {
return compare(new ContrastImgUntil(hashValue));
}
/**
* 与指定的指纹比较相似度
*
* @param hashValue
* @return
* @see #compare(ContrastImgUntil)
*/
public float compare(byte[] hashValue) {
return compare(new ContrastImgUntil(hashValue));
}
/**
* 与指定图像比较相似度
*
* @param image2
* @return
* @see #compare(ContrastImgUntil)
*/
public float compare(BufferedImage image2) {
return compare(new ContrastImgUntil(image2));
}
/**
* 比较指纹相似度
*
* @param src
* @return
* @see #compare(byte[], byte[])
*/
public float compare(ContrastImgUntil src) {
if (src.binaryzationMatrix.length != this.binaryzationMatrix.length)
throw new IllegalArgumentException("length of hashValue is mismatch");
return compare(binaryzationMatrix, src.binaryzationMatrix);
}
/**
* 判断两个数组相似度,数组长度必须一致否则抛出异常
*
* @param f1
* @param f2
* @return 返回相似度(0.0 ~ 1.0)
*/
private static float compare(byte[] f1, byte[] f2) {
if (f1.length != f2.length)
throw new IllegalArgumentException("mismatch ContrastImgUntil length");
int sameCount = 0;
for (int i = 0; i < f1.length; ++i) {
if (f1[i] == f2[i]) ++sameCount;
}
return (float) sameCount / f1.length;
}
public static float compareCompact(byte[] f1, byte[] f2) {
return compare(uncompact(f1), uncompact(f2));
}
public static float compare(BufferedImage image1, BufferedImage image2) {
return new ContrastImgUntil(image1).compare(new ContrastImgUntil(image2));
}
}
测试类代码:
package com.tfjybj.english.provider.controller;
import com.tfjybj.english.provider.until.ContrastImgUntil;
import net.coobird.thumbnailator.Thumbnails;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
public class PhoneficControllerTest {
@Test
public void testFingerPrint() throws IOException {
ContrastImgUntil fp1 = new ContrastImgUntil(ImageIO.read(new File("图片地址")));
ContrastImgUntil fp2 = new ContrastImgUntil(ImageIO.read(new File("图片地址")));
System.out.println(fp1.toString(true));
System.out.println(fp2.toString(true));
System.out.printf(fp1.compare(fp2) + "");
}
}
运行结果:
图片1二进制码:
0001000001100001
0011111101110000
1111100000110000
1111000010000000
1111000010110001
1110000110011111
1110001110011111
1011011100001111
1011011000000111
1111100000000111
1101100000000111
1110000000000111
1110000000000011
0110000000000111
0111000000001111
1111011111111111
图片2二进制码:
0001000001100001
0011111101110000
1111100000110000
1111000010000000
1111000010110001
1110000110011111
1110001110011111
1011011100001111
1011011000000111
1111100000000111
1101100000000111
1110000000000111
1110000000000011
0110000000000111
0111000000001111
0111011111111111
图片相似率
0.99609375
因为上文设计到测试类,关于如何在idea中创建测试类连接: 在idea中创建Test测试类 ,懂得伙伴们可以略掉测句话,希望对伙伴们有所帮助!
有什么错误或者更好的方法,希望留言,勿喷!