【问题标题】:Identifying 2 same images using Java使用 Java 识别 2 个相同的图像
【发布时间】:2026-01-22 11:05:01
【问题描述】:

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

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

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

【问题讨论】:

    标签: java image


    【解决方案1】:

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

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

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

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

    祝你好运!

    【讨论】:

      【解决方案2】:

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

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

      无论您是否想这样做,您都需要:

      • 下载图片
      • 逐字节比较图像

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

      【讨论】:

      • 只有在使用弱散列函数时才真正需要这样做。老实说,你真的可以只使用相当强大的哈希函数并“信任哈希”。
      【解决方案3】:

      查看 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() 最终都会为您提供一个字节数组,它是图像的“签名”。如果有帮助,您可以将其转换为十六进制字符串,例如用于放入 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 哈希构造多个字节序列,而无需使用“蛮力”的反复试验方法(平均而言,您需要尝试大约 2^64 或碰撞前的 160 亿个文件)。这使得 MD5 不适合您试图防御这种威胁模型的地方。如果您担心有人可能故意试图欺骗您的重复标识,而您只是担心“偶然”出现重复哈希的可能性",那么 MD5 绝对没问题。实际上,它不仅很好,而且实际上有点过头了——正如我所说,平均而言,您会期望在大约 160 亿个文件之后出现一个“错误重复”。或者换一种说法,比如说,你可能有十亿个文件,并且发生冲突的可能性非常接近于零。

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

      出于这个目的,我什至会考虑只使用一个像样的 64 位散列函数。这仍然可以让数以千万计的图像与几乎为零的误报机会进行比较。

      【讨论】:

      • 那不行,MD5 不抗碰撞(两个不同的文件可以有相同的 MD5 哈希),但这是一个好的开始,因为碰撞的几率很低(你仍然需要做如果两个 MD5 哈希值相同,则进行逐字节比较)。
      • 查看我的编辑——我不认为在这种情况下的目的是防止该威胁模型。我们可能正在寻找的只是一个“良好的宽散列函数”,而 MD5 就足够了。
      • 你是说两个文件不可能有相同的哈希值,除非受到攻击?有无限数量的文件被映射到有限数量的哈希......(不可能和不可能是两个非常不同的东西)。
      • 它们是不同的,但在这种情况下,“不太可能”的意思是“在你编写代码时,陨石落在你身上的可能性更大”。
      • P.S.我知道这是违反直觉的。但是哈希的“有限数量”是 2^128,这是一个非常大的数字!
      【解决方案4】:

      您还可以生成文件的 MD5 签名并忽略重复条目。但不会帮助您找到相似的图像。

      【讨论】:

        【解决方案5】:

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

        当然,除非您也对识别相似图像感兴趣。

        【讨论】:

        • 如果使用有损算法压缩图像怎么办?您可以有两个相同但具有不同字节的图像。
        • 你可以 - 但你不太可能。这样的图像不会完全相同,但会彼此相似。大多数情况下,这种像素完美的克隆在网络上并不存在——它要么是逐字节的副本,要么会有一些与原始像素不同的像素。也许它会在角落里有一个徽章
        【解决方案6】:

        使用以下方法计算 MD5:

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

        将它们放在哈希图中。

        【讨论】:

          【解决方案7】:

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

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

          如果差值是,您将对两张图像的所有灰度像素的差值求和

          --

          【讨论】:

            【解决方案8】:

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

            1) 简单的逐像素比较

            当有一些移位、旋转、光照变化时,它不会给出很好的结果……

            2) 相对简单但更高级的方法

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

            3) 更高级的算法

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

            【讨论】:

              【解决方案9】:

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

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

              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 必须存在,如果不存在,那么这个想法就是吐司。但也许你已经与网络服务器管理员合作了?

              【讨论】:

                【解决方案10】:

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

                它内部使用了awt api,所以不能用于Android。 由于 imageIO 无法读取大量新类型的图像,因此我使用的是内部使用的 12 个猴子 jar。

                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也可以在不同大小的图像中查找重复。

                【讨论】: