一、原理讲解  
    实现这种功能的关键技术叫做"感知哈希算法"(Perceptual Hash Algorithm), 意思是为图片生成一个指纹(字符串格式), 两张图片的指纹越相似, 说明两张图片就越相似. 但关键是如何根据图片计算出"指纹"呢? 下面用最简单的步骤来说明一下原理:  
      
    《1》、第一步 缩小图片尺寸  
    将图片缩小到8x8的尺寸, 总共64个像素. 这一步的作用是去除各种图片尺寸和图片比例的差异, 只保留结构、明暗等基本信息.  
      
    《2》、第二步 转为灰度图片  
    将缩小后的图片, 转为64级灰度图片.  
      
    《3》、第三步 计算灰度平均值  
    计算图片中所有像素的灰度平均值  
      
    《4》、第四步 比较像素的灰度  
    将每个像素的灰度与平均值进行比较, 如果大于或等于平均值记为1, 小于平均值记为0.  
      
    《5》、第五步 计算哈希值  
    将上一步的比较结果, 组合在一起, 就构成了一个64位的二进制整数, 这就是这张图片的指纹.  
      
    《6》、第六步 对比图片指纹  
    得到图片的指纹后, 就可以对比不同的图片的指纹, 计算出64位中有多少位是不一样的. 如果不相同的数据位数不超过5, 就说明两张图片很相似, 如果大于10, 说明它们是两张不同的图片.

    import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream; import javax.imageio.ImageIO; import com.sun.image.codec.jpeg.ImageFormatException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageDecoder;
