使用Java识别2个相同的图像

我的网络抓取工具有问题,我试图从特定网站检索图像。 问题是,我经常看到的图像完全相同但URL不同,即它们的地址。

是否有任何Java库或实用程序可以识别2个图像的内容是否完全相同(即在像素级别)。

我的输入将是我可以下载它们的图像的URL。

我之前在Java中做过类似的事情,我发现api的java.awt.image包里面的PixelGrabber类非常有帮助(如果不是非常必要的话)。

此外,您肯定想要查看ColorConvertOp类 ,它可以对源图像中的数据执行逐像素颜色转换,并将生成的颜色值缩放到目标图像的精度。 文档继续说,图像甚至可以是相同的图像,在这种情况下,检测它们是否相同是非常简单的。

如果您正在检测相似性,则需要使用某种forms的平均方法,如本问题的答案中所述

如果可以的话,还可以查看Horstman的Core Java(第8版)第2卷第7章,因为有很多关于图像转换等的例子,但是再次确保在java.awt.image包中找到它,因为你应该会发现你几乎为你准备了一切:)

G’luck!

取决于您想要的详细程度:

  • 下载图像
  • 当你下载它时会为它生成一个哈希值
  • 创建一个目录名为哈希值的目录(如果该目录不存在)
  • 如果目录包含2个或更多文件,则比较文件大小
  • 如果文件大小相同,则将图像逐字节比较到文件中图像的字节
  • 如果字节是唯一的,那么你有一个新的图像

无论你是否愿意做所有这些,你都需要:

  • 下载图像
  • 对图像进行逐字节比较

无需依赖任何特殊的成像库,图像只是字节。

查看MessageDigest类。 基本上,您创建它的实例,然后传递一系列字节。 如果您知道两个“相同”的图像将是同一文件/字节流,则字节可以是从URL直接加载的字节。 或者,如果需要,您可以从流中创建BufferedImage,然后拉出像素值,例如:

MessageDigest md = MessageDigest.getInstance("MD5"); ByteBuffer bb = ByteBuffer.allocate(4 * bimg.getWidth()); for (int y = bimg.getHeight()-1; y >= 0; y--) { bb.clear(); for (int x = bimg.getWidth()-1; x >= 0; x--) { bb.putInt(bimg.getRGB(x, y)); } md.update(bb.array()); } byte[] digBytes = md.digest(); 

无论哪种方式,MessageDigest.digest()最终都会为您提供一个字节数组,它是图像的“签名”。 如果它有用,你可以将它转换为hex字符串,例如放入HashMap或数据库表,例如:

 StringBuilder sb = new StringBuilder(); for (byte b : digBytes) { sb.append(String.format("%02X", b & 0xff)); } String signature = sb.toString(); 

如果来自两个URL的内容/图像为您提供相同的签名,则它们是相同的图像。

编辑:我忘了提到如果你是哈希像素值,你可能也希望在哈希中包含图像的尺寸 。 (只是为了类似的事情 – 将两个整数写入一个8字节的ByteBuffer,然后使用相应的8字节数组更新MessageDigest。)

另一件事是有人提到的是MD5不是抗冲突的 。 换句话说,有一种技术可以构建具有相同MD5哈希值的多字节序列,而不必使用试验和错误的“powershell”方法(平均而言,您希望必须尝试大约2 ^ 64或在碰撞之前有160亿个文件)。 这使得MD5 不适合您试图抵御此威胁模型的地方 。 如果你关心有人可能故意试图愚弄你的重复识别的情况,而你只是担心“偶然”重复哈希的可能性,那么MD5绝对没问题。 实际上,它不仅很好,它实际上有点超过顶部 – 正如我所说,平均而言,在大约160亿个文件之后,你会期望一个“假复制”。 换句话说,你可以拥有十亿个文件,并且碰撞的可能性非常接近于零。

如果您担心概述的威胁模型(即您认为有人可能故意将处理器时间用于构建文件以欺骗您的系统),那么解决方案是使用更强大的哈希。 Java支持开箱即用的SHA1(只需将“MD5”替换为“SHA1”)。 现在,这将为您提供更长的哈希值(160位而不是128位),但是根据当前的知识,发现碰撞是不可行的。

就个人而言,我甚至会考虑使用一个像样的64位哈希函数。 这仍然可以将数以千万计的图像与误报率的接近零的机会进行比较。

您还可以生成文件的MD5签名并忽略重复的条目。 虽然不会帮你找到类似的图片。

我认为你不需要一个图像库来做到这一点 – 只需获取URL内容并比较两个流作为字节数组应该这样做。

当然,除非你有兴趣识别类似的图像。

使用以下内容计算MD5:

 MessageDigest m=MessageDigest.getInstance("MD5"); m.update(image.getBytes(),0,image.length()); System.out.println("MD5: "+new BigInteger(1,m.digest()).toString(16)); 

将它们放在hashmap中。

您可以使用以下方法比较图

1)简单的逐像素比较

当存在一些偏移,旋转,光照变化时,它不会给出非常好的结果……

2)相对简单但更先进的方法

http://www.lac.inpe.br/JIPCookbook/6050-howto-compareimages.jsp

3)更高级的算法

例如RadpiMiner和IMMI扩展包含多个图像比较算法,您可以尝试不同的方法并选择,最适合您的目的……

Hashing已经被建议并且识别两个文件是否相同是非常容易的,但是你说像素级别。 如果你想要识别两个图像,即使它们是不同的格式(.png / .jpg / .gif / ..),即使它们被缩放,我建议:(使用图像库,如果图像是中/大没有16×16图标):

  1. 将图像缩放到某个固定大小,这取决于样本
  2. 使用RGB-YUV转换将其转换为灰度级并从那里取Y(非常简单)3执行每个图像的汉明距离并设置阈值以确定它们是否相同。

如果差异为

检查响应头并询问HTTP头ETag值 (如果存在)。 ( RFC2616:ETag )对于来自目标Web服务器的相同图像,它们可能相同。 这是因为ETag值通常是消息摘要,如MD5,这将允许您利用Web服务器已经完成的计算。

这可能会让您甚至无法下载图像!

 for each imageUrl in myList Perform HTTP HEAD imageUrl Pull ETag value from request If ETag is in my map of known ETags move on to next image Else Download image Store ETag in map 

当然ETag必须存在,如果不存在,那么这个想法就是吐司。 但也许你已经与网络服务器管理员拉?

这几天我写了一个纯java库。 您可以使用目录路径(包括子目录)来提供它,它将使用您要删除的绝对路径列出列表中的重复图像。 或者,您也可以使用它来查找目录中的所有唯一图像。

它在内部使用了awt api,因此不能用于Android。 因为,imageIO在阅读很多新类型的图像时遇到了问题,我使用了内部使用的十二只猴子jar。

https://github.com/srch07/Duplicate-Image-Finder-API

可以从https://github.com/srch07/Duplicate-Image-Finder-API/blob/master/archives/duplicate_image_finder_1.0.jar下载内部捆绑的依赖项jar。

api也可以在不同大小的图像中找到重复。