这是我的程序代码,用于比较两个图像是否相同。它基本上使用打开对话框并一次打开1张图像,然后将其放入1个图片框中。

我听说GetPixel()方法可能很慢。如果2张图像相同,是否有更有效,更快速的方法来比较2张图像?

Bitmap image1 = null;
Bitmap image2 = null;

public Form1()
{
    InitializeComponent();
}

private void button1_Click(object sender, EventArgs e) //first image open
{
    OpenFileDialog openDialog = new OpenFileDialog();
    if (openDialog.ShowDialog() == DialogResult.OK)
    {
        image1 = new Bitmap(openDialog.FileName);
        pictureBox1.Image = image1;
    }   
}

private void button2_Click(object sender, EventArgs e) //second image open
{

    OpenFileDialog openDialog = new OpenFileDialog();
    if (openDialog.ShowDialog() == DialogResult.OK)
    {
        image2 = new Bitmap(openDialog.FileName);
        pictureBox2.Image = image2;
    }  
}

private void button3_Click(object sender, EventArgs e) //compare button
{
    if (compare(image1, image2))
    {
        MessageBox.Show("Same Image.");
    }

    else
    {
        MessageBox.Show("Different Image.");
    }
}

private bool compare(Bitmap bmp1, Bitmap bmp2) 
{
    bool equals = true;
    bool flag = true;  //Inner loop isn't broken

    //Test to see if we have the same size of image
    if (bmp1.Size == bmp2.Size)
    {
        for (int x = 0; x < bmp1.Width; ++x)
        {
            for (int y = 0; y < bmp1.Height; ++y)
            {
                if (bmp1.GetPixel(x, y) != bmp2.GetPixel(x, y))
                {
                    equals = false;
                    flag = false;
                    break;
                }
            }
            if (!flag)
            {
                break;
            }
        }
    }
    else
    {
        equals = false;
    }
    return equals;
}


评论

请注意,位图继承自Image,后者实现了IDisposable。这意味着您最好在处理完这些对象后调用Dispose()(使用UI事件处理程序在此处处理许多路径),或者将它们放入使用块中。

哦,OpenFileDialog也是如此。我可能会发布答案以显示其中一些内容。

请参见ImageComparer.Compare方法。具有重载以指定公差。自VS2102起可用。 msdn.microsoft.com/zh-CN/library / ...

#1 楼

您可以使用LockBits方法和指针直接访问图像数据。

24 bpp图像的示例:

bool equals = true;
Rectangle rect = new Rectangle(0, 0, bmp1.Width, bmp1.Height);
BitmapData bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadOnly, bmp1.PixelFormat);
BitmapData bmpData2 = bmp2.LockBits(rect, ImageLockMode.ReadOnly, bmp2.PixelFormat);
unsafe {
  byte* ptr1 = (byte*)bmpData1.Scan0.ToPointer();
  byte* ptr2 = (byte*)bmpData2.Scan0.ToPointer();
  int width = rect.Width * 3; // for 24bpp pixel data
  for (int y = 0; equals && y < rect.Height; y++) {
    for (int x = 0; x < width; x++) {
      if (*ptr1 != *ptr2) {
        equals = false;
        break;
      }
      ptr1++;
      ptr2++;
    }
    ptr1 += bmpData1.Stride - width;
    ptr2 += bmpData2.Stride - width;
  }
}
bmp1.UnlockBits(bmpData1);
bmp2.UnlockBits(bmpData2);


评论


\ $ \ begingroup \ $
我想将其拖到我的compare()方法中?
\ $ \ endgroup \ $
– puretppc
2014年1月24日18:57

\ $ \ begingroup \ $
@puretppc:是的,这是正确的。在检查图像具有相同的尺寸和正确的像素格式之后。
\ $ \ endgroup \ $
–古法
2014年1月24日19:13

\ $ \ begingroup \ $
BitmapData对我而言不存在。此外,只有使用/ unsafe进行编译时,不安全代码才可能出现。
\ $ \ endgroup \ $
– puretppc
2014年1月24日19:16

\ $ \ begingroup \ $
@puretppc:使用System.Drawing.Imaging添加;在顶部,或使用System.Drawing.Imaging.BitmapData。 (如果右键单击BitmapData,则可以在“解决”下自动完成这两个选项。)要使用不安全的代码,需要在项目属性中启用它。
\ $ \ endgroup \ $
–古法
2014年1月24日19:24

\ $ \ begingroup \ $
好的。但是现在我收到一个错误消息,说“ System.Drawing.Imaging.ImageLockMode”不包含“ Read”的定义。在使用System.Drawing.Imaging放置的程序的最顶部;
\ $ \ endgroup \ $
– puretppc
2014年1月24日19:31



#2 楼

我过去做过的一件事是将图像转换为64位编码的字符串,然后进行字符串比较。自然,它不会像使用指针那样快。但是,它可以完全在托管代码中完成,它不需要您知道每个像素的位数,并且适用于Image基类。

byte[] image1Bytes;
byte[] image2Bytes;

using(var mstream = new MemoryStream())
{
    image1.Save(mstream, image1.RawFormat);
    image1Bytes = mstream.ToArray();
}

using(var mstream = new MemoryStream())
{
    image2.Save(mstream, image2.RawFormat);
    image2Bytes = mstream.ToArray();
}

var image164 = Convert.ToBase64String(image1Bytes);
var image264 = Convert.ToBase64String(image2Bytes);

return string.Equals(image164, image264);


评论


\ $ \ begingroup \ $
感谢这项工作:)所以我的旧代码是关于比较BPP(每像素位数)?我真的不知道这一点,因为我是从另一个站点获得的
\ $ \ endgroup \ $
– puretppc
2014年1月24日18:58

\ $ \ begingroup \ $
请注意,这还将比较图像中的任何元数据。例如,如果它们相同但具有不同的分辨率设置,则将返回false。
\ $ \ endgroup \ $
–古法
2014年1月24日19:14

\ $ \ begingroup \ $
转换为base64字符串的目的是什么?为什么不简单地比较两个数组(或流)的元素?
\ $ \ endgroup \ $
–NPSF3000
2014年5月5日晚上8:30

\ $ \ begingroup \ $
使这种方法有效的工作比我想的要多。我读了一些有关对图像数据进行哈希处理的内容,并认为Bitmap.GetHashCode()就足够了。我有几个要从Spritesheets提取的子图像,最后几个Spritesheets有要跳过的空白子图像。要么是这样,要么是循环遍历所有子图像的每个像素,然后检查它们是否为无色(无alpha的黑色),这当然是处理器密集型的。感谢您分享!
\ $ \ endgroup \ $
–Artorias2718
19年5月20日在4:59



#3 楼

我将集中讨论compare()中的问题。

您对位图大小使用了错误的相等比较。您需要比较Size对象的内容,而不是它们是否是相同的引用。

名为flag的变量应该是一个……红旗!它不仅命名模糊,而且它的存在表明您的代码无效。避免使用变量进行流量控制;找到更主动的方法到达需要去的地方。

在这种情况下,解决方案是早期return。一旦找到两个图像之间的单个差异,就完成了!您甚至不需要equals变量。为了清楚起见,我还将重命名compare(),并将其命名为static,因为它是其两个参数的纯函数。

private static bool Equals(Bitmap bmp1, Bitmap bmp2) 
{
    if (!bmp1.Size.Equals(bmp2.Size))
    {
        return false;
    }
    for (int x = 0; x < bmp1.Width; ++x)
    {
        for (int y = 0; y < bmp1.Height; ++y)
        {
            if (bmp1.GetPixel(x, y) != bmp2.GetPixel(x, y))
            {
                return false;
            }
        }
    }
    return true;
}


#4 楼

请注意,位图继承自Image,后者实现了IDisposable。这意味着您最好对这些对象调用Dispose()(是否已使用它们完成操作(使用UI事件处理程序在此处使用许多路径)),或者将它们放入using块中。 OpenFileDialog也是如此。

根据我的评论,一些using用法(和try..finally用法,结合了Guffa的回答):

private Bitmap image1;

private Bitmap image2;

public Form1()
{
    this.InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
    using (var openDialog = new OpenFileDialog())
    {
        if (openDialog.ShowDialog() != DialogResult.OK)
        {
            return;
        }

        this.DisposeImage1();
        this.image1 = new Bitmap(openDialog.FileName);
    }

    this.pictureBox1.Image = this.image1;
}

private void button2_Click(object sender, EventArgs e)
{
    using (var openDialog = new OpenFileDialog())
    {
        if (openDialog.ShowDialog() != DialogResult.OK)
        {
            return;
        }

        this.DisposeImage2();
        this.image2 = new Bitmap(openDialog.FileName);
    }

    this.pictureBox2.Image = this.image2;
}

private void button3_Click(object sender, EventArgs e)
{
    MessageBox.Show(Compare(this.image1, this.image2) ? "Same Image." : "Different Image.");
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
    this.DisposeImage2();
    this.DisposeImage1();
}

