目录
将图片DCT与 平均值进行比较,大于平均值的计为1,否则计为0,将会得到 64 位的Hash值
注意,图片尺寸N越大,搜索准确率越大,耗时越久(一般取值范围在8~32)
计算搜索图与目标图 Hash值的 汉明距离,数值越小,两图结构越相似
三、获取想要搜索的头像的Hash 值 ,与数据库进行匹配,按相似度高低进行升序排列
实现思路
一、对比两图相似度
-
缩小图片尺寸N*N,保证搜索图 和目标图尺寸一致。
private BufferedImage resize(BufferedImage image, int width, int height) { BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = resizedImage.createGraphics(); g.drawImage(image, 0, 0, width, height, null); g.dispose(); return resizedImage; }
-
减少图片颜色,将图片转为灰度图
colorConvert.filter(img, img); double[][] vals = new double[size][size]; for (int x = 0; x < img.getWidth(); x++) { for (int y = 0; y < img.getHeight(); y++) { vals[x][y] = getBlue(img, x, y); } }
-
计算图片DCT,计算出角块 8 * 8DCT平均值
/* * 计算DTC */ long start = System.currentTimeMillis(); double[][] dctVals = applyDCT(vals); System.out.println("DCT: " + (System.currentTimeMillis() - start)); /* * 计算平均值DTC */ double total = 0; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { total += dctVals[x][y]; } } total -= dctVals[0][0]; double avg = total / (double) ((smallerSize * smallerSize) - 1);
/** * 获得DCT * @param val * @return */ private double[][] applyDCT(double[][] val) { int n = size; double[][] f = new double[n][n]; for (int u = 0; u < n; u++) { for (int v = 0; v < n; v++) { double sum = 0.0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { sum += Math.cos(((2 * i + 1) / (2.0 * n)) * u * Math.PI) * Math.cos(((2 * j + 1) / (2.0 * n)) * v * Math.PI) * (val[i][j]); } } sum *= ((c[u] * c[v]) / 4.0); f[u][v] = sum; } } return f; }
-
将图片DCT与 平均值进行比较,大于平均值的计为1,否则计为0,将会得到 64 位的Hash值
/* * 计算hash值 */ String hash = ""; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { if (x != 0 && y != 0) { hash += (dctVals[x][y] > avg ? "1" : "0"); } } }
-
注意,图片尺寸N越大,搜索准确率越大,耗时越久(一般取值范围在8~32)
-
计算搜索图与目标图 Hash值的 汉明距离,数值越小,两图结构越相似
二、保存头像Hash值到数据库
这里会遇到一个问题:Hash 是 64位 ,如果以保存为字符串,会影响汉明距离的计算速度,如果头像上百万的话,就会很慢了。
我的解决办法是将 64 位 Hash 分成4份,转为十进制分别保存在 数据库4个字段中
ImagepHash imagepHash = new ImagepHash();
URL urlGet = new URL(avatar);
URLConnection conn = urlGet.openConnection();
InputStream avatarInputStream = conn.getInputStream();
String a1 = imagepHash.getHash(avatarInputStream);
AvatarLibDao avatarLibDao = new AvatarLibDao();
avatarLibDao.setAvatarHash1(Integer.valueOf(a1.substring(a1.length() / 4 * 0, a1.length() / 4 * 1), 2));
avatarLibDao.setAvatarHash2(Integer.valueOf(a1.substring(a1.length() / 4 * 1, a1.length() / 4 * 2), 2));
avatarLibDao.setAvatarHash3(Integer.valueOf(a1.substring(a1.length() / 4 * 2, a1.length() / 4 * 3), 2));
avatarLibDao.setAvatarHash4(Integer.valueOf(a1.substring(a1.length() / 4 * 3, a1.length() / 4 * 4), 2));
三、获取想要搜索的头像的Hash 值 ,与数据库进行匹配,按相似度高低进行升序排列
select *
from avatar_lib
where BIT_COUNT(avatar_lib.avatar_hash1^#{avatar_hash1})
+ BIT_COUNT(avatar_lib.avatar_hash2^#{avatar_hash2})
+ BIT_COUNT(avatar_lib.avatar_hash3^#{avatar_hash3})
+ BIT_COUNT(avatar_lib.avatar_hash4^#{avatar_hash4})
<=10
下面这个截图是用的线上环境,为了提高速度所以没有对相似度进行排序的
四、名词解释
离散余弦变换(DCT):https://www.jianshu.com/p/f8e36b00fcdf
汉明距离:https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB/475174?fr=aladdin
五、源代码地址
CSDN:https://download.csdn.net/download/fan_hong_sy/16265474
GitHub:https://github.com/fmacro/avatarSearch