import com.sun.image.codec.jpeg.JPEGImageEncoder; public class ImageHelper { // 项目根目录路径
public static final String path = System.getProperty("user.dir"); /**
* 生成缩略图 <br/>
* 保存:ImageIO.write(BufferedImage, imgType[jpg/png/...], File);
*
* @param source
* 原图片
* @param width
* 缩略图宽
* @param height
* 缩略图高
* @param b
* 是否等比缩放
* */
public static BufferedImage thumb(BufferedImage source, int width,
int height, boolean b) {
// targetW,targetH分别表示目标长和宽
int type = source.getType();
BufferedImage target = null;
double sx = (double) width / source.getWidth();
double sy = (double) height / source.getHeight(); if (b) {
if (sx > sy) {
sx = sy;
width = (int) (sx * source.getWidth());
} else {
sy = sx;
height = (int) (sy * source.getHeight());
}
} if (type == BufferedImage.TYPE_CUSTOM) { // handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(width,
height);
boolean alphaPremultiplied = cm.isAlphaPremultiplied();
target = new BufferedImage(cm, raster, alphaPremultiplied, null);
} else
target = new BufferedImage(width, height, type);
Graphics2D g = target.createGraphics();
// smoother than exlax:
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
return target;
} /**
* 图片水印
*
* @param imgPath
* 待处理图片
* @param markPath
* 水印图片
* @param x
* 水印位于图片左上角的 x 坐标值
* @param y
* 水印位于图片左上角的 y 坐标值
* @param alpha
* 水印透明度 0.1f ~ 1.0f
* */
public static void waterMark(String imgPath, String markPath, int x, int y,
float alpha) {
try {
// 加载待处理图片文件
Image img = ImageIO.read(new File(imgPath)); BufferedImage image = new BufferedImage(img.getWidth(null),
img.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.drawImage(img, 0, 0, null); // 加载水印图片文件
Image src_biao = ImageIO.read(new File(markPath));
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
alpha));
g.drawImage(src_biao, x, y, null);
g.dispose(); // 保存处理后的文件
FileOutputStream out = new FileOutputStream(imgPath);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 文字水印
*
* @param imgPath
* 待处理图片
* @param text
* 水印文字
* @param font
* 水印字体信息
* @param color
* 水印字体颜色
* @param x
* 水印位于图片左上角的 x 坐标值
* @param y
* 水印位于图片左上角的 y 坐标值
* @param alpha
* 水印透明度 0.1f ~ 1.0f
*/ public static void textMark(String imgPath, String text, Font font,
Color color, int x, int y, float alpha) {
try {
Font Dfont = (font == null) ? new Font("宋体", 20, 13) : font; Image img = ImageIO.read(new File(imgPath)); BufferedImage image = new BufferedImage(img.getWidth(null),
img.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics(); g.drawImage(img, 0, 0, null);
g.setColor(color);
g.setFont(Dfont);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
alpha));
g.drawString(text, x, y);
g.dispose();
FileOutputStream out = new FileOutputStream(imgPath);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(image);
out.close();
} catch (Exception e) {
System.out.println(e);
}
} /**
* 读取JPEG图片
* @param filename 文件名
* @return BufferedImage 图片对象
*/
public static BufferedImage readJPEGImage(String filename)
{
try {
InputStream imageIn = new FileInputStream(new File(filename));
// 得到输入的编码器,将文件流进行jpg格式编码
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(imageIn);
// 得到编码后的图片对象
BufferedImage sourceImage = decoder.decodeAsBufferedImage(); return sourceImage;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ImageFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} return null;
} /**
* 读取JPEG图片
* @param filename 文件名
* @return BufferedImage 图片对象
*/
public static BufferedImage readPNGImage(String filename)
{
try {
File inputFile = new File(filename);
BufferedImage sourceImage = ImageIO.read(inputFile);
return sourceImage;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ImageFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} return null;
} /**
* 灰度值计算
* @param pixels 像素
* @return int 灰度值
*/
public static int rgbToGray(int pixels) {
// int _alpha = (pixels >> 24) & 0xFF;
int _red = (pixels >> 16) & 0xFF;
int _green = (pixels >> 8) & 0xFF;
int _blue = (pixels) & 0xFF;
return (int) (0.3 * _red + 0.59 * _green + 0.11 * _blue);
} /**
* 计算数组的平均值
* @param pixels 数组
* @return int 平均值
*/
public static int average(int[] pixels) {
float m = 0;
for (int i = 0; i < pixels.length; ++i) {
m += pixels[i];
}
m = m / pixels.length;
return (int) m;
}
}
    import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List; public class SimilarImageSearch { /**
* @param args
*/
public static void main(String[] args) {
List<String> hashCodes = new ArrayList<String>(); String filename = ImageHelper.path + "\\images\\";
String hashCode = null; for (int i = 0; i < 7; i++)
{
hashCode = produceFingerPrint(filename + "example" + (i + 1) + ".jpg");
hashCodes.add(hashCode);
}
System.out.println("Resources: ");
System.out.println(hashCodes);
System.out.println(); String sourceHashCode = produceFingerPrint(filename + "source.jpg");
System.out.println("Source: ");
System.out.println(sourceHashCode);
System.out.println(); for (int i = 0; i < hashCodes.size(); i++)
{
int difference = hammingDistance(sourceHashCode, hashCodes.get(i));
if(difference==0){
System.out.println("source.jpg图片跟example"+(i+1)+".jpg一样");
}else if(difference<=5){
System.out.println("source.jpg图片跟example"+(i+1)+".jpg非常相似");
}else if(difference<=10){
System.out.println("source.jpg图片跟example"+(i+1)+".jpg有点相似");
}else if(difference>10){
System.out.println("source.jpg图片跟example"+(i+1)+".jpg完全不一样");
}
System.out.println(difference);
} } /**
* 计算"汉明距离"(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;
} /**
* 生成图片指纹
* @param filename 文件名
* @return 图片指纹
*/
public static String produceFingerPrint(String filename) {
BufferedImage source = ImageHelper.readPNGImage(filename);// 读取文件 int width = 8;
int height = 8; // 第一步,缩小尺寸。
// 将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
BufferedImage thumb = ImageHelper.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] = ImageHelper.rgbToGray(thumb.getRGB(i, j));
}
} // 第三步,计算平均值。
// 计算所有64个像素的灰度平均值。
int avgPixel = ImageHelper.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 + 3];
hashCode.append(binaryToHex(result));
} // 得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。
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;
}
}