private static bool Compare(Bitmap bmp1, Bitmap bmp2)
{
    // Test to see if we have the same size of image
    if (bmp1.Size != bmp2.Size)
    {
        return false;
    }

    var rect = new Rectangle(0, 0, bmp1.Width, bmp1.Height);
    var bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadOnly, bmp1.PixelFormat);

    try
    {
        var bmpData2 = bmp2.LockBits(rect, ImageLockMode.ReadOnly, bmp1.PixelFormat);

        try
        {
            unsafe
            {
                var ptr1 = (byte*)bmpData1.Scan0.ToPointer();
                var ptr2 = (byte*)bmpData2.Scan0.ToPointer();
                var width = 3 * rect.Width; // for 24bpp pixel data

                for (var y = 0; y < rect.Height; y++)
                {
                    for (var x = 0; x < width; x++)
                    {
                        if (*ptr1 != *ptr2)
                        {
                            return false;
                        }

                        ptr1++;
                        ptr2++;
                    }

                    ptr1 += bmpData1.Stride - width;
                    ptr2 += bmpData2.Stride - width;
                }
            }
        }
        finally
        {
            bmp2.UnlockBits(bmpData2);
        }
    }
    finally
    {
        bmp1.UnlockBits(bmpData1);
    }

    return true;
}

private void DisposeImage1()
{
    if (this.image1 == null)
    {
        return;
    }

    this.pictureBox1.Image = null;
    this.image1.Dispose();
    this.image1 = null;
}

private void DisposeImage2()
{
    if (this.image2 == null)
    {
        return;
    }

    this.pictureBox2.Image = null;
    this.image2.Dispose();
    this.image2 = null;
}


评论


\ $ \ begingroup \ $
ImageLockMode不存在吗?另外,不安全的我得到一个错误
\ $ \ endgroup \ $
– puretppc
2014年1月24日19:17

\ $ \ begingroup \ $
您需要在项目选项的“构建”页面上选中启用不安全代码。另外,在文件顶部的using指令中添加using System.Drawing.Imaging,以访问ImageLockMode。
\ $ \ endgroup \ $
– Jesse C. Slicer
2014年1月24日20:53

\ $ \ begingroup \ $
最后一个参数bmp1.PixelFormat或bm​​p2.PixelFormat是“ var bmpData2 = bmp2.LockBits(rect,ImageLockMode.ReadOnly,bmp1.PixelFormat)”吗?
\ $ \ endgroup \ $
– Sajitha Rathnayake
2014年8月15日下午5:26

\ $ \ begingroup \ $
我收到错误消息“位图区域已被锁定”。在代码“ var bmpData2 = bmp2.LockBits(rect,ImageLockMode.ReadOnly,bmp1.PixelFormat)”中
\ $ \ endgroup \ $
– Sajitha Rathnayake
2014年8月15日5:30



#5 楼

看看这个SO问题。

https://stackoverflow.com/q/35151067/4062881

它会减小图像的大小,使其变为黑白,然后使用GetPixel()生成哈希。

它更快,更有效并且有效!能够找到具有以下相同图像:


不同的文件格式(例如jpg,png,bmp)
旋转(90、180、270)-通过更改迭代顺序i和j的大小
不同的尺寸(需要相同的方面)
不同的压缩(在质量损失的情况下(例如jpeg伪像)需要公差)-您可以接受99%的相等性作为同一图像和50 %成为不同的人。

干杯.. !! ;)

#6 楼

如果进行哈希比较,为什么要转换为base64或比较每个像素?
做类似的事情:

byte[] fileData = File.ReadAllBytes(filePath);
byte[] hash = MD5.Create().ComputeHash(fileData);


然后简单地比较一下哈希值。

您需要MD5CryptoServiceProvider

评论


\ $ \ begingroup \ $
相同的图像在编码方式上可能有所不同(例如,渐进式或隔行扫描或不同级别的纠错码),因此不必位相同。此外,如果存在匹配项,则考虑到可能发生哈希冲突的情况,您仍然还必须比较实际数据。
\ $ \ endgroup \ $
– Toby Speight
18年11月21日在8:38

\ $ \ begingroup \ $
首先,如果是这种情况,base64和像素比较也不起作用。第二,OP在这方面没有提及任何内容。如果计划比较略有不同的图像,则可以使用库OpenCV和类似SIFT或SURF的方法
\ $ \ endgroup \ $
– F.H.
18年11月21日在14:19

\ $ \ begingroup \ $
这没有任何意义-我看不到代码中的base-64,并且我已经解释了像素比较显示来自不同图像文件的相同内容的地方。
\ $ \ endgroup \ $
– Toby Speight
18年11月21日在14:29

\ $ \ begingroup \ $
第二个最重要的答案是建议base64字符串比较。很高兴您花时间检查一下,然后再随机拒绝他人回答。
\ $ \ endgroup \ $
– F.H.
18年11月21日在14:45

\ $ \ begingroup \ $
您不知道我对哪个答案进行了投票(向上或向下),或者这些投票中的任何一个都是随机的,因此请不要提出任何指控。
\ $ \ endgroup \ $
– Toby Speight
18年11月21日在15:16