匹配两个文件名不同但内容相同的图片

3

如果一张图片被用两个不同的文件名保存了两次,有没有办法比较它们是否相同..?

我希望基本的哈希或CRC类型检查可以起作用..?

文件大小可能不行,因为池中有数百万张图片,不同的图片可能具有相同的大小..

希望在C#中有一种简单的方法来实现它..


1
图片的平均大小是多少? - Rusty
5个回答

7
如果文件内容相同,则加密哈希至少可以很好地指示它们相等。在这里,SHA256 类是一个不错的选择,尽管可能有点过头。例如:
static byte[] Sha256HashFile(string file)
{
    using (SHA256 sha256 = SHA256.Create())
    {
        using (Stream input = File.OpenRead(file))
        {
            return sha256.ComputeHash(input);
        }
    }
}

比较返回的两个字节数组最简单的方法可能是使用 Convert.ToBase64 将它们都转换为字符串,然后比较这两个字符串。虽然丑陋但容易 :) 你也可以使用 Enumerable.SequenceEqual

byte[] hash1 = Sha256HashFile("file1.png");
byte[] hash2 = Sha256HashFile("file2.png");
bool same = hash1.SequenceEqual(hash2);    

如果您想将哈希值存储为集合或字典,您可以自己实现IEqualityComparer<byte[]>,但坦率地说,使用base64字符串最容易。例如,这将打印出重复的文件:

var hashToNameMap = new Dictionary<string, string>();
foreach (string file in files)
{
    byte[] hash = Sha256HashFile(file);
    string base64 = Convert.ToBase64(hash);
    string existingName;
    if (hashToNameMap.TryGetValue(base64, out existingName))
    {
        Console.WriteLine("{0} is a duplicate of {1}", file, existingName);
    }
    else
    {
        hashToNameMap[base64] = file;
    }
}

几点说明:

  • 这并不能保证百分之百的准确性,但是如果文件也必须是有效的图片,则发生冲突的概率非常小。
  • 这涉及到阅读每个文件的全部内容 - 即使没有其他大小相同的文件(因此也没有可能存在重复)。这可能或可能不会对您造成问题。
  • 即使有多个大小相同的文件,你只需要读取所有文件来查找重复项... 你可以在读取文件和计算哈希值的过程中进行,一旦发现文件不同就停止。

你如何处理这个问题取决于你的目标是绝对速度、代码简单性等。这也可能取决于池是否会随着时间的推移而增长 - 例如,你可能想要在获得两个或更多大小相同的文件时立即对文件进行哈希,这样当你添加另一个大小相同的文件时,你可以哈希那个文件并添加它,而无需重新读取现有数据。


+1(@Jon Skeet - 恭喜您获得30万声望 :))。正如Henk Holterman所指出的那样,对于这种情况,简单的哈希算法可能同样好,并且可能比加密算法更快。但是Sha*可能是编写代码最容易的选择。 - Alexei Levenkov
值得一提的是:我意识到这是一个有点老的帖子,但在两个相同的100 MB位图文件上,使用您在此处描述的方法需要约7秒钟,而读取两个文件并逐字节比较它们只需要大约200毫秒。 - Anders
@Anders:另一方面,如果有很多文件需要比较,您可以非常快速地比较哈希值,但您不想对这些文件进行成对比较。当然,有解决方法 - 我以前曾将其用作面试问题...另一个有用的时间是如果您偶尔向池中添加文件。存储所有哈希值意味着您只需要对新文件进行哈希处理,仅在获得哈希匹配时才与任何其他文件进行比较。 - Jon Skeet
@Jon:感谢您的见解,您提出了一些非常有价值的观点——对数百万个文件进行成对比较需要很长很长的时间...哎呀!我误读了问题;我以为他只有一张图片,并且正在寻找它的副本,而不是在寻找任何图像的副本。无论如何,我可以看到建立哈希数据库对未来会有多么有用。 - Anders

4

首先,无论如何都要检查长度。只有在它们匹配时,您才需要深入了解。

对于所有具有相同大小的图像,请计算哈希值。当哈希值匹配时,您可以相当确定这些图像是相同的。该库提供了许多加密安全哈希,但您可能希望寻找优化:

  • 采样。如果您的图像很大(>100kB),则可以通过仅计算几个段上的哈希来节省I/O。开头、中间和结尾的几KB可能足以获得良好的指纹。请使用512的倍数作为块的大小和偏移量。Jpeg压缩的工作方式有点像哈希:少量像素的差异通常会导致位流中的大差异。

  • 使用更快的哈希。在这种情况下,简单的异或算法可能就足够了。

  • 如果您真的想一次比较两个图像,那么请使用一个哈希实现,让您检查中间结果。一旦出现差异,您就可以停止。

  • 但是当您有很多相同大小的文件时,请为每个文件计算一次哈希,并查找(大小,哈希)重复项。

如果哈希冲突是一个问题,一旦你找到了冲突,逐字节比较是很便宜的。 - Gabe
我认为哈希算法的选择取决于它是CPU绑定还是IO绑定。但是,确实有一些选项。 - Jon Skeet
@Jon,是的,我的两个建议顺序错了。我会稍作修改。 - H H

1

System.Security.Cryptography;

使用SHA1

using(SHA1 sha = SHA1.Create()) { //added using based on Jon Skeet comment
    byte[] newData = sha.ComputeHash(data);
}

data是文件的byte[]数据

newData是哈希值

仅当您想知道两个图像文件是否完全相同(即字节相同),而不是它们是否只编码了相同的像素(如果元数据不同,则可能是不同的文件)时,才适用此方法。


请注意,HashAlgorithm实现了IDisposable接口,因此您应该在使用完SHA1后进行处理。 - Jon Skeet

0

你可以从每个文件中读取二进制,然后比较所包含的二进制。同一张图片在每个数组中应该具有完全相同的二进制。

只是一个想法。


0

你也可以像这样做

public string ImageToBase64(Image image, 
                            System.Drawing.Imaging.ImageFormat format)
{ 
    using (MemoryStream ms = new MemoryStream())
    { 
         // Convert Image to byte[]
         image.Save(ms, format);  
         byte[] imageBytes = ms.ToArray();

        // Convert byte[] to Base64 String
        string base64String = Convert.ToBase64String(imageBytes);
        return base64String; 
    }
}

然后你可以使用String.Compare()。对于较大的图片来说,这可能会比较慢,因为它会生成一个相当大的字符串,但我仍然发布了它,只是为了完整性而已 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接