感知哈希算法的java实现的更多相关文章

  1. 谷歌百度以图搜图 "感知哈希算法" C#简单实现

    /// <summary> /// 感知哈希算法 /// </summary> public class ImageComparer { /// <summary> ...

  2. 感知哈希算法——Python实现【转】

    转自:https://blog.csdn.net/m_buddy/article/details/78887248 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...

  3. Java_一致性哈希算法与Java实现

    摘自:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  4. 一致性哈希算法与Java实现

    原文:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  5. 负载均衡-基础-一致性哈希算法及java实现

    一致性hash算法,参考: http://www.blogjava.net/hello-yun/archive/2012/10/10/389289.html 针对这篇文章,加入了自己的理解,在原有的代 ...

  6. 感知哈希算法 python 3.4

    #!/usr/bin/python # -*- coding: UTF-8 -*- #Less than 10 add to list and sort import glob import os i ...

  7. Iconfinder 如何杜绝盗版,哈希算法检测图像重复

    原地址:http://blog.jobbole.com/65914/ 本文由 伯乐在线 - 小鱼 翻译自 Silviu Tantos.欢迎加入技术翻译小组.转载请参见文章末尾处的要求. [伯乐在线导读 ...

  8. 一致性哈希算法(consistent hashing)(转)

    原文链接:每天进步一点点——五分钟理解一致性哈希算法(consistent hashing)  一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网 ...

  9. java单向加密算法小结(2)--MD5哈希算法

    上一篇文章整理了Base64算法的相关知识,严格来说,Base64只能算是一种编码方式而非加密算法,这一篇要说的MD5,其实也不算是加密算法,而是一种哈希算法,即将目标文本转化为固定长度,不可逆的字符 ...

随机推荐

  1. Java中的注释

    代码注释是架起程序设计者与程序阅读者之间的通信桥梁,最大限度的提高团队开发合作效率.也是程序代码可维护性的重要环节之一.所以我们不是为写注释而写注释.下面说一下Javadoc注释规范以及楼主在J2EE ...

  2. 微信小程序文档解读(一)--api提供支持有哪些

    本文重点在于小程序API提供的微信功能支持及获取用户信息的解读,具体的用法和调用不在本文讨论范围之内,文章基于20161222版文档解读 API官方文档原文链接 小程序API官方定义: 框架提供丰富的 ...

  3. Linux常用命令汇总及使用方法(二)之文本编辑器VI

    VI可能是在Linux中使用比较频繁的文本编辑器,如果不能熟练使用VI,在一定程度上会影响工作效率,所以在这里记录一下VI的常用命令及操作方式 在[root@test ~]# vi carrie.tx ...

  4. mongo 初级使用

    1.找到mongo所在地址 cd /usr/bin 2.进入mongo 命令:mongo mongo:端口(默认27017) ps:我用的是30005 3.选择使用的database 命令:use 自 ...

  5. 8VC Venture Cup 2017 - Elimination Round

    传送门:http://codeforces.com/contest/755 A题题意是给你一个数字n,让你找到一个数字m,使得n*m+1为合数,范围比较小,直接线性筛出1e6的质数,然后暴力枚举一下就 ...

  6. JavaScript动态加载资源【js|css】示例代码

    在开发过程中会用到各种第三方的插件,或者自己写在单独文件中的js方法库或者css样式,在html头部总是需要写一大堆的script和link标签,如果想要自己实现动态的引入资源文件,可以使用开源的re ...

  7. 分布式版本控制系统Git-----6.Git 常见命令一览表

    说明/备注 命令 备注 保存更新 git add [-i] -i 逐个确认 检查更新 git status 提交更新 git commit [-a] -m "<更新说明>&quo ...

  8. ZZNU 1995: cots' times

    题目描述 XX年XX月XX日小cot学会了回文数,eg:121. 小cot上课容易走神, 经常看看这个,瞧瞧那个.在小cot某一次走神的过程中他发现电子表上的时间也有回文数... 当然,并不是每次走神 ...

  9. STM32笔记总结

    1.命名规则 2.#pragma pack使用 #pragma pack 1保证字节对齐 置结构体的边界对齐为1个字节,也就是所有数据在内存中是连续存储的struct s{    char ch;   ...

  10. SOCKET的一些注意事项

    1. 如果采用同步处理,其实,是可以多次接受数据的, MemoryStream ms = new MemoryStream();    while (true)   {     Console.Wri ...