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

时间:2009-03-26 06:24:48

标签: java image

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

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

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

10 个答案:

答案 0 :(得分:8)

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

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

如果您正在检测相似性,则需要使用this question的答案中提到的某种形式的平均方法

如果可以的话,还可以查看Horstman的Core Java(第8版)第2卷第7章,因为有大量关于图像转换等的例子,但同样,请务必查看java.awt.image因为您应该发现几乎所有东西都为您准备好了:))

G'luck!

答案 1 :(得分:5)

取决于您希望获得的详细程度:

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

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

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

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

答案 2 :(得分:4)

查看MessageDigest类。基本上,您创建它的实例,然后传递一系列字节。如果您知道两个“相同”的图像将是selfsame文件/字节流,则字节可以是从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()最终会为您提供一个字节数组,它是图像的“签名”。如果它有用,您可以将其转换为十六进制字符串,例如用于放入HashMap或数据库表,例如:

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

如果来自两个网址的内容/图片为您提供相同的签名,那么它们就是相同的图片。

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

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

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

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

我认为您不需要图像库来执行此操作 - 只需获取URL内容并将两个流作为字节数组进行比较即可。

除非您有兴趣识别类似的图片。

答案 5 :(得分:1)

使用以下内容计算MD5:

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

将它们放在散列图中。

答案 6 :(得分:1)

您可以使用以下方式比较图像:

1)逐像素比较

当有一些移位,旋转,光照变化时,它不会给出非常好的结果......

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

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

3)更高级的算法

例如RadpiMiner and IMMI extension包含多种图片比较算法,您可以尝试不同的方法并选择,最适合您的目的......

答案 7 :(得分:0)

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

  1. 将图像缩放到某个固定大小,这取决于样本
  2. 使用RGB-YUV转换将其转换为灰度,并从那里取Y(非常简单) 3执行每个图像的汉明距离并设置阈值以确定它们是否相同。
  3. 如果差异小于,则您将获得两个图像的所有灰色像素的差值之和。你认为两个图像相同

    -

答案 8 :(得分:0)

检查响应标头并询问 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必须存在,如果没有,那么这个想法就是吐司。但也许你已经掌握了网络服务器管理员?

答案 9 :(得分:0)

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

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

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

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

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